From 745c1efd633d5ff555342194ac8223e5b8b798fa Mon Sep 17 00:00:00 2001 From: Krasilnikov Roman Date: Sat, 10 Feb 2024 02:16:11 +0300 Subject: [PATCH 1/4] Extract RaylibJsBase --- index.html | 34 +++++++++++- raylib.js | 157 +++++++++++++++++++++++++++-------------------------- 2 files changed, 113 insertions(+), 78 deletions(-) diff --git a/index.html b/index.html index 5f29ff6..496ab4e 100644 --- a/index.html +++ b/index.html @@ -51,6 +51,7 @@ } const raylibExampleSelect = document.getElementById("raylib-example-select"); + const canvas = document.getElementById("game"); for (const exampleCategory in wasmPaths){ raylibExampleSelect.innerHTML += `` @@ -63,16 +64,45 @@ const { protocol } = window.location; const isHosted = protocol !== "file:"; let raylibJs = undefined; + let removeEventListeners = undefined + + function addEventListeners(raylibJs) { + const keyDown = (e) => { + raylibJs.handleKeyDown(glfwKeyMapping[e.code]); + }; + const keyUp = (e) => { + raylibJs.handleKeyUp(glfwKeyMapping[e.code]); + }; + const wheelMove = (e) => { + raylibJs.handleWheelMove(Math.sign(-e.deltaY)); + }; + const mouseMove = (e) => { + raylibJs.handleMouseMove({x: e.clientX, y: e.clientY}); + }; + + window.addEventListener("keydown", keyDown); + window.addEventListener("keyup", keyUp); + window.addEventListener("wheel", wheelMove); + window.addEventListener("mousemove", mouseMove); + return () => { + window.removeEventListener("mousemove", mouseMove); + window.removeEventListener("wheel", wheelMove); + window.removeEventListener("keyup", keyUp); + window.removeEventListener("keydown", keyDown); + } + } function startRaylib(selectedWasm){ if (isHosted) { if (raylibJs !== undefined) { + removeEventListeners(); raylibJs.stop(); } - raylibJs = new RaylibJs(); + raylibJs = new RaylibJs(canvas); + removeEventListeners = addEventListeners(raylibJs); raylibJs.start({ wasmPath: `wasm/${selectedWasm}.wasm`, - canvasId: "game", + debug: true, }); } else { window.addEventListener("load", () => { diff --git a/raylib.js b/raylib.js index 0f8da8f..070b474 100644 --- a/raylib.js +++ b/raylib.js @@ -2,7 +2,7 @@ function make_environment(env) { return new Proxy(env, { get(target, prop, receiver) { if (env[prop] !== undefined) { - return env[prop].bind(env); + return env[prop] } return (...args) => { throw new Error(`NOT IMPLEMENTED: ${prop} ${args}`); @@ -11,7 +11,7 @@ function make_environment(env) { }); } -class RaylibJs { +class RaylibJsBase { // TODO: We stole the font from the website // (https://raylib.com/) and it's slightly different than // the one that is "baked" into Raylib library itself. To @@ -24,7 +24,6 @@ class RaylibJs { #reset() { this.previous = undefined; this.wasm = undefined; - this.ctx = undefined; this.dt = undefined; this.targetFPS = 60; this.entryFunction = undefined; @@ -34,106 +33,85 @@ class RaylibJs { this.currentMousePosition = {x: 0, y: 0}; this.quit = false; } - - constructor() { + + constructor(canvas) { + this.ctx = canvas.getContext("2d"); + if (this.ctx === null) { + throw new Error("Could not create 2d canvas context"); + } this.#reset(); } - stop() { - this.quit = true; + handleKeyDown(keyCode) { + this.currentPressedKeyState.add(keyCode); } - async start({ wasmPath, canvasId }) { - if (this.wasm !== undefined) { - console.error("The game is already running. Please stop() it first."); - return; - } + handleKeyUp(keyCode) { + this.currentPressedKeyState.delete(keyCode); + } - const canvas = document.getElementById(canvasId); - this.ctx = canvas.getContext("2d"); - if (this.ctx === null) { - throw new Error("Could not create 2d canvas context"); - } + handleWheelMove(direction) { + this.currentMouseWheelMoveState = direction + } + handleMouseMove(position) { + this.currentMousePosition = position + } + + async start({ wasmPath, debug }) { + if (this.wasm !== undefined) { + throw new Error("The game is already running. Please stop() it first."); + } this.wasm = await WebAssembly.instantiateStreaming(fetch(wasmPath), { - env: make_environment(this) + env: debug ? make_environment(this) : this }); - - const keyDown = (e) => { - this.currentPressedKeyState.add(glfwKeyMapping[e.code]); - }; - const keyUp = (e) => { - this.currentPressedKeyState.delete(glfwKeyMapping[e.code]); - }; - const wheelMove = (e) => { - this.currentMouseWheelMoveState = Math.sign(-e.deltaY); - }; - const mouseMove = (e) => { - this.currentMousePosition = {x: e.clientX, y: e.clientY}; - }; - window.addEventListener("keydown", keyDown); - window.addEventListener("keyup", keyUp); - window.addEventListener("wheel", wheelMove); - window.addEventListener("mousemove", mouseMove); - this.wasm.instance.exports.main(); - const next = (timestamp) => { - if (this.quit) { - this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); - window.removeEventListener("keydown", keyDown); - this.#reset() - return; - } - this.dt = (timestamp - this.previous)/1000.0; - this.previous = timestamp; - this.entryFunction(); - window.requestAnimationFrame(next); - }; - window.requestAnimationFrame((timestamp) => { - this.previous = timestamp; - window.requestAnimationFrame(next); - }); + } + + stop() { + this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + this.#reset() } - InitWindow(width, height, title_ptr) { + InitWindow = (width, height, title_ptr) => { this.ctx.canvas.width = width; this.ctx.canvas.height = height; const buffer = this.wasm.instance.exports.memory.buffer; document.title = cstr_by_ptr(buffer, title_ptr); } - WindowShouldClose(){ + WindowShouldClose = () => { return false; } - SetTargetFPS(fps) { + SetTargetFPS = (fps) => { console.log(`The game wants to run at ${fps} FPS, but in Web we gonna just ignore it.`); this.targetFPS = fps; } - GetScreenWidth() { + GetScreenWidth = () => { return this.ctx.canvas.width; } - GetScreenHeight() { + GetScreenHeight = () => { return this.ctx.canvas.height; } - GetFrameTime() { + GetFrameTime = () => { // TODO: This is a stopgap solution to prevent sudden jumps in dt when the user switches to a differen tab. // We need a proper handling of Target FPS here. return Math.min(this.dt, 1.0/this.targetFPS); } - BeginDrawing() {} + BeginDrawing = () => {} - EndDrawing() { + EndDrawing = () => { this.prevPressedKeyState.clear(); this.prevPressedKeyState = new Set(this.currentPressedKeyState); this.currentMouseWheelMoveState = 0.0; } - DrawCircleV(center_ptr, radius, color_ptr) { + DrawCircleV = (center_ptr, radius, color_ptr) => { const buffer = this.wasm.instance.exports.memory.buffer; const [x, y] = new Float32Array(buffer, center_ptr, 2); const [r, g, b, a] = new Uint8Array(buffer, color_ptr, 4); @@ -144,13 +122,13 @@ class RaylibJs { this.ctx.fill(); } - ClearBackground(color_ptr) { + ClearBackground = (color_ptr) => { this.ctx.fillStyle = getColorFromMemory(this.wasm.instance.exports.memory.buffer, color_ptr); this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); } // RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) - DrawText(text_ptr, posX, posY, fontSize, color_ptr) { + DrawText = (text_ptr, posX, posY, fontSize, color_ptr) => { const buffer = this.wasm.instance.exports.memory.buffer; const text = cstr_by_ptr(buffer, text_ptr); const color = getColorFromMemory(buffer, color_ptr); @@ -162,32 +140,32 @@ class RaylibJs { } // RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle - DrawRectangle(posX, posY, width, height, color_ptr) { + DrawRectangle = (posX, posY, width, height, color_ptr) => { const buffer = this.wasm.instance.exports.memory.buffer; const color = getColorFromMemory(buffer, color_ptr); this.ctx.fillStyle = color; this.ctx.fillRect(posX, posY, width, height); } - IsKeyPressed(key) { + IsKeyPressed = (key) => { return !this.prevPressedKeyState.has(key) && this.currentPressedKeyState.has(key); } - IsKeyDown(key) { + IsKeyDown = (key) => { return this.currentPressedKeyState.has(key); } - GetMouseWheelMove() { + GetMouseWheelMove = () => { return this.currentMouseWheelMoveState; } - IsGestureDetected() { + IsGestureDetected = () => { return false; } - TextFormat(... args){ + TextFormat = (... args) => { // TODO: Implement printf style formatting for TextFormat return args[0]; } - GetMousePosition(result_ptr) { + GetMousePosition = (result_ptr) => { const bcrect = this.ctx.canvas.getBoundingClientRect(); const x = this.currentMousePosition.x - bcrect.left; const y = this.currentMousePosition.y - bcrect.top; @@ -196,21 +174,21 @@ class RaylibJs { new Float32Array(buffer, result_ptr, 2).set([x, y]); } - CheckCollisionPointRec(point_ptr, rec_ptr) { + CheckCollisionPointRec = (point_ptr, rec_ptr) => { const buffer = this.wasm.instance.exports.memory.buffer; const [x, y] = new Float32Array(buffer, point_ptr, 2); const [rx, ry, rw, rh] = new Float32Array(buffer, rec_ptr, 4); return ((x >= rx) && x <= (rx + rw) && (y >= ry) && y <= (ry + rh)); } - Fade(result_ptr, color_ptr, alpha) { + Fade = (result_ptr, color_ptr, alpha) => { const buffer = this.wasm.instance.exports.memory.buffer; const [r, g, b, _] = new Uint8Array(buffer, color_ptr, 4); const newA = Math.max(0, Math.min(255, 255.0*alpha)); new Uint8Array(buffer, result_ptr, 4).set([r, g, b, newA]); } - DrawRectangleRec(rec_ptr, color_ptr) { + DrawRectangleRec = (rec_ptr, color_ptr) => { const buffer = this.wasm.instance.exports.memory.buffer; const [x, y, w, h] = new Float32Array(buffer, rec_ptr, 4); const color = getColorFromMemory(buffer, color_ptr); @@ -218,7 +196,7 @@ class RaylibJs { this.ctx.fillRect(x, y, w, h); } - DrawRectangleLinesEx(rec_ptr, lineThick, color_ptr) { + DrawRectangleLinesEx = (rec_ptr, lineThick, color_ptr) => { const buffer = this.wasm.instance.exports.memory.buffer; const [x, y, w, h] = new Float32Array(buffer, rec_ptr, 4); const color = getColorFromMemory(buffer, color_ptr); @@ -227,7 +205,7 @@ class RaylibJs { this.ctx.strokeRect(x + lineThick/2, y + lineThick/2, w - lineThick, h - lineThick); } - MeasureText(text_ptr, fontSize) { + MeasureText = (text_ptr, fontSize) => { const buffer = this.wasm.instance.exports.memory.buffer; const text = cstr_by_ptr(buffer, text_ptr); fontSize *= this.#FONT_SCALE_MAGIC; @@ -235,11 +213,38 @@ class RaylibJs { return this.ctx.measureText(text).width; } - raylib_js_set_entry(entry) { + raylib_js_set_entry = (entry) => { this.entryFunction = this.wasm.instance.exports.__indirect_function_table.get(entry); } } +class RaylibJs extends RaylibJsBase { + constructor(canvas) { + super(canvas); + this.frameId = undefined + } + + next = (timestamp) => { + this.dt = (timestamp - this.previous)/1000.0; + this.previous = timestamp; + this.entryFunction(); + this.frameId = requestAnimationFrame(this.next); + } + + async start(params) { + await super.start(params); + this.frameId = requestAnimationFrame((timestamp) => { + this.previous = timestamp + this.frameId= requestAnimationFrame(this.next) + }); + } + + stop() { + cancelAnimationFrame(this.frameId); + super.stop(); + } +} + const glfwKeyMapping = { "Space": 32, "Quote": 39, From 075b3da72f57b7e4c957870f1e86e82f50878c37 Mon Sep 17 00:00:00 2001 From: Krasilnikov Roman Date: Sat, 10 Feb 2024 04:15:28 +0300 Subject: [PATCH 2/4] Run RaylibJs in worker --- index.html | 22 +++-- raylib.js | 253 +++++++++++++++++++++++++++++++++++++---------- raylib.worker.js | 12 +++ 3 files changed, 225 insertions(+), 62 deletions(-) create mode 100644 raylib.worker.js diff --git a/index.html b/index.html index 496ab4e..0dfe6ad 100644 --- a/index.html +++ b/index.html @@ -34,15 +34,14 @@ src: url(fonts/acme_7_wide_xtnd.woff); } - - - - + \ No newline at end of file diff --git a/raylib.js b/raylib.js index 033f272..3d4d114 100644 --- a/raylib.js +++ b/raylib.js @@ -248,16 +248,20 @@ const RESPONSE_MESSAGE_TYPE = { export function makeMessagesHandler(self) { let raylibJs = undefined + const platform = { + updateTitle: (title) => { + self.postMessage({ + type: RESPONSE_MESSAGE_TYPE.UPDATE_TITLE, + title + }) + }, + } const handlers = new Array(Object.keys(REQUEST_MESSAGE_TYPE).length) handlers[REQUEST_MESSAGE_TYPE.INIT] = ({ canvas }) => { - raylibJs = new RaylibJs(canvas, { - updateTitle: (title) => { - self.postMessage({ - type: RESPONSE_MESSAGE_TYPE.UPDATE_TITLE, - title - }) - }, - }) + if (raylibJs) { + raylibJs.stop() + } + raylibJs = new RaylibJs(canvas, platform) } handlers[REQUEST_MESSAGE_TYPE.START] = async ({ params }) => { try { @@ -315,7 +319,7 @@ export class RaylibJsWorker { } } case RESPONSE_MESSAGE_TYPE.UPDATE_TITLE: { - window.document.title = event.data.title + this.platform.updateTitle(event.data.title) break } default: @@ -323,8 +327,9 @@ export class RaylibJsWorker { } } - constructor(worker, canvas) { + constructor(worker, canvas, platform) { this.worker = worker + this.platform = platform this.startPromise = undefined this.onStartSuccess = undefined this.onStartFail = undefined @@ -360,6 +365,7 @@ export class RaylibJsWorker { this.worker.postMessage({ type: REQUEST_MESSAGE_TYPE.STOP }) + this.worker.removeEventListener("message", this.handleMessage) } handleKeyDown(keyCode) { From a3a9aaad259d141ce33318f9e62d99aee11dbd30 Mon Sep 17 00:00:00 2001 From: Krasilnikov Roman Date: Sat, 17 Feb 2024 21:38:27 +0300 Subject: [PATCH 4/4] Update platform implementations --- index.html | 9 ++--- raylib.js | 101 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 82 insertions(+), 28 deletions(-) diff --git a/index.html b/index.html index 3afe038..baf3451 100644 --- a/index.html +++ b/index.html @@ -69,7 +69,7 @@