Skip to content

feat: add compat.godot-cpp 10.0.0-rc1 (Godot 4.6) + TYPED_METHOD_BIND - #145

Merged
Sunrisepeak merged 1 commit into
mainfrom
feat/compat-godot-cpp-10
Aug 3, 2026
Merged

feat: add compat.godot-cpp 10.0.0-rc1 (Godot 4.6) + TYPED_METHOD_BIND#145
Sunrisepeak merged 1 commit into
mainfrom
feat/compat-godot-cpp-10

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

What

Two things, both about compat.godot-cpp:

  1. A second version: 10.0.0-rc1. Upstream godot-cpp moved off "the tag tracks the engine" (godot-4.5-stable) onto its own version line, and this is the first release of it.
  2. TYPED_METHOD_BIND — a bug fix. Every ClassDB::bind_method call currently fails to compile on Windows.

1. Which engine does 10.0.0-rc1 bind?

Not in the tag any more — in the api header:

{ "version_major": 4, "version_minor": 6, "version_patch": 0,
  "version_full_name": "Godot Engine v4.6.stable.official" }
index version upstream tag engine
4.5.0 godot-4.5-stable Godot 4.5
10.0.0-rc1 10.0.0-rc1 Godot 4.6

mcpp xpkg parse takes the prerelease suffix as-is and lint only rejects a leading v, so no special handling.

repack.sh dispatches on signature, not on tag

10.x changed two things, and the script probes for both rather than branching on the version:

  • generate_bindings() grew an interface_filepath parameter (detected with inspect.signature);
  • gdextension_interface.h is no longer checked in — it is generated from gdextension/gdextension_interface.json into gen/include/ (pass whichever file the tag ships, the same logic as cmake's GODOTCPP_GDEXTENSION_INTERFACE_FILE).

Re-running the updated script on godot-4.5-stable still produces b0c36e77..., so the change is backward compatible. 10.0.0-rc1 hashes identically across two independent runs.

Descriptor: union of both layouts

10.x adds one .cpp directly under gen/src/ that 4.5 does not have, so */gen/src/*.cpp joins the source list. A glob that matches nothing is skipped — the same shape compat.catch2 uses for its v2/v3 split. include_dirs keeps both */gdextension and */gen/include because the C ABI header lives in a different place in each version.

Mirrors

region url
GLOBAL github.com/xlings-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz
CN gitcode.com/mcpp-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz

sha256 aaafbf50d4b8469d610fdb2eb76c6f58d758dbabbc6b013f60464d99b20ceb6e, re-downloaded from both and byte-identical. Upstream archive: 6a34530e81eed5bca407d68c6598be521d89d604a795bd9ed2bee9e4a7b0fee7.

2. TYPED_METHOD_BIND: bind_method never compiled on Windows

Surfaced by the module package's Windows CI:

error: cannot reinterpret_cast from member pointer type 'double (TestSprite::*)() const'
       to member pointer type 'double (_gde_UnexistingClass::*)() const' of different size
  592 | MethodBind *a = memnew((MethodBindTRC<R, P...>)(reinterpret_cast<R (MB_T::*)(P...) const>(p_method)));

Without TYPED_METHOD_BIND, method_bind.hpp 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 an incomplete class has to be assumed to use the most general representation — and the sizes do not match. Upstream's cmake/windows.cmake sets it PUBLIC on MSVC for exactly this reason.

It rides on the default feature unconditionally, not per-OS: it is a header switch that changes MethodBindT's template parameter list, so the library and every consumer TU must agree on it, and one uniform answer is cheaper to guarantee than an OS-conditional one. The cost off MSVC is extra template instantiation and no behavioural difference. WINDOWS_ENABLED and NOMINMAX, which upstream sets alongside, are not added: neither appears anywhere in the shipped headers or sources of either version.

Why CI did not catch this before

Because the tests never reached bind_method. The 4.5 member asserted pure Variant math, which compiles fine everywhere — so workspace (windows) was green while a guaranteed compile error sat in the package's main use case.

Both members now declare a GDCLASS subclass with two ClassDB::bind_method bindings and ODR-use what GDCLASS generates. It is compile-and-link only: ClassDB and StringName go through the gdextension_interface_* pointers, which are null outside a Godot process that has loaded the extension.

godot-cpp-v10 additionally asserts GODOT_VERSION_MAJOR/MINOR == 4/6 and that EditorDock (4.6-only) exists, so the two members can never be silently testing the same bindings.

Verification

$ mcpp test -p godot-cpp          # 4.5.0
bind=1 vec2=1 vec3=1 basis=1 color=1 aabb=1 gen=1
 test result ok. 1 passed; 0 failed; finished in 59.15s

$ mcpp test -p godot-cpp-v10      # 10.0.0-rc1
version=1 bind=1 vec2=1 vec3=1 basis=1 color=1 aabb=1 gen=1
 test result ok. 1 passed; 0 failed; finished in 52.72s

All 1075 TUs of 10.0.0-rc1 also compile clean under gcc 13 -std=c++23 -fPIC. Local lint mirrors validate.yml and both CN urls return 200.

Design notes: .agents/docs/2026-08-04-add-godot-cpp-10.0.0-rc1.md.

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
@Sunrisepeak
Sunrisepeak merged commit bca4af4 into main Aug 3, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants