Skip to content

Commit bca4af4

Browse files
feat: add compat.godot-cpp 10.0.0-rc1 (Godot 4.6) + TYPED_METHOD_BIND (#145)
Upstream godot-cpp moved off "tag tracks the engine" (godot-4.5-stable) onto its own version line, and 10.0.0-rc1 is the first of it. Which engine it binds is in the api header rather than the tag: "version_full_name": "Godot Engine v4.6.stable.official" so the index now carries 4.5.0 (Godot 4.5) and 10.0.0-rc1 (Godot 4.6), each with its own workspace member. repack.sh dispatches on the actual signature and the actual files rather than on the tag: generate_bindings() grew an `interface_filepath` parameter, and gdextension_interface.h stopped being checked in -- it is generated from gdextension_interface.json into gen/include/. Re-running the updated script on godot-4.5-stable still reproduces b0c36e77..., so the change is backward compatible; 10.0.0-rc1 hashes identically across two runs. The descriptor takes the union of both layouts, catch2-style (a glob matching nothing is skipped): 10.x adds one .cpp directly under gen/src/. TYPED_METHOD_BIND is the second half of this change, and it is a bug fix. Without it, method_bind.hpp reinterpret_casts member pointers through a FORWARD-DECLARED `_gde_UnexistingClass`; under the MSVC ABI a pointer-to-member's size depends on the class's inheritance model, so for an incomplete class the cast is rejected: error: cannot reinterpret_cast from member pointer type 'double (TestSprite::*)() const' to member pointer type 'double (_gde_UnexistingClass::*)() const' of different size i.e. EVERY ClassDB::bind_method call failed to compile on Windows. Upstream's cmake sets it PUBLIC on MSVC for exactly this reason. It rides on the default feature unconditionally rather than per-OS: it is a header switch that changes MethodBindT's template parameter list, so library and consumer must agree, and the cost off MSVC is only extra template instantiation. WINDOWS_ENABLED and NOMINMAX, which upstream sets alongside, are not needed -- neither appears anywhere in the shipped headers or sources. Both members now build a GDCLASS subclass with bound methods, which is what the Windows leg was missing: the old assertions were pure math and never reached bind_method, so a guaranteed compile error went unseen. godot-cpp-v10 additionally asserts GODOT_VERSION_MAJOR/MINOR == 4/6 and the presence of EditorDock (4.6-only), so the two members cannot be confused for each other. Verified locally with the CI-pinned mcpp 2026.8.3.3: godot-cpp -> bind=1 vec2=1 vec3=1 basis=1 color=1 aabb=1 gen=1 ... ok godot-cpp-v10 -> version=1 bind=1 vec2=1 ... gen=1 ... ok Co-authored-by: Sunrisepeak <x.d2learn.org@gmail.com>
1 parent fc8ba4e commit bca4af4

9 files changed

Lines changed: 320 additions & 11 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# compat.godot-cpp 增加 10.0.0-rc1(Godot 4.6),并补上 MSVC ABI 缺的 define
2+
3+
日期:2026-08-04 · 承接 [2026-08-04-add-godot-cpp-plan.md](2026-08-04-add-godot-cpp-plan.md)(4.5.0,PR #143)
4+
5+
## 1. 版本线:godot-cpp 有了自己的版本号
6+
7+
上游 godot-cpp 的 tag 从「跟 Godot 走」(`godot-4.5-stable`)换成了**自己的版本线**:
8+
[`10.0.0-rc1`](https://github.com/godotengine/godot-cpp/releases/tag/10.0.0-rc1)。它绑定的引擎版本写在
9+
`gdextension/extension_api.json` 的 header 里:
10+
11+
```json
12+
{ "version_major": 4, "version_minor": 6, "version_patch": 0, "version_status": "stable",
13+
"version_full_name": "Godot Engine v4.6.stable.official" }
14+
```
15+
16+
**10.0.0-rc1 = Godot 4.6**。索引里两条版本并存,消费者按需选:
17+
18+
| 索引版本 | 上游 tag | 引擎 |
19+
|---|---|---|
20+
| `4.5.0` | `godot-4.5-stable` | Godot 4.5 |
21+
| `10.0.0-rc1` | `10.0.0-rc1` | Godot 4.6 |
22+
23+
`mcpp xpkg parse` 接受带预发布后缀的 `10.0.0-rc1`,lint 也只拦前导 `v`,无需特殊处理。
24+
25+
## 2. repack 脚本要兼容两代 API
26+
27+
10.x 改了两处,`tools/godot-cpp/repack.sh`**实际签名/实际文件**判定而不是按 tag 判定:
28+
29+
- `generate_bindings()` 多了 `interface_filepath` 参数(用 `inspect.signature` 探测);
30+
- `gdextension_interface.h` **不再签入**,改为由 `gdextension/gdextension_interface.json` 生成到
31+
`gen/include/`(存在哪个就传哪个,与 cmake 的 `GODOTCPP_GDEXTENSION_INTERFACE_FILE` 同逻辑)。
32+
33+
改完后重跑 4.5.0 仍得到同一个 sha `b0c36e77…`,即向后兼容;10.0.0-rc1 连跑两次得
34+
`aaafbf50d4b8469d610fdb2eb76c6f58d758dbabbc6b013f60464d99b20ceb6e`
35+
36+
## 3. 描述符:两种布局取并集
37+
38+
10.x 的 `gen/src/` 多了一个直接位于其下的 `.cpp`(4.5 只有 `classes/``variant/` 两层),故 sources
39+
加一条 `*/gen/src/*.cpp`**匹配不到的 glob 会被跳过**,这是 compat.catch2 已经在用的做法(v2 走
40+
`single_include`、v3 走 `src`,另一个空着)。`include_dirs` 同时保留 `*/gdextension``*/gen/include`,
41+
因为那个 C ABI 头在两代里位置不同。
42+
43+
## 4. TYPED_METHOD_BIND —— MSVC ABI 上不是可选项
44+
45+
`godot-cpp-m` 的 Windows CI 暴露出来的:任何 `ClassDB::bind_method` 调用在 clang-cl 下直接编译失败
46+
47+
```
48+
error: cannot reinterpret_cast from member pointer type 'double (TestSprite::*)() const'
49+
to member pointer type 'double (_gde_UnexistingClass::*)() const' of different size
50+
```
51+
52+
`method_bind.hpp``#ifndef TYPED_METHOD_BIND` 时把成员指针 cast 成一个**前向声明**
53+
`_gde_UnexistingClass`;MSVC ABI 下成员指针的大小取决于该类的继承模型,不完整类型只能按最一般形式
54+
假定,于是尺寸对不上、cast 非法。上游 `cmake/windows.cmake` 正是为此在 MSVC 下把
55+
`TYPED_METHOD_BIND` 设为 **PUBLIC**
56+
57+
本包把它挂在默认 feature 上(与 `GDEXTENSION` 同处),**不按平台分**:它是个改
58+
`MethodBindT` 模板参数表的**头文件开关**,库与消费者必须一致,统一一个答案比按 OS 分更容易保证。
59+
非 MSVC 侧的代价只是多一些模板实例化,无行为差异。上游一起设的 `WINDOWS_ENABLED` / `NOMINMAX`
60+
**不需要** —— 在 4.5 与 10.x 的头文件和源码里都一次都没出现过。
61+
62+
## 5. 测试补了 GDCLASS + bind_method
63+
64+
两个成员(`godot-cpp``godot-cpp-v10`)都加了一个 `GDCLASS` 子类,带两个 `ClassDB::bind_method`
65+
绑定和一次对 GDCLASS 生成物的 ODR-use。**这正是之前 Windows 绿得没有意义的原因**:老测试只碰纯数学,
66+
根本没走到 bind_method,所以上面那个必现的编译错误一次都没被 CI 看见。断言是「编得过且链得上」——
67+
不能真调用,ClassDB/StringName 都要走 `gdextension_interface_*` 函数指针。
68+
69+
`godot-cpp-v10` 另外断言 `GODOT_VERSION_MAJOR/MINOR == 4/6` 且 4.6 才有的 `EditorDock` 存在,
70+
用来证明拿到的确实是 10.x 那套绑定而不是 4.5 的。
71+
72+
## 6. 本地验证
73+
74+
```
75+
$ mcpp test -p godot-cpp # 4.5.0
76+
bind=1 vec2=1 vec3=1 basis=1 color=1 aabb=1 gen=1
77+
test result ok. 1 passed; 0 failed; finished in 59.15s
78+
79+
$ mcpp test -p godot-cpp-v10 # 10.0.0-rc1
80+
version=1 bind=1 vec2=1 vec3=1 basis=1 color=1 aabb=1 gen=1
81+
test result ok. 1 passed; 0 failed; finished in 52.72s
82+
```
83+
84+
另外 10.0.0-rc1 的 1075 个 TU 用 gcc 13 `-std=c++23 -fPIC` 全量编过,零失败。
85+
86+
## 7. 镜像
87+
88+
| 区域 | 地址 |
89+
|---|---|
90+
| GLOBAL | `https://github.com/xlings-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz` |
91+
| CN | `https://gitcode.com/mcpp-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz` |
92+
93+
两侧下载回来核过 sha,与本地打包一致。

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Two kinds of packages live here:
4242
| header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
4343
| Runtime loader compat (pure sources, sidestepping upstream codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua) (the Khronos loader: `loader/generated/` is checked in, and the assembly path degrades to plain C through `UNKNOWN_FUNCTIONS_SUPPORTED`, so no CMake/Python/assembler is needed; windows deferred) · [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) |
4444
| Whole-source direct build + generated config (only where a platform lacks one) | [`compat.curl`](pkgs/c/compat.curl.lua) (win32 uses upstream's checked-in config, unix generates one) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua) (win/mac use upstream's checked-in config; linux generates one and enables X11 by hand) |
45-
| Upstream codegen frozen into the mirror archive | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua) (the ~1000 GDExtension classes under `gen/` exist in no upstream tag archive — upstream's `binding_generator.py` emits them at build time. Running it once offline and publishing upstream's tree byte-for-byte **plus** `gen/` keeps Python off the consumer side entirely; `tools/godot-cpp/repack.sh` reproduces the archive deterministically and refuses to publish if any upstream file differs) |
45+
| Upstream codegen frozen into the mirror archive | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua) (two versions: `4.5.0` = the `godot-4.5-stable` bindings, `10.0.0-rc1` = godot-cpp's own 10.x line, whose bindings target Godot 4.6. The ~1000 GDExtension classes under `gen/` exist in no upstream tag archive — upstream's `binding_generator.py` emits them at build time. Running it once offline and publishing upstream's tree byte-for-byte **plus** `gen/` keeps Python off the consumer side entirely; `tools/godot-cpp/repack.sh` reproduces the archive deterministically and refuses to publish if any upstream file differs) |
4646
| Header package filling a gap in the index | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua) (libglvnd's `GL/glx.h`, absent from the Khronos registry and required by SDL's X11 backend) |
4747
| C++ application framework compat (dependencies reuse packages already in the index) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua) (upstream's `3rd/` ships 8 vendored dependencies; none of them is compiled here — all are redirected to the same-version `compat.*` packages in this index) |
4848
| Mutually exclusive backends (one of several inside one package) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua): `vulkan` / `sdl2` each **replace** the default OpenGL / GLFW, and the default backend is expressed by *naming no feature at all* — there is no `opengl`/`glfw` feature. A `default` feature cannot express exclusivity: its own `defines`/`sources`/`deps` have no effect whatsoever, while its `implies` always applies and cannot be overridden by a named feature (which is, conversely, exactly the solution for the "always-on interface define" row below). The workable answer is to read the `-DMCPP_FEATURE_<NAME>` mcpp passes anyway and decide up front in a force-included header. Note also that `cflags` only reaches C TUs — C++ needs `cxxflags`, so a backend define written only into `cflags` never reaches any `.cpp` |

README.zh-CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
3939
| header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
4040
| 运行时 loader compat(纯源码,绕开上游 codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua)(Khronos loader:`loader/generated/` 已签入,汇编路径经 `UNKNOWN_FUNCTIONS_SUPPORTED` 降级为纯 C,故无需 CMake/Python/汇编器;windows 延后)· [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) |
4141
| 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) |
42-
| 上游 codegen 前置冻结进镜像归档 | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua)(`gen/` 下约 1000 个 GDExtension 类不在任何上游 tag 归档里,由上游 `binding_generator.py` 在构建时生成。改为离线跑一次,把上游源码树逐字节原样 **加上** `gen/` 一起发布,消费侧就完全不需要 Python;`tools/godot-cpp/repack.sh` 可确定性复现该归档,且上游文件一旦有出入即拒绝打包) |
42+
| 上游 codegen 前置冻结进镜像归档 | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua)(两个版本:`4.5.0``godot-4.5-stable` 的绑定,`10.0.0-rc1` 是 godot-cpp 自己的 10.x 线、对应 Godot 4.6。`gen/` 下约 1000 个 GDExtension 类不在任何上游 tag 归档里,由上游 `binding_generator.py` 在构建时生成。改为离线跑一次,把上游源码树逐字节原样 **加上** `gen/` 一起发布,消费侧就完全不需要 Python;`tools/godot-cpp/repack.sh` 可确定性复现该归档,且上游文件一旦有出入即拒绝打包) |
4343
| 补索引空缺的头文件包 | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua)(libglvnd 的 `GL/glx.h`,Khronos registry 不含,SDL 的 X11 后端必需) |
4444
| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) |
4545
| 互斥后端(同包多后端二选一) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua):`vulkan` / `sdl2` 各自**替换**默认的 OpenGL / GLFW,默认后端由"不点名任何 feature"表达,并不存在 `opengl`/`glfw` feature。`default` feature 表达不了互斥 —— 它自带的 `defines`/`sources`/`deps` 完全不生效,而 `implies` 又恒生效、无法被点名的 feature 覆盖(后者反而正好是本表『恒开的 interface define』一行的解法)。可行解是读 mcpp 本就会传的 `-DMCPP_FEATURE_<NAME>`,在强制包含头里做前置判定。另注意 `cflags` 只作用于 C TU,C++ 需 `cxxflags` —— 只写进 `cflags` 的后端 define 到不了任何 `.cpp` |

mcpp.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ members = [
3030
"tests/examples/ffmpeg-module",
3131
"tests/examples/fmtlib.fmt",
3232
"tests/examples/godot-cpp",
33+
"tests/examples/godot-cpp-v10",
3334
"tests/examples/gui-stack",
3435
"tests/examples/imgui",
3536
"tests/examples/imgui-module",

pkgs/c/compat.godot-cpp.lua

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,25 @@
2929
-- Defines
3030
-- GDEXTENSION is upstream's PUBLIC compile definition (cmake sets it on the
3131
-- godot-cpp target's INTERFACE), so it rides on a default feature: the lib
32-
-- and every consumer TU must agree. The layout-affecting ones are left
32+
-- and every consumer TU must agree.
33+
--
34+
-- TYPED_METHOD_BIND rides along, and is not optional on the MSVC ABI.
35+
-- Without it, method_bind.hpp reinterpret_casts member pointers through a
36+
-- FORWARD-DECLARED `_gde_UnexistingClass`; under the MSVC ABI a
37+
-- pointer-to-member's size depends on the class's inheritance model, so for
38+
-- an incomplete class clang-cl rejects the cast outright ("cannot
39+
-- reinterpret_cast ... to member pointer type of different size") and every
40+
-- ClassDB::bind_method call fails to compile. Upstream's cmake sets it
41+
-- PUBLIC for exactly this reason ($<${IS_MSVC}: TYPED_METHOD_BIND ...> in
42+
-- cmake/windows.cmake). It is set unconditionally rather than per-OS
43+
-- because it is a HEADER switch that changes MethodBindT's template
44+
-- parameter list -- library and consumer must agree on it, and one uniform
45+
-- answer is cheaper to guarantee than an OS-conditional one. The cost off
46+
-- MSVC is some extra template instantiation, which is why upstream keeps
47+
-- the untyped path as its default there; there is no behavioural
48+
-- difference. (WINDOWS_ENABLED and NOMINMAX, which upstream sets alongside,
49+
-- are NOT needed: neither appears anywhere in the shipped headers or
50+
-- sources.) The layout-affecting ones are left
3351
-- undefined on both sides, which is upstream's release default:
3452
-- DEBUG_ENABLED / DEV_ENABLED (extra checks), HOT_RELOAD_ENABLED (changes
3553
-- the Wrapped layout) and REAL_T_IS_DOUBLE (needs the double-precision
@@ -54,6 +72,13 @@ package = {
5472
-- gdextension_interface.h ABI).
5573
xpm = {
5674
linux = {
75+
["10.0.0-rc1"] = {
76+
url = {
77+
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
78+
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
79+
},
80+
sha256 = "aaafbf50d4b8469d610fdb2eb76c6f58d758dbabbc6b013f60464d99b20ceb6e",
81+
},
5782
["4.5.0"] = {
5883
url = {
5984
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
@@ -63,6 +88,13 @@ package = {
6388
},
6489
},
6590
macosx = {
91+
["10.0.0-rc1"] = {
92+
url = {
93+
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
94+
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
95+
},
96+
sha256 = "aaafbf50d4b8469d610fdb2eb76c6f58d758dbabbc6b013f60464d99b20ceb6e",
97+
},
6698
["4.5.0"] = {
6799
url = {
68100
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
@@ -72,6 +104,13 @@ package = {
72104
},
73105
},
74106
windows = {
107+
["10.0.0-rc1"] = {
108+
url = {
109+
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
110+
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
111+
},
112+
sha256 = "aaafbf50d4b8469d610fdb2eb76c6f58d758dbabbc6b013f60464d99b20ceb6e",
113+
},
75114
["4.5.0"] = {
76115
url = {
77116
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
@@ -88,20 +127,27 @@ package = {
88127
import_std = false,
89128
-- Three roots, exactly as upstream's build systems expose them:
90129
-- hand-written headers, generated headers, and the GDExtension C ABI
91-
-- header (gdextension_interface.h) that both of them include.
130+
-- header that both of them include. Where that header lives moved
131+
-- between the two versions -- 4.5 checks in gdextension/
132+
-- gdextension_interface.h, 10.x generates it into gen/include/ from
133+
-- gdextension_interface.json -- so both roots stay listed.
92134
include_dirs = { "*/include", "*/gen/include", "*/gdextension" },
93135
-- Enumerated rather than `**`: upstream's own test project ships a
94136
-- test/src/*.cpp that must not be swept into the library, and the two
95137
-- source roots are only ever one and two levels deep.
138+
-- Union of both layouts, catch2-style: a glob that matches nothing on
139+
-- a given version is simply skipped. 10.x adds one .cpp directly under
140+
-- gen/src/ that 4.5 does not have.
96141
sources = {
97142
"*/src/*.cpp",
98143
"*/src/*/*.cpp",
144+
"*/gen/src/*.cpp",
99145
"*/gen/src/*/*.cpp",
100146
},
101147
targets = { ["godot-cpp"] = { kind = "lib" } },
102148
features = {
103149
["default"] = { implies = { "gdextension" } },
104-
["gdextension"] = { defines = { "GDEXTENSION" } },
150+
["gdextension"] = { defines = { "GDEXTENSION", "TYPED_METHOD_BIND" } },
105151
},
106152
deps = { },
107153
-- A GDExtension IS a shared library, so this static library's objects
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# compat.godot-cpp 10.0.0-rc1 (Godot 4.6) test project.
2+
#
3+
# Separate member rather than a second dependency in the 4.5 one: the two
4+
# versions are different bindings of different engine releases, and each has to
5+
# be built and asserted on its own -- same shape as catch2 / catch2-v2.
6+
#
7+
# `compat` is redirected to this checkout by the workspace-root [indices],
8+
# which every member inherits.
9+
10+
[package]
11+
name = "godot-cpp-v10-tests"
12+
version = "0.1.0"
13+
14+
[dependencies.compat]
15+
godot-cpp = "10.0.0-rc1"

0 commit comments

Comments
 (0)