A Zig library for serializing and deserializing ECS (Entity Component System) game state. Designed to work with zig-ecs and the labelle graphics library.
- Full ECS State Persistence: Save and load complete game state including all entities and components
- Entity ID Remapping: Automatically handles entity reference updates when loading saves
- Multiple Formats: Support for JSON (human-readable) and binary (compact) formats
- Versioning: Built-in save format versioning for backward compatibility
- Selective Serialization: Mark components as transient to exclude from saves
- Incremental Saves: Support for delta saves to reduce save file size
- Compression: Optional compression for save files
Add labelle-serialization to your build.zig.zon:
.dependencies = .{
.@"labelle-serialization" = .{
.url = "https://github.com/labelle-toolkit/labelle-serialization/archive/refs/heads/main.tar.gz",
.hash = "...",
},
},Then in your build.zig:
const serialization = b.dependency("labelle-serialization", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("labelle-serialization", serialization.module("labelle-serialization"));const std = @import("std");
const ecs = @import("zig-ecs");
const serialization = @import("labelle-serialization");
// Define your components
const Position = struct { x: f32, y: f32 };
const Health = struct { current: u8, max: u8 };
const Velocity = struct { dx: f32, dy: f32 };
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Create registry and add entities
var registry = ecs.Registry.init(allocator);
defer registry.deinit();
const player = registry.create();
registry.add(player, Position{ .x = 100, .y = 200 });
registry.add(player, Health{ .current = 80, .max = 100 });
// Create serializer with component registration
var serializer = serialization.Serializer.init(allocator, .{
.version = 1,
.format = .json,
});
defer serializer.deinit();
// Register components to serialize
try serializer.registerComponent(Position);
try serializer.registerComponent(Health);
// Don't register Velocity - it won't be saved (transient)
// Save game state
try serializer.save(®istry, "saves/game.json");
// Load game state
var new_registry = ecs.Registry.init(allocator);
defer new_registry.deinit();
try serializer.load(&new_registry, "saves/game.json");
}Components that reference other entities are automatically remapped:
const FollowTarget = struct {
target: ecs.Entity,
distance: f32,
};
const Parent = struct {
entity: ecs.Entity,
};
// Register with entity field hints
try serializer.registerComponent(FollowTarget);
try serializer.registerComponent(Parent);
// Entity references are automatically detected and remapped on loadFor complex types, implement custom serialize/deserialize:
const ComplexComponent = struct {
data: std.ArrayList(u8),
pub fn serialize(self: @This(), writer: anytype) !void {
try writer.writeInt(u32, @intCast(self.data.items.len));
try writer.writeAll(self.data.items);
}
pub fn deserialize(allocator: std.mem.Allocator, reader: anytype) !@This() {
const len = try reader.readInt(u32);
var data = std.ArrayList(u8).init(allocator);
try data.resize(len);
_ = try reader.readAll(data.items);
return .{ .data = data };
}
};Handle save format changes between versions:
try serializer.registerMigration(1, 2, struct {
fn migrate(data: *SaveData) !void {
// Migrate from v1 to v2 format
// e.g., rename component, add default values
}
}.migrate);Include game-specific metadata in saves:
const metadata = SaveMetadata{
.save_name = "Slot 1",
.play_time_seconds = 3600,
.timestamp = std.time.timestamp(),
.screenshot_path = "saves/slot1_thumb.png",
};
try serializer.saveWithMetadata(®istry, "saves/slot1.json", metadata);- Primitives:
bool,u8-u64,i8-i64,f16-f128 - Arrays and slices
- Optionals
- Structs (nested)
- Enums
- Tagged unions
- Entity references (automatically remapped)
- Pointers (serialized as optional values)
const config = serialization.Config{
.version = 1,
.format = .json, // .json or .binary
.pretty_print = true, // JSON formatting
.compression = .none, // .none, .zlib, .zstd
.include_metadata = true, // Save timestamp, version, etc.
};This library is designed for games like flying-platform that need to persist:
- Character State: Position, health, needs (hunger, thirst, tiredness), current tasks
- World State: Room layouts, workstation configurations, item placements
- Progress: Production progress, task queues, time of day
- Inventory: Items, storage contents, delivery tasks
- Relationships: Entity references (who carries what, who works where)
- labelle - 2D graphics library for Zig games
- zig-ecs - Entity Component System for Zig
- flying-platform - Example game using this library
MIT License - see LICENSE for details.