import { readFileSync, writeFileSync } from "fs"; import { RayLibApi, RayLibFunction, RayLibStruct } from "./interfaces"; import { RayLibHeader } from "./raylib-header"; import { HeaderParser } from "./header-parser"; import { RayLibAlias } from "./interfaces"; import { QuickJsGenerator } from "./quickjs"; let api: RayLibApi function getFunction(funList: RayLibFunction[], name: string){ return funList.find(x => x.name === name) } function getStruct(strList: RayLibStruct[], name: string){ return strList.find(x => x.name === name) } function getAliases(aliasList: RayLibAlias[], name: string) { return aliasList.filter(x => x.type === name).map(x => x.name) } function ignore(name: string){ getFunction(api.functions, name)!.binding = { ignore: true } } function main(){ // Load the pre-generated raylib api api = JSON.parse(readFileSync("thirdparty/raylib/parser/output/raylib_api.json", 'utf8')) const parser = new HeaderParser() const rmathHeader = readFileSync("thirdparty/raylib/src/raymath.h","utf8"); const mathApi = parser.parseFunctions(rmathHeader) mathApi.forEach(x => api.functions.push(x)) const rcameraHeader = readFileSync("thirdparty/raylib/src/rcamera.h","utf8"); const cameraApi = parser.parseFunctionDefinitions(rcameraHeader); cameraApi.forEach(x => api.functions.push(x)) const rguiHeader = readFileSync("thirdparty/raygui/src/raygui.h","utf8"); const rguiFunctions = parser.parseFunctionDefinitions(rguiHeader); const rguiEnums = parser.parseEnums(rguiHeader); //rguiApi.forEach(x => console.log(`core.addApiFunctionByName("${x.name}")`)) rguiFunctions.forEach(x => api.functions.push(x)) rguiEnums.forEach(x => api.enums.push(x)) const rlightsHeader = readFileSync("include/rlights.h","utf8"); const rlightsFunctions = parser.parseFunctions(rlightsHeader, true); api.functions.push(rlightsFunctions[0]) api.functions.push(rlightsFunctions[1]) const reasingsHeader = readFileSync("include/reasings.h","utf8"); const reasingsFunctions = parser.parseFunctions(reasingsHeader); reasingsFunctions.forEach(x => api.functions.push(x)) // Custom Rayjs functions api.functions.push({ name: "SetModelMaterial", description: "Replace material in slot materialIndex", returnType: "void", params: [{type: "Model *",name:"model"},{type:"int",name:"materialIndex"},{type:"Material",name:"material"}] }) // Define a new header const core = new RayLibHeader("raylib_core") core.includes.include("raymath.h") core.includes.include("rcamera.h") core.includes.line("#define RAYGUI_IMPLEMENTATION") core.includes.include("raygui.h") core.includes.line("#define RLIGHTS_IMPLEMENTATION") core.includes.include("rlights.h") core.includes.include("reasings.h") getStruct(api.structs, "Color")!.binding = { properties: { r: { get: true, set: true }, g: { get: true, set: true }, b: { get: true, set: true }, a: { get: true, set: true }, }, createConstructor: true } getStruct(api.structs, "Rectangle")!.binding = { properties: { x: { get: true, set: true }, y: { get: true, set: true }, width: { get: true, set: true }, height: { get: true, set: true }, }, createConstructor: true } getStruct(api.structs, "Vector2")!.binding = { properties: { x: { get: true, set: true }, y: { get: true, set: true }, }, createConstructor: true } getStruct(api.structs, "Vector3")!.binding = { properties: { x: { get: true, set: true }, y: { get: true, set: true }, z: { get: true, set: true }, }, createConstructor: true } getStruct(api.structs, "Vector4")!.binding = { properties: { x: { get: true, set: true }, y: { get: true, set: true }, z: { get: true, set: true }, w: { get: true, set: true }, }, createConstructor: true, aliases: getAliases(api.aliases, "Vector4") } getStruct(api.structs, "Ray")!.binding = { properties: { position: { get: false, set: true }, direction: { get: false, set: true }, }, createConstructor: true } getStruct(api.structs, "RayCollision")!.binding = { properties: { hit: { get: true, set: false }, distance: { get: true, set: false }, point: { get: true, set: false }, normal: { get: true, set: false }, }, createConstructor: false } getStruct(api.structs, "Camera2D")!.binding = { properties: { offset: { get: true, set: true }, target: { get: true, set: true }, rotation: { get: true, set: true }, zoom: { get: true, set: true }, }, createConstructor: true } getStruct(api.structs, "Camera3D")!.binding = { properties: { position: { get: true, set: true }, target: { get: true, set: true }, up: { get: false, set: true }, fovy: { get: true, set: true }, projection: { get: true, set: true }, }, createConstructor: true, aliases: getAliases(api.aliases, "Camera3D") } getStruct(api.structs, "BoundingBox")!.binding = { properties: { min: { get: true, set: true }, max: { get: true, set: true }, }, createConstructor: true } getStruct(api.structs, "Matrix")!.binding = { properties: {}, createConstructor: false } getStruct(api.structs, "NPatchInfo")!.binding = { properties: { source: { get: true, set: true }, left: { get: true, set: true }, top: { get: true, set: true }, right: { get: true, set: true }, bottom: { get: true, set: true }, layout: { get: true, set: true }, }, createConstructor: true } getStruct(api.structs, "Image")!.binding = { properties: { //data: { set: true }, width: { get: true }, height: { get: true }, mipmaps: { get: true }, format: { get: true } }, //destructor: "UnloadImage" } getStruct(api.structs, "Wave")!.binding = { properties: { frameCount: { get: true }, sampleRate: { get: true }, sampleSize: { get: true }, channels: { get: true } }, //destructor: "UnloadWave" } getStruct(api.structs, "Sound")!.binding = { properties: { frameCount: { get: true } }, //destructor: "UnloadSound" } getStruct(api.structs, "Music")!.binding = { properties: { frameCount: { get: true }, looping: { get: true, set: true }, ctxType: { get: true }, }, //destructor: "UnloadMusicStream" } getStruct(api.structs, "Model")!.binding = { properties: { transform: { get: true, set: true }, meshCount: { get: true }, materialCount: { get: true }, boneCount: { get: true }, }, //destructor: "UnloadModel" } getStruct(api.structs, "Mesh")!.binding = { properties: { vertexCount: { get: true, set: true }, triangleCount: { get: true, set: true }, // TODO: Free previous pointers before overwriting vertices: { set: true }, texcoords: { set: true }, texcoords2: { set: true }, normals: { set: true }, tangents: { set: true }, colors: { set: true }, indices: { set: true }, animVertices: { set: true }, animNormals: { set: true }, boneIds: { set: true }, boneWeights: { set: true }, }, createEmptyConstructor: true //destructor: "UnloadMesh" } getStruct(api.structs, "Shader")!.binding = { properties: { id: { get: true } }, //destructor: "UnloadShader" } getStruct(api.structs, "Texture")!.binding = { properties: { width: { get: true }, height: { get: true }, mipmaps: { get: true }, format: { get: true }, }, aliases: getAliases(api.aliases, "Texture") //destructor: "UnloadTexture" } getStruct(api.structs, "Font")!.binding = { properties: { baseSize: { get: true }, glyphCount: { get: true }, glyphPadding: { get: true }, }, //destructor: "UnloadFont" } getStruct(api.structs, "RenderTexture")!.binding = { properties: { id: { get: true } }, aliases: getAliases(api.aliases, "RenderTexture") //destructor: "UnloadRenderTexture" } getStruct(api.structs, "MaterialMap")!.binding = { properties: { texture: { set: true }, color: { set: true, get: true }, value: { get: true, set: true } }, //destructor: "UnloadMaterialMap" } getStruct(api.structs, "Material")!.binding = { properties: { shader: { set: true } }, //destructor: "UnloadMaterial" } ignore("SetWindowIcons") ignore("GetWindowHandle") // Custom frame control functions // NOT SUPPORTED BECAUSE NEEDS COMPILER FLAG ignore("SwapScreenBuffer") ignore("PollInputEvents") ignore("WaitTime") ignore("BeginVrStereoMode") ignore("EndVrStereoMode") ignore("LoadVrStereoConfig") ignore("UnloadVrStereoConfig") getFunction(api.functions, "SetShaderValue")!.binding = { body: (gen) => { gen.jsToC("Shader","shader","argv[0]", core.structLookup) gen.jsToC("int","locIndex","argv[1]", core.structLookup) gen.declare("value","void *", false, "NULL") gen.declare("valueFloat", "float") gen.declare("valueInt", "int") gen.jsToC("int","uniformType","argv[3]", core.structLookup) const sw = gen.switch("uniformType") let b = sw.caseBreak("SHADER_UNIFORM_FLOAT") b.jsToC("float", "valueFloat", "argv[2]", core.structLookup, true) b.statement("value = (void *)&valueFloat") b = sw.caseBreak("SHADER_UNIFORM_VEC2") b.jsToC("Vector2 *", "valueV2", "argv[2]", core.structLookup) b.statement("value = (void*)valueV2") b = sw.caseBreak("SHADER_UNIFORM_VEC3") b.jsToC("Vector3 *", "valueV3", "argv[2]", core.structLookup) b.statement("value = (void*)valueV3") b = sw.caseBreak("SHADER_UNIFORM_VEC4") b.jsToC("Vector4 *", "valueV4", "argv[2]", core.structLookup) b.statement("value = (void*)valueV4") b = sw.caseBreak("SHADER_UNIFORM_INT") b.jsToC("int", "valueInt", "argv[2]", core.structLookup, true) b.statement("value = (void*)&valueInt") b = sw.defaultBreak() b.returnExp("JS_EXCEPTION") gen.call("SetShaderValue", ["shader","locIndex","value","uniformType"]) gen.returnExp("JS_UNDEFINED") }} ignore("SetShaderValueV") const traceLog = getFunction(api.functions, "TraceLog")! traceLog.params?.pop() // Memory functions not supported on JS, just use ArrayBuffer ignore("MemAlloc") ignore("MemRealloc") ignore("MemFree") // Callbacks not supported on JS ignore("SetTraceLogCallback") ignore("SetLoadFileDataCallback") ignore("SetSaveFileDataCallback") ignore("SetLoadFileTextCallback") ignore("SetSaveFileTextCallback") // Files management functions const lfd = getFunction(api.functions, "LoadFileData")! lfd.params![lfd.params!.length-1].binding = { ignore: true } lfd.binding = { body: gen => { gen.jsToC("const char *", "fileName", "argv[0]") gen.declare("bytesRead", "unsigned int") gen.call("LoadFileData", ["fileName", "&bytesRead"], { type: "unsigned char *", name: "retVal" }) gen.statement("JSValue buffer = JS_NewArrayBufferCopy(ctx, (const uint8_t*)retVal, bytesRead)") gen.call("UnloadFileData", ["retVal"]) gen.jsCleanUpParameter("const char*","fileName") gen.returnExp("buffer") } } ignore("UnloadFileData") // TODO: SaveFileData works but unnecessary makes copy of memory getFunction(api.functions, "SaveFileData")!.binding = { } ignore("ExportDataAsCode") getFunction(api.functions, "LoadFileText")!.binding = { after: gen => gen.call("UnloadFileText", ["returnVal"]) } getFunction(api.functions, "SaveFileText")!.params![1].binding = { typeAlias: "const char *" } ignore("UnloadFileText") const createFileList = (gen: QuickJsGenerator, loadName: string, unloadName: string, args: string[]) => { gen.call(loadName, args, { type: "FilePathList", name: "files" }) gen.call("JS_NewArray", ["ctx"], { type: "JSValue", name:"ret"}) const f = gen.for("i", "files.count") f.call("JS_SetPropertyUint32", ["ctx","ret", "i", "JS_NewString(ctx,files.paths[i])"]) gen.call(unloadName, ["files"]) } getFunction(api.functions, "LoadDirectoryFiles")!.binding = { jsReturns: "string[]", body: gen => { gen.jsToC("const char *", "dirPath", "argv[0]") createFileList(gen, "LoadDirectoryFiles", "UnloadDirectoryFiles", ["dirPath"]) gen.jsCleanUpParameter("const char *", "dirPath") gen.returnExp("ret") } } getFunction(api.functions, "LoadDirectoryFilesEx")!.binding = { jsReturns: "string[]", body: gen => { gen.jsToC("const char *", "basePath", "argv[0]") gen.jsToC("const char *", "filter", "argv[1]") gen.jsToC("bool", "scanSubdirs", "argv[2]") createFileList(gen, "LoadDirectoryFilesEx", "UnloadDirectoryFiles", ["basePath", "filter", "scanSubdirs"]) gen.jsCleanUpParameter("const char *", "basePath") gen.jsCleanUpParameter("const char *", "filter") gen.returnExp("ret") } } ignore("UnloadDirectoryFiles") getFunction(api.functions, "LoadDroppedFiles")!.binding = { jsReturns: "string[]", body: gen => { createFileList(gen, "LoadDroppedFiles", "UnloadDroppedFiles", []) gen.returnExp("ret") } } ignore("UnloadDroppedFiles") // Compression/encoding functionality ignore("CompressData") ignore("DecompressData") ignore("EncodeDataBase64") ignore("DecodeDataBase64") ignore("DrawLineStrip") ignore("DrawTriangleFan") ignore("DrawTriangleStrip") ignore("CheckCollisionPointPoly") ignore("CheckCollisionLines") ignore("LoadImageAnim") ignore("ExportImageAsCode") getFunction(api.functions, "LoadImageColors")!.binding = { jsReturns: "ArrayBuffer", body: gen => { gen.jsToC("Image","image","argv[0]", core.structLookup) gen.call("LoadImageColors", ["image"], {name:"colors",type:"Color *"}) gen.statement("JSValue retVal = JS_NewArrayBufferCopy(ctx, (const uint8_t*)colors, image.width*image.height*sizeof(Color))") gen.call("UnloadImageColors", ["colors"]) gen.returnExp("retVal") } } ignore("LoadImagePalette") ignore("UnloadImageColors") ignore("UnloadImagePalette") ignore("GetPixelColor") ignore("SetPixelColor") const lfx = getFunction(api.functions, "LoadFontEx")! lfx.params![2].binding = { ignore: true } lfx.params![3].binding = { ignore: true } lfx.binding = { customizeCall: "Font returnVal = LoadFontEx(fileName, fontSize, NULL, 0);" } ignore("LoadFontFromMemory") ignore("LoadFontData") ignore("GenImageFontAtlas") ignore("UnloadFontData") ignore("ExportFontAsCode") ignore("DrawTextCodepoints") ignore("GetGlyphInfo") ignore("LoadUTF8") ignore("UnloadUTF8") ignore("LoadCodepoints") ignore("UnloadCodepoints") ignore("GetCodepointCount") ignore("GetCodepoint") ignore("GetCodepointNext") ignore("GetCodepointPrevious") ignore("CodepointToUTF8") // Not supported, use JS Stdlib instead api.functions.filter(x => x.name.startsWith("Text")).forEach(x => ignore(x.name)) ignore("DrawTriangleStrip3D") ignore("LoadMaterials") ignore("LoadModelAnimations") ignore("UpdateModelAnimation") ignore("UnloadModelAnimation") ignore("UnloadModelAnimations") ignore("IsModelAnimationValid") ignore("ExportWaveAsCode") // Wave/Sound management functions ignore("LoadWaveSamples") ignore("UnloadWaveSamples") ignore("LoadMusicStreamFromMemory") ignore("LoadAudioStream") ignore("IsAudioStreamReady") ignore("UnloadAudioStream") ignore("UpdateAudioStream") ignore("IsAudioStreamProcessed") ignore("PlayAudioStream") ignore("PauseAudioStream") ignore("ResumeAudioStream") ignore("IsAudioStreamPlaying") ignore("StopAudioStream") ignore("SetAudioStreamVolume") ignore("SetAudioStreamPitch") ignore("SetAudioStreamPan") ignore("SetAudioStreamBufferSizeDefault") ignore("SetAudioStreamCallback") ignore("AttachAudioStreamProcessor") ignore("DetachAudioStreamProcessor") ignore("AttachAudioMixedProcessor") ignore("DetachAudioMixedProcessor") ignore("Vector3OrthoNormalize") ignore("Vector3ToFloatV") ignore("MatrixToFloatV") ignore("QuaternionToAxisAngle") core.exportGlobalConstant("DEG2RAD", "(PI/180.0)") core.exportGlobalConstant("RAD2DEG", "(180.0/PI)") const setOutParam = (fun: RayLibFunction, index: number) => { const param = fun!.params![index] param.binding = { jsType: `{ ${param.name}: number }`, customConverter: (gen,src) => { gen.declare(param.name, param.type, false, "NULL"); gen.declare(param.name+"_out", param.type.replace(" *","")) const body = gen.if("!JS_IsNull("+src+")") body.statement(param.name + " = &" + param.name + "_out") body.call("JS_GetPropertyStr", ["ctx",src, '"'+param.name+'"'], { name: param.name+"_js", type: "JSValue" }) body.call("JS_ToInt32", ["ctx",param.name,param.name+"_js"]) }, customCleanup: (gen,src) => { const body = gen.if("!JS_IsNull("+src+")") body.call("JS_SetPropertyStr", ["ctx", src, `"${param.name}"`, "JS_NewInt32(ctx,"+param.name+"_out)"]) } } } const setOutParamString = (fun: RayLibFunction, index: number, indexLen: number) => { const lenParam = fun!.params![indexLen] lenParam.binding = { ignore: true } const param = fun!.params![index] param.binding = { jsType: `{ ${param.name}: string }`, customConverter: (gen,src) => { gen.call("JS_GetPropertyStr", ["ctx",src, '"'+param.name+'"'], { name: param.name+"_js", type: "JSValue" }) gen.declare(param.name+"_len", "size_t"); gen.call("JS_ToCStringLen",["ctx", "&"+param.name+"_len", param.name+"_js"], { name: param.name+"_val", type: "const char *" }) gen.call("memcpy", ["(void *)textbuffer", param.name+"_val", param.name+"_len"]) gen.statement("textbuffer["+param.name+"_len] = 0") gen.declare(param.name, param.type, false, "textbuffer"); gen.declare(lenParam.name, lenParam.type, false, "4096") }, customCleanup: (gen, src) => { gen.jsCleanUpParameter("const char *", param.name + "_val") gen.call("JS_SetPropertyStr", ["ctx", src, `"${param.name}"`, "JS_NewString(ctx,"+param.name+")"]) } } } core.definitions.declare("textbuffer[4096]", "char", true) setOutParam(getFunction(api.functions, "GuiDropdownBox")!, 2) setOutParam(getFunction(api.functions, "GuiSpinner")!, 2) setOutParam(getFunction(api.functions, "GuiValueBox")!, 2) setOutParam(getFunction(api.functions, "GuiListView")!, 2) // const setStringListParam = (fun: RayLibFunction, index: number, indexLen: number) => { // const lenParam = fun!.params![indexLen] // lenParam.binding = { ignore: true } // const param = fun!.params![index] // fun.binding = { customizeCall: "int returnVal = GuiListViewEx(bounds, text, count, focus, scrollIndex, active);" } // param.binding = { // jsType: `{ ${param.name}: string[] }`, // customConverter: (gen,src) => { // gen.line("// TODO: Read string values") // }, // customCleanup: (gen, src) => { // gen.line("// TODO: Dispose strings") // } // } // } //const glve = getFunction(api.functions, "GuiListViewEx")! //setStringListParam(glve, 1,2) //setOutParam(glve, 3) //setOutParam(glve, 4) ignore("GuiListViewEx"); setOutParamString(getFunction(api.functions, "GuiTextBox")!, 1,2) const gtib = getFunction(api.functions, "GuiTextInputBox")! setOutParamString(gtib,4,5) setOutParam(gtib, 6) // needs string array ignore("GuiTabBar") ignore("GuiGetIcons") ignore("GuiLoadIcons") // TODO: Parse and support light struct ignore("CreateLight") ignore("UpdateLightValues") api.structs.forEach(x => core.addApiStruct(x)) api.functions.forEach(x => core.addApiFunction(x)) api.defines.filter(x => x.type === "COLOR").map(x => ({ name: x.name, description: x.description, values: (x.value.match(/\{([^}]+)\}/) || "")[1].split(',').map(x => x.trim()) })).forEach(x => { core.exportGlobalStruct("Color", x.name, x.values, x.description) }) api.enums.forEach(x => core.addEnum(x)) core.exportGlobalConstant("MATERIAL_MAP_DIFFUSE", "Albedo material (same as: MATERIAL_MAP_DIFFUSE") core.exportGlobalConstant("MATERIAL_MAP_SPECULAR", "Metalness material (same as: MATERIAL_MAP_SPECULAR)") core.writeTo("src/bindings/js_raylib_core.h") core.typings.writeTo("examples/lib.raylib.d.ts") const ignored = api.functions.filter(x => x.binding?.ignore).length console.log(`Converted ${api.functions.length-ignored} function. ${ignored} ignored`) console.log("Success!") } main()