node-raylib
repository·master·Indexed 43 days ago
https://github.com/robloach/node-raylibNode.js bindings for raylib 5.5, enabling the creation of games and graphical applications using JavaScript or TypeScript. It provides a CLI for executing scripts and supports DRM mode for ARM devices like Raspberry Pi. Version 0.20.0.
What's inside node-raylib
- To optimize performance, certain structs in the Node.js bindings include a hidden property that stores the pointer location of the struct in C++ memory. When calling specific API functions, you can pass this pointer directly to reference the struct, which is more efficient than passing the full object.
How type converters work in the raylib generator
masterThe
node-raylibgenerator usesToValueandFromValuepatterns to bridge C++ raylib types with Node.js (N-API) objects.ToValue: Converts a C++ struct or primitive into a JavaScript-compatibleNapi::Value. For example, aVector2is converted into a JavaScript object withxandyproperties, while primitives likefloatandintare converted toNapi::Number.FromValue: Extracts C++ types fromNapi::CallbackInfo. These functions typically take a reference to anindexto track the current position in the argument list as they consume values from the JavaScript call.
// Example of converting a C++ struct to a JS Object Napi::Value ToValue(napi_env env, Vector2 obj) { Napi::Object out = Napi::Object::New(env); out.Set("x", ToValue(env, obj.x)); out.Set("y", ToValue(env, obj.y)); return out; } // Example of extracting a C++ struct from JS arguments Vector2 Vector2FromValue(const Napi::CallbackInfo& info, int& index) { return (Vector2) { FloatFromValue(info, index += 1), FloatFromValue(info, index += 1) }; }How pass-by-reference functions work in node-raylib
masterIn the native raylib C++ API, many functions use 'pass-by-reference' (pointers) to modify an object directly instead of returning a new one. For example,
ImageDrawPixel()modifies theImagestruct passed to it.Because JavaScript passes arguments by value to native addons, the C++ side modifies a copy of your object, leaving your original JavaScript object unchanged. To solve this,
node-raylibuses a pattern where the native addon returns the modified object, and the JavaScript wrapper synchronizes the properties back to your original object.Important: When using these specific functions, the JavaScript wrapper handles the synchronization, but you should be aware that the underlying mechanism relies on returning a modified copy and updating the original reference.
Optimize performance using Colors as integers
masterTo reduce the overhead of converting color data from JavaScript to C++, you can pass colors as integer values instead of plain objects. This optimization reduces the conversion cost by approximately 75%.Understand argument flattening in node-raylib
masterTo achieve high performance,
node-raylibuses a technique called argument flattening.In standard Node.js native addons, passing a JavaScript object to represent a C++ struct (like
Texture2DorColor) is extremely expensive because the C++ layer must parse the object properties at runtime.To avoid this,
node-raylibflattens these structs into a long list of individual primitive arguments (numbers, booleans, etc.) before they reach the C++ layer. This allows the native side to construct the C++ structs directly from the argument list without expensive object lookups, which can result in significantly higher framerates (up to 6x faster in benchmarks).How it affects API usage
When calling the underlying native
raylibobject directly, you cannot pass objects. Instead, you must pass the individual properties of those objects in the specific order required by the flattened binding.However, for ease of use, the library also provides wrapper functions that allow you to use the standard raylib API (passing objects like
TextureorColor) by automatically flattening them for you before calling the native layer.// Standard raylib API usage (via wrappers): raylib.DrawTexture(myTexture, 10, 10, myTextureColor) // Underlying native call (flattened): raylib.DrawTexture( myTexture.id, myTexture.width, myTexture.height, myTexture.mipmaps, myTexture.format, 10, 10, myTextureColor.r, myTextureColor.g, myTextureColor.b, myTextureColor.a )Optimize color performance using integers
masterIn
node-raylib, passing colors as 32-bit integers instead of color objects significantly improves performance. This is because reducing the number of arguments passed between JavaScript and C++ reduces the amount of data that must be copied via thenode-addon-api.To use this optimization, you can encode a color object
{r, g, b, a}into a single integer using bitwise operators in JavaScript, and then pass that integer to the Raylib API functions. Raylib's internalGetColor()function can then decode this integer back into aColorstruct on the C++ side.let c = {r: 255, g: 128, b: 0, a: 255} var cr = c.r & 0xFF; var cg = c.g & 0xFF; var cb = c.b & 0xFF; var ca = c.a & 0xFF; var rgba = (cr << 24) + (cg << 16) + (cb << 8) + (ca)Use raylib structs as plain JavaScript objects
masterIn Node.js, raylib structs (like
Vector2orCamera2D) are represented as plain JavaScript objects. You can create them by defining an object with the appropriate field names and types. This allows you to pass them directly into raylib API functions.let myCamera = { offset: {x: 0, y: 0}, target: {x: 50, y: 50}, rotation: 0, zoom: 1 }Use DRM mode on ARM devices
masterOn certain ARM devices like Raspberry Pi, you can use DRM (Direct Rendering Manager) mode instead of rendering to an X11 window. When installing on ARM,
node-raylibincludes both the standard and DRM native addons. To use DRM mode, import fromraylib/drminstead of the standardraylibpackage.// Using ES Modules import * as rl from 'raylib/drm/index.js' // Using CommonJS const rl = require('raylib/drm')Manage automation event recording and playback
masterYou can record and play back user input sequences using automation events.
- Load/Export: Use
LoadAutomationEventList(fileName)to load a list from a file orExportAutomationEventList(list, fileName)to save a list to a text file. - Setup: Before recording, you must set the list to record into using
SetAutomationEventList(list)and optionally set the starting frame withSetAutomationEventBaseFrame(frame). - Recording: Call
StartAutomationEventRecording()to begin andStopAutomationEventRecording()to end. - Playback: Use
PlayAutomationEvent(event)to play a specificAutomationEvent.
// Example workflow const list = LoadAutomationEventList("my_events.txt"); SetAutomationEventList(list); SetAutomationEventBaseFrame(0); StartAutomationEventRecording(); // ... perform actions ... StopAutomationEventRecording(); ExportAutomationEventList(list, "recorded_actions.txt");- Load/Export: Use
How pointer-accessible structs work in node-raylib
masterTo optimize performance, certain raylib structs (like
Texture) are cached in C++ memory rather than being copied back and forth between JavaScript and C++ for every function call.When you load such a struct, the C++ binding allocates space in a memory pool and returns a JavaScript object containing the struct's data and a hidden
pointerproperty. Thispointeris a 64-bit integer representing the memory address in C++.Key Concepts:
- Performance: Passing a single integer (the pointer) to C++ functions is significantly faster than copying large data structures.
- Scope: Only structs with explicit
Load()andUnload()functions in the raylib API (e.g.,Texture) use this mechanism. Small, transient structs likeColororRectangleare passed by value (copied) and do not use pointers. - Memory Management: Because these structs live in C++ memory, they are not managed by the JavaScript garbage collector. You must manually unload them to prevent memory leaks.
Manage drawing modes and canvas setup
masterRaylib uses a state-based approach for drawing. You must wrap drawing operations in specific
BeginandEndfunctions to switch between different rendering contexts like 2D, 3D, textures, or shaders.Core Drawing Lifecycle
BeginDrawing(): Prepares the canvas (framebuffer) to start a new frame of drawing.EndDrawing(): Finalizes the drawing and performs buffer swapping (double buffering) to display the frame on screen.ClearBackground(color): Clears the entire framebuffer with a specificColor.
Specialized Drawing Modes
- 2D Mode: Use
BeginMode2D(camera)andEndMode2D()to draw using aCamera2Dtransform. - 3D Mode: Use
BeginMode3D(camera)andEndMode3D()to draw using aCamera3Dtransform.EndMode3D()returns the context to default 2D orthographic mode. - Render Textures: Use
BeginTextureMode(target)andEndTextureMode()to draw onto aRenderTextureinstead of the screen. - Shaders: Use
BeginShaderMode(shader)andEndShaderMode()to apply aShaderto subsequent drawing commands. - Blending: Use
BeginBlendMode(mode)andEndBlendMode()to change how colors are blended (e.g., additive, multiplied).EndBlendMode()resets to default alpha blending. - Scissor Mode: Use
BeginScissorMode(x, y, width, height)andEndScissorMode()to restrict drawing to a specific rectangular area of the screen.
How Node Addon API bindings are generated
masterThenode-raylibproject usesnode-addon-apito create bindings between Node.js and theraylibC library. To ensure full coverage of the raylib API, the project automates binding creation by parsing the official raylib API JSON file (provided by the raylib repository). This JSON file contains the metadata required to automatically generate the C++node-addon-apicode that maps each raylib function to a usable Node.js function.