Commit a0aeff29 authored by Roque's avatar Roque

erp5_standard_babylonjs: gadget with babylon js basic example running on a web worker

parent 83009bb9
<!DOCTYPE html>
<html>
<!--
data-i18n=Others
data-i18n=Tools
-->
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Drone Simulator</title>
<link rel="http://www.renderjs.org/rel/interface" href="interface_page.html">
<!-- renderjs -->
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<!-- custom script -->
<script src="jiodev.js" type="text/javascript"></script>
<script src="gadget_global.js" type="text/javascript"></script>
<script src="domsugar.js" type="text/javascript"></script>
<script src="gadget_erp5_page_babylonjs_main.js" type="text/javascript"></script>
<script src="gadget_erp5_page_babylonjs_gadget.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
\ No newline at end of file
/*global window, rJS, domsugar, DroneGameManager*/
/*jslint nomen: true, indent: 2, maxlen: 80, white: true, evil: false */
(function (window, rJS, domsugar, DroneGameManager) {
"use strict";
var LOGIC_FILE_LIST = ['gadget_erp5_page_babylonjs_logic.js'],
WIDTH = 680, HEIGHT = 340;
rJS(window)
/////////////////////////////////////////////////////////////////
// Acquired methods
/////////////////////////////////////////////////////////////////
.declareAcquiredMethod("updateHeader", "updateHeader")
.declareAcquiredMethod("translate", "translate")
.declareMethod('render', function (options) {
var gadget = this,
canvas = domsugar('canvas'),
offscreen;
domsugar(gadget.element, [canvas]);
canvas.width = options.width || WIDTH;
canvas.height = options.height || HEIGHT;
// https://doc.babylonjs.com/divingDeeper/scene/offscreenCanvas
offscreen = canvas.transferControlToOffscreen();
gadget.runGame({
logic_url_list: LOGIC_FILE_LIST,
canvas: offscreen,
canvas_original: canvas,
width: canvas.width,
height: canvas.height
});
return gadget.translate('BabylonJS Canvas In Web Worker')
.push(function (translated) {
return gadget.updateHeader({
page_title: translated,
page_icon: 'puzzle-piece'
});
});
})
.declareJob('runGame', function runGame(options) {
var game_manager = new DroneGameManager();
return game_manager.play(options);
});
}(window, rJS, domsugar, DroneGameManager));
\ No newline at end of file
/*global window, rJS, RSVP, BABYLON, console*/
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
var runGame, updateGame;
(function () {
"use strict";
runGame = function (canvas) {
console.log('runGame', canvas);
var finish_deferred = RSVP.defer(), engine, scene, camera, light, sphere;
// Create the Babylon engine
engine = new BABYLON.Engine(canvas, true);
engine.enableOfflineSupport = false;
// Create the base scene
scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(88 / 255, 171 / 255, 217 / 255, 1);
// Camera
camera = new BABYLON.ArcRotateCamera("camera1", 400, 1.25, 10,
new BABYLON.Vector3(0, 5, -10), scene);
camera.setTarget(BABYLON.Vector3.Zero());
camera.wheelPrecision = 10;
camera.maxz = 40000;
camera.attachControl(canvas, true);
// Light
light = new BABYLON.HemisphericLight("light",
new BABYLON.Vector3(0, 1, 0),
scene);
light.intensity = 0.7;
// Some objects (sphere and plane)
sphere = BABYLON.MeshBuilder.CreateSphere("sphere",
{diameter: 2, segments: 32},
scene);
sphere.position.y = 1;
BABYLON.MeshBuilder.CreateGround("ground", {width: 6, height: 6}, scene);
engine.runRenderLoop(function () { scene.render(); });
return finish_deferred.promise;
};
updateGame = function () {
console.log("updateGame loop");
};
}(this));
/*global window, rJS, jIO, RSVP, Set, domsugar, console,
requestAnimationFrame, cancelAnimationFrame,
Worker,
DroneGameManager*/
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
// game.js
(function (RSVP, requestAnimationFrame, cancelAnimationFrame) {
"use strict";
// Events props to send to worker
var mouseEventFields = new Set(['altKey', 'bubbles', 'button', 'buttons',
'cancelBubble', 'cancelable', 'clientX', 'clientY', 'composed', 'ctrlKey',
'defaultPrevented', 'detail', 'eventPhase', 'fromElement', 'isTrusted',
'layerX', 'layerY', 'metaKey', 'movementX', 'movementY', 'offsetX', 'pageX',
'offsetY', 'pageY', 'relatedTarget', 'returnValue', 'screenX', 'screenY',
'shiftKey', 'timeStamp', 'type', 'which', 'x', 'wheelDelta', 'wheelDeltaX',
'wheelDeltaY', 'y', 'deltaX', 'deltaY', 'deltaZ', 'deltaMode'
]), game_result;
//////////////////////////////////////////
// Webworker
//////////////////////////////////////////
function handleWorker(url, callback) {
var worker;
function canceller() {
worker.terminate();
}
function resolver(resolve, reject) {
worker = new Worker(url);
function handleError(error) {
canceller();
reject(error);
}
worker.onerror = handleError;
var result;
try {
result = callback(worker);
} catch (e) {
return handleError(e);
}
new RSVP.Queue(result)
.push(function (value) {
canceller();
console.log('resolve handle worker', value);
resolve(value);
}, handleError);
}
return new RSVP.Promise(resolver, canceller);
}
//////////////////////////////////////////
// promiseAnimationFrame
//////////////////////////////////////////
function promiseAnimationFrame() {
var request_id;
function canceller() {
cancelAnimationFrame(request_id);
}
function resolver(resolve) {
request_id = requestAnimationFrame(resolve);
}
return new RSVP.Promise(resolver, canceller);
}
//////////////////////////////////////////
// DroneGameManager
//////////////////////////////////////////
function DroneGameManager(gadget) {
this._gadget = gadget;
if (!(this instanceof DroneGameManager)) {
return new DroneGameManager();
}
}
DroneGameManager.prototype = {
constructor: DroneGameManager,
pause: function pauseGameManager() {
console.log('pausing', this.loop_promise);
if (!this.hasOwnProperty('loop_promise')) {
throw new Error('Can not pause the game if not started');
}
if (this.hasOwnProperty('pause_defer')) {
throw new Error('Can not pause the game if already paused');
}
this.pause_defer = RSVP.defer();
var pause_defer = this.pause_defer;
this.loop_promise
.push(function () {
return pause_defer.promise;
});
},
unpause: function continueGameManager() {
if (!this.hasOwnProperty('loop_promise')) {
throw new Error('Can not unpause the game if not started');
}
if (!this.hasOwnProperty('pause_defer')) {
throw new Error('Can not unpause the game if not paused');
}
this.pause_defer.resolve('unpause');
var pause_defer = this.pause_defer;
delete this.pause_defer;
},
quit: function stopGameManager() {
if (!this.hasOwnProperty('loop_promise')) {
throw new Error('Can not quit the game if not started');
}
this.loop_promise.cancel('Stopping game manager');
console.log('stopping game manager');
delete this.loop_promise;
delete this.pause_defer;
},
result: function resultGameManager() {
return game_result;
},
play: function startGameManager(options) {
if (this.hasOwnProperty('loop_promise')) {
throw new Error('Can not start the game if already started');
}
this.loop_promise = new RSVP.Queue();
var loop_promise = this.loop_promise,
context = this;
this.pause();
return RSVP.Queue(RSVP.any([
loop_promise,
handleWorker('gadget_erp5_page_babylonjs_web_worker.js',
function (worker) {
var message_error_handler_defer = RSVP.defer(),
update_defer = null;
function step() {
context.loop_promise
.push(function () {
worker.postMessage({
type: 'update'
});
update_defer = RSVP.defer();
return RSVP.all([
promiseAnimationFrame(),
update_defer.promise
]);
})
.push(step);
}
function cloneEvent(event) {
event.preventDefault();
var eventClone = {};
for (let field of mouseEventFields) {
eventClone[field] = event[field];
}
return eventClone;
}
function bindEvent(data) {
var target;
switch (data.targetName) {
case 'window':
target = window;
break;
case 'canvas':
target = options.canvas_original;
break;
case 'document':
target = document;
break;
}
if (!target) {
console.error('Unknown target: ' + data.targetName);
return;
}
target.addEventListener(data.eventName, function (e) {
// We can`t pass original event to the worker
var eventClone = cloneEvent(e);
if (eventClone.type === "pointerout") {
return;
}
worker.postMessage({
type: 'event',
targetName: data.targetName,
eventName: data.eventName,
eventClone: eventClone
});
}, data.opt);
}
function workerToMain(evt) {
switch (evt.data.type) {
case 'loaded':
return worker.postMessage({
type: 'start',
logic_url_list: options.logic_url_list,
canvas: options.canvas,
width: options.width,
height: options.height,
game_parameters: options.game_parameters
}, [options.canvas]);
case 'started':
console.log('GAME: started');
if (context._gadget) {
var loading =
context._gadget.element.querySelector('#loading');
if (loading) { loading.innerHTML = ""; }
}
context.unpause();
return step();
case 'updated':
return update_defer.resolve('updated');
case 'finished':
console.log('GAME: finished');
game_result = evt.data.result;
return context.quit();
case 'event':
bindEvent(evt.data);
break;
case 'canvasMethod':
options.canvas_original[evt.data.method](...evt.data.args);
break;
case 'canvasStyle':
options.canvas_original.style[evt.data.name] = evt.data.value;
break;
case 'error':
message_error_handler_defer.reject(evt.data.error);
break;
default:
message_error_handler_defer.reject(
new Error('Unsupported message ' + JSON.stringify(evt.data))
);
}
}
worker.onmessage = workerToMain;
// Always quit the game when the worker callback usage is over
// to prevent trying to call pause
return message_error_handler_defer.promise;
})
]))
.push(undefined, function (error) {
// As the loop_promise will be cancelled at some point when calling
// quit, we will get a CancellationError as result.
// This is expected, and play must be successfully resolved
if (!(error instanceof RSVP.CancellationError)) {
throw error;
}
});
}
};
window.DroneGameManager = DroneGameManager;
}(RSVP, requestAnimationFrame, cancelAnimationFrame));
\ No newline at end of file
/*global self, window, rJS, jIO, RSVP, console, importScripts, bindHandler,
handlers, prepareCanvas, postMessage, runGame, updateGame, handleEvent*/
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
self.window = {
addEventListener: function (event, fn, opt) {
bindHandler('window', event, fn, opt);
},
setTimeout: self.setTimeout.bind(self),
PointerEvent: true
};
self.document = {
addEventListener: function (event, fn, opt) {
bindHandler('document', event, fn, opt);
},
// Uses to detect wheel event like at src/Inputs/scene.inputManager.ts:797
createElement: function () {
return {onwheel: true};
},
defaultView: self.window
};
importScripts('babylon.js', 'babylon.gui.js', 'rsvp.js');
function mainToWorker(evt) {
var i, offscreen_canvas;
switch (evt.data.type) {
case 'start':
console.log("[WEB WORKER] Ready to handle the folliwing events:",
handlers.keys());
for (i = 0; i < evt.data.logic_url_list.length; i += 1) {
importScripts(evt.data.logic_url_list[i]);
}
offscreen_canvas = prepareCanvas(evt.data);
RSVP = window.RSVP;
return new RSVP.Queue()
.push(function () {
postMessage({'type': 'started'});
return runGame(offscreen_canvas, evt.data.game_parameters);
})
.push(function (result) {
return postMessage({'type': 'finished', 'result': result});
}, function (error) {
console.log("ERROR:", error);
return postMessage({'type': 'error', 'error': error});
});
case 'update':
return new RSVP.Queue()
.push(function () {
return updateGame();
})
.push(function () {
return postMessage({'type': 'updated'});
});
case 'event':
handleEvent(evt.data);
break;
default:
throw new Error('Unsupported message ' + JSON.stringify(evt.data));
}
}
// Doesn't work without it
class HTMLElement {}
self.handlers = new Map();
self.canvas = null;
// getBoundingInfo()
var rect = {
top: 0,
left: 0,
right: 0,
bottom: 0,
x: 0,
y: 0,
height: 0,
width: 0
};
function bindHandler(targetName, eventName, fn, opt) {
var handlerId = targetName + eventName;
handlers.set(handlerId, fn);
postMessage({
type: 'event',
targetName: targetName,
eventName: eventName,
opt: opt
});
}
function noop() {}
function handleEvent(event) {
var handlerId = event.targetName + event.eventName;
event.eventClone.preventDefault = noop;
event.eventClone.target = self.canvas;
if (!handlers.has(handlerId)) {
throw new Error('Unknown handlerId: ' + handlerId);
}
handlers.get(handlerId)(event.eventClone);
}
function prepareCanvas(data) {
var canvas = data.canvas, style;
self.canvas = canvas;
canvas.clientWidth = data.width;
canvas.clientHeight = data.height;
canvas.width = data.width;
canvas.height = data.height;
rect.right = rect.width = data.width;
rect.bottom = rect.height = data.height;
canvas.setAttribute = function (name, value) {
postMessage({
type: 'canvasMethod',
method: 'setAttribute',
args: [name, value]
});
};
canvas.addEventListener = function (event, fn, opt) {
bindHandler('canvas', event, fn, opt);
};
canvas.getBoundingClientRect = function () {
return rect;
};
canvas.focus = function () {
postMessage({
type: 'canvasMethod',
method: 'focus',
args: []
});
};
// noinspection JSUnusedGlobalSymbols
style = {
set touchAction (value) {
postMessage({
type: 'canvasStyle',
name: 'touchAction',
value: value
});
}
};
Object.defineProperty(canvas, 'style', {get () { return style }});
return canvas;
}
(function (worker) {
worker.onmessage = mainToWorker;
worker.postMessage({
'type': 'loaded'
});
}(this));
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment