diff --git a/.eslintrc b/.eslintrc index e77de23..949f670 100644 --- a/.eslintrc +++ b/.eslintrc @@ -11,7 +11,8 @@ }, "env": { "node": true, - "browser": true + "browser": true, + "worker": true }, "extends": "eslint:recommended" } diff --git a/.gitignore b/.gitignore index 1fcd898..a24e9cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ .idea/workspace.xml .idea/dictionaries /node_modules +/build/render-webgl.js /build/render-webgl.js.map /build/render-webgl.min.js -/build/render-webgl.js /build/render-webgl.min.js.map +/build/render-webgl-worker.js +/build/render-webgl-worker.js.map +/build/render-webgl-worker.min.js +/build/render-webgl-worker.min.js.map diff --git a/build/demo.html b/build/demo.html index 77c4d66..e38f76e 100644 --- a/build/demo.html +++ b/build/demo.html @@ -19,10 +19,6 @@ diff --git a/build/demoWorker.js b/build/demoWorker.js new file mode 100644 index 0000000..a9f5258 --- /dev/null +++ b/build/demoWorker.js @@ -0,0 +1,56 @@ + +importScripts('./render-webgl-worker.js'); + +var remote; +var drawableID; +var drawableID2; +var fudge = 90; + +onmessage = function(message) { + + if (message.data.id == 'RendererConnected') { + initWorker(); + } + else if (message.data.fudge != undefined) { + fudge = message.data.fudge; + } + + remote.onmessage(message); +}; + +function initWorker() { + remote = new RenderWebGLRemote(); + var create1 = remote.createDrawable(); + var create2 = remote.createDrawable(); + + create1.then(function (id) { + drawableID = id; + remote.updateDrawableProperties(drawableID, { + position: [0, 0], + scale: 100, + direction: 90 + }); + }); + create2.then(function (id) { + drawableID2 = id; + remote.updateDrawableProperties(drawableID2, { + skin: '09dc888b0b7df19f70d81588ae73420e.svg' + }); + }); + + Promise.all([create1, create2]).then(function () { + setInterval(thinkStep, 1 / 60); + }); +} + +function thinkStep() { + //direction += 0.1; + + var props = {}; + //props.position = [posX, posY]; + props.direction = fudge; + //props.pixelate = fudge; + //props.scale = 100; + + remote.updateDrawableProperties(drawableID, props); +} diff --git a/package.json b/package.json index 1cc2d7a..e9c17fa 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "eslint": "2.7.0", "json-loader": "0.5.4", + "promise": "^7.1.1", "svg-to-image": "1.1.3", "tap": "5.7.1", "twgl.js": "1.5.2", diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 4d6e8fa..f88c3b8 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -3,6 +3,7 @@ var twgl = require('twgl.js'); var util = require('util'); var Drawable = require('./Drawable'); +var WorkerMessages = require('./WorkerMessages'); var ShaderManager = require('./ShaderManager'); /** @@ -196,20 +197,6 @@ RenderWebGL.prototype.destroyDrawable = function (drawableID) { return false; }; -/** - * Update the position, direction, scale, or effect properties of this Drawable. - * @param {int} drawableID The ID of the Drawable to update. - * @param {Object.} properties The new property values to set. - */ -RenderWebGL.prototype.updateDrawableProperties = function ( - drawableID, properties) { - - var drawable = Drawable.getDrawableByID(drawableID); - if (drawable) { - drawable.updateProperties(properties); - } -}; - /** * Retrieve the renderer's projection matrix. * @returns {module:twgl/m4.Mat4} The projection matrix. @@ -470,3 +457,45 @@ RenderWebGL.prototype.isTouchingColor = function(drawableID, color3b, mask3b) { return false; }; + +/** + * Handle an event (message) from the specified worker. + * @param {Worker} worker The originating worker for the event. + * @param {MessageEvent} message The event to be handled. + * @private + */ +RenderWebGL.prototype._onWorkerMessage = function(worker, message) { + if (message.data.drawableID != null) { + var drawable = Drawable.getDrawableByID(message.data.drawableID); + } + switch(message.data.id) { + case WorkerMessages.ToRenderer.Ping: + worker.postMessage(WorkerMessages.FromRenderer.Pong); + break; + case WorkerMessages.ToRenderer.CreateDrawable: + worker.postMessage({ + id: WorkerMessages.FromRenderer.ResultValue, + token: message.data.token, + value: this.createDrawable() + }); + break; + case WorkerMessages.ToRenderer.UpdateDrawableProperties: + drawable.updateProperties(message.data.properties); + break; + } +}; + +/** + * Listen for messages from a worker. + * The renderer will post a message to this worker with data='rendererConnected' + * immediately. After that, the renderer will not send messages to the worker + * except in response to messages from that worker. + * @param {Worker} worker Listen to this worker. + */ +RenderWebGL.prototype.connectWorker = function(worker) { + var instance = this; + worker.addEventListener('message', function (event) { + instance._onWorkerMessage(worker, event); + }); + worker.postMessage({id: WorkerMessages.FromRenderer.RendererConnected}); +}; diff --git a/src/WorkerMessages.js b/src/WorkerMessages.js new file mode 100644 index 0000000..eaf5662 --- /dev/null +++ b/src/WorkerMessages.js @@ -0,0 +1,58 @@ + +/** + * All messages sent to or from the renderer. + */ +var WorkerMessages = { + + /** + * Messages that are sent to the renderer from a worker. + * A message should have this form: + * postMessage({ + * id: MessagesToRenderer.ping, + * token: 'uniqueString', + * ... + * }); + * If the renderer replies to the message, the 'token' property will be + * copied into the reply message. If a message generates no reply, the + * 'token' property is optional. + * @enum {string} + */ + ToRenderer: { + Ping: 'Ping', + CreateDrawable: 'CreateDrawable', + UpdateDrawableProperties: 'UpdateDrawableProperties' + }, + + /** + * Messages that are sent from the renderer to a worker. + * A message will have this form: + * postMessage({ + * id: MessagesFromRenderer.ping, + * token: 'uniqueString', + * ... + * }); + * If the message is being sent in reply to another message from the worker, + * the 'token' property will match the originating message. Otherwise the + * 'token' property will be undefined. + * @enum {string} + */ + FromRenderer: { + /** + * The renderer has connected to this worker. + */ + RendererConnected: 'RendererConnected', + + /** + * The response to a Ping from a worker. + */ + Pong: 'Pong', + + /** + * The message will contain a 'value' field with the result of the + * request with matching token. + */ + ResultValue: 'ResultValue' + } +}; + +module.exports = WorkerMessages; diff --git a/src/WorkerRemote.js b/src/WorkerRemote.js new file mode 100644 index 0000000..b62f2d3 --- /dev/null +++ b/src/WorkerRemote.js @@ -0,0 +1,84 @@ + +var Promise = require('promise'); + +var WorkerMessages = require('./WorkerMessages'); + +function WorkerRemote() { + var instance = this; + + /** + * Handle a message from this worker's host. + * Call this from your worker's onmessage function or install it directly. + * @param {MessageEvent} message The message to be handled. + */ + this.onmessage = function(message) { + instance._onmessage(message); + }; + + /** + * Mapping of message token to Promise resolve function. + * @type {Object.} + * @private + */ + this._pendingTokens = {}; + + this._nextToken = 0; +} + +module.exports = WorkerRemote; + +WorkerRemote.prototype._getToken = function() { + return (this._nextToken++) + ''; +}; + +/** + * Actually handle a message from this worker's host. + * @param {MessageEvent} message The message to be handled. + * @private + */ +WorkerRemote.prototype._onmessage = function(message) { + + // It's sometimes valid for a message to have no token + var token = message.data.token; + if (token != undefined) { + var resolve = this._pendingTokens[token]; + delete this._pendingTokens[token]; + } + + switch(message.data.id) { + case WorkerMessages.FromRenderer.ResultValue: + resolve(message.data.value); + break; + } +}; + +/** + * Create a new Drawable and add it to the scene. + * @returns {int} The ID of the new Drawable. + */ +WorkerRemote.prototype.createDrawable = function() { + var instance = this; + return new Promise(function (resolve) { + var token = instance._getToken(); + instance._pendingTokens[token] = resolve; + self.postMessage({ + id: WorkerMessages.ToRenderer.CreateDrawable, + token: token + }); + }); +}; + +/** + * Update the position, direction, scale, or effect properties of this Drawable. + * @param {int} drawableID The ID of the Drawable to update. + * @param {Object.} properties The new property values to set. + */ +WorkerRemote.prototype.updateDrawableProperties = function ( + drawableID, properties) { + + self.postMessage({ + id: WorkerMessages.ToRenderer.UpdateDrawableProperties, + drawableID: drawableID, + properties: properties + }); +}; diff --git a/src/index.js b/src/index.js index a383065..a9f326e 100644 --- a/src/index.js +++ b/src/index.js @@ -4,4 +4,4 @@ var RenderWebGL = require('./RenderWebGL'); * Export and bind to `window` */ module.exports = RenderWebGL; -if (typeof window !== 'undefined') window.RenderWebGL = RenderWebGL; +if (typeof self !== 'undefined') self.RenderWebGL = RenderWebGL; diff --git a/src/worker.js b/src/worker.js new file mode 100644 index 0000000..4de73c3 --- /dev/null +++ b/src/worker.js @@ -0,0 +1,4 @@ +var WorkerRemote = require('./WorkerRemote'); + +module.exports = WorkerRemote; +if (typeof self !== 'undefined') self.RenderWebGLRemote = WorkerRemote; diff --git a/webpack.config.js b/webpack.config.js index 1a5f8a2..6d43c79 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,7 +4,9 @@ var webpack = require('webpack'); module.exports = { entry: { 'render-webgl': './src/index.js', - 'render-webgl.min': './src/index.js' + 'render-webgl.min': './src/index.js', + 'render-webgl-worker': './src/worker.js', + 'render-webgl-worker.min': './src/worker.js' }, devtool: 'source-map', output: {