Huge refactor to sort out the game loop flow.

This commit is contained in:
photonstorm 2017-01-25 17:10:19 +00:00
parent 0b63a4fa01
commit 6ee499132c
22 changed files with 392 additions and 569 deletions

View file

@ -270,29 +270,23 @@ When the DOM Content Loaded event happens, the Game calls Game.boot. This sets-u
### A single tick ### A single tick
Every time RAF ticks it calls the following: Every time RAF ticks it calls the following (in order)
1. --removed-- 1. `Game.step` is called, which calls ...
2. Calls `StateManager.step`, which ... 2. `Game.mainloop.step` which checks frame rate, updates delta values, etc
3. Iterates through all _active_ States, and ... 3. This calls `State.sys.begin` once for all _active_ States
4. Calls `State.sys.mainloop.step`, which ... 4. While the frame delta is within range it calls `State.sys.update`
5. Checks if the frame rate is throttled, and returns if so, otherwise ... 5. This iterates all `State.children`, and calls `update` if they exist
6. It updates the frame delta values, then ... 6. It then calls `State.update` (dev level callback)
7. Calls `State.sys.begin` (which is an empty function by default) 7. When the loop exits (because the frameDelta is > the step size) it ..
8. While the frame delta is within range, it ... 8. `Renderer.preRender` which resets the canvas, cls, then ...
9. Calls `State.sys.update` (empty by default) 9. Calls `State.sys.render` on all _active_ States, which each calls ...
10. If then iterates through all `State.children`, and if they exist calls `update` on each of them 10. `State.sys.updates.start` which processes the Update Managers list
11. It then calls `State.update` 11. If the State is visible, it then calls `Game.renderer.render`, which ...
12. When the while loop exits (because the frameDelta is > the step size), it ... 12. Iterates through all children calling `child.render` on each one.
13. Calls `State.sys.preRender` (empty) 13. It then calls `State.sys.updates.stop` - which stops the Update Manager
14. Calls `State.sys.updates.start` - this process the Update Managers list 14. Then calls `State.render` (dev level callback)
15. If the State is visible, it then calls `Game.renderer.render`, passing in the State ... 15. Finally mainloop calls `Renderer.postRender` and resets the panic flags.
16. If the Renderer is running it performs all of the related set-up calls (setting the blend mode, clearing the canvas, etc), then ...
17. Starts the Batch Manager (`src/renderer/webgl/BatchManager.start`)
18. Calls `StateManager.renderChildren`, which ...
19. Iterates through all children of the current State, calling `child.render` on each one.
20. It then calls `State.sys.updates.stop` - which stops the Update Manager
21. Finally it calls `State.sys.end` which just resets the frame delta / panic flags.
In a tree form it maps to the following: In a tree form it maps to the following:
@ -321,6 +315,9 @@ In a tree form it maps to the following:
+- State.sys.end (resets frame delta and panic flags) +- State.sys.end (resets frame delta and panic flags)
``` ```
The above is subject to change heavily! There are currently lots of empty function calls in there (State.sys.update for example), so we may well optimize this path considerably. The above is subject to change heavily! There are currently lots of empty function calls in there (State.sys.update for example), so we may well optimize this path considerably.
In essence though the concept is: Once per frame we update all the timing values, update the core systems, update the children, and repeat this until our step value is high enough, then we render everything. In essence though the concept is: Once per frame we update all the timing values, update the core systems, update the children, and repeat this until our step value is high enough, then we render everything.

View file

@ -51,6 +51,7 @@ var Config = function (config)
this.bannerTextColor = GetObjectValue(config, 'banner.text', defaultBannerTextColor); this.bannerTextColor = GetObjectValue(config, 'banner.text', defaultBannerTextColor);
this.bannerBackgroundColor = GetObjectValue(config, 'banner.background', defaultBannerColor); this.bannerBackgroundColor = GetObjectValue(config, 'banner.background', defaultBannerColor);
this.fps = GetObjectValue(config, 'fps', 60);
this.forceSetTimeOut = GetObjectValue(config, 'forceSetTimeOut', false); this.forceSetTimeOut = GetObjectValue(config, 'forceSetTimeOut', false);
this.transparent = GetObjectValue(config, 'transparent', false); this.transparent = GetObjectValue(config, 'transparent', false);
this.pixelArt = GetObjectValue(config, 'pixelArt', false); this.pixelArt = GetObjectValue(config, 'pixelArt', false);

View file

@ -12,6 +12,7 @@ var AddToDOM = require('../dom/AddToDOM');
var RequestAnimationFrame = require('../dom/RequestAnimationFrame'); var RequestAnimationFrame = require('../dom/RequestAnimationFrame');
var DOMContentLoaded = require('../dom/DOMContentLoaded'); var DOMContentLoaded = require('../dom/DOMContentLoaded');
var MainLoop = require('./MainLoop');
var CreateRenderer = require('./CreateRenderer'); var CreateRenderer = require('./CreateRenderer');
var StateManager = require('../state/StateManager'); var StateManager = require('../state/StateManager');
var TextureManager = require('../textures/TextureManager'); var TextureManager = require('../textures/TextureManager');
@ -58,6 +59,12 @@ var Game = function (config)
*/ */
this.device = Device; this.device = Device;
/**
* @property {Phaser.MainLoop} mainloop - Main Loop handler.
* @protected
*/
this.mainloop = new MainLoop(this.config.fps);
// Wait for the DOM Ready event, then call boot. // Wait for the DOM Ready event, then call boot.
DOMContentLoaded(this.boot.bind(this)); DOMContentLoaded(this.boot.bind(this));
@ -87,7 +94,15 @@ Game.prototype = {
this.config.postBoot(); this.config.postBoot();
this.raf.start(this.state.step.bind(this.state), this.config.forceSetTimeOut); this.mainloop.start();
this.raf.start(this.step.bind(this), this.config.forceSetTimeOut);
},
step: function (timestamp)
{
// Pass in via game to 'start' instead of every update?
this.mainloop.step(timestamp, this.state.active, this.renderer);
} }
}; };

232
v3/src/boot/MainLoop.js Normal file
View file

@ -0,0 +1,232 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
// My thanks to Isaac Sukin for creating MainLoop.js, on which lots of this is based.
var MainLoop = function (framerate)
{
/**
* @property {number} timestep - The amount of time (in milliseconds) to simulate each time update() runs.
*/
this.timestep = 1000 / framerate;
/**
* @property {number} physicsStep - 1 / framerate.
*/
this.physicsStep = 1 / framerate;
/**
* @property {number} frameDelta - The cumulative amount of in-app time that hasn't been simulated yet.
*/
this.frameDelta = 0;
/**
* The timestamp in milliseconds of the last time the main loop was run.
* Used to compute the time elapsed between frames.
* @property {number} lastFrameTimeMs
*/
this.lastFrameTimeMs = 0;
/**
* @property {number} fps - An exponential moving average of the frames per second.
*/
this.fps = 60;
/**
* @property {number} lastFpsUpdate - The timestamp (in milliseconds) of the last time the `fps` moving average was updated.
*/
this.lastFpsUpdate = 0;
/**
* @property {number} framesThisSecond - The number of frames delivered in the current second.
*/
this.framesThisSecond = 0;
/**
* @property {number} numUpdateSteps - The number of times update() is called in a given frame.
*/
this.numUpdateSteps = 0;
/**
* The minimum amount of time in milliseconds that must pass since the last frame was executed
* before another frame can be executed.
* The multiplicative inverse caps the FPS (the default of zero means there is no cap)
* @property {number} minFrameDelay
*/
this.minFrameDelay = 0;
/**
* @property {boolean} running - Whether the main loop is running.
*/
this.running = false;
/**
* `true` if `MainLoop.start()` has been called and the most recent time it
* was called has not been followed by a call to `MainLoop.stop()`. This is
* different than `running` because there is a delay of a few milliseconds
* after `MainLoop.start()` is called before the application is considered
* "running." This delay is due to waiting for the next frame.
* @property {boolean} started
*/
this.started = false;
/**
* Whether the simulation has fallen too far behind real time.
* Specifically, `panic` will be set to `true` if too many updates occur in
* one frame. This is only relevant inside of animate(), but a reference is
* held externally so that this variable is not marked for garbage
* collection every time the main loop runs.
* @property {boolean} panic - Whether the simulation has fallen too far behind real time.
*/
this.panic = false;
};
MainLoop.prototype.constructor = MainLoop;
MainLoop.prototype = {
setMaxFPS: function (fps)
{
if (fps === 0)
{
this.stop();
}
else
{
this.minFrameDelay = 1000 / fps;
}
},
getMaxFPS: function ()
{
return 1000 / this.minFrameDelay;
},
resetFrameDelta: function ()
{
var oldFrameDelta = this.frameDelta;
this.frameDelta = 0;
return oldFrameDelta;
},
start: function ()
{
if (this.started)
{
return this;
}
this.started = true;
this.running = true;
this.lastFrameTimeMs = window.performance.now();
this.lastFpsUpdate = window.performance.now();
this.framesThisSecond = 0;
},
// timestamp = DOMHighResTimeStamp
// active = array containing: ({ index: i, state: state })
step: function (timestamp, active, renderer)
{
// Throttle the frame rate (if minFrameDelay is set to a non-zero value by
// `MainLoop.setMaxAllowedFPS()`).
if (active.length === 0 || timestamp < this.lastFrameTimeMs + this.minFrameDelay)
{
return;
}
// frameDelta is the cumulative amount of in-app time that hasn't been
// simulated yet. Add the time since the last frame. We need to track total
// not-yet-simulated time (as opposed to just the time elapsed since the
// last frame) because not all actually elapsed time is guaranteed to be
// simulated each frame. See the comments below for details.
this.frameDelta += timestamp - this.lastFrameTimeMs;
this.lastFrameTimeMs = timestamp;
// Run any updates that are not dependent on time in the simulation.
// Here we'll need to run things like tween.update, input.update, etc.
for (var i = 0; i < active.length; i++)
{
active[i].state.sys.begin(timestamp, this.frameDelta);
}
// Update the estimate of the frame rate, `fps`. Every second, the number
// of frames that occurred in that second are included in an exponential
// moving average of all frames per second, with an alpha of 0.25. This
// means that more recent seconds affect the estimated frame rate more than
// older seconds.
if (timestamp > this.lastFpsUpdate + 1000)
{
// Compute the new exponential moving average with an alpha of 0.25.
// Using constants inline is okay here.
this.fps = 0.25 * this.framesThisSecond + 0.75 * this.fps;
this.lastFpsUpdate = timestamp;
this.framesThisSecond = 0;
}
this.framesThisSecond++;
this.numUpdateSteps = 0;
while (this.frameDelta >= this.timestep)
{
for (var i = 0; i < active.length; i++)
{
active[i].state.sys.update(this.timestep, this.physicsStep);
}
this.frameDelta -= this.timestep;
if (++this.numUpdateSteps >= 240)
{
this.panic = true;
break;
}
}
// Render
var interpolation = this.frameDelta / this.timestep;
renderer.preRender();
for (i = 0; i < active.length; i++)
{
active[i].state.sys.render(interpolation, renderer);
}
renderer.postRender();
if (this.panic)
{
// This pattern introduces non-deterministic behavior, but in this case
// it's better than the alternative (the application would look like it
// was running very quickly until the simulation caught up to real
// time).
var discardedTime = Math.round(this.resetFrameDelta());
console.warn('Main loop panicked, tab probably put in the background. Discarding ' + discardedTime + 'ms');
}
this.panic = false;
},
stop: function ()
{
this.running = false;
this.started = false;
return this;
}
};
module.exports = MainLoop;

View file

@ -8,6 +8,9 @@ var Component = require('../components');
var MATH_CONST = require('../math/const'); var MATH_CONST = require('../math/const');
var WrapAngle = require('../math/angle/Wrap'); var WrapAngle = require('../math/angle/Wrap');
// Swap to extending a BaseTransform
// var BaseTransform = require('');
/** /**
* A Camera is your view into the game world. It has a position and size and renders only those objects within its field of view. * A Camera is your view into the game world. It has a position and size and renders only those objects within its field of view.
* The game automatically creates a single Stage sized camera on boot. Move the camera around the world with Phaser.Camera.x/y * The game automatically creates a single Stage sized camera on boot. Move the camera around the world with Phaser.Camera.x/y
@ -30,11 +33,6 @@ var Camera = function (state, x, y, viewportWidth, viewportHeight)
*/ */
this.state = state; this.state = state;
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = state.game;
this.viewportWidth = viewportWidth; this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight; this.viewportHeight = viewportHeight;

View file

@ -1,4 +1,4 @@
var CHECKSUM = { var CHECKSUM = {
build: '56117780-e25d-11e6-9df1-9b17ba01ef95' build: '1f102830-e320-11e6-897c-f1a016edce8a'
}; };
module.exports = CHECKSUM; module.exports = CHECKSUM;

View file

@ -1,8 +1,3 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/** /**
* The GameObject Factory is a global level container of Factory instances. * The GameObject Factory is a global level container of Factory instances.

View file

@ -24,7 +24,7 @@ var GameObject = function (state, x, y, texture, frame, parent)
{ {
this.state = state; this.state = state;
this.game = state.game; this.game = state.sys.game;
this.name = ''; this.name = '';

View file

@ -28,7 +28,7 @@ var Children = require('../../components/Children');
*/ */
var Blitter = function (state, x, y, key, frame) var Blitter = function (state, x, y, key, frame)
{ {
var _texture = state.game.textures.get(key); var _texture = state.sys.textures.get(key);
var _frame = _texture.get(frame); var _frame = _texture.get(frame);
GameObject.call(this, state, x, y, _texture, _frame); GameObject.call(this, state, x, y, _texture, _frame);

View file

@ -1,8 +1,3 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
var Blitter = require('./Blitter'); var Blitter = require('./Blitter');
var FactoryContainer = require('../../gameobjects/FactoryContainer'); var FactoryContainer = require('../../gameobjects/FactoryContainer');
@ -11,36 +6,15 @@ var BlitterFactory = {
KEY: 'blitter', KEY: 'blitter',
/**
* Create a new `Blitter` object.
*
* An Image is a light-weight object you can use to display anything that doesn't need physics or animation.
*
* It can still rotate, scale, crop and receive input events.
* This makes it perfect for logos, backgrounds, simple buttons and other non-Sprite graphics.
*
* @method Phaser.GameObject.Factory#image
* @param {number} [x=0] - The x coordinate of the Image. The coordinate is relative to any parent container this Image may be in.
* @param {number} [y=0] - The y coordinate of the Image. The coordinate is relative to any parent container this Image may be in.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} [key] - The image used as a texture by this display object during rendering. If a string Phaser will get for an entry in the Image Cache. Or it can be an instance of a RenderTexture, BitmapData, Video or PIXI.Texture.
* @param {string|number} [frame] - If a Texture Atlas or Sprite Sheet is used this allows you to specify the frame to be used. Use either an integer for a Frame ID or a string for a frame name.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @return {Phaser.Image} The newly created Image object.
*/
add: function (x, y, key, frame, group) add: function (x, y, key, frame, group)
{ {
if (group === undefined) { group = this.state; } if (group === undefined) { group = this.state; }
// console.log('ImageFactory.add', key, x, y, frame, group);
// console.log('into State', this.state);
return group.children.add(new Blitter(this.state, x, y, key, frame)); return group.children.add(new Blitter(this.state, x, y, key, frame));
}, },
make: function (x, y, key, frame) make: function (x, y, key, frame)
{ {
// console.log('ImageFactory.make', key, x, y, frame);
return new Blitter(this.state, x, y, key, frame); return new Blitter(this.state, x, y, key, frame);
} }

View file

@ -22,7 +22,7 @@ var GameObject = require('../GameObject');
*/ */
var Image = function (state, x, y, key, frame) var Image = function (state, x, y, key, frame)
{ {
var _texture = state.game.textures.get(key); var _texture = state.sys.textures.get(key);
var _frame = _texture.get(frame); var _frame = _texture.get(frame);
GameObject.call(this, state, x, y, _texture, _frame); GameObject.call(this, state, x, y, _texture, _frame);

View file

@ -1,4 +1,6 @@
// Include all of the Game Object Factories // Include all of the Game Object Factories
require('./image/ImageFactory'); require('./blitter/BlitterFactory');
require('./container/ContainerFactory'); require('./container/ContainerFactory');
require('./image/ImageFactory');
require('./sprite/SpriteFactory');

View file

@ -10,7 +10,7 @@ var Children = require('../../components/Children');
var Sprite = function (state, x, y, key, frame) var Sprite = function (state, x, y, key, frame)
{ {
var _texture = state.game.textures.get(key); var _texture = state.sys.textures.get(key);
var _frame = _texture.get(frame); var _frame = _texture.get(frame);
GameObject.call(this, state, x, y, _texture, _frame); GameObject.call(this, state, x, y, _texture, _frame);

View file

@ -226,16 +226,8 @@ WebGLRenderer.prototype = {
// this.projection.y = -(this.height / 2) / res; // this.projection.y = -(this.height / 2) / res;
}, },
/** // Call at the start of the render loop
* Renders the State. preRender: function ()
*
* @method render
* @param {Phaser.State} state - The State to be rendered.
* @param {number} interpolationPercentage - The cumulative amount of time that hasn't been simulated yet, divided
* by the amount of time that will be simulated the next time update()
* runs. Useful for interpolating frames.
*/
render: function (state, interpolationPercentage)
{ {
// console.log('%c render start ', 'color: #ffffff; background: #00ff00;'); // console.log('%c render start ', 'color: #ffffff; background: #00ff00;');
@ -249,50 +241,50 @@ WebGLRenderer.prototype = {
var gl = this.gl; var gl = this.gl;
// This is the old render loop - add what you need here to replace it,
// but please allow each State to render to its own Quad FBO
//var fbo = state.sys.fbo;
//fbo.activate();
// clear is needed for the FBO, otherwise corruption ... // clear is needed for the FBO, otherwise corruption ...
gl.clear(gl.COLOR_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT);
this.setBlendMode(BlendModes.NORMAL); this.setBlendMode(BlendModes.NORMAL);
},
/**
* Renders a single State.
*
* @method render
* @param {Phaser.State} state - The State to be rendered.
* @param {number} interpolationPercentage - The cumulative amount of time that hasn't been simulated yet, divided
* by the amount of time that will be simulated the next time update()
* runs. Useful for interpolating frames.
*/
render: function (state, interpolationPercentage)
{
var batch = this.batch;
// Could move to the State Systems or MainLoop // Could move to the State Systems or MainLoop
for (var c = 0; c < state.sys.children.list.length; c++) for (var c = 0; c < state.sys.children.list.length; c++)
{ {
var child = state.sys.children.list[c]; var child = state.sys.children.list[c];
child.renderWebGL(this, child, interpolationPercentage); child.renderWebGL(this, child, interpolationPercentage);
var batch = this.batch;
if (batch && batch.isFull()) if (batch && batch.isFull())
{
batch.flush(); batch.flush();
}
} }
var batch = this.batch; },
if (batch)
batch.flush();
//this.batch.stop(); // Called at the end of the render loop (tidy things up, etc)
postRender: function ()
// Call state.render here, so we can do some extra shizzle on the top {
// Maybe pass in the FBO texture too? if (this.batch)
{
//fbo.render(null); this.batch.flush();
}
// Unbind the fbo texture and replace it with an empty texture.
// If we forget this we corrupt the main context texture!
// or get `RENDER WARNING: there is no texture bound to the unit 0` spam in the console
//gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture);
// console.log('%c render end ', 'color: #ffffff; background: #ff0000;');
// Add Post-render hook // Add Post-render hook
// console.log('%c render end ', 'color: #ffffff; background: #ff0000;');
}, },
destroy: function () destroy: function ()

View file

@ -2,7 +2,7 @@ var CONST = require('./const');
var ScaleModes = require('../renderer/ScaleModes'); var ScaleModes = require('../renderer/ScaleModes');
var GetObjectValue = require('../utils/GetObjectValue'); var GetObjectValue = require('../utils/GetObjectValue');
var Settings = function (state, config) var Settings = function (config)
{ {
if (typeof config === 'string') if (typeof config === 'string')
{ {
@ -14,37 +14,32 @@ var Settings = function (state, config)
config = {}; config = {};
} }
this.state = state; // Do we actually need this reference? This could just be a property bucket return {
this.status = CONST.PENDING; status: CONST.PENDING,
// Which part of this State is currently being processed? op: CONST.BOOT,
// preload, create, update, shutdown, etc
this.op = CONST.BOOT;
this.key = GetObjectValue(config, 'key', ''); key: GetObjectValue(config, 'key', ''),
this.active = GetObjectValue(config, 'active', false); active: GetObjectValue(config, 'active', false),
this.visible = GetObjectValue(config, 'visible', true); visible: GetObjectValue(config, 'visible', true),
this.scaleMode = GetObjectValue(config, 'scaleMode', ScaleModes.DEFAULT); scaleMode: GetObjectValue(config, 'scaleMode', ScaleModes.DEFAULT),
this.fps = GetObjectValue(config, 'fps', 60); x: GetObjectValue(config, 'x', 0),
this.x = GetObjectValue(config, 'x', 0); y: GetObjectValue(config, 'y', 0),
this.y = GetObjectValue(config, 'y', 0);
// -1 means the State Manager will set it to be the Game dimensions // -1 means the State Manager will set it to be the Game dimensions
this.width = GetObjectValue(config, 'width', -1); width: GetObjectValue(config, 'width', -1),
this.height = GetObjectValue(config, 'height', -1); height: GetObjectValue(config, 'height', -1),
// Renderer Settings // Renderer Settings
this.clearBeforeRender = GetObjectValue(config, 'clearBeforeRender', true); clearBeforeRender: GetObjectValue(config, 'clearBeforeRender', true),
this.transparent = GetObjectValue(config, 'transparent', false); transparent: GetObjectValue(config, 'transparent', false),
this.autoResize = GetObjectValue(config, 'autoResize', false); autoResize: GetObjectValue(config, 'autoResize', false),
this.roundPixels = GetObjectValue(config, 'roundPixels', false); roundPixels: GetObjectValue(config, 'roundPixels', false),
this.drawToPrimaryCanvas = GetObjectValue(config, 'drawToPrimaryCanvas', false); drawToPrimaryCanvas: GetObjectValue(config, 'drawToPrimaryCanvas', false),
};
}; };
// Unless we add some actual functions in here, we'll make this just return an Object instead of an instance
Settings.prototype.constructor = Settings;
module.exports = Settings; module.exports = Settings;

View file

@ -4,7 +4,6 @@
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/ */
var Settings = require('./Settings');
var Systems = require('./Systems'); var Systems = require('./Systems');
/** /**
@ -15,39 +14,22 @@ var Systems = require('./Systems');
*/ */
var State = function (config) var State = function (config)
{ {
// The properties a State *must* have, that cannot be changed without breaking it: // The State Systems. You must never overwrite this property, or all hell will break lose.
this.game = null;
// Maybe just an object? Doesn't have to instantiate I don't think ...
this.settings = new Settings(this, config);
this.sys = new Systems(this, config); this.sys = new Systems(this, config);
// Reference to sys.children, set during sys.init only this.settings = this.sys.settings;
this.children;
}; };
State.prototype.constructor = State; State.prototype.constructor = State;
State.prototype = { State.prototype = {
// Can be overridden by your own States // Should be overridden by your own States
preUpdate: function ()
{
},
// Can be overridden by your own States
update: function () update: function ()
{ {
}, },
// Can be overridden by your own States // Should be overridden by your own States
postUpdate: function ()
{
},
// Can be overridden by your own States
render: function () render: function ()
{ {
} }

View file

@ -10,7 +10,7 @@ var State = require('./State');
var Settings = require('./Settings'); var Settings = require('./Settings');
var Systems = require('./Systems'); var Systems = require('./Systems');
var GetObjectValue = require('../utils/GetObjectValue'); var GetObjectValue = require('../utils/GetObjectValue');
// var LoaderEvent = require('../loader/events/'); var EventDispatcher = require('../events/EventDispatcher');
/** /**
* The State Manager is responsible for loading, setting up and switching game states. * The State Manager is responsible for loading, setting up and switching game states.
@ -72,9 +72,6 @@ StateManager.prototype = {
*/ */
boot: function () boot: function ()
{ {
// this.game.onPause.add(this.pause, this);
// this.game.onResume.add(this.resume, this);
for (var i = 0; i < this._pending.length; i++) for (var i = 0; i < this._pending.length; i++)
{ {
var entry = this._pending[i]; var entry = this._pending[i];
@ -185,11 +182,9 @@ StateManager.prototype = {
createStateFromInstance: function (key, newState) createStateFromInstance: function (key, newState)
{ {
newState.game = this.game;
newState.settings.key = key; newState.settings.key = key;
newState.sys.init(); newState.sys.init(this.game);
if (this.game.config.renderType === CONST.WEBGL) if (this.game.config.renderType === CONST.WEBGL)
{ {
@ -203,9 +198,7 @@ StateManager.prototype = {
{ {
var newState = new State(stateConfig); var newState = new State(stateConfig);
newState.game = this.game; newState.sys.init(this.game);
newState.sys.init();
if (this.game.config.renderType === CONST.WEBGL) if (this.game.config.renderType === CONST.WEBGL)
{ {
@ -230,7 +223,7 @@ StateManager.prototype = {
newState.settings = new Settings(newState, key); newState.settings = new Settings(newState, key);
newState.sys = new Systems(newState); newState.sys = new Systems(newState);
newState.sys.init(); newState.sys.init(this.game);
if (this.game.config.renderType === CONST.WEBGL) if (this.game.config.renderType === CONST.WEBGL)
{ {
@ -253,9 +246,9 @@ StateManager.prototype = {
newState.create = GetObjectValue(stateConfig, 'create', NOOP); newState.create = GetObjectValue(stateConfig, 'create', NOOP);
newState.shutdown = GetObjectValue(stateConfig, 'shutdown', NOOP); newState.shutdown = GetObjectValue(stateConfig, 'shutdown', NOOP);
newState.preUpdate = GetObjectValue(stateConfig, 'preUpdate', NOOP); // Game Loop level callbacks
newState.update = GetObjectValue(stateConfig, 'update', NOOP); newState.update = GetObjectValue(stateConfig, 'update', NOOP);
newState.postUpdate = GetObjectValue(stateConfig, 'postUpdate', NOOP);
newState.render = GetObjectValue(stateConfig, 'render', NOOP); newState.render = GetObjectValue(stateConfig, 'render', NOOP);
return newState; return newState;
@ -375,7 +368,6 @@ StateManager.prototype = {
// No preload? Then there was nothing to load either // No preload? Then there was nothing to load either
this.startCreate(state); this.startCreate(state);
} }
} }
}, },
@ -411,8 +403,6 @@ StateManager.prototype = {
this.active.sort(this.sortStates.bind(this)); this.active.sort(this.sortStates.bind(this));
state.sys.updates.running = true; state.sys.updates.running = true;
state.sys.mainloop.start();
}, },
pause: function (key) pause: function (key)
@ -446,96 +436,7 @@ StateManager.prototype = {
{ {
return 0; return 0;
} }
}, }
// See if we can reduce this down to just update and render
// timestamp = DOMHighResTimeStamp
step: function (timestamp)
{
for (var i = 0; i < this.active.length; i++)
{
var state = this.active[i].state;
if (state.sys.mainloop.running)
{
state.sys.mainloop.step(timestamp);
}
}
},
/*
preUpdate: function ()
{
for (var i = 0; i < this.active.length; i++)
{
var state = this.active[i].state;
for (var c = 0; c < state.sys.children.list.length; c++)
{
state.sys.children.list[c].preUpdate();
}
state.preUpdate();
}
},
update: function ()
{
for (var i = 0; i < this.active.length; i++)
{
var state = this.active[i].state;
// Invoke State Main Loop here - updating all of its systems (tweens, physics, etc)
// This shouldn't be called if the State is still loading
// Have a State.STATUS const in the Settings, dictating what is going on
for (var c = 0; c < state.sys.children.list.length; c++)
{
var child = state.sys.children.list[c];
if (child.exists)
{
child.update();
}
}
state.update();
}
},
postUpdate: function ()
{
for (var i = 0; i < this.active.length; i++)
{
var state = this.active[i].state;
for (var c = 0; c < state.sys.children.list.length; c++)
{
state.sys.children.list[c].postUpdate();
}
state.postUpdate();
}
},
render: function ()
{
for (var i = 0; i < this.active.length; i++)
{
var state = this.active[i].state;
// Can put all kinds of other checks in here, like MainLoop, FPS, etc.
if (!state.settings.visible || state.sys.color.alpha === 0 || state.sys.children.list.length === 0)
{
continue;
}
this.game.renderer.render(state);
}
},
*/
}; };

View file

@ -6,34 +6,37 @@
var EventDispatcher = require('../events/EventDispatcher'); var EventDispatcher = require('../events/EventDispatcher');
var GameObjectFactory = require('./systems/GameObjectFactory'); var GameObjectFactory = require('./systems/GameObjectFactory');
// var GameObjectCreator = require('./systems/GameObjectCreator'); var GameObjectCreator = require('./systems/GameObjectCreator');
var Loader = require('./systems/Loader'); var Loader = require('./systems/Loader');
var MainLoop = require('./systems/MainLoop');
var UpdateManager = require('./systems/UpdateManager'); var UpdateManager = require('./systems/UpdateManager');
var Component = require('../components'); var Component = require('../components');
var Camera = require('../camera/Camera'); var Camera = require('../camera/Camera');
var Settings = require('./Settings');
var Systems = function (state, config) var Systems = function (state, config)
{ {
this.state = state; this.state = state;
this.game = null;
this.config = config; this.config = config;
this.events; this.settings = Settings(config);
// Reference to the global Game level TextureManager. // Reference to the global Game level systems
this.textures; this.textureManager;
this.stateManager;
// State specific managers (Factory, Tweens, Loader, Physics, etc) // CORE SYSTEMS / PROPERTIES
// Reference to State specific managers (Factory, Tweens, Loader, Physics, etc)
this.add; this.add;
this.make; this.make;
this.input;
this.load; this.load;
this.tweens; this.events;
this.mainloop;
this.updates; this.updates;
// State specific properties (transform, data, children, etc) // State properties
this.camera; this.camera;
this.children; this.children;
this.color; this.color;
@ -47,40 +50,35 @@ Systems.prototype.constructor = Systems;
Systems.prototype = { Systems.prototype = {
init: function () init: function (game)
{ {
console.log('State.Systems.init'); console.log('State.Systems.init');
this.textures = this.state.game.textures; this.game = game;
// All of the systems can use the State level EventDispatcher, or their own
this.events = new EventDispatcher();
// State specific managers (Factory, Tweens, Loader, Physics, etc) // State specific managers (Factory, Tweens, Loader, Physics, etc)
// All these to be set by a State Config package
this.textures = game.textures;
this.events = new EventDispatcher();
this.add = new GameObjectFactory(this.state); this.add = new GameObjectFactory(this.state);
// this.make = GameObjectCreator(this.state); this.make = GameObjectCreator(this.state);
this.mainloop = new MainLoop(this.state, this.state.settings.fps);
this.updates = new UpdateManager(this.state); this.updates = new UpdateManager(this.state);
this.load = new Loader(this.state); this.load = new Loader(this.state);
// this.tweens = new Phaser.TweenManager(this.state);
// this.input = new Phaser.State.Input(this.state);
// this.physics = new Phaser.Physics.Arcade(this.state, 800, 600);
// State specific properties (transform, data, children, etc) // State specific properties (transform, data, children, etc)
this.camera = new Camera(this.state, 0, 0, 800, 600); this.camera = new Camera(this.state, 0, 0, 800, 600);
this.children = new Component.Children(this.state); this.children = new Component.Children(this.state);
this.color = new Component.Color(this.state); this.color = new Component.Color(this.state);
this.data = new Component.Data(this.state); this.data = new Component.Data(this.state);
this.transform = this.camera.transform; this.transform = this.camera.transform;
// Boot this.inject();
},
// this.input.init(); inject: function ()
{
// Defaults // Defaults properties injected into the State
this.state.events = this.events; this.state.events = this.events;
this.state.add = this.add; this.state.add = this.add;
@ -88,43 +86,47 @@ Systems.prototype = {
this.state.children = this.children; this.state.children = this.children;
this.state.color = this.color; this.state.color = this.color;
this.state.data = this.data; this.state.data = this.data;
this.state.state = this.game.state; // StateManager
this.state.camera = this.camera; this.state.camera = this.camera;
this.state.transform = this.camera.transform; this.state.transform = this.camera.transform;
this.state.textures = this.textures; this.state.textures = this.textures;
// this.state.input = this.input;
// this.state.state = this.state.game.state;
// Here we can check which Systems to install as properties into the State object
// (default systems always exist in here, regardless)
}, },
// Called just once per frame, regardless of speed
begin: function (timestamp, frameDelta) begin: function (timestamp, frameDelta)
{ {
}, },
// Potentially called multiple times per frame (on super-fast systems)
update: function (timestep, physicsStep) update: function (timestep, physicsStep)
{ {
}, for (var c = 0; c < this.children.list.length; c++)
preRender: function ()
{
},
end: function (fps, panic)
{
if (panic)
{ {
// This pattern introduces non-deterministic behavior, but in this case var child = this.children.list[c];
// it's better than the alternative (the application would look like it
// was running very quickly until the simulation caught up to real
// time).
var discardedTime = Math.round(this.mainloop.resetFrameDelta());
console.warn('Main loop panicked, probably because the browser tab was put in the background. Discarding ' + discardedTime + 'ms'); if (child.exists)
{
child.update(timestep);
}
} }
this.state.update(timestep, physicsStep);
},
// Called just once per frame, regardless of speed
render: function (interpolationPercentage, renderer)
{
this.updates.start();
if (this.settings.visible && this.color.alpha !== 0)
{
renderer.render(this.state, interpolationPercentage);
}
this.updates.stop();
this.state.render(interpolationPercentage);
} }
}; };

View file

@ -26,15 +26,10 @@ var GameObjectCreator = {
function init (state) function init (state)
{ {
console.log('Creating GameObjectCreator instance for State', state);
GameObjectCreator.state = state; GameObjectCreator.state = state;
// Load the factories into this Object // Load the factories into this Object
return FactoryContainer.load(GameObjectCreator, false);
FactoryContainer.load(GameObjectCreator, false);
return GameObjectCreator;
} }
module.exports = init; module.exports = init;

View file

@ -26,15 +26,10 @@ var GameObjectFactory = {
function init (state) function init (state)
{ {
console.log('Creating GameObjectFactory instance for State');
GameObjectFactory.state = state; GameObjectFactory.state = state;
// Load the factories into this Object // Load the factories into this Object
return FactoryContainer.load(GameObjectFactory, true); return FactoryContainer.load(GameObjectFactory, true);
// return GameObjectFactory;
} }
module.exports = init; module.exports = init;

View file

@ -1,251 +0,0 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
// My thanks to Isaac Sukin for creating MainLoop.js, on which lots of this is based.
var MainLoop = function (state, framerate)
{
/**
* @property {Phaser.State} state
*/
this.state = state;
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = state.game;
// The amount of time (in milliseconds) to simulate each time update() runs.
this.timestep = 1000 / framerate;
this.physicsStep = 1 / framerate;
// The cumulative amount of in-app time that hasn't been simulated yet.
// See the comments inside animate() for details.
this.frameDelta = 0;
// The timestamp in milliseconds of the last time the main loop was run.
// Used to compute the time elapsed between frames.
this.lastFrameTimeMs = 0;
// An exponential moving average of the frames per second.
this.fps = 60;
// The timestamp (in milliseconds) of the last time the `fps` moving
// average was updated.
this.lastFpsUpdate = 0;
// The number of frames delivered in the current second.
this.framesThisSecond = 0;
// The number of times update() is called in a given frame. This is only
// relevant inside of animate(), but a reference is held externally so that
// this variable is not marked for garbage collection every time the main
// loop runs.
this.numUpdateSteps = 0;
// The minimum amount of time in milliseconds that must pass since the last
// frame was executed before another frame can be executed. The
// multiplicative inverse caps the FPS (the default of zero means there is
// no cap).
this.minFrameDelay = 0;
// Whether the main loop is running.
this.running = false;
// `true` if `MainLoop.start()` has been called and the most recent time it
// was called has not been followed by a call to `MainLoop.stop()`. This is
// different than `running` because there is a delay of a few milliseconds
// after `MainLoop.start()` is called before the application is considered
// "running." This delay is due to waiting for the next frame.
this.started = false;
// Whether the simulation has fallen too far behind real time.
// Specifically, `panic` will be set to `true` if too many updates occur in
// one frame. This is only relevant inside of animate(), but a reference is
// held externally so that this variable is not marked for garbage
// collection every time the main loop runs.
this.panic = false;
};
MainLoop.prototype.constructor = MainLoop;
MainLoop.prototype = {
setMaxFPS: function (fps)
{
if (fps === 0)
{
this.stop();
}
else
{
this.minFrameDelay = 1000 / fps;
}
},
getMaxFPS: function ()
{
return 1000 / this.minFrameDelay;
},
resetFrameDelta: function ()
{
var oldFrameDelta = this.frameDelta;
this.frameDelta = 0;
return oldFrameDelta;
},
start: function ()
{
if (this.started)
{
return this;
}
this.started = true;
this.running = true;
this.lastFrameTimeMs = window.performance.now();
this.lastFpsUpdate = window.performance.now();
this.framesThisSecond = 0;
},
// timestamp = DOMHighResTimeStamp
step: function (timestamp)
{
// Throttle the frame rate (if minFrameDelay is set to a non-zero value by
// `MainLoop.setMaxAllowedFPS()`).
if (timestamp < this.lastFrameTimeMs + this.minFrameDelay)
{
return;
}
// frameDelta is the cumulative amount of in-app time that hasn't been
// simulated yet. Add the time since the last frame. We need to track total
// not-yet-simulated time (as opposed to just the time elapsed since the
// last frame) because not all actually elapsed time is guaranteed to be
// simulated each frame. See the comments below for details.
this.frameDelta += timestamp - this.lastFrameTimeMs;
this.lastFrameTimeMs = timestamp;
// Run any updates that are not dependent on time in the simulation.
// Here we'll need to run things like tween.update, input.update, etc.
this.state.sys.begin(timestamp, this.frameDelta);
// Update the estimate of the frame rate, `fps`. Every second, the number
// of frames that occurred in that second are included in an exponential
// moving average of all frames per second, with an alpha of 0.25. This
// means that more recent seconds affect the estimated frame rate more than
// older seconds.
if (timestamp > this.lastFpsUpdate + 1000)
{
// Compute the new exponential moving average with an alpha of 0.25.
// Using constants inline is okay here.
this.fps = 0.25 * this.framesThisSecond + 0.75 * this.fps;
this.lastFpsUpdate = timestamp;
this.framesThisSecond = 0;
}
this.framesThisSecond++;
this.numUpdateSteps = 0;
var step = this.timestep;
while (this.frameDelta >= step)
{
// this.update(this.timestep);
this.state.sys.update(step, this.physicsStep);
for (var c = 0; c < this.state.sys.children.list.length; c++)
{
var child = this.state.sys.children.list[c];
if (child.exists)
{
child.update(step);
}
}
// Dev level callback
this.state.update(step);
this.frameDelta -= this.timestep;
if (++this.numUpdateSteps >= 240)
{
this.panic = true;
break;
}
}
this.state.sys.preRender();
this.state.sys.updates.start();
if (this.state.settings.visible && this.state.sys.color.alpha !== 0)
{
this.game.renderer.render(this.state, this.frameDelta / this.timestep);
}
this.state.sys.updates.stop();
// Run any updates that are not dependent on time in the simulation.
this.state.sys.end(this.fps, this.panic);
this.panic = false;
},
/*
update: function (timestep)
{
this.state.sys.update(timestep);
var c;
var child;
for (var c = 0; c < this.state.sys.children.list.length; c++)
{
var child = this.state.sys.children.list[c];
if (child.exists)
{
child.update(timestep);
}
}
// Dev level callback
this.state.update(timestep);
for (c = 0; c < this.state.sys.children.list.length; c++)
{
var child = this.state.sys.children.list[c];
if (child.exists)
{
child.update(timestep);
}
}
},
*/
stop: function ()
{
this.running = false;
this.started = false;
return this;
}
};
module.exports = MainLoop;

View file

@ -13,8 +13,6 @@ var UpdateManager = function (state)
{ {
this.state = state; this.state = state;
this.game = state.game;
this.list = []; this.list = [];
// this.i = 1; // this.i = 1;