Serialization Pattern
This page documents the manual world serialization pattern demonstrated in src/Test/Ghost.Entities.Test/SerializationTest.cs.
Scope
The current test shows a low-level JSON snapshot workflow for ECS data:
- Enumerate archetypes and chunks.
- Read entity IDs and raw component bytes.
- Convert bytes to managed instances.
- Serialize component payloads as JSON.
- Parse JSON back into typed objects.
This is useful for debugging, tooling, and prototype save/load workflows.
Write Path (World -> JSON)
Core flow from the test:
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
writer.WriteStartObject();
writer.WriteString("Name", "world 1");
writer.WriteStartArray("Entities");
for (var i = 0; i < world.ComponentManager.ArchetypeCount; i++)
{
ref var archetype = ref world.ComponentManager.GetArchetypeReference(i);
for (var j = 0; j < archetype.ChunkCount; j++)
{
ref var chunk = ref archetype.GetChunkReference(j);
for (var k = 0; k < chunk._count; k++)
{
writer.WriteStartObject();
writer.WriteNumber("ID", entityID);
writer.WriteStartArray("Components");
// For each component in archetype layout:
// - resolve runtime type
// - marshal bytes to managed object
// - JsonSerializer.Serialize(writer, instance, type, options)
writer.WriteEndArray();
writer.WriteEndObject();
}
}
}
writer.WriteEndArray();
writer.WriteEndObject();
writer.Flush();
Data shape used in the test:
{
"Name": "world 1",
"Entities": [
{
"ID": 1,
"Components": [
{
"Type": "Full.AssemblyQualified.TypeName",
"Data": { }
}
]
}
]
}
Read Path (JSON -> Typed Data)
The test parses JSON and reconstructs typed component instances:
var root = JsonDocument.ParseValue(ref reader).RootElement;
var entityData = new List<(int EntityID, Type ComponentType, object Instance)>();
foreach (var entityElement in root.GetProperty("Entities").EnumerateArray())
{
var id = entityElement.GetProperty("ID").GetInt32();
foreach (var componentElement in entityElement.GetProperty("Components").EnumerateArray())
{
var typeName = componentElement.GetProperty("Type").GetString();
var type = Type.GetType(typeName!);
if (type == null)
{
continue;
}
var instance = componentElement.GetProperty("Data").Deserialize(type, options);
if (instance != null)
{
entityData.Add((id, type, instance));
}
}
}
Caveats
- The example is intentionally low-level and may use internal layout details.
AssemblyQualifiedNamebased type resolution is convenient but can be brittle across assembly/version changes.- For production save/load, prefer stable type identifiers and versioned schemas.
- Reflection + marshaling has overhead; avoid in per-frame hot paths.
Recommended Use Cases
- Inspecting world state during development.
- Exporting compact debugging snapshots.
- Prototyping serializer architecture before introducing a dedicated format.
For public runtime API details, see generated docs in doc/api/.