Mercurial
view mrjunejune/src/public/raylib.js @ 279:b3b547563ec7
Add Google connector service and agent wiki
Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 22:22:36 -0700 |
| parents | 65e5a5b89a4e |
| children |
line wrap: on
line source
/** * I Stole this from https://github.com/tsoding/c3-demo/blob/main/raylib.js and added few functions for functionality. * It seems to be from raylib.com/examples */ function make_environment(env) { return new Proxy(env, { get(_target, prop, _receiver) { if (env[prop] !== undefined) { return env[prop].bind(env); } return (...args) => { throw new Error(`NOT IMPLEMENTED: ${prop} ${args}`); }; }, }); } let iota = 0; const LOG_ALL = iota++; // Display all logs const LOG_TRACE = iota++; // Trace logging, intended for internal use only const LOG_DEBUG = iota++; // Debug logging, used for internal debugging, it should be disabled on release builds const LOG_INFO = iota++; // Info logging, used for program execution info const LOG_WARNING = iota++; // Warning logging, used on recoverable failures const LOG_ERROR = iota++; // Error logging, used on unrecoverable failures const LOG_FATAL = iota++; // Fatal logging, used to abort program: exit(EXIT_FAILURE) const LOG_NONE = iota++; // Disable logging class RaylibJs { #FONT_SCALE_MAGIC = 0.65; #reset() { this.previous = undefined; this.wasm = undefined; this.ctx = undefined; this.dt = undefined; this.targetFPS = 60; this.entryFunction = undefined; this.prevPressedKeyState = new Set(); this.currentPressedKeyState = new Set(); this.currentMouseWheelMoveState = 0; /* -1 since 0 is left click, 1 middle, 2 is right click */ this.currentIsMouseButtonPressed = -1; this.currentMousePosition = { x: 0, y: 0 }; this.images = []; this.quit = false; } constructor() { this.#reset(); } stop() { this.quit = true; } async start({ wasmPath, canvasId }) { if (this.wasm !== undefined) { console.error("The game is already running. Please stop() it first."); return; } const canvas = document.getElementById(canvasId); this.ctx = canvas.getContext("2d"); if (this.ctx === null) { throw new Error("Could not create 2d canvas context"); } this.wasm = await WebAssembly.instantiateStreaming(fetch(wasmPath), { env: make_environment(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 }; }; const mouseClick = (e) => { this.currentIsMouseButtonPressed = e.button; }; const touchStarted = () => { this.currentIsMouseButtonPressed = 0; }; const touchOrClickEnded = () => { this.currentIsMouseButtonPressed = -1; }; window.addEventListener("keydown", keyDown); window.addEventListener("keyup", keyUp); window.addEventListener("wheel", wheelMove); window.addEventListener("mousemove", mouseMove); window.addEventListener("mousedown", mouseClick); window.addEventListener("mouseup", touchOrClickEnded); /* For phones */ window.addEventListener("touchstart", touchStarted); window.addEventListener("touchend", touchOrClickEnded); window.addEventListener("touchcancel", touchOrClickEnded); 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); }); } 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() { return false; } SetTargetFPS(fps) { console.log( `The game wants to run at ${fps} FPS, but in Web we gonna just ignore it.`, ); this.targetFPS = fps; } GetScreenWidth() { return this.ctx.canvas.width; } GetScreenHeight() { return this.ctx.canvas.height; } 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() {} EndDrawing() { this.prevPressedKeyState.clear(); this.prevPressedKeyState = new Set(this.currentPressedKeyState); this.currentMouseWheelMoveState = 0.0; } 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); const color = color_hex_unpacked(r, g, b, a); this.ctx.beginPath(); this.ctx.arc(x, y, radius, 0, 2 * Math.PI, false); this.ctx.fillStyle = color; this.ctx.fill(); } 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) { const buffer = this.wasm.instance.exports.memory.buffer; const text = cstr_by_ptr(buffer, text_ptr); const color = getColorFromMemory(buffer, color_ptr); fontSize *= this.#FONT_SCALE_MAGIC; this.ctx.fillStyle = color; // TODO: since the default font is part of Raylib the css that defines it should be located in raylib.js and not in index.html this.ctx.font = `${fontSize}px grixel`; const lines = text.split("\n"); for (var i = 0; i < lines.length; i++) { this.ctx.fillText(lines[i], posX, posY + fontSize + i * fontSize); } } // 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) { 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); } DrawRectangleV(position_ptr, size_ptr, color_ptr) { const buffer = this.wasm.instance.exports.memory.buffer; const color = getColorFromMemory(buffer, color_ptr); const position = new Float32Array(buffer, position_ptr, 2); const size = new Float32Array(buffer, size_ptr, 2); this.ctx.fillStyle = color; this.ctx.fillRect(position[0], position[1], size[0], size[1]); } IsKeyPressed(key) { return ( !this.prevPressedKeyState.has(key) && this.currentPressedKeyState.has(key) ); } IsKeyDown(key) { return this.currentPressedKeyState.has(key); } GetMouseWheelMove() { return this.currentMouseWheelMoveState; } IsGestureDetected() { return false; } IsMouseButtonPressed(key) { return this.currentIsMouseButtonPressed == key; } TextFormat(...args) { // TODO: Implement printf style formatting for TextFormat return args[0]; } TraceLog(logLevel, text_ptr, ...args) { // TODO: Implement printf style formatting for TraceLog const buffer = this.wasm.instance.exports.memory.buffer; const text = cstr_by_ptr(buffer, text_ptr); switch (logLevel) { case LOG_ALL: console.log(`ALL: ${text} ${args}`); break; case LOG_TRACE: console.log(`TRACE: ${text} ${args}`); break; case LOG_DEBUG: console.log(`DEBUG: ${text} ${args}`); break; case LOG_INFO: console.log(`INFO: ${text} ${args}`); break; case LOG_WARNING: console.log(`WARNING: ${text} ${args}`); break; case LOG_ERROR: console.log(`ERROR: ${text} ${args}`); break; case LOG_FATAL: throw new Error(`FATAL: ${text}`); case LOG_NONE: console.log(`NONE: ${text} ${args}`); break; } } GetMousePosition(result_ptr) { const bcrect = this.ctx.canvas.getBoundingClientRect(); const x = this.currentMousePosition.x - bcrect.left; const y = this.currentMousePosition.y - bcrect.top; const buffer = this.wasm.instance.exports.memory.buffer; new Float32Array(buffer, result_ptr, 2).set([x, y]); } 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) { 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) { 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); this.ctx.fillStyle = color; this.ctx.fillRect(x, y, w, h); } 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); this.ctx.strokeStyle = color; this.ctx.lineWidth = lineThick; this.ctx.strokeRect( x + lineThick / 2, y + lineThick / 2, w - lineThick, h - lineThick, ); } 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; this.ctx.font = `${fontSize}px grixel`; return this.ctx.measureText(text).width; } TextSubtext(text_ptr, position, length) { const buffer = this.wasm.instance.exports.memory.buffer; const text = cstr_by_ptr(buffer, text_ptr); const subtext = text.substring(position, length); var bytes = new Uint8Array(buffer, 0, subtext.length + 1); for (var i = 0; i < subtext.length; i++) { bytes[i] = subtext.charCodeAt(i); } bytes[subtext.length] = 0; return bytes; } // RLAPI Texture2D LoadTexture(const char *fileName); LoadTexture(result_ptr, filename_ptr) { const buffer = this.wasm.instance.exports.memory.buffer; const filename = cstr_by_ptr(buffer, filename_ptr); var result = new Uint32Array(buffer, result_ptr, 5); var img = new Image(); const isLocalhost = window.location.hostname === "localhost"; const baseUrl = isLocalhost ? "http://localhost:6969/" : `https://${window.location.hostname}/`; img.src = `${baseUrl}${filename}`; this.images.push(img); result[0] = this.images.indexOf(img); // TODO: get the true width and height of the image result[1] = 256; // width result[2] = 256; // height result[3] = 1; // mipmaps result[4] = 7; // format PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 return result; } // RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); DrawTexture(texture_ptr, posX, posY, color_ptr) { const buffer = this.wasm.instance.exports.memory.buffer; const [id, width, height, mipmaps, format] = new Uint32Array( buffer, texture_ptr, 5, ); // // TODO: implement tinting for DrawTexture // const tint = getColorFromMemory(buffer, color_ptr); this.ctx.drawImage(this.images[id], posX, posY); } // TODO: codepoints are not implemented LoadFontEx( result_ptr, fileName_ptr /*, fontSize, codepoints, codepointCount*/, ) { const buffer = this.wasm.instance.exports.memory.buffer; const fileName = cstr_by_ptr(buffer, fileName_ptr); // TODO: dynamically generate the name for the font // Support more than one custom font const font = new FontFace("myfont", `url(${fileName})`); document.fonts.add(font); font.load(); } GenTextureMipmaps() {} SetTextureFilter() {} MeasureTextEx(result_ptr, font, text_ptr, fontSize, spacing) { const buffer = this.wasm.instance.exports.memory.buffer; const text = cstr_by_ptr(buffer, text_ptr); const result = new Float32Array(buffer, result_ptr, 2); this.ctx.font = fontSize + "px myfont"; const metrics = this.ctx.measureText(text); result[0] = metrics.width; result[1] = fontSize; } DrawTextEx(font, text_ptr, position_ptr, fontSize, spacing, tint_ptr) { const buffer = this.wasm.instance.exports.memory.buffer; const text = cstr_by_ptr(buffer, text_ptr); const [posX, posY] = new Float32Array(buffer, position_ptr, 2); const tint = getColorFromMemory(buffer, tint_ptr); this.ctx.fillStyle = tint; this.ctx.font = fontSize + "px myfont"; this.ctx.fillText(text, posX, posY + fontSize); } GetRandomValue(min, max) { return min + Math.floor(Math.random() * (max - min + 1)); } ColorFromHSV(result_ptr, hue, saturation, value) { const buffer = this.wasm.instance.exports.memory.buffer; const result = new Uint8Array(buffer, result_ptr, 4); // Red channel let k = (5.0 + hue / 60.0) % 6; let t = 4.0 - k; k = t < k ? t : k; k = k < 1 ? k : 1; k = k > 0 ? k : 0; result[0] = Math.floor((value - value * saturation * k) * 255.0); // Green channel k = (3.0 + hue / 60.0) % 6; t = 4.0 - k; k = t < k ? t : k; k = k < 1 ? k : 1; k = k > 0 ? k : 0; result[1] = Math.floor((value - value * saturation * k) * 255.0); // Blue channel k = (1.0 + hue / 60.0) % 6; t = 4.0 - k; k = t < k ? t : k; k = k < 1 ? k : 1; k = k > 0 ? k : 0; result[2] = Math.floor((value - value * saturation * k) * 255.0); result[3] = 255; } raylib_js_set_entry(entry) { this.entryFunction = this.wasm.instance.exports.__indirect_function_table.get(entry); } } const glfwKeyMapping = { Space: 32, Quote: 39, Comma: 44, Minus: 45, Period: 46, Slash: 47, Digit0: 48, Digit1: 49, Digit2: 50, Digit3: 51, Digit4: 52, Digit5: 53, Digit6: 54, Digit7: 55, Digit8: 56, Digit9: 57, Semicolon: 59, Equal: 61, KeyA: 65, KeyB: 66, KeyC: 67, KeyD: 68, KeyE: 69, KeyF: 70, KeyG: 71, KeyH: 72, KeyI: 73, KeyJ: 74, KeyK: 75, KeyL: 76, KeyM: 77, KeyN: 78, KeyO: 79, KeyP: 80, KeyQ: 81, KeyR: 82, KeyS: 83, KeyT: 84, KeyU: 85, KeyV: 86, KeyW: 87, KeyX: 88, KeyY: 89, KeyZ: 90, BracketLeft: 91, Backslash: 92, BracketRight: 93, Backquote: 96, // GLFW_KEY_WORLD_1 161 /* non-US #1 */ // GLFW_KEY_WORLD_2 162 /* non-US #2 */ Escape: 256, Enter: 257, Tab: 258, Backspace: 259, Insert: 260, Delete: 261, ArrowRight: 262, ArrowLeft: 263, ArrowDown: 264, ArrowUp: 265, PageUp: 266, PageDown: 267, Home: 268, End: 269, CapsLock: 280, ScrollLock: 281, NumLock: 282, PrintScreen: 283, Pause: 284, F1: 290, F2: 291, F3: 292, F4: 293, F5: 294, F6: 295, F7: 296, F8: 297, F9: 298, F10: 299, F11: 300, F12: 301, F13: 302, F14: 303, F15: 304, F16: 305, F17: 306, F18: 307, F19: 308, F20: 309, F21: 310, F22: 311, F23: 312, F24: 313, F25: 314, NumPad0: 320, NumPad1: 321, NumPad2: 322, NumPad3: 323, NumPad4: 324, NumPad5: 325, NumPad6: 326, NumPad7: 327, NumPad8: 328, NumPad9: 329, NumpadDecimal: 330, NumpadDivide: 331, NumpadMultiply: 332, NumpadSubtract: 333, NumpadAdd: 334, NumpadEnter: 335, NumpadEqual: 336, ShiftLeft: 340, ControlLeft: 341, AltLeft: 342, MetaLeft: 343, ShiftRight: 344, ControlRight: 345, AltRight: 346, MetaRight: 347, ContextMenu: 348, // GLFW_KEY_LAST GLFW_KEY_MENU }; function cstrlen(mem, ptr) { let len = 0; while (mem[ptr] != 0) { len++; ptr++; } return len; } function cstr_by_ptr(mem_buffer, ptr) { const mem = new Uint8Array(mem_buffer); const len = cstrlen(mem, ptr); const bytes = new Uint8Array(mem_buffer, ptr, len); return new TextDecoder().decode(bytes); } function color_hex_unpacked(r, g, b, a) { r = r.toString(16).padStart(2, "0"); g = g.toString(16).padStart(2, "0"); b = b.toString(16).padStart(2, "0"); a = a.toString(16).padStart(2, "0"); return "#" + r + g + b + a; } function color_hex(color) { const r = ((color >> (0 * 8)) & 0xff).toString(16).padStart(2, "0"); const g = ((color >> (1 * 8)) & 0xff).toString(16).padStart(2, "0"); const b = ((color >> (2 * 8)) & 0xff).toString(16).padStart(2, "0"); const a = ((color >> (3 * 8)) & 0xff).toString(16).padStart(2, "0"); return "#" + r + g + b + a; } function getColorFromMemory(buffer, color_ptr) { const [r, g, b, a] = new Uint8Array(buffer, color_ptr, 4); return color_hex_unpacked(r, g, b, a); }