brdb/wrapper/
save.rs

1use std::collections::HashMap;
2
3use itertools::Itertools;
4
5use crate::{
6    errors::{BrdbError, BrdbWorldError},
7    pending::BrdbPendingFs,
8    schema::{BrdbSchema, BrdbSchemaGlobalData},
9    wrapper::{
10        Brick, BrickChunkIndexSoA, BrickChunkSoA, ChunkIndex, ComponentChunkSoA, Entity,
11        EntityChunkIndexSoA, EntityChunkSoA, LocalWirePortSource, OwnerTableSoA,
12        RemoteWirePortSource, WireChunkSoA, WireConnection, WirePortTarget, WorldMeta, schemas,
13    },
14};
15
16/// All of the dynamic data needed to serialize a world
17pub struct UnsavedFs {
18    /// Meta/
19    pub meta: WorldMeta,
20    /// World/
21    pub worlds: HashMap<usize, UnsavedWorld>,
22}
23
24impl UnsavedFs {
25    pub fn to_pending(self) -> Result<BrdbPendingFs, BrdbError> {
26        BrdbPendingFs::from_unsaved(self)
27    }
28}
29
30pub struct UnsavedWorld {
31    /// World/N/GlobalData.mps
32    pub global_data: BrdbSchemaGlobalData,
33    /// World/N/Owners.mps
34    pub owners: OwnerTableSoA,
35    /// World/N/Bricks/Grids/ComponentsShared.mps
36    pub component_schema: BrdbSchema,
37    /// World/N/Bricks/Grids/[key.0]/
38    pub grids: HashMap<usize, UnsavedGrid>,
39    /// World/N/Bricks/Entities/Chunks/[key].mps
40    pub entity_chunks: HashMap<ChunkIndex, EntityChunkSoA>,
41    /// World/N/Bricks/Entities/ChunksShared.schema
42    pub entity_schema: BrdbSchema,
43    /// World/N/Bricks/Entities/ChunkIndex.mps
44    pub entity_chunk_index: EntityChunkIndexSoA,
45
46    /// World/N/Minigame.bp
47    pub minigame: Option<()>, // TODO: minigames serialization
48    /// World/N/Environment.bp
49    pub environment: Option<()>, // TODO: environment serialization
50
51    /// Internal map of brick id to (grid_id, chunk_index, brick_index_in_chunk)
52    /// This is used to connect wires
53    /// and is not saved to the world file.
54    brick_id_map: HashMap<usize, (usize, ChunkIndex, usize)>,
55    // Maps entity internal id to persistent index
56    entity_index_map: HashMap<usize, u32>,
57}
58
59impl Default for UnsavedWorld {
60    fn default() -> Self {
61        Self {
62            global_data: Default::default(),
63            owners: Default::default(),
64            component_schema: schemas::bricks_components_schema_min(),
65            grids: Default::default(),
66            entity_chunks: Default::default(),
67            entity_schema: schemas::entities_chunks_schema(),
68            entity_chunk_index: Default::default(),
69            minigame: Default::default(),
70            environment: Default::default(),
71            brick_id_map: Default::default(),
72            entity_index_map: Default::default(),
73        }
74    }
75}
76
77impl UnsavedWorld {
78    fn add_brick_meta(&mut self, brick: &Brick) {
79        // Adding the brick's data to the global data
80        self.global_data.add_brick_meta(brick);
81
82        // Iterate the components of the brick and register
83        // their respective struct metadata with the component schema
84        for component in &brick.components {
85            // Skip components without a type
86            let Some((ty_name, _)) = component.get_schema_struct() else {
87                continue;
88            };
89
90            // If the component type is already registered, skip it
91            if self.global_data.has_component_type(ty_name.as_ref()) {
92                continue;
93            }
94            self.global_data.add_component_meta(component.as_ref());
95
96            let Some((enums, structs)) = component.get_schema() else {
97                continue;
98            };
99            self.component_schema.add_meta(enums, structs);
100        }
101    }
102
103    fn add_entity_meta(&mut self, entity: &Entity) {
104        let Some((ty_name, _)) = entity.data.get_schema_struct() else {
105            return;
106        };
107        self.global_data.add_entity_type(&ty_name);
108        let Some((enums, structs)) = entity.data.get_schema() else {
109            return;
110        };
111        self.entity_schema.add_meta(enums, structs);
112    }
113
114    pub(super) fn add_bricks_to_grid(&mut self, grid_id: usize, bricks: &[Brick]) {
115        let mut grid = UnsavedGrid::default();
116
117        // Bricks are sorted by brick type, size, and position
118        for b in bricks.iter().sorted_by(|a, b| a.cmp(b)) {
119            self.add_brick_meta(b);
120
121            // Update the owner table
122            let owner_id = b.owner_index.unwrap_or(0);
123            self.owners.inc_bricks(owner_id);
124            self.owners
125                .inc_components(owner_id, b.components.len() as u32);
126
127            // Add the brick to the grid
128            let (chunk_index, brick_index) = grid.add_brick(&self.global_data, b);
129            // Track the brick for wire connections
130            if let Some(id) = b.id {
131                self.brick_id_map
132                    .insert(id, (grid_id, chunk_index, brick_index));
133            }
134        }
135
136        // Add the grid to the world
137        self.grids.insert(grid_id, grid);
138    }
139
140    pub(super) fn add_entity(&mut self, entity: &Entity) -> usize {
141        // Add the entity metadata to the global data
142        self.add_entity_meta(entity);
143
144        // Update the owner table
145        let owner_id = entity.owner_index.unwrap_or(0);
146        self.owners.inc_entities(owner_id as usize);
147
148        // Increment the entity persistent index
149        let entity_index = self.entity_chunk_index.next_persistent_index;
150        self.entity_chunk_index.next_persistent_index += 1;
151
152        // There is only one entity chunk right now...
153        let chunk_index = ChunkIndex::ZERO;
154        // Create a new entity chunk if it doesn't exist
155        if self.entity_chunk_index.chunk_3d_indices.is_empty() {
156            self.entity_chunk_index.chunk_3d_indices.push(chunk_index);
157        }
158        if self.entity_chunk_index.num_entities.is_empty() {
159            self.entity_chunk_index.num_entities.push(0);
160        }
161        self.entity_chunk_index.num_entities[0] += 1;
162
163        self.entity_chunks
164            .entry(chunk_index)
165            .or_insert_with(EntityChunkSoA::default)
166            .add_entity(&self.global_data, entity, entity_index);
167
168        // Map the internal entity id to its persistent index
169        if let Some(id) = entity.id {
170            self.entity_index_map.insert(id, entity_index);
171        }
172
173        entity_index as usize
174    }
175
176    pub(super) fn add_wire(&mut self, wire: &WireConnection) -> Result<(), BrdbError> {
177        // Resolve source wire metadata
178        let (s_grid, s_chunk, s_brick) = self
179            .brick_id_map
180            .get(&wire.source.brick_id)
181            .ok_or_else(|| BrdbWorldError::UnknownBrickId(wire.source.brick_id))?;
182        let s_comp_ty = self
183            .global_data
184            .get_component_type_index(&wire.source.component_type)
185            .ok_or_else(|| {
186                BrdbWorldError::UnknownComponent(wire.source.component_type.to_string())
187            })?;
188        let s_port_index = self
189            .global_data
190            .get_port_index(&wire.source.port_name)
191            .ok_or_else(|| BrdbWorldError::UnknownPort(wire.source.port_name.to_string()))?;
192
193        // Resolve target wire metadata
194        let (t_grid, t_chunk, t_brick) = self
195            .brick_id_map
196            .get(&wire.target.brick_id)
197            .ok_or_else(|| BrdbWorldError::UnknownBrickId(wire.target.brick_id))?;
198        let t_comp_ty = self
199            .global_data
200            .get_component_type_index(&wire.target.component_type)
201            .ok_or_else(|| {
202                BrdbWorldError::UnknownComponent(wire.target.component_type.to_string())
203            })?;
204        let t_port_index = self
205            .global_data
206            .get_port_index(&wire.target.port_name)
207            .ok_or_else(|| BrdbWorldError::UnknownPort(wire.target.port_name.to_string()))?;
208
209        // Create the target port
210        let target = WirePortTarget {
211            brick_index_in_chunk: *t_brick as u32,
212            component_type_index: t_comp_ty,
213            port_index: t_port_index,
214        };
215
216        // Wires are inserted in the target grid
217        let grid = self
218            .grids
219            .get_mut(t_grid)
220            .ok_or_else(|| BrdbWorldError::UnknownGridId(*t_grid))?;
221
222        // Increment the wire count for the target chunk
223        let chunk_id = grid.get_chunk_index(*t_chunk);
224        grid.chunk_index.num_wires[chunk_id] += 1;
225
226        // If the target and source are in the same grid and chunk,
227        // we can use a local wire source.
228        if t_grid == s_grid && t_chunk == s_chunk {
229            let source = LocalWirePortSource {
230                brick_index_in_chunk: *s_brick as u32,
231                component_type_index: s_comp_ty,
232                port_index: s_port_index,
233            };
234            grid.add_local_wire(*t_chunk, source, target);
235        } else {
236            // Otherwise, we need to use a remote wire source.
237            let source = RemoteWirePortSource {
238                grid_persistent_index: *s_grid as u32,
239                chunk_index: *s_chunk,
240                brick_index_in_chunk: *s_brick as u32,
241                component_type_index: s_comp_ty,
242                port_index: s_port_index,
243            };
244            grid.add_remote_wire(*t_chunk, source, target);
245        }
246
247        Ok(())
248    }
249}
250
251#[derive(Default)]
252pub struct UnsavedGrid {
253    /// World/N/Bricks/Grids/I/ChunkIndex.mps
254    pub chunk_index: BrickChunkIndexSoA,
255    /// World/N/Bricks/Grids/I/Chunks/[key].mps
256    pub bricks: HashMap<ChunkIndex, BrickChunkSoA>,
257    /// World/N/Bricks/Grids/I/Components/[key].mps
258    pub components: HashMap<ChunkIndex, ComponentChunkSoA>,
259    /// World/N/Bricks/Grids/I/Wires/[key].mps
260    pub wires: HashMap<ChunkIndex, WireChunkSoA>,
261
262    /// Map of 3d chunk index to serial index in the `chunk_index` array
263    /// Used to quickly find the index of a chunk in the `chunk_index` array
264    chunk_index_map: HashMap<ChunkIndex, usize>,
265}
266
267impl UnsavedGrid {
268    /// Appends a new chunk to the chunk_index SoA, returning the index of the chunk
269    fn get_chunk_index(&mut self, chunk_index: ChunkIndex) -> usize {
270        // Add the chunk to the index if it doesn't exist
271        if let Some(index) = self.chunk_index_map.get(&chunk_index) {
272            *index
273        } else {
274            self.chunk_index.chunk_3d_indices.push(chunk_index);
275            self.chunk_index.num_bricks.push(0);
276            self.chunk_index.num_components.push(0);
277            self.chunk_index.num_wires.push(0);
278            let index = self.chunk_index_map.len();
279            self.chunk_index_map.insert(chunk_index, index);
280            index
281        }
282    }
283
284    /// Add a brick to the grid, returning the chunk index and the brick index
285    fn add_brick(
286        &mut self,
287        global_data: &BrdbSchemaGlobalData,
288        brick: &Brick,
289    ) -> (ChunkIndex, usize) {
290        let chunk_index = brick.position.to_relative().0;
291        // Lookup chunk by chunk index (or create a default one if it doesn't exist)
292        self.bricks
293            .entry(chunk_index)
294            .or_insert_with(BrickChunkSoA::default)
295            .add_brick(global_data, brick); // Add the brick to the chunk
296        // Get the chunk_index SoA index for that chunk
297        let i = self.get_chunk_index(chunk_index);
298        // Get the brick index
299        let brick_index = self.chunk_index.num_bricks[i];
300        // Increment the counts for the chunk index
301        self.chunk_index.num_bricks[i] += 1;
302        self.chunk_index.num_components[i] += brick.components.len() as u32;
303
304        // Write the components to the respective component chunk
305        if !brick.components.is_empty() {
306            let chunk = self
307                .components
308                .entry(chunk_index)
309                .or_insert_with(ComponentChunkSoA::default);
310            for c in &brick.components {
311                chunk.add_component(global_data, brick_index, c.as_ref());
312            }
313        }
314
315        (chunk_index, brick_index as usize)
316    }
317
318    fn add_local_wire(
319        &mut self,
320        chunk: ChunkIndex,
321        source: LocalWirePortSource,
322        target: WirePortTarget,
323    ) {
324        self.wires
325            .entry(chunk)
326            .or_insert_with(WireChunkSoA::default)
327            .add_local_wire(source, target);
328    }
329
330    fn add_remote_wire(
331        &mut self,
332        chunk: ChunkIndex,
333        source: RemoteWirePortSource,
334        target: WirePortTarget,
335    ) {
336        self.wires
337            .entry(chunk)
338            .or_insert_with(WireChunkSoA::default)
339            .add_remote_wire(source, target);
340    }
341}