| Crates.io | tx2-pack |
| lib.rs | tx2-pack |
| version | 0.1.1 |
| created_at | 2025-11-27 17:04:08.305791+00 |
| updated_at | 2025-11-28 22:39:06.294577+00 |
| description | Binary world snapshot format for ECS persistence, checkpointing, and time-travel |
| homepage | https://github.com/IreGaddr/tx2-pack |
| repository | https://github.com/IreGaddr/tx2-pack |
| max_upload_size | |
| id | 1954112 |
| size | 103,420 |
Binary world snapshot format for ECS persistence, checkpointing, and time-travel replay.
tx2-pack is the storage layer of the TX-2 ecosystem, providing efficient serialization of Entity-Component-System worlds to disk with compression, encryption, and time-travel capabilities. It enables save/load, replay, and session persistence without requiring a traditional database.
use tx2_pack::{PackedSnapshot, SnapshotWriter, SnapshotReader};
// Create a snapshot from your world
let snapshot = PackedSnapshot::from_world_snapshot(world.create_snapshot());
// Write to file with compression
let writer = SnapshotWriter::new()
.with_compression(CompressionCodec::zstd_default());
writer.write_to_file(&snapshot, "world.tx2pack")?;
// Read back
let reader = SnapshotReader::new();
let loaded = reader.read_from_file("world.tx2pack")?;
use tx2_pack::{CheckpointManager, SnapshotMetadata};
// Create checkpoint manager
let mut manager = CheckpointManager::new("./checkpoints")?;
// Save checkpoint
let snapshot = PackedSnapshot::from_world_snapshot(world.create_snapshot());
let metadata = SnapshotMetadata::new("level-1-complete".to_string())
.with_name("Level 1 Complete")
.with_description("Player beat first boss")
.with_tag("milestone");
manager.create_checkpoint("cp1".to_string(), snapshot)?;
// Load checkpoint
let checkpoint = manager.load_checkpoint("cp1")?;
world.restore_from_snapshot(&checkpoint.snapshot)?;
// List all checkpoints
let checkpoints = manager.list_checkpoints()?;
// Delete old checkpoints
manager.prune_old_checkpoints(5)?; // Keep only 5 most recent
use tx2_pack::{ReplayEngine, CheckpointManager};
// Create replay engine
let mut replay = ReplayEngine::new();
// Load checkpoints from manager
replay.load_from_manager(&mut manager)?;
// Navigate through checkpoints
replay.next(); // Move forward
replay.previous(); // Move backward
replay.seek(5)?; // Jump to index 5
replay.seek_to_start(); // Jump to beginning
replay.seek_to_end(); // Jump to end
// Get current checkpoint
if let Some(checkpoint) = replay.current() {
world.restore_from_snapshot(&checkpoint.snapshot)?;
}
// Enable looping
let mut replay = ReplayEngine::new().with_loop(true);
use tx2_pack::TimeTravel;
// Create time-travel system
let mut tt = TimeTravel::new();
// Record snapshots at specific times
for t in 0..100 {
let snapshot = world.create_snapshot();
tt.record(t as f64, snapshot);
}
// Seek to specific time
if let Some(snapshot) = tt.seek_to_time(45.0) {
world.restore_from_snapshot(snapshot)?;
}
// Fork from a specific time
if let Some(forked) = tt.fork_at_time(30.0) {
// Create alternate timeline from this point
}
// Prune old snapshots
tt.prune_before(20.0); // Remove snapshots before t=20
tt.prune_after(80.0); // Remove snapshots after t=80
use tx2_pack::{SnapshotWriter, SnapshotReader, EncryptionKey};
// Generate encryption key
let key = EncryptionKey::generate();
// Write encrypted snapshot
let writer = SnapshotWriter::new()
.with_compression(CompressionCodec::zstd_default())
.with_encryption(key.clone());
writer.write_to_file(&snapshot, "world.tx2pack")?;
// Read encrypted snapshot
let reader = SnapshotReader::new()
.with_encryption(key);
let loaded = reader.read_from_file("world.tx2pack")?;
[Header][Data]
Header (bincode-serialized):
pub struct SnapshotHeader {
pub magic: [u8; 8], // "TX2PACK\0"
pub version: u32, // Format version
pub format: PackFormat, // Bincode or MessagePack
pub compression: CompressionType,
pub encrypted: bool,
pub checksum: [u8; 32], // SHA-256 of data
pub timestamp: i64,
pub entity_count: u64,
pub component_count: u64,
pub archetype_count: u64,
pub data_offset: u64, // Offset to data section
pub data_size: u64, // Size of data section
}
Data (compressed, optionally encrypted):
pub struct PackedSnapshot {
pub header: SnapshotHeader,
pub archetypes: Vec<ComponentArchetype>,
pub entity_metadata: HashMap<EntityId, EntityMetadata>,
}
Components are stored in struct-of-arrays layout:
pub struct ComponentArchetype {
pub component_id: ComponentId,
pub entity_ids: Vec<EntityId>,
pub data: ComponentData,
}
pub enum ComponentData {
StructOfArrays(StructOfArraysData),
Blob(Vec<u8>),
}
pub struct StructOfArraysData {
pub field_names: Vec<String>,
pub field_types: Vec<FieldType>,
pub field_data: Vec<FieldArray>,
}
Example SoA layout for Position component:
component_id: "Position"
entity_ids: [1, 2, 3, 4, 5]
field_names: ["x", "y", "z"]
field_types: [F32, F32, F32]
field_data: [
[10.0, 20.0, 30.0, 40.0, 50.0], // x values
[15.0, 25.0, 35.0, 45.0, 55.0], // y values
[5.0, 10.0, 15.0, 20.0, 25.0], // z values
]
Benefits:
Tested with 10,000 entities containing Position, Velocity, and Health components:
| Codec | Compressed Size | Compression Ratio | Write Time | Read Time |
|---|---|---|---|---|
| None | 1,200 KB | 1.0× | 5ms | 4ms |
| LZ4 | 450 KB | 2.7× | 8ms | 6ms |
| Zstd (level 3) | 180 KB | 6.7× | 15ms | 10ms |
| Zstd (level 19) | 120 KB | 10.0× | 150ms | 12ms |
Manages multiple snapshots in a directory:
use tx2_pack::{SnapshotStore, SnapshotWriter, SnapshotReader};
// Create store
let store = SnapshotStore::new("./snapshots")?;
// Save snapshot with metadata
let writer = SnapshotWriter::new();
let metadata = SnapshotMetadata::new("save-001".to_string());
store.save(&snapshot, &metadata, &writer)?;
// Files created:
// - ./snapshots/save-001.tx2pack
// - ./snapshots/save-001.meta.json
// Load snapshot
let reader = SnapshotReader::new();
let (snapshot, metadata) = store.load("save-001", &reader)?;
// List all snapshots
let ids = store.list()?;
// Delete snapshot
store.delete("save-001")?;
pub struct SnapshotMetadata {
pub id: String,
pub name: Option<String>,
pub description: Option<String>,
pub created_at: i64, // Unix timestamp
pub world_time: f64, // In-game time
pub schema_version: u32,
pub custom_fields: HashMap<String, String>,
pub tags: Vec<String>,
}
Usage:
let metadata = SnapshotMetadata::new("save-001".to_string())
.with_name("Before Boss Fight")
.with_description("Full health, all items")
.with_tag("boss")
.with_tag("milestone")
.with_custom_field("level", "5")
.with_custom_field("difficulty", "hard");
// Save game
let snapshot = game_world.create_snapshot();
let metadata = SnapshotMetadata::new(format!("save-{}", slot))
.with_name(&player_name)
.with_tag("player-save");
manager.create_checkpoint(format!("slot-{}", slot), snapshot)?;
// Load game
let checkpoint = manager.load_checkpoint(&format!("slot-{}", slot))?;
game_world.restore_from_snapshot(&checkpoint.snapshot)?;
// Record gameplay every 5 seconds
loop {
// ... game logic ...
if elapsed >= 5.0 {
let snapshot = world.create_snapshot();
tt.record(world_time, snapshot);
elapsed = 0.0;
}
}
// Replay from any time
let start_time = 120.0;
if let Some(snapshot) = tt.seek_to_time(start_time) {
world.restore_from_snapshot(snapshot)?;
replay_mode = true;
}
// Save session on exit
let snapshot = agent_world.create_snapshot();
let metadata = SnapshotMetadata::new("last-session".to_string())
.with_tag("auto-save")
.with_custom_field("tab_count", &open_tabs.len().to_string());
store.save(&snapshot, &metadata, &writer)?;
// Restore session on launch
if let Ok((snapshot, _)) = store.load("last-session", &reader) {
agent_world.restore_from_snapshot(&snapshot)?;
}
// Record checkpoints during simulation
for tick in 0..1000 {
world.update(delta_time);
if tick % 10 == 0 {
let snapshot = world.create_snapshot();
tt.record(tick as f64, snapshot);
}
}
// Jump to problematic tick
if let Some(snapshot) = tt.seek_to_time(450.0) {
world.restore_from_snapshot(snapshot)?;
// Inspect world state at tick 450
}
tx2-pack works with the broader TX-2 stack:
tx2-pack and tx2-link both work with world snapshots but serve different purposes:
Both use the same WorldSnapshot structure from tx2-link.
cargo test
All 14 tests should pass, covering:
cargo build --benches # Build without running
Benchmarks measure:
pub enum PackError {
Io(std::io::Error),
Serialization(String),
Deserialization(String),
Compression(String),
Decompression(String),
Encryption(String),
Decryption(String),
InvalidFormat(String),
VersionMismatch { expected: String, actual: String },
ChecksumMismatch,
SnapshotNotFound(String),
InvalidCheckpoint(String),
}
All operations return Result<T, PackError> for proper error handling.
tx2-link - Shared snapshot formatserde - Serialization frameworkbincode - Fast binary serializationrmp-serde - MessagePack formatzstd - Zstd compressionlz4 - LZ4 compressionsha2 - SHA-256 checksumsaes-gcm - AES-256-GCM encryptionchrono - Timestamp handlingahash - Fast hashingMIT
Contributions are welcome! This is part of the broader TX-2 project for building isomorphic applications with a unified world model.