# LeanCLR Godot 浅尝一下 LeanCLR 跑 Godot 4 脚本。 (目前是娱乐向,请勿用于生产环境,建议等官方绑定) 原版不带Mono Godot加载GDExtension,C# 代码交给 LeanCLR 解释执行。现在 demo 里可以把 `.cs` 直接挂到节点上,也可以在游戏窗口里的 CodeEdit 改 C#,点运行后编译一个新 assembly,然后不重启切过去。 已完成: - Godot API 绑定尽量全量生成,热更代码后面会直接用 - C# 脚本资源能在编辑器里加载、保存、挂节点 - 运行时热重载保留字段状态,接近 Python/Lua 那种 reload 手感 - demo 是一个简单 Flappy Bird,用来测输入、Process、状态迁移和重载 ## 编译 拉子模块: ```bash git submodule update --init --recursive ``` LeanCLR 默认会把所有未实现的 icall 视为致命错误并 abort。本项目对接的 Godot API 只是一小部分,所以必须先把这个开关关掉,否则跑 demo 时第一碰到未实现接口就崩。修改 `thirdparty/leanclr/src/runtime/build_config.h`: ```c -#define LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 1 +#define LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 0 ``` > 这个 patch 在子模块里,`git submodule update` 之后需要重新打。 编 native 扩展: ```bash cmake -S . -B build-master cmake --build build-master --config Debug --target leanclr_godot ``` > 第一次 `cmake -S . -B build-master` 会通过 FetchContent 拉 godot-cpp 4.5,但此时 `src/generated/godot_api.generated.cpp` 还不存在,配置会失败。所以顺序是: > > ```bash > cmake -S . -B build-master > python3 tools/binding_generator/generate_bindings.py --api build-master/_deps/godot-cpp-src/gdextension/extension_api.json > cmake -S . -B build-master # 再配一次,让 add_library 看到生成的 cpp > cmake --build build-master --config Debug --target leanclr_godot > ``` > `GODOT_CPP_BRANCH` 默认是 `4.5`,仓库里已经为 4.5 打过两个补丁(见下方「已知补丁」)。 编 demo 的 C#: ```bash dotnet msbuild managed/GodotSharpCompat/GodotSharpCompat.csproj -p:Configuration=Debug dotnet msbuild project/Game.csproj -p:Configuration=Debug ``` > `dotnet msbuild` 只能用单横线参数(`-p:...`),双横线(`/p:...`)会报 `MSB1008: 只能指定一个项目`。 > Windows 上要用 `python3`,别用 `python`(会被 Microsoft Store 别名劫持弹出安装器)。 ## 运行 ```bash "/path/to/Godot" --path project ``` > 把 `/path/to/Godot` 换成你机器上实际的 Godot 可执行文件路径。这台机器是 `D:/Projects/godot-build/build/godot.exe`,Godot 4.6.3。 ## 已知补丁 ### `thirdparty/leanclr` 子模块 - `src/runtime/build_config.h`:`LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR` 设为 `0`(见上文)。 ### `src/leanclr_script.cpp` godot-cpp 4.5 把 `gdextension_interface_script_instance_create3` 从 `gdextension_interface::` 命名空间搬到了 `godot::internal::` 里,且原来的 `` 头文件已经不存在。要把: ```cpp #include // ... void* script_instance = gdextension_interface::script_instance_create3(&script_instance_info(), instance); ``` 改成: ```cpp #include // ... void* script_instance = ::godot::internal::gdextension_interface_script_instance_create3(&script_instance_info(), instance); ``` ### `tools/binding_generator/generate_bindings.py` godot-cpp 4.5 把 `ClassDB` 整体重命名成了 `ClassDBSingleton` 单例包装。生成器原本的 `CPP_HEADER_NAME_OVERRIDES["ClassDB"] = "class_db_singleton"` 只重命名了头文件路径,C++ 类名仍是 `ClassDB`,编译会爆 `get_class_static` 不存在。补丁:新增 `SINGLETON_CLASS_CPP_NAMES = {"ClassDB": "ClassDBSingleton"}`,让生成出的 C++ 走 `ClassDBSingleton::get_singleton()->method(...)`,C# 侧 `ClassDB` 的方法全部变成 `static`。 ### `CMakeLists.txt`(Web 构建) 仓库里已经为 Web 构建持久化地打了两个补丁,都在 `if(EMSCRIPTEN)` 块(或紧邻它的 `option` 块)里: 1. **强制单线程**(Web 当前不支持多线程,且 `-sSIDE_MODULE + pthreads` 在 emcc 里是实验性的): ```cmake if(EMSCRIPTEN) set(GODOTCPP_THREADS OFF CACHE BOOL "godot-cpp threading support" FORCE) endif() ``` 2. **降到 `-O1`**:自动生成的 `godot_api.generated.cpp` 里有几千个 invoker 函数,`-O3` 下编译器会把参数拆包全部内联,单个函数会塞进 11 万+ 局部变量,浏览器加载时会报 `Uncaught (in promise) CompileError: WebAssembly.instantiate(): Compiling function #N failed: local count too large`(V8 单函数局部变量上限 50,000)。`leanclr_godot` target 在 `if(EMSCRIPTEN)` 块里被显式加了 `target_compile_options(leanclr_godot PRIVATE -O1)`。 Web 构建方法: ```bash # 需要 Emscripten 3.1+、Ninja 都在 PATH 上 rm -rf build-web cmake -S . -B build-web -G Ninja \ -DCMAKE_TOOLCHAIN_FILE="/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \ -DCMAKE_BUILD_TYPE=Release cmake --build build-web --target leanclr_godot ``` > 这台机器 Emscripten 在 `D:/Projects/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake`。 > `leanclr.gdextension` 已经配了 `web.release.wasm32 = "res://bin/Release/leanclr_godot.wasm"`,构建产物会自动落到 `project/bin/Release/libleanclr_godot.wasm`。要跑起来用编辑器导出一份 Web 即可(或 `godot --headless --export-release "Web" output.html` 配 `project/export_presets.cfg`)。 > > 注意事项: > - `-O1` 让 wasm 从 `-O3` 时的 26 MB 涨到 10 MB 左右(Release 去掉符号),是必要的代价。 > - `leanclr_wasm_libcxx_shim.cpp` 提供 Emscripten libc++ 缺的 `std::__1::__hash_memory` 符号,Web 链接时会自动包含。 > - `em++: warning: ignoring unsupported linker flag: -soname` 是无害的,`-soname` 是 ELF 链接器标志,emcc 不识别但 wasm 产物仍然正确。 ## 几个常看的文件 `src/` : GDExtension 和 bridge。 `managed/GodotSharpCompat/` : 给 C# 用的 Godot API 外观。`Generated/` 子目录是绑定生成器的产物,`.gitignore` 已忽略。 `project/runtime_hot_reload_demo.tscn` : 现在的主 demo。 `project/scripts/HotReloadSmoke.cs` : Flappy 的 C# 脚本。 `project/scripts/RuntimeCSharpEditor.gd` : 游戏里的 C# 编辑器窗口。 `project/leanclr/live_reload.txt` : 热重载 marker。写 `Game` 就回到默认 `Game.dll`,写别的 assembly 名就切到那个 dll。