Skip to content

Serialization API ​

GodotECS.Serialize plus GodotECS.Core.EntityScene — deterministic little-endian binary snapshots, prefabs and save slots.

Documentation target: GodotECS commit 699338bcefd3edc31ff6b82a054932402f4d2970.

Snapshot format v4 ​

All snapshot integers/floats are little-endian. The v4 layout contains the magic/version/tick, the EcsRng state and fixed-step accumulator, the archetype table, chunk rows/entity keys, enableable masks, component data and known dynamic buffers.

The header starts with:

text
magic u32 = 'GECS'
formatVersion u32 = 4
tick u32
rngState u64
fixedAccumulator f64
archetypeCount u32

For every chunk the canonical data contains the saved entity (Index, Version) key for each row, enableable masks, non-shared component bytes, then the v4 per-row buffers. The built-in serialized buffers are CollisionEvent, NavPath and AudioEvent.

Canonical order is deterministic: archetypes by id, chunks by creation order, rows 0..Count-1, component columns in archetype order. Buffers were missing from pre-v4 round-trips; v4 persists them.

Transient presentation data ​

RenderTransform is a presentation cache. Save writes that component as zero bytes and Load/Append zero it again. It is therefore excluded from meaningful simulation state even though the canonical byte stream still contains its fixed-size slot. PrevTransform is not transient and is serialized normally.

Alpha is not serialized. It is derived from FixedConfig.Accumulator / FixedDelta by the fixed-step state and is recalculated after load. SpillCount is diagnostic and is not serialized.

BinaryReader / BinaryWriter ​

csharp
public struct BinaryWriter
{
    public BinaryWriter(int capacity = 256);
    public int Length { get; }
    public void WriteInt(int v);
    public void WriteUInt(uint v);
    public void WriteFloat(float v);
    public void WriteBytes(System.ReadOnlySpan<byte> b);
    public void WriteZeros(int count);
    public byte[] ToArray();
}

public struct BinaryReader
{
    public BinaryReader(byte[] data);
    public bool End { get; }
    public int Position { get; }
    public int Remaining { get; }
    public int ReadInt();
    public uint ReadUInt();
    public float ReadFloat();
    public byte[] ReadBytes(int count);
    public void Skip(int count);
}

Reader access is bounds checked and truncated input raises InvalidDataException.

Snapshot API ​

csharp
public static class Snapshot
{
    public const uint Magic = 0x53434547;
    public const uint FormatVersion = 4;
    public static byte[] Save(World world);
    public static void Load(World world, byte[] data);
    public static ulong Hash(World world);
    public static ulong HashBytes(byte[] data);
    public static Entity[] Append(World world, byte[] data, Godot.Vector3 posOffset,
        int[] srcIndices, int[] srcVersions);
    public static void CaptureCanonicalKeys(World world, out int[] indices, out int[] versions);
    public static string[] GetComponentTypeNames(byte[] data);
}

Save emits only archetypes containing at least one non-empty chunk. Shared+enableable combinations are rejected by the format contract. Entity keys, enableable masks, component bytes and v4 buffers are emitted per chunk.

Load validates the entire stream before mutating the world. It accepts v3 and v4 and rejects v1/v2. Validation covers magic/version, table consistency, registered TypeIndex values, component sizes, row/chunk limits, enableable masks, component/blob lengths and trailing data. Corrupt input raises InvalidDataException without first destroying the live session.

After validation, Load clears the world, restores Tick, RNG state and fixed accumulator, recreates rows with their saved entity keys preserved, and remaps internal Entity references (including nested value-type references up to the supported depth) to the recreated entities. CollisionEvent.Other is remapped using the same key table; references outside the snapshot remain unchanged.

Hash semantics ​

Hash(world) is exactly the FNV-1a 64 hash of the canonical bytes produced by Save(world).

The hash therefore includes tick, RNG state, fixed accumulator, component data, enableable masks and v4 buffer contents. It does not serialize Alpha or SpillCount. Crucially, the canonical byte stream also contains each row's saved entity (Index, Version) keys, so those keys are part of the hash. Two snapshots with identical simulation payload but different entity keys can legitimately hash differently.

This means Save → Load → Hash is stable because Load preserves the saved keys. It does not mean that two independently created worlds with different registration/entity histories will necessarily hash identically.

Append and EntityScene ​

Append is additive: it does not clear the destination and does not modify World.Tick. It creates rows in canonical order, remaps internal entity references using the supplied source key tables, and applies posOffset to LocalTransform and PrevTransform when non-zero.

csharp
[Godot.GlobalClass]
public partial class EntityScene : Godot.Resource
{
    [Export] public byte[] Data;
    [Export] public int EntityCount;
    [Export] public string[] ComponentNames;
    [Export] public int[] SourceIndices;
    [Export] public int[] SourceVersions;

    public static EntityScene FromWorld(World world);
    public Entity Instantiate(World world, in Godot.Vector3 pos);
}

EntityScene is a global Godot Resource. FromWorld captures the snapshot metadata and canonical source keys. Instantiate appends the scene and returns the first created entity, or Entity.Null for an empty scene. Missing/invalid source-key tables skip entity-reference remapping but still append the scene and apply position offsets.

TypeIndex compatibility ​

TypeIndex values are assigned in runtime registration order. Snapshots are therefore stable for the same registration sequence, but the current implementation does not provide cross-build stable numeric component IDs. Load requires the component types referenced by the file to already be registered and rejects size mismatches.

GodotECS docs — version 0.2.0. All rights reserved until a license is added.