chunked/
snapshot.rs

1//! A snapshot implementation which snapshots the state of a World.
2
3use std::borrow::Cow;
4use std::collections::BTreeMap;
5use std::fmt::{self, Debug};
6use std::ops::Deref;
7use std::sync::Arc;
8
9use rayon::iter::{
10    IntoParallelRefIterator,
11    ParallelIterator,
12};
13
14use crate::{Archetype, ComponentTypeID};
15use crate::archetype::{ComponentSetExt, ComponentVecSet};
16use crate::chunk::{Chunk, ChunkAction, ChunkEdit, EntityEntry};
17use crate::chunk_set::ChunkSet;
18use crate::component_data::{ComponentDataVecWriter, ComponentValueRef};
19use crate::entity::EntityID;
20use crate::universe::Universe;
21
22/// A single action as part of an edit list.
23///
24/// This can set or remove a single component.
25#[derive(Clone, Debug)]
26pub enum EditAction<'a> {
27    SetComponent(ComponentValueRef<'a>),
28    RemoveComponent(ComponentTypeID),
29}
30
31/// A single edit in an edit list.
32#[derive(Clone, Debug)]
33pub struct Edit<'a>(pub EntityID, pub EditAction<'a>);
34
35/// A snapshot of the state of the world.
36#[derive(Clone)]
37pub struct Snapshot {
38    universe: Arc<Universe>,
39    chunk_sets: Vec<ChunkSet>,
40    entities: BTreeMap<EntityID, usize>,
41}
42
43impl Snapshot {
44    /// Create a new snapshot of an empty world.
45    pub fn empty(universe: Arc<Universe>) -> Snapshot {
46        Snapshot {
47            universe,
48            chunk_sets: Vec::new(),
49            entities: BTreeMap::new(),
50        }
51    }
52
53    /// Get a weak reference to the owning universe of this snapshot.
54    pub fn universe(&self) -> &Arc<Universe> {
55        &self.universe
56    }
57
58    /// Get all the `ChunkSet`s in this snapshot.
59    pub fn chunk_sets(&self) -> &[ChunkSet] {
60        &self.chunk_sets
61    }
62
63    /// Create a parallel iterator over the chunks in the snapshot.
64    pub fn par_iter_chunk_sets(&self) -> impl ParallelIterator<Item=&ChunkSet> {
65        self.chunk_sets.par_iter()
66    }
67
68    /// Create an iterator over all chunk sets.
69    pub fn iter_chunk_sets(&self) -> impl Iterator<Item=&ChunkSet> {
70        self.chunk_sets.iter()
71    }
72
73    /// Create a parallel iterator over the chunks in the snapshot.
74    pub fn par_iter_chunks(&self) -> impl ParallelIterator<Item=&Arc<Chunk>> {
75        self.chunk_sets.par_iter().flat_map(|chunks| chunks.par_iter())
76    }
77
78    /// Create an iterator over all the chunks in the snapshot.
79    pub fn iter_chunks(&self) -> impl Iterator<Item=&Arc<Chunk>> {
80        self.chunk_sets.iter().flat_map(|chunks| chunks.iter())
81    }
82
83    /// Get the `ChunkSet` for a particular archetype.
84    pub fn chunk_set(&self, a: &Arc<Archetype>) -> Option<&ChunkSet> {
85        self.chunk_sets.get(a.id())
86    }
87
88    /// Get an `EntityEntry` for the entity with the given ID.
89    ///
90    /// Returns None if the entity doesn't exist in this snapshot.
91    pub fn entity(&self, id: EntityID) -> Option<EntityEntry> {
92        self.entities.get(&id)
93            .cloned()
94            .and_then(|arch_idx| self.chunk_sets.get(arch_idx))
95            .and_then(|chunks| chunks.chunk_for_entity(id))
96            .and_then(|chunk| {
97                let ids = chunk.components::<EntityID>().unwrap();
98                ids.binary_search(&id).ok()
99                    .and_then(|idx| chunk.entity_by_index(idx))
100            })
101    }
102
103    /// Modify this snapshot, producing another snapshot, with the given edit list applied.
104    ///
105    /// If this is the only `Arc` to this snapshot, the memory will be reused. This is
106    /// also true of the contained `ChunkSet`s and `Chunk`s.
107    pub fn modify<'a, E>(self: &mut Arc<Self>, edits: E)
108        where E: Iterator<Item=Edit<'a>>
109    {
110        let edit_list = SnapshotEditList::from_edits(self, edits);
111        if !edit_list.is_empty() {
112            let archetype_edits = edit_list.chunk_set_edits;
113
114            let universe = self.universe.clone();
115            let edit_snap = Arc::make_mut(self);
116            if edit_snap.chunk_sets.len() < archetype_edits.len() {
117                edit_snap.chunk_sets.resize(archetype_edits.len(), ChunkSet::new());
118            }
119
120            let chunk_sets = edit_snap.chunk_sets.iter_mut();
121            let arch_edits = archetype_edits.into_iter();
122            let arch_edit_sets = arch_edits.zip(chunk_sets)
123                .enumerate()
124                .map(|(id, (edits, chunk_set))|
125                    (universe.archetype_by_id(id).unwrap(), edits, chunk_set));
126
127            // TODO: make this parallel.
128            for (arch, edits, chunk_set) in arch_edit_sets {
129                chunk_set.modify(arch, edits, &edit_list.component_data);
130            }
131        }
132    }
133}
134
135impl Debug for Snapshot {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        write!(f, "Snapshot {{\n")?;
138
139        write!(f, "  Chunk Sets:\n")?;
140        for (arch_id, chunk_set) in self.chunk_sets.iter().enumerate() {
141            if chunk_set.is_empty() {
142                continue
143            }
144
145            let archetype = self.universe.archetype_by_id(arch_id).unwrap();
146
147            write!(f, "    #{} - ", arch_id)?;
148            for ty in archetype.component_types().as_slice() {
149                write!(f, "{:?}, ", ty)?;
150            }
151            write!(f, "\n")?;
152
153            write!(f, "      Chunks:\n")?;
154            for chunk in chunk_set.iter() {
155                write!(f, "        {:?} - {} entities\n", chunk.deref() as *const _, chunk.len())?;
156
157                let ids = chunk.components::<EntityID>().unwrap();
158
159                for entity_id in ids {
160                    write!(f, "          Entity {:?} - Chunk {:?}\n", entity_id, chunk.deref() as *const _)?;
161                }
162            }
163        }
164
165        write!(f, "}}\n")
166    }
167}
168
169struct SnapshotEditList<'a> {
170    component_data: Vec<ComponentValueRef<'a>>,
171    chunk_set_edits: Vec<Vec<ChunkEdit>>,
172}
173
174impl<'a> SnapshotEditList<'a> {
175    fn get_chunk_set_edits<'b: 'c, 'c>(edits: &'b mut Vec<Vec<ChunkEdit>>, arch: &Arc<Archetype>) -> &'c mut Vec<ChunkEdit> {
176        let id = arch.id();
177        if edits.len() <= id {
178            edits.resize(id + 1, Vec::new());
179        }
180        &mut edits[id]
181    }
182
183    pub fn from_edits<E>(snap: &mut Arc<Snapshot>, edits: E) -> SnapshotEditList<'a>
184        where E: Iterator<Item=Edit<'a>>
185    {
186        let mut component_data = Vec::new();
187        let mut chunk_set_edits = Vec::new();
188        let mut edits = edits.peekable();
189
190        while let Some(Edit(id, _)) = edits.peek().cloned() {
191            let old_archetype = snap.entities.get(&id)
192                .copied()
193                .and_then(|idx| snap.universe.archetype_by_id(idx));
194            let mut component_types = match old_archetype {
195                Some(ref a) => Cow::Borrowed(a.component_types()),
196                None => Cow::Owned(ComponentVecSet::new(Vec::new())),
197            };
198
199            let mut component_data = ComponentDataVecWriter::new(&mut component_data);
200
201            while edits.peek().map_or(false, |e| e.0 == id) {
202                match edits.next().unwrap().1 {
203                    EditAction::SetComponent(value) => {
204                        if !component_types.includes(&value.type_id()) {
205                            component_types.to_mut().insert(value.type_id());
206                        }
207                        component_data.set_component(value);
208                    }
209                    EditAction::RemoveComponent(type_id) => {
210                        if component_types.includes(&type_id) {
211                            component_types.to_mut().remove(type_id);
212                        }
213                        component_data.remove_component(type_id);
214                    }
215                }
216            }
217
218            let new_archetype = match component_types {
219                Cow::Borrowed(_) => Some(old_archetype.clone().unwrap()),
220                Cow::Owned(component_set) => {
221                    if component_set.len() > 1 {
222                        Some(snap.universe.ensure_archetype(component_set))
223                    } else {
224                        None
225                    }
226                }
227            };
228
229            let is_empty = old_archetype.is_none() && new_archetype.is_none();
230            let is_move = old_archetype
231                .clone()
232                .and_then(|old| new_archetype.clone().map(|new| (old, new)))
233                .map_or(true, |(a, b)| !Arc::ptr_eq(&a, &b));
234            let is_noop = !is_empty && !is_move && component_data.len() == 0;
235            if is_noop {
236                continue;
237            }
238
239            // Remove old entity.
240            if let Some(arch) = old_archetype.filter(|_| is_move) {
241                SnapshotEditList::get_chunk_set_edits(&mut chunk_set_edits, &arch)
242                    .push(ChunkEdit(id, ChunkAction::Remove));
243
244                let edit_snap = Arc::make_mut(snap);
245                edit_snap.entities.remove(&id);
246            }
247
248            // Upsert entity.
249            if let Some(arch) = new_archetype {
250                let (start, end) = component_data.range();
251                SnapshotEditList::get_chunk_set_edits(&mut chunk_set_edits, &arch)
252                    .push(ChunkEdit(id, ChunkAction::Upsert(start, end)));
253
254                if is_move {
255                    let edit_snap = Arc::make_mut(snap);
256                    edit_snap.entities.insert(id, arch.id());
257                }
258            }
259        }
260
261        SnapshotEditList {
262            component_data,
263            chunk_set_edits,
264        }
265    }
266
267    /// Returns true if there are no changes associated with this edit list.
268    pub fn is_empty(&self) -> bool {
269        self.chunk_set_edits.is_empty()
270    }
271}
272