Files
2026-06-07 22:55:56 +08:00

142 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LeanCLR Godot
浅尝一下 LeanCLR 跑 Godot 4 脚本。
(目前是娱乐向,请勿用于生产环境,建议等官方绑定)
原版不带Mono Godot加载GDExtensionC# 代码交给 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::` 里,且原来的 `<godot_cpp/core/gdextension_interface_loader.hpp>` 头文件已经不存在。要把:
```cpp
#include <godot_cpp/core/gdextension_interface_loader.hpp>
// ...
void* script_instance = gdextension_interface::script_instance_create3(&script_instance_info(), instance);
```
改成:
```cpp
#include <godot_cpp/godot.hpp>
// ...
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="<emsdk>/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。