build steps
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
LeanCLR-godot runs the [LeanCLR](https://github.com/focus-creative-games/leanclr) interpreter inside Godot 4 as a GDExtension, so C# scripts (.cs) can be loaded and hot-reloaded by Godot without a Mono build. The bridge lets a C# class extend `Godot.Node`, and the engine instantiates it through the interpreter at runtime.
|
||||
|
||||
This is a hobby project (per the README) — not for production. Expect rough edges, no official bindings, and Godot 4.5+ as the target.
|
||||
|
||||
## Build & Run
|
||||
|
||||
The README's steps are missing pieces. The full sequence:
|
||||
|
||||
```bash
|
||||
# 1. Pull the LeanCLR git submodule.
|
||||
git submodule update --init --recursive
|
||||
|
||||
# 2. Configure (also fetches godot-cpp 4.5 via FetchContent on first run).
|
||||
cmake -S . -B build-master
|
||||
|
||||
# 3. Generate the Godot API bindings. extension_api.json is shipped inside
|
||||
# the just-fetched godot-cpp tree, not at the repo root.
|
||||
python3 tools/binding_generator/generate_bindings.py \
|
||||
--api build-master/_deps/godot-cpp-src/gdextension/extension_api.json
|
||||
# (on Windows use `python3` explicitly — `python` is aliased to the
|
||||
# Microsoft Store stub and will pop the installer UI)
|
||||
|
||||
# 4. Re-configure now that src/generated/godot_api.generated.cpp exists,
|
||||
# then build the native GDExtension.
|
||||
cmake -S . -B build-master
|
||||
cmake --build build-master --config Debug --target leanclr_godot
|
||||
|
||||
# 5. Build the managed C# facade and the demo (depends on step 4 + the
|
||||
# facade's own output).
|
||||
dotnet msbuild managed/GodotSharpCompat/GodotSharpCompat.csproj -p:Configuration=Debug
|
||||
dotnet msbuild project/Game.csproj -p:Configuration=Debug
|
||||
|
||||
# 6. Run.
|
||||
"/d/Projects/godot-build/build/godot.exe" --path project # adjust to your Godot install
|
||||
```
|
||||
|
||||
Outputs:
|
||||
- `project/bin/<Config>/leanclr_godot.{dll,so,dylib,wasm}` — the GDExtension, loaded via `project/leanclr.gdextension`.
|
||||
- `project/leanclr/Game.dll`, `project/leanclr/GodotSharpCompat.dll` — the C# assemblies LeanCLR loads at runtime.
|
||||
- `project/leanclr/{mscorlib,System,System.Core,...}.dll` — LeanCLR's bundled .NET Framework 4.7.2 BCL, referenced by `project/Game.csproj` and `managed/GodotSharpCompat/GodotSharpCompat.csproj` via `HintPath`. They are not built here; they ship inside `thirdparty/leanclr/src/libraries/dotnetframework4.x-linux/`.
|
||||
|
||||
`project/Game.csproj` and `managed/GodotSharpCompat/GodotSharpCompat.csproj` both use legacy .NET Framework v4.7.2 with `<NoStandardLib>true</NoStandardLib>` and `<NoConfig>true</NoConfig>` (they link against LeanCLR's bundled mscorlib/System, not the host BCL). They emit a single DLL straight into `project/leanclr/`.
|
||||
|
||||
WASM build is supported (`-sSIDE_MODULE=1`, output `.wasm` in `project/bin/Release/`) — see CMakeLists.txt's `EMSCRIPTEN` branch.
|
||||
|
||||
## Two Source Patches Required for godot-cpp 4.5
|
||||
|
||||
The `GODOT_CPP_BRANCH` was bumped from 4.4 → 4.5 (uncommitted change in `CMakeLists.txt`). Two 4.5 API changes break the as-published code; both are fixed in this working tree:
|
||||
|
||||
1. **`ClassDB` was renamed to a `ClassDBSingleton` wrapper** in godot-cpp 4.5. The `CPP_HEADER_NAME_OVERRIDES["ClassDB"] = "class_db_singleton"` in `tools/binding_generator/generate_bindings.py` only renames the include path; the C++ class symbol in generated code is still `ClassDB` and breaks compilation. Fix: added a `SINGLETON_CLASS_CPP_NAMES` map in the generator that:
|
||||
- Maps `ClassDB` → `ClassDBSingleton` in the C++ class name emitted in the generated native function.
|
||||
- Forces `is_static=True` for all ClassDB methods (since 4.5 only exposes them as non-static on the singleton instance, accessed via `ClassDBSingleton::get_singleton()`).
|
||||
- Routes the singleton dispatch through `ClassDBSingleton::get_singleton()->method(...)` in both the regular and vararg `generate_native_method_function` paths.
|
||||
- The C# side correspondingly gets `static` methods on `ClassDB` (still extending `GodotObject`, so existing type-checks pass).
|
||||
|
||||
2. **`gdextension_interface_script_instance_create3` moved** in godot-cpp 4.5 from `<godot_cpp/core/gdextension_interface_loader.hpp>` (which no longer exists) to `namespace godot::internal` inside `<godot_cpp/godot.hpp>`. Fix in `src/leanclr_script.cpp`:
|
||||
- Replaced `#include <godot_cpp/core/gdextension_interface_loader.hpp>` with `#include <godot_cpp/godot.hpp>`.
|
||||
- Changed the call site from `gdextension_interface::script_instance_create3(...)` to `::godot::internal::gdextension_interface_script_instance_create3(...)`.
|
||||
|
||||
## One Patch Required Inside the LeanCLR Submodule
|
||||
|
||||
`thirdparty/leanclr/` is a git submodule, so the patch lives outside this repo's history and won't survive a `git submodule update`. In `src/runtime/build_config.h`, the upstream default is:
|
||||
|
||||
```c
|
||||
#define LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 1
|
||||
```
|
||||
|
||||
Set it to `0`. The binding generator emits ~5800 native icalls but the C# side only calls a tiny fraction; with the default `1`, hitting an unwired `NotImplemented` site calls `fatal_on_not_implemented_error()` and aborts the process (see `LEANCLR_CODEGEN_RETURN_NOT_IMPLEMENTED_ERROR` in `codegen/leanclr_common.h` and `RETURN_NOT_IMPLEMENTED_ERROR` in `core/rt_base.h`). With `0`, the interpreter returns `RtErr::NotImplemented` and execution continues, which is what the demo needs to be runnable today.
|
||||
|
||||
This patch must be reapplied after every `git submodule update --init --recursive` (or after pulling new commits into the submodule).
|
||||
|
||||
## Repository Layout
|
||||
|
||||
- `src/` — GDExtension C++ (one .h/.cpp per component, see below)
|
||||
- `managed/GodotSharpCompat/` — C# façade: hand-written `GD`, `Node`, `GodotObject`, `ExportAttribute`, `Variant`/marshalling helpers, `CallableDelegateRegistry`; plus a `Generated/` subdir produced by the binding generator (gitignored — must be regenerated).
|
||||
- `project/` — the Godot project itself (`.gdextension`, `.csproj`, scenes, scripts, `leanclr/` output dir, `bin/` for the GDExtension binaries)
|
||||
- `tools/binding_generator/generate_bindings.py` — Python generator for both `src/generated/godot_api.generated.cpp` and `managed/GodotSharpCompat/Generated/*.generated.cs`
|
||||
- `thirdparty/leanclr/` — LeanCLR git submodule, pulled via `add_subdirectory(${LEANCLR_ROOT}/src/runtime leanclr_runtime)` in the top-level CMake
|
||||
- `gdextension_interface.h` — pinned Godot 4 GDExtension interface header (the matching headers used by the binding generator live inside the fetched godot-cpp tree under `_deps/.../gdextension/`)
|
||||
|
||||
## Native Bridge Architecture (src/)
|
||||
|
||||
The GDExtension wires LeanCLR into Godot through six cooperating pieces, registered at `MODULE_INITIALIZATION_LEVEL_SCENE` in `register_types.cpp`:
|
||||
|
||||
- **`LeanCLRRuntimeBridge`** (`leanclr_runtime_bridge.{h,cpp}`) — the only file that knows about both godot-cpp and LeanCLR. Owns the global `LeanCLRVirtualMachine`, loads assemblies by name from the configured assembly directory, creates/release managed objects, and exposes overloaded `invoke_script_method` / `get_script_property` / `set_script_property` for the typed value marshallers LeanCLR needs. Also handles owner→managed-object registration and `migrate_script_state` for hot reload.
|
||||
- **`LeanCLRScriptLanguage`** (`leanclr_script_language.{h,cpp}`) — `ScriptLanguageExtension` singleton. Tells Godot there's a "LeanCLR" language; provides reserved words, validation, completion, debug hooks (most are stubs), and `_reload_*` entry points. `_reload_all_scripts` / `_reload_scripts` are what the runtime editor hits.
|
||||
- **`LeanCLRScript`** (`leanclr_script.{h,cpp}`) — `ScriptExtension` for an individual `.cs` / `.lcs` file. `parse_source()` extracts `assembly_name`, `type_name`, optional `entry_point`, and `base_type` (defaults to `"Node"`) from header comments via a simple parser in this file. Calls `godot::internal::gdextension_interface_script_instance_create3` to allocate script instances.
|
||||
- **`LeanCLRScriptLoader` / `LeanCLRScriptSaver`** (`leanclr_script_{loader,saver}.{h,cpp}`) — `ResourceFormatLoader`/`Saver` pair. Recognizes `cs` and `lcs` extensions, hands a `LeanCLRScript` resource to Godot so `.cs` files can be attached to nodes and saved in `.tscn`/`.tres`.
|
||||
- **`LeanCLRMain`** (`leanclr_main_node.{h,cpp}`) — a `Node` that, on `NOTIFICATION_READY`, instantiates the configured C# class (default `Game.ClassDbMain`) through the bridge. Used by `main.tscn` as the bootstrapping demo node.
|
||||
- **`LeanCLRHotReloadHost`** (`leanclr_hot_reload_host.{h,cpp}`) — a `Node` that owns the *current* managed object for runtime hot reload. Wires `_Input`/process through the bridge, and on `reload_assembly` captures state via `CaptureHotReloadState` (custom `Variant` blob from the script), instantiates the new assembly, calls `migrate_script_state`, then `RestoreHotReloadState` and `OnHotReloaded` on the new instance. Skips itself while in the editor or while a scene is being edited.
|
||||
- **`leanclr_wasm_libcxx_shim.cpp`** — minimal `std::__1::__hash_memory` shim for Emscripten (libc++ is missing the symbol LeanCLR's stdlib expects when linking into a wasm side module).
|
||||
- **`src/generated/godot_api.generated.cpp`** — auto-generated, ~5800+ per-class marshalling functions. Do not edit; regenerate via the binding generator.
|
||||
|
||||
## Demo / Hot Reload Flow
|
||||
|
||||
`project/runtime_hot_reload_demo.tscn` is the main scene and exercises the hot-reload path end to end:
|
||||
|
||||
1. `FlappyScript` (a `LeanCLRScript` pointing at `project/scripts/HotReloadSmoke.cs`) is the gameplay node.
|
||||
2. `LiveHotReloadHost` (a `LeanCLRHotReloadHost`) is the bridge that drives the C# object's lifecycle.
|
||||
3. `HotReloadInputRelay.gd` forwards input and polls `res://leanclr/live_reload.txt` every `reload_poll_seconds`. When the marker changes from the default `Game`, it calls `reload_assembly` on the host.
|
||||
4. `RuntimeCSharpEditor.gd` is a `CodeEdit` window inside the game. Pressing *Run* writes `HotReloadSmoke.cs` back, shells out to `dotnet msbuild` to produce a uniquely-named `GameRuntimeEdit<unix_ms>.dll` into `project/leanclr/`, then writes that name to `live_reload.txt` so the relay picks it up on its next tick. It also rebuilds the default `Game.dll` afterward and restores the runtime-edit dll from `user://` so the next editor run is reproducible.
|
||||
5. `HotReloadSmoke.cs` implements `CaptureHotReloadState` / `RestoreHotReloadState` / `OnHotReloaded` so the bird/physics state survives across reloads. Setting the env var `LEANCLR_RUNTIME_EDITOR_AUTORUN` triggers a one-shot edit-and-rebuild on startup to verify the path.
|
||||
|
||||
## Conventions & Gotchas
|
||||
|
||||
- The C# `GodotSharpCompat` shim has a `Generated/` subdir in its `.gitignore`; if you regenerate bindings keep it out of the repo.
|
||||
- The native bridge is large (`leanclr_runtime_bridge.cpp` is ~87 KB) — it is one translation unit by design. The other bridge files stay focused.
|
||||
- `project/leanclr/*.dll` is gitignored; rebuilt DLLs land alongside but should not be committed.
|
||||
- `compatibility_minimum = "4.4"` in `leanclr.gdextension`, project `config/features = "4.6"`. The current `GODOT_CPP_BRANCH` is 4.5 — bump together (see "Two Source Patches" above).
|
||||
- CMake honors `GODOT_CPP_ROOT` / `GODOT_CPP_LIBRARY` for prebuilt godot-cpp; otherwise it `FetchContent`s from GitHub. On Windows + MSVC the runtime is forced to `MultiThreadedDLL` to match what `godot-cpp` ships.
|
||||
- LeanCLR is a submodule: changes inside `thirdparty/leanclr/` are not part of this repo's history. Don't edit it locally — patch upstream.
|
||||
- No automated test suite. The demo *is* the smoke test; run the Flappy scene and exercise hot reload to validate a change.
|
||||
- The Godot binary on this machine is at `D:\Projects\godot-build\build\godot.exe` (Godot 4.6.3). Pass it as a positional arg to `--path project` to launch.
|
||||
+14
-1
@@ -9,7 +9,14 @@ option(LEANCLR_GODOT_FETCH_GODOT_CPP "Fetch godot-cpp when GODOT_CPP_ROOT is not
|
||||
set(GODOT_CPP_ROOT "" CACHE PATH "Path to a checked-out godot-cpp tree")
|
||||
set(GODOT_CPP_LIBRARY "" CACHE FILEPATH "Path to a prebuilt godot-cpp static library. When set, godot-cpp is imported instead of built.")
|
||||
set(GODOT_CPP_GEN_INCLUDE_DIR "" CACHE PATH "Path to godot-cpp generated headers, for example <godot-cpp-build>/gen/include")
|
||||
set(GODOT_CPP_BRANCH "4.4" CACHE STRING "godot-cpp branch or tag to use when fetching")
|
||||
set(GODOT_CPP_BRANCH "4.5" CACHE STRING "godot-cpp branch or tag to use when fetching")
|
||||
|
||||
# Only the Web build is single-threaded today — pthreads in a wasm side
|
||||
# module is experimental and the demo does not need it. Forward to godot-cpp
|
||||
# so its own -sUSE_PTHREADS=1 (see godot-cpp/cmake/web.cmake) is not emitted.
|
||||
if(EMSCRIPTEN)
|
||||
set(GODOTCPP_THREADS OFF CACHE BOOL "godot-cpp threading support" FORCE)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "Select the MSVC runtime library for GDExtension binaries." FORCE)
|
||||
@@ -119,6 +126,12 @@ endif()
|
||||
if(EMSCRIPTEN)
|
||||
target_link_options(leanclr_godot PRIVATE -sSIDE_MODULE=1)
|
||||
set_target_properties(leanclr_godot PROPERTIES SUFFIX ".wasm")
|
||||
# The auto-generated godot_api.generated.cpp has thousands of invoker
|
||||
# functions; at -O3 the compiler inlines argument unpacking and blows
|
||||
# past V8's 50,000-locals-per-function limit ("local count too large" at
|
||||
# runtime). Cap the web build at -O1 to keep each function's locals
|
||||
# manageable.
|
||||
target_compile_options(leanclr_godot PRIVATE -O1)
|
||||
endif()
|
||||
|
||||
set_target_properties(leanclr_godot PROPERTIES
|
||||
|
||||
@@ -12,12 +12,23 @@
|
||||
- 运行时热重载保留字段状态,接近 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
|
||||
@@ -25,23 +36,101 @@ 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 project/Game.csproj /p:Configuration=Debug
|
||||
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
|
||||
/Applications/Godot.app/Contents/MacOS/Godot --path project
|
||||
"/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 外观。
|
||||
`managed/GodotSharpCompat/` : 给 C# 用的 Godot API 外观。`Generated/` 子目录是绑定生成器的产物,`.gitignore` 已忽略。
|
||||
|
||||
`project/runtime_hot_reload_demo.tscn` : 现在的主 demo。
|
||||
|
||||
@@ -50,4 +139,3 @@ dotnet msbuild project/Game.csproj /p:Configuration=Debug
|
||||
`project/scripts/RuntimeCSharpEditor.gd` : 游戏里的 C# 编辑器窗口。
|
||||
|
||||
`project/leanclr/live_reload.txt` : 热重载 marker。写 `Game` 就回到默认 `Game.dll`,写别的 assembly 名就切到那个 dll。
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter="leanclr/*.dll,leanclr/live_reload.txt"
|
||||
exclude_filter=""
|
||||
export_path="deploy/html/index.html"
|
||||
export_path="../../builds/leanCLR/web/index.html"
|
||||
patches=PackedStringArray()
|
||||
patch_delta_encoding=false
|
||||
patch_delta_compression_level_zstd=19
|
||||
@@ -24,8 +24,8 @@ script_export_mode=2
|
||||
|
||||
[preset.0.options]
|
||||
|
||||
custom_template/debug="/Volumes/External/Misc/templates_4.6/web_dlink_nothreads_debug.zip"
|
||||
custom_template/release="/Volumes/External/Misc/templates_4.6/web_dlink_nothreads_release.zip"
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
variant/extensions_support=true
|
||||
variant/thread_support=false
|
||||
vram_texture_compression/for_desktop=true
|
||||
|
||||
@@ -13,3 +13,4 @@ linux.release.x86_64 = "res://bin/Release/libleanclr_godot.so"
|
||||
macos.debug = "res://bin/Debug/libleanclr_godot.dylib"
|
||||
macos.release = "res://bin/Release/libleanclr_godot.dylib"
|
||||
web.release.wasm32 = "res://bin/Release/libleanclr_godot.wasm"
|
||||
web.debug.wasm32 = "res://bin/Release/libleanclr_godot.wasm"
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
@echo off
|
||||
REM Build the Web (WASM) target. Requires Emscripten 3.1+ and Ninja on PATH.
|
||||
|
||||
setlocal
|
||||
|
||||
if not exist thirdparty\leanclr\CMakeLists.txt (
|
||||
git submodule update --init --recursive
|
||||
)
|
||||
|
||||
findstr /C:"LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 1" thirdparty\leanclr\src\runtime\build_config.h >nul 2>&1
|
||||
if %errorlevel% == 0 (
|
||||
echo [build-web] Re-applying leanclr NotImplemented patch...
|
||||
powershell -Command "(Get-Content thirdparty/leanclr/src/runtime/build_config.h) -replace 'LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 1', 'LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 0' | Set-Content thirdparty/leanclr/src/runtime/build_config.h"
|
||||
)
|
||||
|
||||
REM Find the Emscripten toolchain. Override via EMSDK_TOOLCHAIN if needed.
|
||||
if "%EMSDK_TOOLCHAIN%"=="" (
|
||||
for /f "delims=" %%i in ('where emcc 2^>nul') do set "EMCC_PATH=%%i"
|
||||
if not "%EMCC_PATH%"=="" (
|
||||
REM <emsdk>\upstream\emscripten\emcc -> <emsdk>\upstream\emscripten\cmake\Modules\Platform\Emscripten.cmake
|
||||
for %%i in ("%EMCC_PATH%") do set "EMCC_DIR=%%~dpi"
|
||||
set "EMSDK_TOOLCHAIN=%EMCC_DIR%cmake\Modules\Platform\Emscripten.cmake"
|
||||
)
|
||||
)
|
||||
if "%EMSDK_TOOLCHAIN%"=="" (
|
||||
echo [build-web] Emscripten toolchain not found. Set EMSDK_TOOLCHAIN to: 1>&2
|
||||
echo [build-web] ^<emsdk^>\upstream\emscripten\cmake\Modules\Platform\Emscripten.cmake 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
echo [build-web] Using toolchain: %EMSDK_TOOLCHAIN%
|
||||
|
||||
cmake -S . -B build-web -G Ninja -DCMAKE_TOOLCHAIN_FILE="%EMSDK_TOOLCHAIN%" -DCMAKE_BUILD_TYPE=Release || exit /b 1
|
||||
python tools\binding_generator\generate_bindings.py --api build-web\_deps\godot-cpp-src\gdextension\extension_api.json
|
||||
if errorlevel 1 (
|
||||
py tools\binding_generator\generate_bindings.py --api build-web\_deps\godot-cpp-src\gdextension\extension_api.json
|
||||
)
|
||||
cmake -S . -B build-web -G Ninja -DCMAKE_TOOLCHAIN_FILE="%EMSDK_TOOLCHAIN%" -DCMAKE_BUILD_TYPE=Release || exit /b 1
|
||||
cmake --build build-web --target leanclr_godot || exit /b 1
|
||||
|
||||
dotnet msbuild managed\GodotSharpCompat\GodotSharpCompat.csproj -p:Configuration=Debug || exit /b 1
|
||||
dotnet msbuild project\Game.csproj -p:Configuration=Debug || exit /b 1
|
||||
|
||||
echo [build-web] Done. Output: project\bin\Release\libleanclr_godot.wasm
|
||||
echo [build-web] Export the project for Web in the Godot editor to run.
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the Web (WASM) target.
|
||||
# Requires Emscripten 3.1+ and Ninja on PATH.
|
||||
# Run from the repo root.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
if [ ! -f "thirdparty/leanclr/CMakeLists.txt" ]; then
|
||||
git submodule update --init --recursive
|
||||
fi
|
||||
|
||||
# Re-apply the LeanCLR NotImplemented patch if needed.
|
||||
if grep -q "LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 1" \
|
||||
thirdparty/leanclr/src/runtime/build_config.h 2>/dev/null; then
|
||||
echo "[build-web] Re-applying leanclr NotImplemented patch..."
|
||||
sed -i 's/LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 1/LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 0/' \
|
||||
thirdparty/leanclr/src/runtime/build_config.h
|
||||
fi
|
||||
|
||||
# Find the Emscripten toolchain. Override via $EMSDK_TOOLCHAIN if your install
|
||||
# is in a non-default location.
|
||||
if [ -z "${EMSDK_TOOLCHAIN:-}" ]; then
|
||||
if command -v emcc >/dev/null 2>&1; then
|
||||
# `which emcc` may resolve to either <emsdk>/upstream/emscripten/emcc
|
||||
# (POSIX) or <emsdk>/upstream/bin/emcc.bat (Windows launcher). Walk up
|
||||
# until we find a directory that contains cmake/Modules/Platform/.
|
||||
EMCC_PATH="$(command -v emcc)"
|
||||
d="$(dirname "$EMCC_PATH")"
|
||||
for _ in 1 2 3 4 5 6 7 8; do
|
||||
candidate="$d/cmake/Modules/Platform/Emscripten.cmake"
|
||||
if [ -f "$candidate" ]; then
|
||||
EMSDK_TOOLCHAIN="$candidate"
|
||||
break
|
||||
fi
|
||||
d="$(dirname "$d")"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
if [ -z "${EMSDK_TOOLCHAIN:-}" ] || [ ! -f "$EMSDK_TOOLCHAIN" ]; then
|
||||
echo "[build-web] Emscripten toolchain not found. Set EMSDK_TOOLCHAIN to the" >&2
|
||||
echo "[build-web] full path of Emscripten.cmake, e.g.:" >&2
|
||||
echo "[build-web] <emsdk>/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[build-web] Using toolchain: $EMSDK_TOOLCHAIN"
|
||||
|
||||
# Configure (first pass — fetches godot-cpp). Single-threaded is forced by
|
||||
# the CMakeLists's own if(EMSCRIPTEN) block (GODOTCPP_THREADS=OFF, -O1).
|
||||
cmake -S . -B build-web -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$EMSDK_TOOLCHAIN" \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
# Generate bindings (uses the same extension_api.json path, just inside build-web/).
|
||||
python3 tools/binding_generator/generate_bindings.py \
|
||||
--api build-web/_deps/godot-cpp-src/gdextension/extension_api.json
|
||||
|
||||
# Re-configure so the generated cpp is part of the target, then build.
|
||||
cmake -S . -B build-web -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$EMSDK_TOOLCHAIN" \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build-web --target leanclr_godot
|
||||
|
||||
# Managed C# (same output dir as the desktop build).
|
||||
dotnet msbuild managed/GodotSharpCompat/GodotSharpCompat.csproj -p:Configuration=Debug
|
||||
dotnet msbuild project/Game.csproj -p:Configuration=Debug
|
||||
|
||||
echo "[build-web] Done. Output:"
|
||||
echo "[build-web] project/bin/Release/libleanclr_godot.wasm"
|
||||
echo "[build-web] Export the project for Web in the Godot editor to run."
|
||||
@@ -0,0 +1,30 @@
|
||||
@echo off
|
||||
REM Build the Windows (Desktop) target: native GDExtension + managed C# DLLs.
|
||||
REM Run from the repo root.
|
||||
|
||||
setlocal
|
||||
|
||||
if not exist thirdparty\leanclr\CMakeLists.txt (
|
||||
git submodule update --init --recursive
|
||||
)
|
||||
|
||||
REM Re-apply the LeanCLR NotImplemented patch if a fresh submodule overwrote it.
|
||||
findstr /C:"LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 1" thirdparty\leanclr\src\runtime\build_config.h >nul 2>&1
|
||||
if %errorlevel% == 0 (
|
||||
echo [build-windows] Re-applying leanclr NotImplemented patch...
|
||||
powershell -Command "(Get-Content thirdparty/leanclr/src/runtime/build_config.h) -replace 'LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 1', 'LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 0' | Set-Content thirdparty/leanclr/src/runtime/build_config.h"
|
||||
)
|
||||
|
||||
cmake -S . -B build-master -DCMAKE_BUILD_TYPE=Debug || exit /b 1
|
||||
python tools\..\..\tools\binding_generator\generate_bindings.py --api build-master\_deps\godot-cpp-src\gdextension\extension_api.json
|
||||
if errorlevel 1 (
|
||||
REM python3 not on PATH; try py launcher as a fallback.
|
||||
py tools\binding_generator\generate_bindings.py --api build-master\_deps\godot-cpp-src\gdextension\extension_api.json
|
||||
)
|
||||
cmake -S . -B build-master -DCMAKE_BUILD_TYPE=Debug || exit /b 1
|
||||
cmake --build build-master --config Debug --target leanclr_godot || exit /b 1
|
||||
|
||||
dotnet msbuild managed\GodotSharpCompat\GodotSharpCompat.csproj -p:Configuration=Debug || exit /b 1
|
||||
dotnet msbuild project\Game.csproj -p:Configuration=Debug || exit /b 1
|
||||
|
||||
echo [build-windows] Done. Run with: "C:\path\to\Godot.exe" --path project
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the Windows (Desktop) target: native GDExtension + managed C# DLLs.
|
||||
# Run from the repo root.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# Pull submodules if missing.
|
||||
if [ ! -f "thirdparty/leanclr/CMakeLists.txt" ]; then
|
||||
git submodule update --init --recursive
|
||||
fi
|
||||
|
||||
# Re-apply the LeanCLR NotImplemented patch if a fresh submodule overwrote it.
|
||||
if grep -q "LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 1" \
|
||||
thirdparty/leanclr/src/runtime/build_config.h 2>/dev/null; then
|
||||
echo "[build-windows] Re-applying leanclr NotImplemented patch..."
|
||||
sed -i 's/LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 1/LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 0/' \
|
||||
thirdparty/leanclr/src/runtime/build_config.h
|
||||
fi
|
||||
|
||||
# Configure CMake (first pass — fetches godot-cpp via FetchContent).
|
||||
cmake -S . -B build-master -DCMAKE_BUILD_TYPE=Debug
|
||||
# Generate bindings (needs the just-fetched extension_api.json).
|
||||
python3 tools/binding_generator/generate_bindings.py \
|
||||
--api build-master/_deps/godot-cpp-src/gdextension/extension_api.json
|
||||
# Re-configure so add_library sees the generated cpp, then build.
|
||||
cmake -S . -B build-master -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake --build build-master --config Debug --target leanclr_godot
|
||||
|
||||
# Managed C#.
|
||||
dotnet msbuild managed/GodotSharpCompat/GodotSharpCompat.csproj -p:Configuration=Debug
|
||||
dotnet msbuild project/Game.csproj -p:Configuration=Debug
|
||||
|
||||
echo "[build-windows] Done. Run with: \"/path/to/Godot\" --path project"
|
||||
@@ -0,0 +1,16 @@
|
||||
@echo off
|
||||
REM Remove all build outputs. Leaves sources, scripts, and the leanclr
|
||||
REM submodule intact.
|
||||
|
||||
setlocal
|
||||
|
||||
if exist build rmdir /s /q build
|
||||
if exist build-master rmdir /s /q build-master
|
||||
if exist build-web rmdir /s /q build-web
|
||||
if exist src\generated rmdir /s /q src\generated
|
||||
if exist managed\GodotSharpCompat\Generated rmdir /s /q managed\GodotSharpCompat\Generated
|
||||
if exist project\bin rmdir /s /q project\bin
|
||||
if exist project\leanclr rmdir /s /q project\leanclr
|
||||
if exist project\obj rmdir /s /q project\obj
|
||||
|
||||
echo [clean] Removed build directories, generated bindings, C# outputs.
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# Remove all build outputs. Leaves sources, scripts, and the leanclr
|
||||
# submodule intact.
|
||||
# Run from the repo root.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
rm -rf build build-master build-web
|
||||
rm -rf src/generated
|
||||
rm -rf managed/GodotSharpCompat/Generated
|
||||
rm -rf project/bin
|
||||
rm -rf project/leanclr
|
||||
rm -rf project/obj
|
||||
rm -f tools/binding_generator/__pycache__
|
||||
|
||||
echo "[clean] Removed build directories, generated bindings, C# outputs."
|
||||
@@ -10,7 +10,8 @@
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/classes/os.hpp>
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/core/gdextension_interface_loader.hpp>
|
||||
#include <godot_cpp/godot.hpp>
|
||||
|
||||
#include <godot_cpp/variant/packed_string_array.hpp>
|
||||
#include <godot_cpp/variant/string_name.hpp>
|
||||
#include <godot_cpp/variant/variant.hpp>
|
||||
@@ -491,7 +492,7 @@ void* LeanCLRScript::_instance_create(Object* p_for_object) const
|
||||
instance->managed_object = managed_object;
|
||||
LeanCLRRuntimeBridge::register_script_object(p_for_object, managed_object);
|
||||
|
||||
void* script_instance = gdextension_interface::script_instance_create3(&script_instance_info(), instance);
|
||||
void* script_instance = ::godot::internal::gdextension_interface_script_instance_create3(&script_instance_info(), instance);
|
||||
if (script_instance == nullptr)
|
||||
{
|
||||
LeanCLRRuntimeBridge::unregister_script_object(p_for_object, managed_object);
|
||||
|
||||
Vendored
+1
-1
Submodule thirdparty/leanclr updated: dbe2516ac6...f8f545be20
@@ -26,6 +26,10 @@ CPP_HEADER_NAME_OVERRIDES = {
|
||||
"Generic6DOFJoint3D": "generic6_dof_joint3d",
|
||||
"GradientTexture1D": "gradient_texture1_d",
|
||||
}
|
||||
# godot-cpp 4.5 renamed ClassDB to a ClassDBSingleton wrapper. Map the API
|
||||
# class name to the renamed C++ class and treat all of its methods as static
|
||||
# (so the generated native side calls ClassDBSingleton::get_singleton()->...).
|
||||
SINGLETON_CLASS_CPP_NAMES = {"ClassDB": "ClassDBSingleton"}
|
||||
HANDWRITTEN_PARTIAL_CLASSES = {"GodotObject", "Node"}
|
||||
CS_KEYWORDS = {
|
||||
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const",
|
||||
@@ -386,14 +390,14 @@ def build_generated_method(class_name, api_method, class_names, classes, global_
|
||||
return GeneratedMethod(
|
||||
class_name=class_name,
|
||||
cs_class_name=cs_class_name(class_name),
|
||||
cpp_class_name=class_name,
|
||||
cpp_class_name=SINGLETON_CLASS_CPP_NAMES.get(class_name, class_name),
|
||||
api_name=api_name,
|
||||
cpp_name=cpp_name,
|
||||
cs_name=cs_name,
|
||||
icall_suffix=icall_suffix,
|
||||
return_type=return_type,
|
||||
args=tuple(args),
|
||||
is_static=bool(api_method.get("is_static")),
|
||||
is_static=bool(api_method.get("is_static")) or class_name in SINGLETON_CLASS_CPP_NAMES,
|
||||
is_virtual=is_virtual,
|
||||
is_vararg=is_vararg,
|
||||
), None, None
|
||||
@@ -3311,7 +3315,10 @@ def generate_native_vararg_method_function(method):
|
||||
for arg in method.args:
|
||||
setup_lines.append(append_formal_vararg("args", arg))
|
||||
setup_lines.append(" append_managed_varargs(args, p_varargs);")
|
||||
target = method.cpp_class_name if method.is_static else "self"
|
||||
if method.is_static and method.class_name in SINGLETON_CLASS_CPP_NAMES:
|
||||
target = f"{method.cpp_class_name}::get_singleton()"
|
||||
else:
|
||||
target = method.cpp_class_name if method.is_static else "self"
|
||||
if method.return_type.category == "void":
|
||||
call_lines = [f" {target}->callv({call_method}, args);", *vararg_return_statement(method, "result").splitlines()]
|
||||
else:
|
||||
@@ -3331,7 +3338,13 @@ def generate_native_method_function(method):
|
||||
params = ([] if method.is_static else ["intptr_t p_native_ptr"]) + [cpp_native_param_declaration(arg) for arg in method.args]
|
||||
call_args = ", ".join(cpp_call_arg(arg) for arg in method.args)
|
||||
if method.is_static:
|
||||
call = f"{method.cpp_class_name}::{method.cpp_name}({call_args})" if call_args else f"{method.cpp_class_name}::{method.cpp_name}()"
|
||||
if method.class_name in SINGLETON_CLASS_CPP_NAMES:
|
||||
# godot-cpp 4.5 exposes ClassDB methods as non-static on
|
||||
# ClassDBSingleton; route them through the singleton instance.
|
||||
target = f"{method.cpp_class_name}::get_singleton()"
|
||||
call = f"{target}->{method.cpp_name}({call_args})" if call_args else f"{target}->{method.cpp_name}()"
|
||||
else:
|
||||
call = f"{method.cpp_class_name}::{method.cpp_name}({call_args})" if call_args else f"{method.cpp_class_name}::{method.cpp_name}()"
|
||||
return f"""{method.return_type.native_type} {fn}({', '.join(params)}) noexcept
|
||||
{{
|
||||
{cpp_return_statement(method, call)}
|
||||
|
||||
Reference in New Issue
Block a user