Compare commits

...

3 Commits

6 changed files with 188 additions and 130 deletions

View File

@ -8,10 +8,15 @@ import Renderer from "./modules/renderer/renderer.mjs";
globalThis.TIMINGS = DEBUG && localStorage.getItem("timings"); globalThis.TIMINGS = DEBUG && localStorage.getItem("timings");
globalThis.RELEASE = !DEBUG; globalThis.RELEASE = !DEBUG;
const renderer = new Renderer(); const thread = new Thread("./workers/base.mjs", "model", true);
renderer.render(); await thread.setup();
const settings = { x: 200, y: 200, seed: 0, jitter: 0.5, width: window.innerWidth, height: window.innerHeight };
const grid = new Float32Array(await thread.model.fillArr(settings));
console.log(grid);
const thread = new Thread("./workers/base.mjs", "backend"); const view = new Float32Array(await thread.view.lerpToViewport(grid.buffer, settings));
await thread.setup().complete(); console.log(view);
const grid = await thread.test_fillArr({x: 20, y: 20, depth: 2, seed: 0, jitter: 3 });
Renderer.init();
Renderer.initGrid(view, settings);
})(); })();

View File

@ -1,13 +1,58 @@
import * as Three from "../../libs/three.mjs"; import * as Three from "../../libs/three.mjs";
import Thread from "../../utils/workerUtils.mjs"; import { Thread } from "../../utils/workerUtils.mjs";
class Renderer class Renderer
{ {
static async init() static async init(threaded)
{ {
const renderer_thread = new Thread("../../workers/renderer.mjs", "renderer"); this._threaded = threaded === undefined ? false : threaded;
await renderer_thread.setup().complete(); if(threaded)
renderer_thread.init(document.createElement("canvas").transferControlToOffscreen(), window.innerWidth, window.innerHeight); {
this._renderer_thread = new Thread("../../workers/renderer.mjs", "view", true);
await this._renderer_thread.setup();
this._canvas = document.createElement("canvas");
const offcanvas = this._canvas.transferControlToOffscreen();
await this._renderer_thread.render.init(offcanvas, window.innerWidth, window.innerHeight);
}
else
{
this._renderer = new Three.WebGLRenderer();
this._renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(this._renderer.domElement);
this._width = window.innerWidth;
this._height = window.innerHeight;
this._scene = new Three.Scene();
this._camera = new Three.OrthographicCamera(this._width / - 2, this._width / 2, this._height / 2, this._height / - 2, 0.1, 1000);
Renderer.render();
}
}
static render()
{
if(this._threaded)
return;
this._rAF = requestAnimationFrame(Renderer.render.bind(Renderer));
this._renderer.render(this._scene, this._camera);
}
static async initGrid(grid, settings) //Share the list of cell and the resolution in the model with the view
{
if(this._threaded)
{
//We can share the cells with the rendering thread because it will only be used as read-only to create the point data
await this._renderer_thread.view.lerpToView(grid, settings);
}
else
{
const geometry = new Three.BufferGeometry();
geometry.setAttribute('position', new Three.Float32BufferAttribute(grid, 3));
const points = new Three.Points(geometry);
this._scene.add(points);
}
} }
} }

View File

@ -1,78 +0,0 @@
// WORKER SIDE //
const Constants = {
STATE: {
WAITING: 1,
OK: 2,
ERROR: 3,
},
REQUEST: {
SETUP: 1,
CALL: 2,
TERMINATE: 3
},
};
function send(ret)
{
globalThis.VERBOSE && console.debug(`Sending ${ret} to the main thread`);
postMessage([Constants.STATE.OK, ret]);
}
class Process
{
static _isSetup = false;
static setup()
{
globalThis.DEBUG && console.log(`Setting up process`);
Process._isSetup = true;
Process._customFn = {};
onmessage = Process.onmessage;
}
static onmessage(e)
{
globalThis.VERBOSE && console.debug(`Received ${e.data}`);
if(e && e.data)
{
switch(e.data[0])
{
case Constants.REQUEST.SETUP:
send(Object.keys(Process._customFn));
break;
case Constants.REQUEST.CALL:
send(Process._customFn[e.data[1]](...e.data[2]));
break;
case Constants.REQUEST.TERMINATE:
Process.cleanUp();
send();
break;
default:
throw new Error("Invalid message received in the Process");
break;
}
}
else
{
throw new Error("Can't find any data");
}
}
static register(name, fn)
{
if(!Process._isSetup)
Process.setup();
if(name in Process._customFn)
throw new Error("This function name is already registered in the process. Please use a different one.");
else
Process._customFn[name] = fn;
}
static cleanUp()
{
}
}
export { Process, Constants };

View File

@ -14,8 +14,19 @@ const Constants = {
WAKEUP: 1, WAKEUP: 1,
SETUP: 2, SETUP: 2,
CALL: 3, CALL: 3,
TERMINATE: 4 TERMINATE: 4,
}, },
TRANSFERABLE: [
ArrayBuffer,
MessagePort,
ReadableStream,
WritableStream,
TransformStream,
AudioData,
ImageBitmap,
VideoFrame,
OffscreenCanvas,
]
}; };
function cleanUp(thread) function cleanUp(thread)
@ -23,21 +34,44 @@ function cleanUp(thread)
thread._waiting = false; thread._waiting = false;
thread._resolver = undefined; thread._resolver = undefined;
thread._rejecter = undefined; thread._rejecter = undefined;
thread._calledMod = "";
thread._calledFn = ""; thread._calledFn = "";
} }
function request(thread, req) function request(thread, req, shared)
{ {
thread._promise = new Promise(function(res, rej) { thread._promise = new Promise(function(res, rej) {
thread._waiting = true; thread._waiting = true;
thread._resolver = res; thread._resolver = res;
thread._rejecter = rej; thread._rejecter = rej;
thread._worker && thread._worker.postMessage(req); thread._worker && thread._worker.postMessage(req, shared);
}); });
return thread._promise; return thread._promise;
} }
function getTransferables(args)
{
const arr = [];
if(!Array.isArray(args))
args = [args];
for(let i = 0; i < args.length; ++i)
{
for(let j = 0; j < Constants.TRANSFERABLE.length; ++j)
{
if(args[i] instanceof Constants.TRANSFERABLE[j])
{
arr.push(args[i]);
break;
}
}
}
return arr.length === 0 ? undefined : arr;
}
function parseReturns(data) function parseReturns(data)
{ {
if(data.length != 2) if(data.length != 2)
@ -50,12 +84,17 @@ function parseReturns(data)
} }
return data[1]; return data[1];
} }
function wrapper(thread, fn) function wrapper(thread, mod, fn)
{ {
return function() return function(...args)
{ {
globalThis.TIMINGS && console.time("Measuring " + fn); if(globalThis.TIMINGS)
return request(thread, [Constants.REQUEST.CALL, fn, [...arguments]]); {
console.time("Measuring " + mod + "." + fn);
thread._calledMod = mod;
thread._calledFn = fn;
}
return request(thread, [Constants.REQUEST.CALL, mod, fn, [...args]], getTransferables([...args]));
} }
} }
@ -63,7 +102,8 @@ function onmessage(thread)
{ {
return function(e) { return function(e) {
const ret = parseReturns(e.data); const ret = parseReturns(e.data);
globalThis.VERBOSE && console.debug(`Received message from ${thread._name} containing ${ret}`); globalThis.VERBOSE && console.debug(`Received message from ${thread._name} containing `, ret);
globalThis.TIMINGS && thread._calledFn !== "" && console.timeEnd("Measuring " + thread._calledMod + "." + thread._calledFn);
if(thread._waiting && ret) if(thread._waiting && ret)
{ {
@ -77,7 +117,8 @@ function onmessage(thread)
function onerror(thread) function onerror(thread)
{ {
return function(e) { return function(e) {
console.error(`Received error from ${thread._name} containing ${e}`); globalThis.DEBUG && console.error(`Received error from ${thread._name} containing `, e);
globalThis.TIMINGS && thread._calledFn !== "" && console.timeEnd("Measuring " + thread._calledMod + "." + thread._calledFn);
if(thread._waiting) if(thread._waiting)
{ {
@ -93,9 +134,9 @@ const Thread = Object.freeze(class
* Creates a new Thread loading the linked file. The linked file must follow the Thread expectations. * Creates a new Thread loading the linked file. The linked file must follow the Thread expectations.
* You can give it a optionnal name to make the debug easier. * You can give it a optionnal name to make the debug easier.
*/ */
constructor(url, name) constructor(url, name, mod)
{ {
this._worker = new Worker(url, {type: "module"}); this._worker = new Worker(url, mod ? {type: "module"} : undefined);
this._name = name || url; this._name = name || url;
this._worker.onmessage = onmessage(this); this._worker.onmessage = onmessage(this);
@ -107,27 +148,25 @@ const Thread = Object.freeze(class
*/ */
async setup() async setup()
{ {
const result = await request(this, [Constants.REQUEST.SETUP]); const result = await request(this, [Constants.REQUEST.SETUP], undefined);
if(result && result.length) if(result)
{ {
for(let i = 0; i < result.length; i++) for(let mod of Object.getOwnPropertyNames(result))
{ {
const custom = result[i]; this[mod] = {};
this[custom] = wrapper(this, custom); for(let i = 0; i < result[mod].length; ++i)
{
const fn = result[mod][i];
this[mod][fn] = wrapper(this, mod, fn);
}
} }
} }
} }
complete() async terminate()
{ {
if(this._waiting) await request(this, [Constants.REQUEST.TERMINATE]);
{ this._worker.terminate();
return this._promise;
}
else
{
return Promise.resolve(this._ret);
}
} }
}); });
@ -135,8 +174,8 @@ const Thread = Object.freeze(class
function send(ret) function send(ret)
{ {
globalThis.VERBOSE && console.debug(`Sending ${ret} to the main thread`); globalThis.VERBOSE && console.debug(`Sending `, ret, ` back to the main thread`);
postMessage([Constants.STATE.OK, ret]); postMessage([Constants.STATE.OK, ret], getTransferables(ret));
} }
class Process class Process
@ -151,17 +190,27 @@ class Process
} }
static onmessage(e) static onmessage(e)
{ {
globalThis.VERBOSE && console.debug(`Received ${e.data}`); globalThis.VERBOSE && console.debug(`Received `, e.data);
if(e && e.data) if(e && e.data)
{ {
switch(e.data[0]) switch(e.data[0])
{ {
case Constants.REQUEST.SETUP: case Constants.REQUEST.SETUP:
send(Object.keys(Process._customFn)); const obj = {};
for(let mod of Object.getOwnPropertyNames(Process._customFn))
{
obj[mod] = [];
for(let fn of Object.getOwnPropertyNames(Process._customFn[mod]))
{
obj[mod].push(fn);
}
}
send(obj);
break; break;
case Constants.REQUEST.CALL: case Constants.REQUEST.CALL:
send(Process._customFn[e.data[1]](...e.data[2])); const args = e.data[3];
send(Process._customFn[e.data[1]][e.data[2]](...args));
break; break;
case Constants.REQUEST.TERMINATE: case Constants.REQUEST.TERMINATE:
@ -186,21 +235,28 @@ class Process
if(!Array.isArray(arr)) if(!Array.isArray(arr))
arr = [arr]; arr = [arr];
if(mod in Process._customFn)
throw new Error("This module has already been registered in the process.");
const modObj = {};
for(let i = 0; i < arr.length; ++i) for(let i = 0; i < arr.length; ++i)
{ {
if(typeof arr[i] != 'function') if(typeof arr[i] != 'function')
throw new Error("You can only register functions"); throw new Error("You can only register functions");
const name = mod + "_" arr[i].name; const name = arr[i].name;
if(name in Process._customFn) if(name in modObj)
throw new Error("This function name is already registered in the process. Please use a different one."); throw new Error("This function name is already registered in the process. Please use a different one.");
else else
Process._customFn[name] = arr[i]; modObj[name] = arr[i];
} }
Process._customFn[mod] = modObj;
} }
static cleanUp() static cleanUp()
{ {
Process._customFn = {};
} }
} }

View File

@ -1,18 +1,38 @@
import { Process } from "../utils/workerUtils.mjs"; import { Process } from "../utils/workerUtils.mjs";
import Noise from '../libs/alea.mjs' import Noise from '../libs/alea.mjs'
import Delaunator from '../libs/delaunator.mjs'
//Settings contains x, y depth, seed, jitter //Settings contains x, y depth, seed, jitter
function fillArr(settings) function fillArr(settings)
{ {
const noise = Noise(settings.seed); const noise = Noise(settings.seed);
const arr = new Float32Array(settings.x * settings.y * settings.depth); const grid = new Float32Array(settings.x * settings.y * 2);
for(let i = arr.length - 1; i >= 0; --i) for(let i = grid.length * 2 - 2; i > 0; i -= 2)
{ {
const _x = i % settings.x; const _x = i / 2 % settings.x;
const _y = (i - _x) / settings.x; const _y = (i / 2 - _x) / settings.x;
arr[i] =
grid[i] = _x + (noise() - 0.5) * settings.jitter;
grid[i + 1] = _y + (noise() - 0.5) * settings.jitter;
} }
return grid.buffer;
}
function lerpToViewport(buffer, settings)
{
const size = settings.x * settings.y;
const grid = new Float32Array(buffer);
const view = new Float32Array(size * 3);
for(let i = size - 1; i >= 0; --i)
{
view[i * 3] = settings.width * grid[i * 2] / settings.x - settings.width / 2;
view[i * 3 + 1] = settings.height * grid[i * 2 + 1] / settings.y - settings.height / 2;
view[i * 3 + 2] = -3;
}
return view.buffer;
} }
//Process.register is the equivalent of export //Process.register is the equivalent of export
Process.register("test", [fillArr]); Process.register("model", [fillArr]);
Process.register("view", [lerpToViewport]);

10
src/workers/renderer.mjs Normal file
View File

@ -0,0 +1,10 @@
import { Process } from "../utils/workerUtils.mjs";
import * as Three from '../libs/three.mjs'
class Renderer
{
static init(canvas, width, height)
}
//Process.register is the equivalent of export
Process.register("render", [Renderer.init]);