xvc_ecs/ecs/
xvcstore.rs

1//! The basic store type used across Xvc.
2//!
3//! It's used to store and retrieve a [crate::Storable] type to text files.
4use super::event::Event;
5use super::event::EventLog;
6use super::*;
7use crate::error::{Error, Result};
8use crate::{HStore, Storable};
9use std::collections::{BTreeMap, HashMap};
10use std::fmt::Debug;
11use std::path::Path;
12use std::path::PathBuf;
13
14use std::ops::Deref;
15use std::ops::Index;
16use std::sync::{Arc, RwLock};
17
18/// A database table like store for type `T`
19///
20/// It's used as the general purpose persistence data structure for all components.
21/// It contains an `XvcEntity -> T` map, and an index `T -> Vec<XvcEntity>` for these entries for
22/// quick reverse lookup.
23/// It loads these data from [EventLog] collections.
24/// It has basic functionality to insert, delete, filter and iterate over the elements.
25#[derive(Debug, Clone, Hash)]
26pub struct XvcStore<T>
27where
28    T: Storable,
29{
30    map: BTreeMap<XvcEntity, T>,
31    entity_index: BTreeMap<T, Vec<XvcEntity>>,
32    previous: EventLog<T>,
33    current: EventLog<T>,
34}
35
36/// A shared version of [XvcStore] that we use to pass around for thread safety.
37pub type SharedXStore<T> = Arc<RwLock<XvcStore<T>>>;
38
39impl<T> Deref for XvcStore<T>
40where
41    T: Storable,
42{
43    type Target = BTreeMap<XvcEntity, T>;
44
45    fn deref(&self) -> &Self::Target {
46        &self.map
47    }
48}
49
50impl<T> Index<&XvcEntity> for XvcStore<T>
51where
52    T: Storable,
53{
54    type Output = T;
55
56    fn index(&self, entity: &XvcEntity) -> &Self::Output {
57        self.map.index(entity)
58    }
59}
60
61impl<T> Default for XvcStore<T>
62where
63    T: Storable,
64{
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl<T> XvcStore<T>
71where
72    T: Storable,
73{
74    /// Creates an empty store with empty maps and event logs.
75    pub fn new() -> Self {
76        Self::from_event_logs(EventLog::<T>::new(), EventLog::<T>::new())
77    }
78
79    /// Creates a store from previous and current [EventLog].
80    ///
81    /// This is used when conversions between stores are required.
82    /// When elements are inserted with [XvcStore::insert], they are added to `current` and
83    /// serialized to disk over and over.
84    /// See https://github.com/iesahin/xvc/issues/45
85    ///
86    pub fn from_event_logs(previous: EventLog<T>, current: EventLog<T>) -> Self {
87        let map = Self::build_map(&previous, &current);
88        let entity_index = Self::build_index(&map);
89        Self {
90            map,
91            entity_index,
92            previous,
93            current,
94        }
95    }
96
97    /// Returns all events associated with the entity
98    pub fn all_event_log_for_entity(&self, entity: XvcEntity) -> Result<EventLog<T>> {
99        let mut prev_events = Self::filter_event_log_by_entity(&self.previous, entity)?;
100        let mut current_events = Self::filter_event_log_by_entity(&self.current, entity)?;
101        prev_events.append(&mut current_events);
102        Ok(EventLog::from_events(prev_events))
103    }
104
105    /// Returns (loaded) previous events for the entity
106    ///
107    /// Doesn't return events in the current invocation
108    pub fn previous_event_log_for_entity(&self, entity: XvcEntity) -> Result<EventLog<T>> {
109        Ok(EventLog::from_events(Self::filter_event_log_by_entity(
110            &self.previous,
111            entity,
112        )?))
113    }
114
115    fn filter_event_log_by_entity(event_log: &EventLog<T>, xe: XvcEntity) -> Result<Vec<Event<T>>> {
116        let events = event_log
117            .iter()
118            .filter_map(|e| match e {
119                event @ Event::Add { entity, .. } => {
120                    if *entity == xe {
121                        Some(event.clone())
122                    } else {
123                        None
124                    }
125                }
126                event @ Event::Remove { entity } => {
127                    if *entity == xe {
128                        Some(event.clone())
129                    } else {
130                        None
131                    }
132                }
133            })
134            .collect();
135        Ok(events)
136    }
137
138    /// Inserts an entity into the current event log, the map and the index.
139    ///
140    /// Note that this shouldn't be used in store conversions (from [crate::VStore] or
141    /// [crate::HStore]). This function adds events to `current` set, and these are serialized.
142    /// See https://github.com/iesahin/xvc/issues/45
143    pub fn insert(&mut self, entity: XvcEntity, value: T) -> Option<T> {
144        self.current.push_event(Event::Add {
145            entity,
146            value: value.clone(),
147        });
148
149        match self.entity_index.get_mut(&value) {
150            Some(v) => {
151                v.push(entity);
152            }
153            None => {
154                self.entity_index.insert(value.clone(), vec![entity]);
155            }
156        }
157        self.map.insert(entity, value)
158    }
159
160    /// Updates the data associated with an entity.
161    ///
162    /// This is equivalent to [remove] and [insert], and adds two events to the event log.
163    /// Returns the previous value if there is one.
164    pub fn update(&mut self, entity: XvcEntity, value: T) -> Option<T> {
165        if self.map.contains_key(&entity) {
166            self.remove(entity);
167        }
168        self.insert(entity, value)
169    }
170
171    /// Removes the data associated with entity.
172    ///
173    /// It returns the value if found, otherwise returns `None`.
174    pub fn remove(&mut self, entity: XvcEntity) -> Option<T> {
175        if let Some(value) = self.map.remove(&entity) {
176            if let Some(vec_e) = self.entity_index.get_mut(&value) {
177                // Add a remove event to the current event log
178                self.current.push_event(Event::Remove { entity });
179                vec_e.retain(|e| *e != entity);
180                return Some(value);
181            }
182        }
183        Option::<T>::None
184    }
185
186    fn build_map(previous: &EventLog<T>, current: &EventLog<T>) -> BTreeMap<XvcEntity, T> {
187        let mut map = BTreeMap::<XvcEntity, T>::new();
188
189        for event in previous.iter() {
190            match event {
191                Event::Add { entity, value } => map.insert(*entity, value.clone()),
192                Event::Remove { entity } => map.remove(entity),
193            };
194        }
195
196        for event in current.iter() {
197            match event {
198                Event::Add { entity, value } => map.insert(*entity, value.clone()),
199                Event::Remove { entity } => map.remove(entity),
200            };
201        }
202
203        map
204    }
205
206    fn build_index(map: &BTreeMap<XvcEntity, T>) -> BTreeMap<T, Vec<XvcEntity>> {
207        let mut entity_index = BTreeMap::<T, Vec<XvcEntity>>::new();
208
209        map.iter()
210            .for_each(|(entity, value)| match entity_index.get_mut(value) {
211                Some(v) => {
212                    v.push(*entity);
213                }
214                None => {
215                    entity_index.insert(value.clone(), vec![*entity]);
216                }
217            });
218
219        entity_index
220    }
221
222    /// Loads the timestamp named [EventLog] files from `dir` and replays them to build maps.
223    pub fn from_dir(dir: &Path) -> Result<Self> {
224        let previous = EventLog::<T>::from_dir(dir)?;
225        let current = EventLog::<T>::new();
226        Ok(Self::from_event_logs(previous, current))
227    }
228
229    /// Saves the current [EventLog] to the directory.
230    /// This is enough to reload it to the saved state.
231    pub fn to_dir(&self, dir: &Path) -> Result<()> {
232        self.current.to_dir(dir)
233    }
234
235    /// Return the number of elements
236    pub fn len(&self) -> usize {
237        self.map.len()
238    }
239
240    /// Returns true if the map is empty
241    pub fn is_empty(&self) -> bool {
242        self.map.is_empty()
243    }
244
245    /// A subset of this maps identified by `XvcEntity` elements of the iterator.
246    ///
247    /// This can be used to split the map arbitrarily.
248    pub fn subset<I>(&self, keys: I) -> Result<HStore<T>>
249    where
250        I: Iterator<Item = XvcEntity>,
251    {
252        let mut store = HStore::<T>::new();
253        for e in keys {
254            if let Some(v) = self.map.get(&e) {
255                store.map.insert(e, v.clone());
256            } else {
257                Error::CannotFindKeyInStore { key: e.to_string() }.warn();
258            }
259        }
260        Ok(store)
261    }
262
263    /// Creates a new map by calling the `predicate` with each value.
264    ///
265    /// `predicate` must be a function or closure that returns `bool`.
266    ///
267    /// This returns [HStore] not to create a new event log.
268    pub fn filter<F>(&self, predicate: F) -> HStore<T>
269    where
270        F: Fn(&XvcEntity, &T) -> bool,
271    {
272        let mut s = HashMap::new();
273        for (e, v) in self.map.iter() {
274            if predicate(e, v) {
275                s.insert(*e, v.clone());
276            }
277        }
278
279        HStore::from(s)
280    }
281
282    /// Runs `predicate` for all elements and returns true if one of them is true.
283    ///
284    /// `predicate` must be a function or closure that returns `bool`.
285    pub fn any<F>(&self, predicate: F) -> bool
286    where
287        F: Fn(&XvcEntity, &T) -> bool,
288    {
289        for (e, v) in self.map.iter() {
290            if predicate(e, v) {
291                return true;
292            }
293        }
294        false
295    }
296
297    /// Returns the first element of the map
298    ///
299    /// This is useful when there is only one element after [Self::filter]
300    pub fn first(&self) -> Option<(&XvcEntity, &T)> {
301        self.map.iter().next()
302    }
303
304    /// Returns the entities for a `value`.
305    ///
306    /// There may be more than one entity for a given value, hence it returns a `Vec`.
307    /// It uses internal reverse index for fast lookup.
308    pub fn entities_for(&self, value: &T) -> Option<&Vec<XvcEntity>>
309    where
310        T: PartialEq,
311    {
312        self.entity_index.get(value)
313    }
314
315    /// Returns the first entity matched with [Self::entities_for]
316    pub fn entity_by_value(&self, value: &T) -> Option<XvcEntity> {
317        match self.entities_for(value) {
318            Some(vec_e) => vec_e.first().copied(),
319            None => None,
320        }
321    }
322
323    /// Return the previous (immutable) [EventLog].
324    ///
325    /// The event log contains the [Event] records before the last load.
326    pub fn previous_events(&self) -> &EventLog<T> {
327        &self.previous
328    }
329
330    /// Return the current (mutable) [EventLog].
331    ///
332    /// The event log contains the [Event] records since the last load.
333    pub fn current_events(&self) -> &EventLog<T> {
334        &self.current
335    }
336
337    /// Performs a left join with [XvcEntity] keys.
338    ///
339    /// The returned store contains `(T, Option<U>)` values that correspond to the identical
340    /// `XvcEntity` values.
341    ///
342    /// In SQL terms, this is a LEFT JOIN.
343    ///
344    /// Note that, it may be more convenient to keep this relationship in a [crate::R11Store] relative to your use case.
345    ///
346    /// ```rust
347    ///
348    /// use xvc_ecs::{XvcEntity, XvcStore};
349    ///
350    /// let mut store1 = XvcStore::<String>::new();
351    /// store1.insert(10u128.into(), "John Doe".into());
352    /// store1.insert(12u128.into(), "George Mason".into());
353    /// store1.insert(19u128.into(), "Ali Canfield".into());
354    ///
355    /// let mut store2 = XvcStore::<String>::new();
356    /// store2.insert(10u128.into(), "Carpenter".into());
357    /// store2.insert(17u128.into(), "Developer".into());
358    /// store2.insert(15u128.into(), "Plumber".into());
359    /// store2.insert(19u128.into(), "Artist".into());
360    ///
361    /// let result = store1.left_join(store2);
362    ///
363    /// assert_eq!(result.len(), 3);
364    /// assert_eq!(result[&10u128.into()], ("John Doe".into(), Some("Carpenter".into())));
365    /// assert_eq!(result[&12u128.into()], ("George Mason".into(), None));
366    /// assert_eq!(result[&19u128.into()], ("Ali Canfield".into(), Some("Artist".into())));
367    ///
368    /// ```
369    ///
370    ///
371    pub fn left_join<U>(&self, other: XvcStore<U>) -> XvcStore<(T, Option<U>)>
372    where
373        U: Storable,
374    {
375        let mut joined = XvcStore::<(T, Option<U>)>::new();
376        for (entity, value) in self.map.iter() {
377            joined.insert(*entity, (value.clone(), other.get(entity).cloned()));
378        }
379
380        joined
381    }
382
383    /// Returns an inverted map to get entity values quickly
384    ///
385    /// This uses entity_index with a caveat:
386    /// - Each value of T must be unique.
387    ///
388    /// Otherwise this panics
389    pub fn index_map(&self) -> Result<BTreeMap<T, XvcEntity>> {
390        let mut map = BTreeMap::new();
391
392        for (val, vec_e) in &self.entity_index {
393            map.insert(val.clone(), vec_e[0]);
394        }
395
396        Ok(map)
397    }
398
399    fn store_path(store_root: &Path) -> PathBuf {
400        store_root.join(format!("{}-store", <T as Storable>::type_description()))
401    }
402
403    /// Loads a store from the directory built from `store_root` and the type name.
404    pub fn load_store(store_root: &Path) -> Result<Self> {
405        let dir = Self::store_path(store_root);
406        Self::from_dir(&dir)
407    }
408
409    /// Records the given `store` to the directory built from `store_root` and type description of `T`.
410    pub fn save_store(store: &XvcStore<T>, store_root: &Path) -> Result<()> {
411        let dir = Self::store_path(store_root);
412        store.to_dir(&dir)
413    }
414
415    /// Saves the current store using [save_store] to a directory built from `store_root` and type
416    /// description of `T`.
417    pub fn save(&self, store_root: &Path) -> Result<()> {
418        Self::save_store(self, store_root)
419    }
420}
421
422#[cfg(test)]
423mod test {
424    use tempdir::TempDir;
425
426    use super::*;
427
428    #[test]
429    fn new() -> Result<()> {
430        let mut store = XvcStore::<String>::new();
431        store.insert((0, 123).into(), "0".into());
432        store.insert((1, 123).into(), "1".into());
433        assert_eq!(store.len(), 2);
434
435        assert_eq!(*store.get(&XvcEntity(0, 123)).unwrap(), String::from("0"));
436        assert_eq!(*store.get(&XvcEntity(1, 123)).unwrap(), String::from("1"));
437        Ok(())
438    }
439
440    #[test]
441    fn serde() -> Result<()> {
442        let td = TempDir::new("bstore-test")?;
443        let dir = td.path();
444
445        let mut store = XvcStore::<String>::new();
446
447        store.insert((0, 123).into(), "0".into());
448        store.insert((1, 123).into(), "1".into());
449        store.insert((2, 123).into(), "2".into());
450
451        store.to_dir(dir)?;
452
453        let reincarnation = XvcStore::<String>::from_dir(dir)?;
454
455        assert!(store.len() == reincarnation.len());
456        assert!(store.current_events().len() == reincarnation.previous_events().len());
457
458        assert!(reincarnation.current_events().is_empty());
459
460        let n_files_before = jwalk::WalkDir::new(dir).into_iter().count();
461        reincarnation.to_dir(dir)?;
462        let n_files_after = jwalk::WalkDir::new(dir).into_iter().count();
463
464        assert!(
465            n_files_before == n_files_after,
466            "before: {n_files_before} after: {n_files_after}"
467        );
468
469        Ok(())
470    }
471}