1use serde::{Deserialize, Serialize};
2use tx2_link::{EntityId, ComponentId};
3use ahash::AHashMap;
4use std::collections::HashMap;
5
6pub const MAGIC_NUMBER: &[u8; 8] = b"TX2PACK\0";
7pub const FORMAT_VERSION: u32 = 1;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum PackFormat {
11 Bincode,
12 MessagePack,
13 Custom,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct SnapshotHeader {
18 pub magic: [u8; 8],
19 pub version: u32,
20 pub format: PackFormat,
21 pub compression: CompressionType,
22 pub encrypted: bool,
23 pub checksum: [u8; 32],
24 pub timestamp: i64,
25 pub entity_count: u64,
26 pub component_count: u64,
27 pub archetype_count: u64,
28 pub data_offset: u64,
29 pub data_size: u64,
30 pub metadata_offset: u64,
31 pub metadata_size: u64,
32}
33
34impl SnapshotHeader {
35 pub fn new() -> Self {
36 Self {
37 magic: *MAGIC_NUMBER,
38 version: FORMAT_VERSION,
39 format: PackFormat::Bincode,
40 compression: CompressionType::Zstd,
41 encrypted: false,
42 checksum: [0u8; 32],
43 timestamp: chrono::Utc::now().timestamp(),
44 entity_count: 0,
45 component_count: 0,
46 archetype_count: 0,
47 data_offset: 0,
48 data_size: 0,
49 metadata_offset: 0,
50 metadata_size: 0,
51 }
52 }
53
54 pub fn validate(&self) -> crate::Result<()> {
55 if self.magic != *MAGIC_NUMBER {
56 return Err(crate::PackError::InvalidFormat(
57 "Invalid magic number".to_string()
58 ));
59 }
60
61 if self.version != FORMAT_VERSION {
62 return Err(crate::PackError::VersionMismatch {
63 expected: FORMAT_VERSION.to_string(),
64 actual: self.version.to_string(),
65 });
66 }
67
68 Ok(())
69 }
70}
71
72impl Default for SnapshotHeader {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
79pub enum CompressionType {
80 None,
81 Zstd,
82 Lz4,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct ComponentArchetype {
87 pub component_id: ComponentId,
88 pub entity_ids: Vec<EntityId>,
89 pub data: ComponentData,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub enum ComponentData {
94 StructOfArrays(StructOfArraysData),
95 Blob(Vec<u8>),
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct StructOfArraysData {
100 pub field_names: Vec<String>,
101 pub field_types: Vec<FieldType>,
102 pub field_data: Vec<FieldArray>,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
106pub enum FieldType {
107 Bool,
108 I8,
109 I16,
110 I32,
111 I64,
112 U8,
113 U16,
114 U32,
115 U64,
116 F32,
117 F64,
118 String,
119 Bytes,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub enum FieldArray {
124 Bool(Vec<bool>),
125 I8(Vec<i8>),
126 I16(Vec<i16>),
127 I32(Vec<i32>),
128 I64(Vec<i64>),
129 U8(Vec<u8>),
130 U16(Vec<u16>),
131 U32(Vec<u32>),
132 U64(Vec<u64>),
133 F32(Vec<f32>),
134 F64(Vec<f64>),
135 String(Vec<String>),
136 Bytes(Vec<Vec<u8>>),
137}
138
139impl FieldArray {
140 pub fn len(&self) -> usize {
141 match self {
142 FieldArray::Bool(v) => v.len(),
143 FieldArray::I8(v) => v.len(),
144 FieldArray::I16(v) => v.len(),
145 FieldArray::I32(v) => v.len(),
146 FieldArray::I64(v) => v.len(),
147 FieldArray::U8(v) => v.len(),
148 FieldArray::U16(v) => v.len(),
149 FieldArray::U32(v) => v.len(),
150 FieldArray::U64(v) => v.len(),
151 FieldArray::F32(v) => v.len(),
152 FieldArray::F64(v) => v.len(),
153 FieldArray::String(v) => v.len(),
154 FieldArray::Bytes(v) => v.len(),
155 }
156 }
157
158 pub fn is_empty(&self) -> bool {
159 self.len() == 0
160 }
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct PackedSnapshot {
165 pub header: SnapshotHeader,
166 pub archetypes: Vec<ComponentArchetype>,
167 pub entity_metadata: HashMap<EntityId, EntityMetadata>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct EntityMetadata {
172 pub created_at: i64,
173 pub modified_at: i64,
174 pub tags: Vec<String>,
175}
176
177impl PackedSnapshot {
178 pub fn new() -> Self {
179 Self {
180 header: SnapshotHeader::new(),
181 archetypes: Vec::new(),
182 entity_metadata: HashMap::new(),
183 }
184 }
185
186 pub fn from_world_snapshot(snapshot: tx2_link::WorldSnapshot) -> Self {
187 let mut packed = Self::new();
188 packed.header.timestamp = snapshot.timestamp as i64;
189
190 let entity_count = snapshot.entities.len() as u64;
191
192 let mut component_map: AHashMap<ComponentId, ComponentArchetype> = AHashMap::new();
193
194 for entity in &snapshot.entities {
195 for component in &entity.components {
196 let archetype = component_map
197 .entry(component.id.clone())
198 .or_insert_with(|| ComponentArchetype {
199 component_id: component.id.clone(),
200 entity_ids: Vec::new(),
201 data: ComponentData::Blob(Vec::new()),
202 });
203
204 archetype.entity_ids.push(entity.id);
205 }
206 }
207
208 packed.archetypes = component_map.into_values().collect();
209 packed.header.entity_count = entity_count;
210 packed.header.component_count = packed.archetypes.len() as u64;
211 packed.header.archetype_count = packed.archetypes.len() as u64;
212
213 packed
214 }
215}
216
217impl Default for PackedSnapshot {
218 fn default() -> Self {
219 Self::new()
220 }
221}