Created
September 5, 2016 08:01
-
-
Save IceCreamYou/6b16d8350bc4125a08c76b499e0d4c59 to your computer and use it in GitHub Desktop.
Extends MainLoop.js to support multiple loops.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Adds the ability to have multiple main loops to MainLoop.js. | |
* | |
* The functionality in this file modifies the MainLoop object. | |
*/ | |
(function(root) { | |
// All the active MainLoop instances. | |
var instances = [], | |
// The default behavior for each hook is to do nothing. Using an empty | |
// function is slightly faster than a conditional existence check. | |
noop = function() {}, | |
/** | |
* The MainLoop Instance class. | |
* | |
* Use this class when you need multiple main loops, for example when you | |
* are independently animating multiple canvases or if you are writing a | |
* library and your users want to use MainLoop to do things that your | |
* library does not cover. You can also use this if you have multiple | |
* modules that need to act during the main loop. | |
* | |
* Using this class will be slightly slower than just using one main loop. | |
* Additionally, using this class increases the chance of mistakes due to | |
* multiple loops touching shared state. Shared state is not always | |
* obvious. One example is that the `panic` parameter to the `update` | |
* callback is shared, so if multiple loops respond to it (for example, by | |
* prompting the user) the result may be unintended. For more discussion, | |
* see this thread: https://github.com/IceCreamYou/MainLoop.js/issues/5 | |
* | |
* Note that MainLoop Instances do not have all the same capabilities as | |
* the root MainLoop object because the MainLoop object manages state that | |
* is global for everything in the active tab. Methods on the MainLoop | |
* object affect all MainLoop Instances. | |
* | |
* @class Instance | |
* @member MainLoop | |
*/ | |
Instance = { | |
/** | |
* Starts this main loop. | |
* | |
* If the loop is already started, calling this method does nothing. | |
* | |
* See also `Instance#stop()`, `MainLoop.start()`, and | |
* `Instance#isRunning()`. | |
*/ | |
start: function() { | |
if (!this.isRunning()) { | |
instances.push(this); | |
} | |
return this; | |
}, | |
/** | |
* Stops this main loop. | |
* | |
* If the loop is already stopped, calling this method does nothing. | |
* | |
* See also `Instance#start()`, `MainLoop.stop()`, and | |
* `Instance#isRunning()`. | |
*/ | |
stop: function() { | |
for (var i = instances.length - 1; i >= 0; i--) { | |
if (instances[i] === this) { | |
instances.splice(i, 1); | |
} | |
} | |
return this; | |
}, | |
/** | |
* Returns whether this main loop is currently running. | |
* | |
* Note that it is possible for this method to return `true` while | |
* `MainLoop.isRunning()` returns `false` during the frame when the | |
* root main loop is started. See `MainLoop.isRunning()` for more | |
* detail on this and how to work around it if needed. | |
* | |
* See also `Instance#start()` and `Instance#stop()`. | |
* | |
* @return {Boolean} | |
* Whether this main loop is currently running. | |
*/ | |
isRunning: function() { | |
return instances.indexOf(this) > -1; | |
}, | |
// Instance callbacks. See the MainLoop documentation for explanations. | |
begin: noop, | |
update: noop, | |
draw: noop, | |
end: noop, | |
// Setters for the Instance callbacks. See the documentation for the | |
// corresponding functions on the MainLoop object for explanations. | |
setBegin: function(callback) { this.begin = callback; return this; }, | |
setUpdate: function(callback) { this.update = callback; return this; }, | |
setDraw: function(callback) { this.draw = callback; return this; }, | |
setEnd: function(callback) { this.end = callback; return this; }, | |
}; | |
/** | |
* Creates a new main loop. | |
* | |
* MainLoop instance functionality can be used as a mixin by passing a | |
* prototype or instance of another class as the first parameter to this | |
* function. | |
* | |
* Example usage: | |
* | |
* ```javascript | |
* var ctx1 = document.getElementById('canvas1').getContext('2d'), | |
* ctx2 = document.getElementById('canvas2').getContext('2d'); | |
* | |
* // Create a loop and do something in the "draw" hook | |
* var loop1 = MainLoop.createInstance().setDraw(function(timestep) { | |
* ctx1.fillRect((Math.cos(Date.now()/5) + 1) * ctx1.canvas.width - 10, 0, 10, 10); | |
* }).start(); | |
* | |
* // Create a class that displays on a second canvas | |
* function Thing() { | |
* this.x = 0; | |
* this.y = 0; | |
* this.start(); | |
* } | |
* MainLoop.createInstance(Thing.prototype); | |
* Thing.prototype.draw = function() { | |
* ctx2.fillRect(x - 5, y - 5, 10, 10); | |
* }; | |
* var t = new Thing(); | |
* ``` | |
* | |
* @param {Object} [obj] | |
* If `null` or not specified, a new main loop will be created. Otherwise, | |
* the functionality of a main loop instance will be mixed into the passed | |
* object. This makes it easy to have classes that automatically register | |
* themselves with the main loop. | |
* @param {Object} [MainLoop] | |
* If this function is imported using a module system, it won't be able to | |
* find the MainLoop object on its own. Instead, the MainLoop object needs to | |
* be passed into this function at least once so that instances can register | |
* themselves to be called during the tab's render loop. | |
* | |
* @return {Object} | |
* A MainLoop Instance. | |
* | |
* @member Instance | |
*/ | |
function createMainLoopInstance(obj, MainLoop) { | |
// Configure the MainLoop object if needed. | |
if (typeof MainLoop !== 'undefined') { | |
setUpMainLoop(MainLoop); | |
} | |
// Create a new object to be the MainLoop instance or mix in the | |
// functionality into an existing object if available. | |
if (!(obj instanceof Object) || obj === null) { | |
obj = {}; | |
} | |
for (var key in Instance) { | |
if (Instance.hasOwnProperty(key)) { | |
obj[key] = Instance[key]; | |
} | |
} | |
return obj; | |
} | |
/** | |
* Configure the MainLoop object to manage multiple loops. | |
* | |
* This modifies the MainLoop object. | |
* | |
* @param {Object} MainLoop | |
* The MainLoop object to modify. | |
*/ | |
function setUpMainLoop(MainLoop) { | |
// Don't do anything if the MainLoop object has already been configured. | |
if (typeof MainLoop.setBegin === 'undefined') { | |
return; | |
} | |
// Run the instance callbacks during each hook. | |
MainLoop.setBegin(function(timestamp, frameDelta) { | |
for (var i = 0, l = instances.length; i < l; i++) instances[i].begin(timestamp, frameDelta); | |
}); | |
MainLoop.setUpdate(function(timestep) { | |
for (var i = 0, l = instances.length; i < l; i++) instances[i].update(timestep); | |
}); | |
MainLoop.setDraw(function(timestep) { | |
for (var i = 0, l = instances.length; i < l; i++) instances[i].draw(timestep); | |
}); | |
MainLoop.setEnd(function(fps, panic) { | |
for (var i = 0, l = instances.length; i < l; i++) instances[i].end(fps, panic); | |
}); | |
// Remove the hooks on the MainLoop object so that the ability to register | |
// instances is not overwritten. | |
delete MainLoop.setBegin; | |
delete MainLoop.setUpdate; | |
delete MainLoop.setDraw; | |
delete MainLoop.setEnd; | |
// Attach the instance creation function. | |
MainLoop.createInstance = createMainLoopInstance; | |
// Start the loop so that when instances call their `start` methods, their | |
// callbacks start getting called. | |
MainLoop.start(); | |
} | |
// If we can detect the MainLoop object, go ahead and configure it. | |
if (typeof root.MainLoop !== 'undefined') { | |
setUpMainLoop(root.MainLoop); | |
} | |
// Support AMD. | |
if (typeof define === 'function' && define.amd) { | |
define(createMainLoopInstance); | |
} | |
// Support CommonJS. | |
else if (typeof module === 'object' && module !== null && typeof module.exports === 'object') { | |
module.exports = createMainLoopInstance; | |
} | |
})(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment