diff --git a/modules/core/src/lib/animation-loop-proxy.js b/modules/core/src/lib/animation-loop-proxy.js index a1a80623f..7bc0b980f 100644 --- a/modules/core/src/lib/animation-loop-proxy.js +++ b/modules/core/src/lib/animation-loop-proxy.js @@ -3,6 +3,33 @@ import {getPageLoadPromise, getCanvas} from '@luma.gl/webgl'; import {requestAnimationFrame, cancelAnimationFrame} from '@luma.gl/webgl'; import {log, assert} from '../utils'; +function initializeCanvas(_self, canvas) { + const eventHandlers = new Map(); + + canvas.addEventListener = (type, handler) => { + _self.postMessage({command: 'addEventListener', type}); + if (!eventHandlers.has(type)) { + eventHandlers.set(type, []); + } + eventHandlers.get(type).push(handler); + }; + canvas.removeEventListener = (type, handler) => { + _self.postMessage({command: 'removeEventListener', type}); + const handlers = eventHandlers.get(type); + if (handlers) { + handlers.splice(handlers.indexOf(handler), 1); + } + }; + canvas.dispatchEvent = (type, event) => { + const handlers = eventHandlers.get(type); + if (handlers) { + handlers.forEach(handler => handler(event)); + } + }; + + _self.canvas = canvas; +} + export default class AnimationLoopProxy { // Create the script for the rendering worker. // @param opts {object} - options to construct an AnimationLoop instance @@ -15,39 +42,12 @@ export default class AnimationLoopProxy { }); self.canvas = null; - - function initializeCanvas(canvas) { - const eventHandlers = new Map(); - - canvas.addEventListener = (type, handler) => { - self.postMessage({command: 'addEventListener', type}); - if (!eventHandlers.has(type)) { - eventHandlers.set(type, []); - } - eventHandlers.get(type).push(handler); - }; - canvas.removeEventListener = (type, handler) => { - self.postMessage({command: 'removeEventListener', type}); - const handlers = eventHandlers.get(type); - if (handlers) { - handlers.splice(handlers.indexOf(handler), 1); - } - }; - canvas.dispatchEvent = (type, event) => { - const handlers = eventHandlers.get(type); - if (handlers) { - handlers.forEach(handler => handler(event)); - } - }; - - self.canvas = canvas; - } - - self.addEventListener('message', evt => { - switch (evt.data.command) { + self.onmessage = evt => { + const message = evt.data; + switch (message.command) { case 'start': - initializeCanvas(evt.data.opts.canvas); - animationLoop.start(evt.data.opts); + initializeCanvas(self, message.opts.canvas); + animationLoop.start(message.opts); break; case 'stop': @@ -55,17 +55,17 @@ export default class AnimationLoopProxy { break; case 'resize': - self.canvas.width = evt.data.width; - self.canvas.height = evt.data.height; + self.canvas.width = message.width; + self.canvas.height = message.height; break; case 'event': - self.canvas.dispatchEvent(evt.data.type, evt.data.event); + self.canvas.dispatchEvent(message.type, message.event); break; default: } - }); + }; }; } @@ -99,8 +99,6 @@ export default class AnimationLoopProxy { this._running = false; this._animationFrameId = null; - this._resolveNextFrame = null; - this._nextFramePromise = null; // bind methods this._onMessage = this._onMessage.bind(this); @@ -151,8 +149,6 @@ export default class AnimationLoopProxy { if (this._running) { cancelAnimationFrame(this._animationFrameId); this._animationFrameId = null; - this._nextFramePromise = null; - this._resolveNextFrame = null; this._running = false; this.props.onFinalize(this); } @@ -160,15 +156,6 @@ export default class AnimationLoopProxy { return this; } - waitForRender() { - if (!this._nextFramePromise) { - this._nextFramePromise = new Promise(resolve => { - this._resolveNextFrame = resolve; - }); - } - return this._nextFramePromise; - } - // PRIVATE METHODS _onMessage(evt) { @@ -210,11 +197,6 @@ export default class AnimationLoopProxy { _updateFrame() { this._resizeCanvasDrawingBuffer(); - if (this._resolveNextFrame) { - this._resolveNextFrame(this); - this._nextFramePromise = null; - this._resolveNextFrame = null; - } this._animationFrameId = requestAnimationFrame(this._updateFrame); } @@ -224,7 +206,7 @@ export default class AnimationLoopProxy { // Create an offscreen canvas controlling the main canvas if (!screenCanvas.transferControlToOffscreen) { - log.error('OffscreenCanvas is not available in your browser.')(); // eslint-disable-line + log.error('OffscreenCanvas is not available in your browser.')(); } const offscreenCanvas = screenCanvas.transferControlToOffscreen(); diff --git a/modules/core/test/lib/animation-loop-proxy.spec.js b/modules/core/test/lib/animation-loop-proxy.spec.js index 3254adb5f..d445676ea 100644 --- a/modules/core/test/lib/animation-loop-proxy.spec.js +++ b/modules/core/test/lib/animation-loop-proxy.spec.js @@ -1,12 +1,105 @@ -import {_AnimationLoopProxy} from '@luma.gl/core'; +import {AnimationLoop, _AnimationLoopProxy as AnimationLoopProxy} from '@luma.gl/core'; import test from 'tape-catch'; -// import {fixture} from 'test/setup'; +import {fixture} from 'test/setup'; -test('core#AnimationLoopProxy', t => { - // const {gl} = fixture; - t.ok(_AnimationLoopProxy); +function getMockCanvas() { + return { + getContext: () => fixture.gl + }; +} + +test('core#AnimationLoopProxy.createWorker', t => { + const mockWorkerContext = {}; + + const animationLoop = new AnimationLoop(); + + const getWorker = AnimationLoopProxy.createWorker(animationLoop); + t.ok(typeof getWorker === 'function', 'createWorker returns function'); + + getWorker(mockWorkerContext); + t.ok(typeof mockWorkerContext.onmessage === 'function', 'worker is initialized'); + + t.end(); +}); + +test('core#AnimationLoopProxy#events', t => { + // create mock worker context + const outboundMessages = []; + const mockWorkerContext = { + postMessage: data => outboundMessages.push(data) + }; + + // create mock canvas + const mockCanvas = getMockCanvas(); + const clickCalled = []; + const onClick = evt => clickCalled.push(evt); + + const animationLoop = new AnimationLoop(); + + AnimationLoopProxy.createWorker(animationLoop)(mockWorkerContext); + + mockWorkerContext.onmessage({ + data: { + command: 'start', + opts: {canvas: mockCanvas} + } + }); + + mockCanvas.addEventListener('click', onClick); + t.deepEqual( + outboundMessages.pop(), + {command: 'addEventListener', type: 'click'}, + 'notifies main thread' + ); + + mockWorkerContext.onmessage({ + data: { + command: 'event', + type: 'click', + event: {id: 1} + } + }); + t.deepEqual(clickCalled, [{id: 1}], 'event is handled'); + + mockWorkerContext.onmessage({ + data: { + command: 'event', + type: 'hover', + event: {id: 2} + } + }); + t.deepEqual(clickCalled, [{id: 1}], 'event is not handled'); + + mockCanvas.removeEventListener('click', onClick); + t.deepEqual( + outboundMessages.pop(), + {command: 'removeEventListener', type: 'click'}, + 'notifies main thread' + ); + + mockWorkerContext.onmessage({ + data: { + command: 'event', + type: 'hover', + event: {id: 3} + } + }); + t.deepEqual(clickCalled, [{id: 1}], 'event is not handled'); + + mockWorkerContext.onmessage({ + data: { + command: 'resize', + width: 100, + height: 100 + } + }); + t.is(mockCanvas.width, 100, 'canvas size is updated'); + + mockWorkerContext.onmessage({ + data: { + command: 'stop' + } + }); - // const animationLoop = new _AnimationLoopProxy(gl).start().stop(); - // animationLoop.delete(); t.end(); }); diff --git a/test/apps/webpack.config.local.js b/test/apps/webpack.config.local.js new file mode 100644 index 000000000..7ad6d9c72 --- /dev/null +++ b/test/apps/webpack.config.local.js @@ -0,0 +1 @@ +module.exports = require('../../examples/webpack.config.local'); diff --git a/test/apps/wip/worker/app.js b/test/apps/wip/worker/app.js index a6324ce2d..4b537d383 100644 --- a/test/apps/wip/worker/app.js +++ b/test/apps/wip/worker/app.js @@ -1,14 +1,7 @@ import {_AnimationLoopProxy as AnimationLoopProxy} from '@luma.gl/core'; -import createWorker from 'webworkify-webpack'; -// Required by webworkify-webpack :( -const worker = createWorker(require.resolve('./worker')); +const worker = new Worker('./worker.js'); const animationLoop = new AnimationLoopProxy(worker); -export default animationLoop; - -/* global window */ -if (!window.website) { - animationLoop.start(); -} +animationLoop.start(); \ No newline at end of file diff --git a/test/apps/wip/worker/package.json b/test/apps/wip/worker/package.json index ac16d05f6..224cb5da9 100644 --- a/test/apps/wip/worker/package.json +++ b/test/apps/wip/worker/package.json @@ -6,8 +6,7 @@ "start-local": "webpack-dev-server --env.local --progress --hot --open -d" }, "dependencies": { - "luma.gl": "^5.2.0", - "webworkify-webpack": "^2.1.0" + "luma.gl": "^7.1.0" }, "devDependencies": { "html-webpack-plugin": "^3.2.0", diff --git a/test/apps/wip/worker/webpack.config.js b/test/apps/wip/worker/webpack.config.js index 69f0a3ca1..a19d796f1 100644 --- a/test/apps/wip/worker/webpack.config.js +++ b/test/apps/wip/worker/webpack.config.js @@ -1,12 +1,20 @@ const {resolve} = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const EXAMPLE_DIR = resolve(__dirname, '../../../../examples') + const CONFIG = { mode: 'development', devServer: { // Static assets - contentBase: resolve(__dirname, '../../core/picking/') + contentBase: resolve(__dirname, 'core/picking/') + }, + + resolve: { + alias: { + examples: EXAMPLE_DIR + } }, entry: { @@ -16,5 +24,21 @@ const CONFIG = { plugins: [new HtmlWebpackPlugin({title: 'Shadowmap'})] }; +const WORKER_CONFIG = { + target: 'webworker', + + entry: { + worker: resolve('./worker.js') + }, + + plugins: [] +}; + // This line enables bundling against src in this repo rather than installed module -module.exports = env => (env ? require('../../webpack.config.local')(CONFIG)(env) : CONFIG); +module.exports = env => { + const config = env ? require('../../webpack.config.local')(CONFIG)(env) : CONFIG; + + const workerConfig = Object.assign({}, config, WORKER_CONFIG); + + return [config, workerConfig]; +}; diff --git a/test/apps/wip/worker/worker.js b/test/apps/wip/worker/worker.js index 3d9e431cd..5aded36f3 100644 --- a/test/apps/wip/worker/worker.js +++ b/test/apps/wip/worker/worker.js @@ -1,12 +1,12 @@ import {_AnimationLoopProxy as AnimationLoopProxy} from '@luma.gl/core'; -import animationLoop from '../../core/cubemap/app'; -// import animationLoop from '../../core/fragment/app'; -// import animationLoop from '../../core/instancing/app'; -// import animationLoop from '../../core/mandelbrot/app'; -// import animationLoop from '../../core/picking/app'; -// import animationLoop from '../../core/shadowmap/app'; -// import animationLoop from '../../core/transform/app'; -// import animationLoop from '../../core/transform-feedback/app'; +// import AnimationLoop from 'examples/core/cubemap/app'; +// import AnimationLoop from 'examples/core/fragment/app'; +// import AnimationLoop from 'examples/core/instancing/app'; +// import AnimationLoop from 'examples/core/mandelbrot/app'; +// import AnimationLoop from 'examples/core/picking/app'; +import AnimationLoop from 'examples/core/shadowmap/app'; +// import AnimationLoop from 'examples/core/transform/app'; +// import AnimationLoop from 'examples/core/transform-feedback/app'; -export default AnimationLoopProxy.createWorker(animationLoop); +export default AnimationLoopProxy.createWorker(new AnimationLoop())(self);