From a54d235146afd21dbccfc3198c368dee12fffaf5 Mon Sep 17 00:00:00 2001 From: Krasilnikov Roman Date: Fri, 23 Feb 2024 01:16:04 +0300 Subject: [PATCH] Locking implementation --- index.html | 4 ++- raylib.js | 17 ++++++++++++ raylib_factory.js | 9 +++++- raylib_worker.js | 70 +++++++++++++++++++++++++++++++++++++---------- 4 files changed, 83 insertions(+), 17 deletions(-) diff --git a/index.html b/index.html index e190a31..2228876 100644 --- a/index.html +++ b/index.html @@ -149,6 +149,7 @@ case IMPL.GAME_FRAME: return `wasm/${example}.wasm` case IMPL.BLOCKING: + case IMPL.LOCKING: return `wasm/${example}.native.wasm` } } @@ -239,7 +240,8 @@ const impls = { [IMPL.GAME_FRAME]: "game frame", - [IMPL.BLOCKING]: "blocking" + [IMPL.BLOCKING]: "blocking", + [IMPL.LOCKING]: "locking", } const defaultImpl = IMPL.GAME_FRAME const raylibImplSelect = document.getElementById("raylib-impl-select"); diff --git a/raylib.js b/raylib.js index 638f4d8..0fa47cd 100644 --- a/raylib.js +++ b/raylib.js @@ -165,6 +165,23 @@ export class BlockingRaylibJs extends RaylibJsBase { } + +export class LockingRaylibJs extends BlockingRaylibJs { + constructor(canvas, platform, eventsQueue, statusBuffer) { + super(canvas, platform, eventsQueue); + this.status = new Int32Array(statusBuffer); + } + + BeginDrawing() { + Atomics.wait(this.status, 0, 0); + this.eventsQueue.pop(this.handleEvent) + const now = performance.now(); + this.dt = (now - this.previous)/1000.0; + this.previous = now; + } + +} + export const glfwKeyMapping = { "Space": 32, "Quote": 39, diff --git a/raylib_factory.js b/raylib_factory.js index 0bed6ca..a0b0559 100644 --- a/raylib_factory.js +++ b/raylib_factory.js @@ -1,8 +1,9 @@ -import { BlockingRaylibJs, RaylibJs } from './raylib.js' +import { BlockingRaylibJs, RaylibJs, LockingRaylibJs } from './raylib.js' export const IMPL = { GAME_FRAME: "gameFrame", BLOCKING: "blocking", + LOCKING: "locking", } export const RENDERING_CTX = { @@ -23,6 +24,7 @@ export function createRaylib({ platform, rendering, eventsQueue, + statusBuffer, }) { switch (impl) { case IMPL.GAME_FRAME: { @@ -34,6 +36,11 @@ export function createRaylib({ const ctx = remoteContextFactories[rendering](canvas) return new BlockingRaylibJs(ctx, platform, eventsQueue) } + case IMPL.LOCKING: { + const canvas = new OffscreenCanvas(0, 0) + const ctx = remoteContextFactories[rendering](canvas) + return new LockingRaylibJs(ctx, platform, eventsQueue, statusBuffer) + } default: throw new Error(`Unknown impl: ${impl}`) } diff --git a/raylib_worker.js b/raylib_worker.js index 0f76b3a..2d1273e 100644 --- a/raylib_worker.js +++ b/raylib_worker.js @@ -40,7 +40,14 @@ function makePlatform({ self, impl, rendering, renderer, rendererPort }) { family, fileName, }) - } + }, + [IMPL.LOCKING]: (family, fileName) => { + self.postMessage({ + type: RESPONSE_MESSAGE_TYPE.LOAD_FONT, + family, + fileName, + }) + }, }[impl] const render = { [RENDERING_CTX.DD]: (ctx) => { @@ -118,7 +125,8 @@ export function makeWorkerMessagesHandler(self) { rendering, renderer, rendererPort, - eventsBuffer + eventsBuffer, + statusBuffer, }) => { if (raylibJs) { raylibJs.stop() @@ -135,6 +143,7 @@ export function makeWorkerMessagesHandler(self) { }), rendering, eventsQueue: new EventsQueue(eventsBuffer), + statusBuffer, }) } handlers[REQUEST_MESSAGE_TYPE.START] = async ({ params }) => { @@ -254,7 +263,7 @@ export class RaylibJsWorker { this.rendererWorker = rendererWorker // For manage event commits this.impl = impl - this.nextCommitId = undefined + this.animationFrameId = undefined this.startPromise = undefined this.onStartSuccess = undefined this.onStartFail = undefined @@ -298,14 +307,25 @@ export class RaylibJsWorker { this.eventsSender = { [IMPL.GAME_FRAME]: (event) => this.worker.postMessage(event), [IMPL.BLOCKING]: (event) => this.eventsQueue.push(event), + [IMPL.LOCKING]: (event) => this.eventsQueue.push(event), }[impl] + const statusBuffer = window.SharedArrayBuffer + ? new SharedArrayBuffer(4) + : new ArrayBuffer(4) + this.status = new Int32Array(statusBuffer) + const channel = new MessageChannel() // https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas - const offscreen = { - [IMPL.GAME_FRAME]: () => canvas.transferControlToOffscreen(), - [IMPL.BLOCKING]: () => { - switch (renderer) { + let offscreen = undefined + switch (impl) { + case IMPL.GAME_FRAME: { + offscreen = canvas.transferControlToOffscreen() + break + } + case IMPL.LOCKING: + case IMPL.BLOCKING: { + switch (renderer) { case RENDERER.MAIN_THREAD: { this.handlers[RESPONSE_MESSAGE_TYPE.UPDATE_WINDOW] = ({ title, width, height }) => { platform.updateWindow(title, width, height) @@ -330,14 +350,18 @@ export class RaylibJsWorker { throw new Error(`Unknown renderer: ${renderer}`) } // Fake canvas to measure the text in worker thread - return new OffscreenCanvas(800, 600) - } - }[impl]() + offscreen = new OffscreenCanvas(800, 600) + break + } + default: + throw new Error(`Unknown impl: ${impl}`) + } this.worker.addEventListener("message", this.handleMessage) this.worker.postMessage({ type: REQUEST_MESSAGE_TYPE.INIT, canvas: offscreen, eventsBuffer, + statusBuffer, rendering, impl, renderer, @@ -361,19 +385,35 @@ export class RaylibJsWorker { type: REQUEST_MESSAGE_TYPE.START, params }) - if (this.impl === IMPL.BLOCKING) { + switch (this.impl) { + case IMPL.BLOCKING: { + const commitEvents = () => { + this.eventsQueue.commit() + this.animationFrameId = requestAnimationFrame(commitEvents) + } + this.animationFrameId = requestAnimationFrame(commitEvents) + break + } + case IMPL.LOCKING: { const commitEvents = () => { this.eventsQueue.commit() - this.nextCommitId = requestAnimationFrame(commitEvents) + Atomics.notify(this.status, 0) + this.animationFrameId = requestAnimationFrame(commitEvents) } - this.nextCommitId = requestAnimationFrame(commitEvents) + this.animationFrameId = requestAnimationFrame(commitEvents) + break + } } return this.startPromise } stop() { - if (this.impl === IMPL.BLOCKING) { - cancelAnimationFrame(this.nextCommitId) + switch (this.impl) { + case IMPL.BLOCKING: + case IMPL.LOCKING: { + cancelAnimationFrame(this.animationFrameId) + break + } } this.eventsSender({ type: REQUEST_MESSAGE_TYPE.STOP,