Skip to content

feat: add godotengine.godot-cpp 0.0.1 (import godot_cpp) - #144

Merged
Sunrisepeak merged 4 commits into
mainfrom
feat/godotengine-godot-cpp-module
Aug 4, 2026
Merged

feat: add godotengine.godot-cpp 0.0.1 (import godot_cpp)#144
Sunrisepeak merged 4 commits into
mainfrom
feat/godotengine-godot-cpp-module

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

Stacked on #143, and draft on purpose. This one cannot go green until #143 merges and publish-artifact reruns — see Ordering at the bottom. Based on feat/compat-godot-cpp so the diff shows only this change; GitHub retargets it to main when #143 merges.

What

Registers mcpplibs/godot-cpp-m 0.0.1 as godotengine.godot-cpp — the C++23 module layer over godot-cpp's unchanged API:

import std;
import godot_cpp;

int main() {
    godot::Vector2 v{3, 4};
    std::println("{}", v.length());   // 5 — the same godot-cpp, no #include
}

Form A: the index carries the descriptor, nothing else. The 1022-TU source build stays in compat.godot-cpp (#143) and the module layer lives in its own repo — the same split ffmpeg/opencv use, and what keeps this index from growing a second full godot-cpp build.

Module name

One segment, godot_cpp — matching the library and the #include <godot_cpp/...> root users already type. The dotted spelling is reserved here for packages that expose several submodules (opencv.cv, ffmpeg.av); this one has a single interface unit.

The wrapper is generated, not curated

godot-cpp's public surface is ~1750 names — every engine class, every builtin Variant type, the global enums and their enumerators (so godot::OK and godot::ERR_FILE_NOT_FOUND still spell the same), godot::Math, the templates — and it moves with every Godot release. So src/godot_cpp.cppm is produced by a brace/namespace-aware scan of the headers (tools/gen_module_cppm.py in the package repo) and checked by the compiler.

Two things it deliberately does not carry

Both are upstream's shape, not a wrapper choice:

Macros. GDCLASS, GDREGISTER_CLASS, GDVIRTUAL_*, D_METHOD, memnew/memdelete, the ERR_* family — a named module cannot export macros, and GDExtension code is written in them. The package ships <godot-cpp-m/macros.h> to include next to the import, the same answer ffmpeg-m gives:

#include <godot-cpp-m/macros.h>
import godot_cpp;

class Player : public godot::Node2D {
    GDCLASS(Player, Node2D)
    // ...
};

godot-cpp's headers sit in the module's global module fragment, so both spellings denote the same entities and mixing them is well-formed. The package's own tests/godot_cpp_macros.cpp is exactly this shape.

HashMap/HashSet and their default hashers (7 names). Their inline bodies reach hash_murmur3_one_float/double, which are static and declare an unnamed union in their bodies. An unnamed union type is TU-local, and exposing a TU-local type from a module interface is a hard error — not the -Wexpose-global-module-tu-local warning. Found by bisecting the 1759 exports, not by guessing. They are godot-cpp's internal container plumbing (extension code uses Dictionary/Array/TypedArray) and remain reachable through the headers.

Mirrors

region url
GLOBAL github.com/mcpplibs/godot-cpp-m/archive/refs/tags/v0.0.1.tar.gz
CN gitcode.com/mcpp-res/godot-cpp/releases/download/v0.0.1/godot-cpp-m-0.0.1.tar.gz

sha256 5145f1e539b4b42bdb4064a2df0dcb54ffac4d70f88edc42f38bf2304a37a344, hashed twice from GitHub and re-downloaded from GitCode — byte-identical. The archive carries no symlinks (checked, for the Windows leg).

Test member

tests/examples/godot-cpp-module/ consumes the package the way a user would and asserts the same numbers tests/examples/godot-cpp asserts through headers — that member is the header spelling of this one, so a divergence means the re-export changed behaviour. Both halves can fail: the Variant math is defined in compat.godot-cpp's src/variant/*.cpp (so it only resolves if that dependency linked), and Node/Node2D/Node::PROCESS_MODE_*/godot::OK exist only in the pre-generated gen/ tree.

Verification so far

In the package repo, against compat.godot-cpp resolved from the #143 checkout:

$ mcpp test
godot_cpp_macros ... ok (0.11s)     # GDCLASS + bound method + engine virtual, next to the import
godot_cpp_module ... ok (0.15s)     # the module surface
 test result ok. 2 passed; 0 failed
$ cd examples/summator && mcpp build   # a real GDExtension, kind = "shared"
    Finished dev [unoptimized + debuginfo] in 74.16s
$ nm -D .../libsummator.so | grep summator_library_init
0000000000b228fc T summator_library_init

That example is also what caught compat.godot-cpp missing -fPIC — now fixed in #143.

Lint here is clean (mcpp xpkg parse, mirror table, package name, cross-package refs) and the CN url returns 200.

Ordering

This member declares [indices] godotengine, and a member-level table replaces the root's rather than merging — so the transitive compat.godot-cpp resolves from the published index. That is the constraint tests/examples/ffmpeg-module documents, and it means workspace (*) here stays red until #143 merges and publish-artifact republishes the index.

Simulating a published index locally does not substitute for that: mcpp refreshes the index on a resolution miss and wipes the injected state, so the real gate is CI after the merge.

Design notes: .agents/docs/2026-08-04-add-godot-cpp-plan.md §6.

@Sunrisepeak
Sunrisepeak marked this pull request as ready for review August 3, 2026 20:35
@Sunrisepeak
Sunrisepeak force-pushed the feat/godotengine-godot-cpp-module branch from 05bff3a to ef3780e Compare August 3, 2026 20:36
@Sunrisepeak
Sunrisepeak changed the base branch from feat/compat-godot-cpp to main August 3, 2026 20:36
@Sunrisepeak Sunrisepeak closed this Aug 3, 2026
@Sunrisepeak Sunrisepeak reopened this Aug 3, 2026
Form A registration of mcpplibs/godot-cpp-m: the C++23 module layer over
godot-cpp's unchanged API. `import godot_cpp;` re-exports the whole public
`godot` namespace, so a file that opened with a stack of

The index stays light: the 1022-TU source build lives in compat.godot-cpp
and the module layer lives in its own repo, so this side carries only the
descriptor -- the same split ffmpeg/opencv use.

Module name is one segment, `godot_cpp`, matching the library and the
reserved for packages exposing several submodules (opencv.cv, ffmpeg.av).

Two things the module deliberately does not carry, both upstream's shape
rather than a wrapper choice:

  * MACROS -- GDCLASS, GDREGISTER_CLASS, GDVIRTUAL_*, D_METHOD, memnew and
    the ERR_* family. A named module cannot export them, and GDExtension
    code is written in them, so the package ships <godot-cpp-m/macros.h> to
    include next to the import. godot-cpp's headers are in the module's
    global module fragment, so both spellings denote the same entities.
  * HashMap/HashSet and their default hashers, which reach
    hash_murmur3_one_float/double -- `static` functions declaring an unnamed
    union. Exposing a TU-local TYPE from a module interface is a hard error,
    not the -Wexpose-global-module-tu-local warning.

The member consumes the package the way a user would and asserts the same
numbers tests/examples/godot-cpp asserts through headers, so a divergence
means the re-export changed behaviour.

Note on ordering: this member declares [indices] godotengine, and a
member-level table REPLACES the root's, so the transitive compat.godot-cpp
resolves from the PUBLISHED index -- the constraint ffmpeg-module documents.
It therefore only goes green after the compat.godot-cpp PR merges and
publish-artifact reruns. Simulating a published index locally does not work:
mcpp refreshes on a resolution miss and wipes the injected state.
The workspace jobs never refreshed the published index. The snapshot in play
is whatever the pinned mcpp release vendored -- the Download step `cp -a`s the
release's registry/ over ~/.mcpp/registry, on top of the restored cache -- so
it is by construction older than main, and it never moves, because the cache
is saved with that same stale copy inside it.

This stayed invisible because mcpp DOES refresh on a miss for a direct
dependency, and because every member so far resolved its packages either from
this checkout or from compat packages that have been in the index far longer
than any snapshot. tests/examples/godot-cpp-module is the first member to
depend on a package added in the same cycle THROUGH a Form-A package -- a
transitive dependency, which is the path with no refresh -- and it failed with

  error: dependency 'compat.godot-cpp': no package found ...
    index: local index 160c389 (never refreshed)

on all three platforms, minutes after `Publish Index Artifact` had already
republished an index that contained it. Reproduced locally against a snapshot
of the same age, and `mcpp index update` alone turns that run green:

  test result ok. 1 passed; 0 failed; finished in 72.94s
Two changes that only make sense together.

The version becomes 4.5.0 rather than 0.0.1: a module layer that only
re-exports an API has no version of its own worth inventing, and what a
consumer needs from the number is which Godot they are targeting -- the same
version compat.godot-cpp carries.

That is exactly what makes the short name matter. This package and its own
compat.godot-cpp dependency are always resolved together, and mcpp's
installed-package lookup matches on (name, version) WITHOUT the namespace.
With both named `godot-cpp` at 4.5.0, resolving this one lands on compat's
unpacked directory:

  error: dependency 'godotengine.godot-cpp': index entry has no `mcpp = ...`
  field, and no mcpp.toml was found at <verdir>/mcpp.toml or
  <verdir>/*/mcpp.toml

and it is cache-order dependent, so a clean runner passes and a warm one
fails. Reproduced with a namespace unrelated to either package: leave a single
<anything>-x-godot-cpp/4.5.0 in the store and the resolution goes to it;
remove it and the same descriptor installs and builds fine. The store
DIRECTORIES are namespaced (ns-x-name) -- the lookup is not.

`godot-cpp-m` sidesteps it, keeps the version tracking upstream, and matches
the repository name. `import godot_cpp;` is unchanged.

Verified both ways with the CI-pinned mcpp 2026.8.3.3: cold store (both
packages downloaded, 27.80s) and warm store with compat-x-godot-cpp and
godotengine-x-godot-cpp-m side by side (3.52s) -- the state that previously
failed. Both `test result ok`.
Second version of the module layer, tracking upstream one for one as
compat.godot-cpp's do: 10.0.0-rc1 binds Godot 4.6, 4.5.0 binds Godot 4.5.

Getting there needed a shim on the package side. godot-cpp declares
hash_murmur3_one_float/_double `static`, and each declares an unnamed union
in its body -- a class with no linkage inside a TU-local function. With 10.x
the module interface exposes those functions (reachable from many inline
bodies) and GCC rejects it outright:

  error: 'uint32_t godot::hash_murmur3_one_float(float, uint32_t)' exposes
         TU-local entity 'union ...::<unnamed>'

-Wno-expose-global-module-tu-local, -fpermissive and -Wno-error= all leave it
standing, and exporting less does not converge -- a single engine class is
enough to trigger it. The package now generates a shim: upstream's header with
`static` dropped from those two, scoped to its own include path, so
compat.godot-cpp still compiles upstream's copy untouched. With it the export
surface actually GREW (1818 names) because nothing has to be held back any
more -- HashMap, HashSet, AHashMap and PairHash re-export normally.

The new member asserts the version through TYPES rather than
GODOT_VERSION_MAJOR/_MINOR: those are macros, and a module cannot export
macros. EditorDock and AHashMap exist in 4.6 and not in 4.5, which also
doubles as proof the shim did its job.

Verified with the CI-pinned mcpp 2026.8.3.3:
  godot-cpp-module     -> ok (1 passed)
  godot-cpp-module-v10 -> version=true ... ok (1 passed)
@Sunrisepeak
Sunrisepeak force-pushed the feat/godotengine-godot-cpp-module branch from 7371423 to f9bded9 Compare August 4, 2026 04:57
@Sunrisepeak
Sunrisepeak merged commit 2d65b39 into main Aug 4, 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