Memory structures
Usage guide of the memory structures: the entity rows and the poses an engine reads in place (gfk_zone, gfk_present_poses, gfk_structs, gfk_copied), the generated accessors for C#, C++, GDScript, Rust and JavaScript, the check at load and its messages, rules for a module in another language.
Reading the kits' memory in place (structures) #
How an engine, a tool or a module in another language reads what the kits write in memory, without going through JSON: the fixed structures of the entity rows and of the poses, their generated accessors for each language, and the check that refuses a library that lays them out differently. Readable offline: gripforge docs structs, or the MCP tool gripforge_docs (topic structs).
What it is for #
A game runs its logic in the kit host (the logic modules on the web, the library libgfkits natively). Each frame an engine needs the entities, their transforms and some state. Asking for them as JSON copies and parses text every frame. The structures contract lets the engine read the same bytes the kit host wrote, in place:
- the rows of the entity table of the shared zone (
ZoneEntityRow: id, flags, position, yaw, then the columns of the game); - the interpolated poses of the entities being drawn (
PresentPose); - the headers of the zone, the inputs, the events, and the free-shape values kits exchange (
FixedValue,FixedBlock…).
You need this guide if you write an engine adapter, a tool that reads the zone, or a kit module in a language other than Rust. A game that uses the catalogue never touches it.
A layout GripForge defines #
- Integers are little-endian; a field lies at the next multiple of its alignment (the C layout rules); an address inside a
Fixed*structure is 64 bits wide whatever the address space. - Nothing depends on a compiler or a language: any code that fills these bytes is a valid peer.
- Every structure has a number and a version (
major.minor). A structure added, or a field added in reserved bytes, is a minor; a size, an offset or a meaning changed is a major. - The reference of every structure, field by field with its offset, is
docs/kit-structures.md(gripforge docs kit-structures). It is generated from one declaration file, and so is everything below.
There are two tables, both at contract 1.0:
| Table | Holds | Who checks it |
|---|---|---|
Module structures (FixedValue, FixedPair, FixedBlock, Bytes, Ctx, the Zone* structures) | what the kit modules share in the common memory | each kit module carries its table in a custom section gripforge.structs; the build compares it to the declaration, the loader refuses a module of another major or a later minor, by name |
Engine table (ZoneEntityRow, PresentPose, ZoneWorldHeader) | what an engine reads in libgfkits or in the logic module | the library answers it (gfk_structs); the accessors of each language check it once at start |
Read the entity rows #
gfk_zone gives the table (rows, row_bytes, capacity, used, owner, epoch); row i starts at rows + row_bytes * i. The start of each row is a ZoneEntityRow (40 bytes):
| At | Type | Field | Meaning |
|---|---|---|---|
| 0 | u32 | id | the number of the entity's id (e12 is 12); 0 an empty row, 0xffffffff a row left by a destroyed entity |
| 4 | u32 | flags | bit 0: the entity has a transform |
| 8, 16, 24 | f64 | x, y, z | position, right handed, Y up |
| 32 | f64 | yaw | radians about Y |
The columns of the game schema follow (gfk_column gives the offset, size and type of one by name). epoch is the clock value of the last change of the world: if it did not move, no row changed, and you read nothing.
Valid until the next gfk_* call of the game (the zone may grow); the offsets are valid for the whole play.
Read the poses of what you draw #
gfk_present_poses(game, &n) returns the interpolated pose of each presented entity, one PresentPose per entity of the snapshot and in its order (same fields as the row start: id, flags, x, y, z, yaw). Nothing is copied. It is valid until the next gfk_present_frame of the game.
Start the presentation with the options that go with it (gfk_present_start, JSON):
| Option | Effect |
|---|---|
"poses": "table" | the entities of the snapshot carry no transform: read them from gfk_present_poses |
"unchanged": "omit" | with the table: a snapshot leaves out entities that did not change since the previous one, and after the first carries, in place of vars, only varsSet: { path: value } and varsGone: [path] when some changed; the engine keeps what it has. The entities carry no velocity |
gfk_copied(reset) counts the bytes copied between the library and the engine since the last reset (every answer written to the output, every JSON text handed in); reading in place is what keeps it low. gfk_physics_copied is the same count for the engine's physics callbacks.
The generated accessors #
Never edited by hand; regenerated from the declaration, and checked in the continuous integration.
| Language | File | Use |
|---|---|---|
| C# (Unity) | GfStructs.g.cs | explicit-layout structures read by pointer (GfZoneEntityRow*, GfPresentPose*); GfStructs.Check(table, len, "libgfkits") |
| C++ (Unreal) | GfStructs.g.h | aligned structures held by static_assert; GfStructs::Check; UGfGameHost::PresentPoses(Count) |
| GDScript (Godot) | gf_structs.gd | sizes, offsets and accessors over bytes (GfStructs.check); the extension reads in place in Rust |
| Rust | engine_structs.rs (crate gripforge-kitcall) | repr(C) types held by const asserts; check; used by libgfkits, the Godot extension and the native player |
| JavaScript (web) | kit-structs.generated.mjs | DataView views on the memory, shared or not; checkStructs(table, library); host.zoneRows() |
The Unity and Unreal accessors need no setup beyond using the package: the host checks the library once when it loads.
The check at load #
The library answers its table with gfk_structs(&len): the magic GFST, the version of the engine table, then every structure with its size and the offset of every field as its compiler laid them out. An accessor refuses, with a message that names the reason, a library:
- without the table (
... answers no structures table (magic GFST missing): it predates the engine structures, update it); - of another major ("the package and the library do not lay the memory out the same way; update them together");
- of an earlier minor than the package needs;
- that lays a structure out differently from the declaration.
Update the package and the library together; never try to read a library the check refused.
Writing a module in another language #
A kit module that shares memory with the others must fill these bytes exactly. The rules to follow:
- Take the layouts from
docs/kit-structures.md(or the declaration it is generated from), never from your compiler. - Carry the table of the structures you use in a custom section
gripforge.structs, so the loader can compare it. - Respect who owns each structure and how long it is valid: the page of each structure says whether the core or a kit writes it, and until when a pointer to it is valid.
- Test your reading against the fixture of the contract: it holds every structure once, every field a value of its own, and each accessor must read the expected text line for line.
For writing a kit in Rust, see gripforge docs kit-third-party.
When it does not work #
| Message | What to do |
|---|---|
... answers no structures table | the library is older than the package: update the library |
engine structures 1.x expected, ... provides ... (another major) | update the package and the library together |
... the package needs a later minor than this library has | update the library |
a structure ... laid out differently | the library was built with another declaration: use the one shipped with your package |