mirror of https://github.com/mode777/rayjs.git
raylib 5.5 migration: type conversions, auto-ignore, buffer returns, bunnymark_opt
- Added char, short, long, unsigned short/unsigned long, const float*, bool*, char* type conversions to jsToC/jsToJs - Fixed unsigned int*/unsigned char* pointer returns in jsToJs - Updated VrDeviceInfo (removed vScreenCenter), GuiListView (active param), GuiTextInputBox (bool* ignored) - Added auto-ignore filter for unsupported types (105 functions ignored) - Added needsBufferReturn() handler for 5 functions returning unsigned char* - Removed GLFW-specific F5 reload from quickjs.c - Created CHANGELOG_5.5.md, bunnymark_opt.js (Float32Array), updated readme - Deleted temp analysis scripts and failed build-sdl directory
This commit is contained in:
parent
52be0e6a7d
commit
b59455c3a7
|
|
@ -0,0 +1,100 @@
|
|||
# Changelog: raylib 4.5 → 5.5 Migration
|
||||
|
||||
## Overview
|
||||
|
||||
This changelog documents all changes made during the migration of rayjs from raylib **4.5** to **5.5** (stable), including raygui **3.6** → **4.0**.
|
||||
|
||||
## Git
|
||||
|
||||
- Branch: `update-raylib`
|
||||
- Commit: `52be0e6` — "switch raylib to v5.5 and raygui to v4.0 stable releases"
|
||||
|
||||
---
|
||||
|
||||
## Submodules Updated
|
||||
|
||||
| Submodule | From | To |
|
||||
|-----------|------|-----|
|
||||
| `thirdparty/raylib` | `dbc56a8` (6.0-dev) | `c1ab645` (tag **5.5**) |
|
||||
| `thirdparty/raygui` | `aa81c16` (tag **3.6**) | `25c8c65` (tag **4.0**) |
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
### `src/quickjs.c`
|
||||
- Removed `#include <GLFW/glfw3.h>` and `GLFWwindow`-specific F5 reload code (now uses `IsKeyPressed(KEY_F5)` only, platform-agnostic)
|
||||
|
||||
### `bindings/src/quickjs.ts`
|
||||
- **`jsToC()`** — Added type conversions:
|
||||
- `char` → first char of JS string
|
||||
- `short`, `int *`, `long`
|
||||
- `unsigned short`, `unsigned long`
|
||||
- `const float *`
|
||||
- `bool *` → local `bool` variable
|
||||
- `char *` (both `const char *` and non-const)
|
||||
- **`jsToJs()`** — Fixed pointer return types:
|
||||
- `unsigned int *`, `unsigned char *` → `*ptr ? JS_NewUint32(ctx, *ptr) : JS_NULL`
|
||||
- `int *` → `JS_NewInt32`
|
||||
- Separated non-pointer unsigned types from pointer versions
|
||||
|
||||
### `bindings/src/index.ts`
|
||||
- **Removed** `ignore()` calls for functions that no longer exist in 5.5:
|
||||
- `IsAudioStreamReady`, `Vector3OrthoNormalize`, `Vector3ToFloatV`, `MatrixToFloatV`, `QuaternionToAxisAngle`
|
||||
- **Fixed `VrDeviceInfo`** struct — removed non-existent field `vScreenCenter` (not present in 5.5)
|
||||
- **Fixed `GuiListView`** — added second out-parameter `active` at index 3 (raygui 4.0 has `scrollIndex` at 2 + `active` at 3)
|
||||
- **Fixed `GuiTextInputBox`** — ignored `secretViewActive` (bool*) parameter, wrote custom body without `setOutParamString` conflict
|
||||
- **Added `LoadRandomSequence`** to `ignore()` (returns `int*`)
|
||||
- **Added auto-ignore filter** — automatically ignores functions with unsupported parameter/return types (`supportedTypes` set with all raylib types + aliases like `Texture2D`, `Camera`, `Quaternion`, `ModelSkeleton`, `GestureEvent`)
|
||||
- **Added `needsBufferReturn()` handler** — special codegen for 5 functions that return `unsigned char *` + `int *` size param (produces `JS_NewArrayBufferCopy` instead of `JS_NewUint32`):
|
||||
- `LoadFileData`, `CompressData`, `DecompressData`, `DecodeDataBase64`, `ExportImageToMemory`
|
||||
|
||||
### `src/bindings/js_raylib_core.h`
|
||||
- **Fully regenerated** (718 functions bound, 105 ignored)
|
||||
- 0 JavaScript errors
|
||||
|
||||
### `examples/lib.raylib.d.ts`
|
||||
- **Fully regenerated** TypeScript declarations
|
||||
|
||||
### `thirdparty/raylib/parser/output/raylib_api.json`
|
||||
- **Regenerated** from raylib 5.5 (12,137 lines)
|
||||
|
||||
### `generate-bindings.js`
|
||||
- **Rebuilt** via webpack from updated sources
|
||||
|
||||
---
|
||||
|
||||
## New Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `examples/textures/bunnymark_opt.js` | Optimized bunnymark benchmark (Float32Array, no GC pressure) |
|
||||
| `MIGRATION_TABLE.md` | Full function-by-function migration table (581 functions) |
|
||||
| `CHANGELOG_5.5.md` | This file |
|
||||
|
||||
---
|
||||
|
||||
## Deleted/Failed Build Directories
|
||||
|
||||
| Path | Reason |
|
||||
|------|--------|
|
||||
| `build-sdl/` | Unsuccessful SDL backend build (GLFW symbols not available) |
|
||||
|
||||
---
|
||||
|
||||
## Build & Runtime Status
|
||||
|
||||
- **Build**: `[100%] Built target rayjs` — 0 errors, 1 warning (LoadFileData `unsigned int *` → `int *` sign mismatch — safe)
|
||||
- **Runtime**: raylib 5.5 initializes successfully, OpenGL 4.1 on Apple M2
|
||||
- **Examples tested**: `main.js`, `bunnymark.js`, `bunnymark_opt.js` — all run without errors
|
||||
|
||||
---
|
||||
|
||||
## Known Issues / Future Work
|
||||
|
||||
1. **LoadFileData warning** — `unsigned int*` passed where `int*` expected (cosmetic, runtime-safe)
|
||||
2. **SDL backend** — needs `#ifdef`-separated GLFW/SDL code in `quickjs.c`
|
||||
3. **GPU Skinning** — `Mesh` struct missing `boneMatrices` and `boneCount` fields in binding (P1 — silent runtime misalignment)
|
||||
4. **Model animations** — still `ignore()`'d as in 4.5 (can be un-ignored if needed)
|
||||
5. **GetScreenToWorldRay** — available but no JS-side alias for old `GetMouseRay`
|
||||
6. **Auto-ignore audit** — 105 ignored functions, some may be bindable
|
||||
|
|
@ -330,7 +330,6 @@ function main(){
|
|||
vResolution: { set: true, get: true },
|
||||
hScreenSize: { set: true, get: true },
|
||||
vScreenSize: { set: true, get: true },
|
||||
vScreenCenter: { set: true, get: true },
|
||||
eyeToScreenDistance: { set: true, get: true },
|
||||
lensSeparationDistance: { set: true, get: true },
|
||||
interpupillaryDistance: { set: true, get: true },
|
||||
|
|
@ -395,6 +394,67 @@ function main(){
|
|||
const traceLog = getFunction(api.functions, "TraceLog")!
|
||||
traceLog.params?.pop()
|
||||
|
||||
// Auto-ignore functions with unsupported parameter/return types
|
||||
const supportedTypes = new Set([
|
||||
'void', 'int', 'float', 'double', 'bool', 'unsigned int', 'unsigned char', 'long',
|
||||
'char *', 'const char *',
|
||||
'void *', 'const void *',
|
||||
'unsigned char *', 'const unsigned char *', 'unsigned short *', 'float *',
|
||||
'int *', 'unsigned int *',
|
||||
'Color', 'Rectangle', 'Vector2', 'Vector3', 'Vector4', 'Matrix',
|
||||
'Image', 'Texture', 'Texture2D', 'TextureCubemap', 'RenderTexture', 'RenderTexture2D',
|
||||
'NPatchInfo', 'GlyphInfo', 'Font',
|
||||
'Camera', 'Camera3D', 'Camera2D', 'Mesh', 'Shader', 'Material', 'MaterialMap',
|
||||
'Transform', 'BoneInfo', 'Model', 'ModelAnimation', 'ModelAnimPose',
|
||||
'Ray', 'RayCollision', 'BoundingBox', 'Wave', 'AudioStream', 'Sound', 'Music',
|
||||
'VrDeviceInfo', 'VrStereoConfig',
|
||||
'FilePathList', 'AutomationEvent', 'AutomationEventList',
|
||||
'Light', 'Lightmapper', 'LightmapperConfig',
|
||||
'Quaternion', 'ModelSkeleton', 'GestureEvent',
|
||||
'TraceLogCallback', 'LoadFileDataCallback', 'SaveFileDataCallback',
|
||||
'LoadFileTextCallback', 'SaveFileTextCallback'
|
||||
])
|
||||
api.functions.forEach(fn => {
|
||||
if (fn.binding?.ignore) return;
|
||||
const stripType = (t: string) => t.replace(/^const /, '').replace(/\*+$/, '').replace(/ \*$/, '').trim()
|
||||
const allParamsOk = (fn.params || []).every(p => {
|
||||
const t = p.binding?.typeAlias || p.type
|
||||
return supportedTypes.has(t) || supportedTypes.has(stripType(t))
|
||||
})
|
||||
const retStripped = stripType(fn.returnType)
|
||||
if (!allParamsOk || !supportedTypes.has(fn.returnType) && !supportedTypes.has(retStripped)) {
|
||||
if (!fn.name.startsWith('Text')) {
|
||||
console.log(`Auto-ignoring ${fn.name} (return: ${fn.returnType}, params: [${(fn.params||[]).map(p=>p.type).join(', ')}])`)
|
||||
}
|
||||
fn.binding = { ignore: true }
|
||||
}
|
||||
})
|
||||
|
||||
// Special case: functions returning unsigned char * with int * size parameter → ArrayBuffer
|
||||
const needsBufferReturn = (fn: RayLibFunction) => {
|
||||
if (fn.returnType !== 'unsigned char *') return false;
|
||||
const last = fn.params?.[fn.params.length - 1];
|
||||
return last && (last.type === 'int *' || last.type === 'unsigned int *');
|
||||
}
|
||||
api.functions.filter(needsBufferReturn).forEach(fn => {
|
||||
const sizeParam = fn.params![fn.params!.length - 1];
|
||||
const dataVar = fn.name === 'CompressData' ? 'compData' : 'data';
|
||||
fn.binding = {
|
||||
body: gen => {
|
||||
for (let i = 0; i < fn.params!.length - 1; i++) {
|
||||
const p = fn.params![i];
|
||||
gen.jsToC(p.type, p.name, `argv[${i}]`, core.structLookup)
|
||||
}
|
||||
gen.declare(sizeParam.name, 'int', false, '0')
|
||||
gen.call(fn.name, fn.params!.map(p => p.name).slice(0, -1).concat(['&' + sizeParam.name]), { type: 'unsigned char *', name: dataVar })
|
||||
gen.statement(`JSValue buf = JS_NewArrayBufferCopy(ctx, (const uint8_t*)${dataVar}, ${sizeParam.name})`)
|
||||
gen.statement(`if (${dataVar}) MemFree(${dataVar})`)
|
||||
gen.returnExp('buf')
|
||||
}
|
||||
}
|
||||
console.log(`Special buffer binding for ${fn.name}`)
|
||||
})
|
||||
|
||||
// Memory functions not supported on JS, just use ArrayBuffer
|
||||
ignore("MemAlloc")
|
||||
ignore("MemRealloc")
|
||||
|
|
@ -426,6 +486,7 @@ function main(){
|
|||
// TODO: SaveFileData works but unnecessary makes copy of memory
|
||||
getFunction(api.functions, "SaveFileData")!.binding = { }
|
||||
ignore("ExportDataAsCode")
|
||||
ignore("LoadRandomSequence")
|
||||
getFunction(api.functions, "LoadFileText")!.binding = { after: gen => gen.call("UnloadFileText", ["returnVal"]) }
|
||||
getFunction(api.functions, "SaveFileText")!.params![1].binding = { typeAlias: "const char *" }
|
||||
ignore("UnloadFileText")
|
||||
|
|
@ -538,7 +599,6 @@ function main(){
|
|||
ignore("UnloadWaveSamples")
|
||||
ignore("LoadMusicStreamFromMemory")
|
||||
ignore("LoadAudioStream")
|
||||
ignore("IsAudioStreamReady")
|
||||
ignore("UnloadAudioStream")
|
||||
ignore("UpdateAudioStream")
|
||||
ignore("IsAudioStreamProcessed")
|
||||
|
|
@ -557,10 +617,6 @@ function main(){
|
|||
ignore("AttachAudioMixedProcessor")
|
||||
ignore("DetachAudioMixedProcessor")
|
||||
|
||||
ignore("Vector3OrthoNormalize")
|
||||
ignore("Vector3ToFloatV")
|
||||
ignore("MatrixToFloatV")
|
||||
ignore("QuaternionToAxisAngle")
|
||||
core.exportGlobalDouble("DEG2RAD", "(PI/180.0)")
|
||||
core.exportGlobalDouble("RAD2DEG", "(180.0/PI)")
|
||||
|
||||
|
|
@ -611,6 +667,7 @@ function main(){
|
|||
setOutParam(getFunction(api.functions, "GuiSpinner")!, 2)
|
||||
setOutParam(getFunction(api.functions, "GuiValueBox")!, 2)
|
||||
setOutParam(getFunction(api.functions, "GuiListView")!, 2)
|
||||
setOutParam(getFunction(api.functions, "GuiListView")!, 3)
|
||||
|
||||
// const setStringListParam = (fun: RayLibFunction, index: number, indexLen: number) => {
|
||||
// const lenParam = fun!.params![indexLen]
|
||||
|
|
@ -637,9 +694,19 @@ function main(){
|
|||
|
||||
setOutParamString(getFunction(api.functions, "GuiTextBox")!, 1,2)
|
||||
|
||||
const gtib = getFunction(api.functions, "GuiTextInputBox")!
|
||||
setOutParamString(gtib,4,5)
|
||||
setOutParam(gtib, 6)
|
||||
getFunction(api.functions, "GuiTextInputBox")!.binding = {
|
||||
body: gen => {
|
||||
gen.jsToC("Rectangle", "bounds", "argv[0]", core.structLookup)
|
||||
gen.jsToC("const char *", "title", "argv[1]", core.structLookup)
|
||||
gen.jsToC("const char *", "message", "argv[2]", core.structLookup)
|
||||
gen.jsToC("const char *", "buttons", "argv[3]", core.structLookup)
|
||||
gen.jsToC("char *", "text", "argv[4]", core.structLookup)
|
||||
gen.jsToC("int", "textMaxSize", "argv[5]", core.structLookup)
|
||||
gen.call("GuiTextInputBox", ["bounds", "title", "message", "buttons", "text", "textMaxSize", "NULL"], { type: "int", name: "returnVal" })
|
||||
gen.jsCleanUpParameter("const char *", "text")
|
||||
gen.statement("return JS_NewInt32(ctx, returnVal)")
|
||||
}
|
||||
}
|
||||
|
||||
// needs string array
|
||||
ignore("GuiTabBar")
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ export abstract class GenericQuickJsGenerator<T extends QuickJsGenerator> extend
|
|||
case "const void *":
|
||||
case "void *":
|
||||
case "float *":
|
||||
case "const float *":
|
||||
case "unsigned short *":
|
||||
case "unsigned char *":
|
||||
case "const unsigned char *":
|
||||
|
|
@ -92,7 +93,7 @@ export abstract class GenericQuickJsGenerator<T extends QuickJsGenerator> extend
|
|||
break;
|
||||
// String
|
||||
case "const char *":
|
||||
//case "char *":
|
||||
case "char *":
|
||||
if(!supressDeclaration) this.statement(`${type} ${name} = (JS_IsNull(${src}) || JS_IsUndefined(${src})) ? NULL : (${type})JS_ToCString(ctx, ${src})`)
|
||||
else this.statement(`${name} = (JS_IsNull(${src}) || JS_IsUndefined(${src})) ? NULL : (${type})JS_ToCString(ctx, ${src})`)
|
||||
break;
|
||||
|
|
@ -106,14 +107,39 @@ export abstract class GenericQuickJsGenerator<T extends QuickJsGenerator> extend
|
|||
if(!supressDeclaration) this.statement(`${type} ${name} = (${type})_double_${name}`)
|
||||
else this.statement(`${name} = (${type})_double_${name}`)
|
||||
break;
|
||||
case "char":
|
||||
if(!supressDeclaration) this.declare(name, type, false, `(JS_IsNull(${src}) || JS_IsUndefined(${src})) ? 0 : JS_ToCString(ctx, ${src})[0]`)
|
||||
else this.statement(`${name} = (JS_IsNull(${src}) || JS_IsUndefined(${src})) ? 0 : JS_ToCString(ctx, ${src})[0]`)
|
||||
break;
|
||||
case "short":
|
||||
if(!supressDeclaration) this.statement(`${type} ${name}`)
|
||||
this.statement(`{ int _tmp; JS_ToInt32(ctx, &_tmp, ${src}); ${name} = (short)_tmp; }`)
|
||||
break;
|
||||
case "int":
|
||||
if(!supressDeclaration) this.statement(`${type} ${name}`)
|
||||
this.statement(`JS_ToInt32(ctx, &${name}, ${src})`)
|
||||
break;
|
||||
case "int *":
|
||||
if(!supressDeclaration) this.statement(`int ${name}_int`)
|
||||
this.statement(`JS_ToInt32(ctx, &${name}_int, ${src})`)
|
||||
this.statement(`${type} ${name} = &${name}_int`)
|
||||
break;
|
||||
case "long":
|
||||
if(!supressDeclaration) this.statement(`${type} ${name}`)
|
||||
this.statement(`JS_ToInt64(ctx, &${name}, ${src})`)
|
||||
break;
|
||||
case "unsigned int":
|
||||
if(!supressDeclaration) this.statement(`${type} ${name}`)
|
||||
this.statement(`JS_ToUint32(ctx, &${name}, ${src})`)
|
||||
break;
|
||||
case "unsigned short":
|
||||
if(!supressDeclaration) this.statement(`${type} ${name}`)
|
||||
this.statement(`{ unsigned int _tmp; JS_ToUint32(ctx, &_tmp, ${src}); ${name} = (unsigned short)_tmp; }`)
|
||||
break;
|
||||
case "unsigned long":
|
||||
if(!supressDeclaration) this.statement(`${type} ${name}`)
|
||||
this.statement(`{ unsigned long long _tmp; JS_ToInt64(ctx, (int64_t *)&_tmp, ${src}); ${name} = (unsigned long)_tmp; }`)
|
||||
break;
|
||||
case "unsigned char":
|
||||
this.statement("unsigned int _int_"+name)
|
||||
this.statement(`JS_ToUint32(ctx, &_int_${name}, ${src})`)
|
||||
|
|
@ -124,6 +150,11 @@ export abstract class GenericQuickJsGenerator<T extends QuickJsGenerator> extend
|
|||
if(!supressDeclaration) this.statement(`${type} ${name} = JS_ToBool(ctx, ${src})`)
|
||||
else this.statement(`${name} = JS_ToBool(ctx, ${src})`)
|
||||
break;
|
||||
case "bool *":
|
||||
if(!supressDeclaration) this.statement(`bool ${name}_val`)
|
||||
this.statement(`${name}_val = JS_ToBool(ctx, ${src})`)
|
||||
this.statement(`${type} ${name} = &${name}_val`)
|
||||
break;
|
||||
default:
|
||||
const isConst = type.startsWith('const')
|
||||
const isPointer = type.endsWith(' *')
|
||||
|
|
@ -139,15 +170,20 @@ export abstract class GenericQuickJsGenerator<T extends QuickJsGenerator> extend
|
|||
jsToJs(type: string, name: string, src: string, classIds: StructLookup = {}){
|
||||
switch (type) {
|
||||
case "int":
|
||||
case "int *":
|
||||
case "long":
|
||||
this.declare(name,'JSValue', false, `JS_NewInt32(ctx, ${src})`)
|
||||
this.declare(name, 'JSValue', false, `JS_NewInt32(ctx, ${src})`)
|
||||
break;
|
||||
case "long":
|
||||
this.declare(name,'JSValue', false, `JS_NewInt64(ctx, ${src})`)
|
||||
break;
|
||||
case "unsigned int":
|
||||
case "unsigned char":
|
||||
this.declare(name,'JSValue', false, `JS_NewUint32(ctx, ${src})`)
|
||||
this.declare(name, 'JSValue', false, `JS_NewUint32(ctx, ${src})`)
|
||||
break;
|
||||
case "unsigned int *":
|
||||
case "unsigned char *":
|
||||
this.declare(name, 'JSValue', false, `${src} ? JS_NewUint32(ctx, *${src}) : JS_NULL`)
|
||||
break;
|
||||
case "bool":
|
||||
this.declare(name, 'JSValue', false, `JS_NewBool(ctx, ${src})`)
|
||||
|
|
@ -160,9 +196,6 @@ export abstract class GenericQuickJsGenerator<T extends QuickJsGenerator> extend
|
|||
case "char *":
|
||||
this.declare(name, 'JSValue', false, `JS_NewString(ctx, ${src})`)
|
||||
break;
|
||||
// case "unsigned char *":
|
||||
// this.declare(name, 'JSValue', false, `JS_NewString(ctx, ${src})`)
|
||||
// break;
|
||||
default:
|
||||
const classId = classIds[type]
|
||||
if(!classId) throw new Error("Cannot convert parameter type to Javascript: " + type)
|
||||
|
|
@ -231,7 +264,6 @@ export abstract class GenericQuickJsGenerator<T extends QuickJsGenerator> extend
|
|||
const body = this.function(`js_${structName}_finalizer`, "void", args, true)
|
||||
body.statement(`${structName}* ptr = JS_GetOpaque(val, ${classId})`)
|
||||
body.if("ptr", cond => {
|
||||
//cond.call("TraceLog", ["LOG_INFO",`"Finalize ${structName} %p"`,"ptr"])
|
||||
if(onFinalize) onFinalize(<T>cond, "ptr")
|
||||
cond.call("js_free_rt", ["rt","ptr"])
|
||||
})
|
||||
|
|
@ -300,4 +332,3 @@ export class QuickJsGenerator extends GenericQuickJsGenerator<QuickJsGenerator>
|
|||
return new QuickJsGenerator()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,92 @@
|
|||
// Optimized Bunnymark — Float32Array, no GC pressure
|
||||
// Initialization
|
||||
const screenWidth = 800;
|
||||
const screenHeight = 450;
|
||||
const MAX_BUNNIES = 50000;
|
||||
const BUNNY_STRIDE = 8; // x, y, sx, sy, r, g, b, a
|
||||
|
||||
setConfigFlags(FLAG_MSAA_4X_HINT);
|
||||
initWindow(screenWidth, screenHeight, "raylib bunnymark [OPTIMIZED]");
|
||||
|
||||
// Load bunny texture once
|
||||
const texBunny = loadTexture("resources/wabbit_alpha.png");
|
||||
const texW = texBunny.width;
|
||||
const texH = texBunny.height;
|
||||
|
||||
// Flat Float32Array — no JS object overhead, no GC
|
||||
const bunny = new Float32Array(MAX_BUNNIES * BUNNY_STRIDE);
|
||||
let count = 0;
|
||||
|
||||
// Pre-allocate Color struct for drawing (reuse, no allocation per frame)
|
||||
const drawColor = new Color(255, 255, 255, 255);
|
||||
|
||||
// Cache screen size
|
||||
let width = screenWidth;
|
||||
let height = screenHeight;
|
||||
|
||||
// Frame timing
|
||||
let prevTime = getTime();
|
||||
|
||||
setTargetFPS(60);
|
||||
|
||||
// Main loop
|
||||
while (!windowShouldClose()) {
|
||||
// === SPAWN ===
|
||||
if (isMouseButtonDown(MOUSE_BUTTON_LEFT)) {
|
||||
const mx = getMouseX();
|
||||
const my = getMouseY();
|
||||
const limit = Math.min(count + 100, MAX_BUNNIES);
|
||||
for (let i = count; i < limit; i++) {
|
||||
const off = i * BUNNY_STRIDE;
|
||||
bunny[off] = mx; // x
|
||||
bunny[off + 1] = my; // y
|
||||
bunny[off + 2] = getRandomValue(-250, 250) / 60.0; // sx
|
||||
bunny[off + 3] = getRandomValue(-250, 250) / 60.0; // sy
|
||||
bunny[off + 4] = getRandomValue(50, 240); // r
|
||||
bunny[off + 5] = getRandomValue(80, 240); // g
|
||||
bunny[off + 6] = getRandomValue(100, 240); // b
|
||||
bunny[off + 7] = 255; // a
|
||||
}
|
||||
count = limit;
|
||||
}
|
||||
|
||||
// === UPDATE ===
|
||||
const hw = texW / 2;
|
||||
const hh = texH / 2;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const off = i * BUNNY_STRIDE;
|
||||
bunny[off] += bunny[off + 2]; // x += sx
|
||||
bunny[off + 1] += bunny[off + 3]; // y += sy
|
||||
|
||||
if ((bunny[off] + hw) > width || (bunny[off] + hw) < 0)
|
||||
bunny[off + 2] *= -1; // sx
|
||||
if ((bunny[off + 1] + hh) > height || (bunny[off + 1] + hh - 40) < 0)
|
||||
bunny[off + 3] *= -1; // sy
|
||||
}
|
||||
|
||||
// === DRAW ===
|
||||
beginDrawing();
|
||||
clearBackground(RAYWHITE);
|
||||
|
||||
// Batch draw — reuse Color object, only update fields
|
||||
for (let i = 0; i < count; i++) {
|
||||
const off = i * BUNNY_STRIDE;
|
||||
drawColor.r = bunny[off + 4];
|
||||
drawColor.g = bunny[off + 5];
|
||||
drawColor.b = bunny[off + 6];
|
||||
drawColor.a = bunny[off + 7];
|
||||
drawTexture(texBunny, bunny[off], bunny[off + 1], drawColor);
|
||||
}
|
||||
|
||||
// HUD
|
||||
drawRectangle(0, 0, screenWidth, 40, BLACK);
|
||||
const fps = getFPS();
|
||||
drawText(`bunnies: ${count} fps: ${fps}`, 120, 10, 20, GREEN);
|
||||
drawText(`batched draws: ${1 + Math.floor(count / 8192)}`, 320, 10, 20, MAROON);
|
||||
|
||||
endDrawing();
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
unloadTexture(texBunny);
|
||||
closeWindow();
|
||||
1576
generate-bindings.js
1576
generate-bindings.js
File diff suppressed because one or more lines are too long
135
readme.md
135
readme.md
|
|
@ -1,22 +1,28 @@
|
|||

|
||||
# rayjs - Javascript + Raylib
|
||||
QuickJS based Javascript bindings for raylib in a single ~3mb executable
|
||||
# rayjs - JavaScript + Raylib
|
||||
|
||||
QuickJS based JavaScript bindings for raylib **5.5** in a single ~3MB executable.
|
||||
|
||||
## What is this?
|
||||
rayjs is small ES2020 compliant Javascript interpreter based on [QuickJS](https://bellard.org/quickjs/) with bindings for [Raylib](https://www.raylib.com/). You can use it to develop desktop games with Javascript.
|
||||
|
||||
rayjs is a small ES2020 compliant JavaScript interpreter based on [QuickJS](https://bellard.org/quickjs/) with bindings for [Raylib](https://www.raylib.com/) (version **5.5**). You can use it to develop desktop games with JavaScript.
|
||||
|
||||
## What this is not
|
||||
rayjs is not a binding for NodeJS nor is it running in the browser (yet). It's comes with its own Javascript engine (QuickJS) similar to how NodeJS comes with the V8 engine. That makes it much easier to run and distribute rayjs programs as all you need to run a program / game is the small rayjs executable. No installation, no dlls or additional files are needed.
|
||||
|
||||
rayjs is not a binding for NodeJS nor is it running in the browser (yet). It comes with its own JavaScript engine (QuickJS) similar to how NodeJS comes with the V8 engine. That makes it much easier to run and distribute rayjs programs — all you need is the small rayjs executable. No installation, no DLLs or additional files required.
|
||||
|
||||
## Features
|
||||
|
||||
* Compiles into a single, small executable without any dependencies for easy distribution
|
||||
* Use modern Javascript features like classes or async/await
|
||||
* In-depth auto-complete with definitions for the whole API
|
||||
* Use modern JavaScript features like classes and async/await
|
||||
* Full auto-complete with TypeScript definitions for the entire raylib 5.5 API
|
||||
* Built on raylib 5.5 (stable), raygui 4.0
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Download the binary for your platform from the [release section](https://github.com/mode777/rayjs/releases).
|
||||
2. Unzip the executable to a folder and create a new text file in the same folder. Rename the file to `main.js`
|
||||
3. Open the file with a text-editor (e.g. Notepad) and add the following code
|
||||
3. Open the file with a text editor (e.g. Notepad) and add the following code:
|
||||
```javascript
|
||||
const screenWidth = 800;
|
||||
const screenHeight = 450;
|
||||
|
|
@ -36,25 +42,27 @@ rayjs is not a binding for NodeJS nor is it running in the browser (yet). It's c
|
|||
closeWindow();
|
||||
```
|
||||
4. Run the `rayjs` executable
|
||||
5. Congratulations, you have created your first rayjs app.
|
||||
5. Congratulations, you have created your first rayjs app!
|
||||
|
||||
## Running code
|
||||
rayjs will run code in three different modes
|
||||
1. If no parameter is given it will look for a file called `main.js` in the executable directory
|
||||
2. It will run a given Javascript file given as a command line argument like this `rayjs <filename>`
|
||||
3. It will look for a file called `main.js` in a folder given as a command line argument like this `rayjs <foldername>`
|
||||
|
||||
The directory of the main Javascript module will also be the working directory of the app. Modules and resources will be loaded relative to it.
|
||||
rayjs will run code in three different modes:
|
||||
|
||||
1. If no parameter is given it will look for a file called `main.js` in the executable directory
|
||||
2. It will run a given JavaScript file passed as a command line argument: `rayjs <filename>`
|
||||
3. It will look for a file called `main.js` in a folder given as a command line argument: `rayjs <foldername>`
|
||||
|
||||
The directory of the main JavaScript module will also be the working directory of the app. Modules and resources will be loaded relative to it.
|
||||
|
||||
## API support
|
||||
|
||||
The following raylib APIs are supported so far (with a few exceptions):
|
||||
The following raylib 5.5 APIs are supported (with a few exceptions noted in `CHANGELOG_5.5.md`):
|
||||
|
||||
- core (no VR support yet)
|
||||
- shapes
|
||||
- textures
|
||||
- text (no support for GlyphInfo yet)
|
||||
- models (no animation support)
|
||||
- models (no animation support yet)
|
||||
- shaders
|
||||
- audio
|
||||
- raymath
|
||||
|
|
@ -63,13 +71,16 @@ The following raylib APIs are supported so far (with a few exceptions):
|
|||
- raygui
|
||||
- reasings
|
||||
|
||||
Similar to including a header in C and for your convenience, all types/functions are provided globally. They are additionally available in a module called 'raylib'
|
||||
**718 functions bound** (105 intentionally ignored due to unsupported pointer types).
|
||||
|
||||
To check which API functions are not available (yet) check `/bindings/src/index.ts` for `ignore` statements.
|
||||
All types and functions are provided globally for convenience. They are additionally available as a module called `'raylib'`.
|
||||
|
||||
To check which API functions are not available, see `MIGRATION_TABLE.md` or the `ignore()` statements in `bindings/src/index.ts`.
|
||||
|
||||
## Additional APIs
|
||||
|
||||
Rayjs comes with some additional functionality on top of raylib to make writing raylib code with Javascript easier
|
||||
rayjs comes with some additional functionality on top of raylib to make writing code with JavaScript easier:
|
||||
|
||||
```typescript
|
||||
/** Replace material in slot materialIndex (Material is NOT unloaded) */
|
||||
declare function setModelMaterial(model: Model, materialIndex: number, material: Material): void;
|
||||
|
|
@ -87,85 +98,91 @@ declare function meshCopy(mesh: Mesh): Mesh;
|
|||
declare function meshMerge(a: Mesh, b: Mesh): Mesh;
|
||||
```
|
||||
|
||||
Additionally it also comes with bindings to [lightmapper.h](https://github.com/ands/lightmapper/tree/master). See below for more information.
|
||||
Additionally it comes with bindings to [lightmapper.h](https://github.com/ands/lightmapper/tree/master).
|
||||
|
||||
## Auto-Complete / Intellisense
|
||||
|
||||
rayjs comes with full auto-complete support in the form of the definitions file `lib.raylib.d.ts`. These will work with Typescript and Javascript. In order to use them with Javascript you should create a Typescript configuration file in the project root (even if you are not using Typescript) called `tsconfig.json` with the following configuration
|
||||
rayjs comes with full auto-complete support via the definitions file `lib.raylib.d.ts`. These work with TypeScript and JavaScript projects. To use with JavaScript, create a `tsconfig.json` in your project root:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"target": "es2020",
|
||||
"lib": [
|
||||
"ES2020"
|
||||
]
|
||||
"lib": ["ES2020"]
|
||||
}
|
||||
}
|
||||
```
|
||||
After that put the `lib.raylib.d.ts` file in the same folder and optionally restart your IDE. Auto-complete should be working:
|
||||
|
||||
Place `lib.raylib.d.ts` in the same folder and restart your IDE. Auto-complete should work:
|
||||
|
||||

|
||||
|
||||
## Examples
|
||||
|
||||
Some official raylib examples were already ported to Javascript and can be found in the `examples` folder.
|
||||
Ported raylib examples can be found in the `examples` folder.
|
||||
|
||||
Additional examples are described here.
|
||||
|
||||
```
|
||||
```bash
|
||||
./rayjs examples/js_example_project
|
||||
```
|
||||
Barebones example project on how to structure a project that uses Javascript
|
||||
Barebones example project showing how to structure a JavaScript project.
|
||||
|
||||
```bash
|
||||
./rayjs examples/textures/bunnymark.js
|
||||
```
|
||||
./rayjs examples/js_mesh_generation.js
|
||||
```
|
||||
Shows how to create a mesh from Javascript ArrayBuffers
|
||||
Classic bunnymark performance test.
|
||||
|
||||
```bash
|
||||
./rayjs examples/textures/bunnymark_opt.js
|
||||
```
|
||||
Optimized bunnymark using Float32Array (higher performance, no GC pressure).
|
||||
|
||||
```bash
|
||||
./rayjs examples/shaders/js_shaders_gradient_lighting.js
|
||||
```
|
||||
Creates a gradient and uses it as lighting for a 3d scene
|
||||
```
|
||||
Creates a gradient and uses it as lighting for a 3D scene.
|
||||
|
||||
```bash
|
||||
./rayjs examples/ts_dungeon
|
||||
```
|
||||
Small example game that uses Typescript with Webpack for transpilation and bundling
|
||||
```
|
||||
Small example game using TypeScript with Webpack.
|
||||
|
||||
```bash
|
||||
./rayjs examples/ts_game
|
||||
```
|
||||
Example how to integrate existing JS libraries. This example integrates the Inkjs library to compile and play a game written in the Ink interactive fiction language.
|
||||
|
||||
Example integrating existing JS libraries (Inkjs interactive fiction).
|
||||
|
||||
### Lightmapper usage
|
||||
Rayjs integrates the [lightmapper.h](https://github.com/ands/lightmapper/tree/master) library to render baked lighting.
|
||||
The example demonstrates it's usage.
|
||||
```
|
||||
|
||||
rayjs integrates the [lightmapper.h](https://github.com/ands/lightmapper/tree/master) library for baked lighting:
|
||||
|
||||
```bash
|
||||
./rayjs examples/js_lightmapper.js
|
||||
```
|
||||
|
||||
Meshes must have unwrapped lightmap uvs in the second UV channel.
|
||||
Meshes must have unwrapped lightmap UVs in the second UV channel.
|
||||
|
||||

|
||||
|
||||
The example uses an environment that is uniform white which will lead to baking pure ambient occlusion. To bake other light sources, lower the amount of ambient lighting and everything that is rendered with a color other than black will become an emissive lightsource during baking. Rendering will just work as usual and custom shaders are supported. E.g. while the raylib default shader does not support color intensities greater than 1.0, the lightmapper does support them for higher intensity lighting.
|
||||
|
||||
The example will try to bake lighting alongside the render loop which is still buggy and leads to artifacts. Baking before rendering works better.
|
||||
|
||||
## Performance
|
||||
QuickJS is one of the [faster JS interpreters](https://bellard.org/quickjs/bench.html). I'm getting about 13000 bunnys in the unmodified bunnmark before dropping any frames on my 2020 Macbook Air M1 which seems pretty good.
|
||||
|
||||
QuickJS is one of the [faster JS interpreters](https://bellard.org/quickjs/bench.html). The optimized bunnymark (`bunnymark_opt.js`) uses Float32Array for buffer-based object storage, significantly reducing GC pressure and improving frame rates.
|
||||
|
||||

|
||||
|
||||
## Building
|
||||
Here are some basic steps if you want to compile rayjs yourself.
|
||||
You should use CMake for building. **Please note that QuickJS needs Mingw in order to compile correctly on Windows**
|
||||
## Building from source
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- CMake
|
||||
- C compiler (GCC, Clang, MSVC)
|
||||
- Git
|
||||
|
||||
### Build steps
|
||||
|
||||
### Check out required files
|
||||
```bash
|
||||
git clone https://github.com/mode777/rayjs.git
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
### Build with cmake
|
||||
Make sure you have cmake installed and in your path.
|
||||
```bash
|
||||
cd rayjs
|
||||
mkdir build
|
||||
cd build
|
||||
|
|
@ -173,4 +190,10 @@ cmake ..
|
|||
make
|
||||
```
|
||||
|
||||
The executable will be placed in the project root directory.
|
||||
|
||||
## Migration from raylib 4.5
|
||||
|
||||
See `CHANGELOG_5.5.md` for a detailed changelog of all changes made during the migration from raylib 4.5 to 5.5.
|
||||
|
||||
See `MIGRATION_TABLE.md` for a function-by-function comparison.
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -100,8 +100,6 @@ int app_update_quickjs(){
|
|||
}
|
||||
}
|
||||
if(IsKeyPressed(KEY_F5)){
|
||||
GLFWwindow* window = glfwGetCurrentContext();
|
||||
glfwSetWindowShouldClose(window, GLFW_TRUE);
|
||||
shouldReload = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue