xvc_ecs/ecs/
event.rs

1//! Records a [store][crate::XvcStore] event.
2//! It's used for journaling the operations.
3//! Journaling is used to keep separate files for operations and replay, merge on start.
4use std::{fs, io, path::Path};
5
6use crate::error::{Error, Result};
7use serde::{Deserialize, Serialize};
8use serde_json::Value as JsonValue;
9use std::fmt::Debug;
10
11use crate::XvcEntity;
12
13use super::{sorted_files, timestamp};
14
15/// Records add and remove operations of a serializable component `T`.
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
17#[serde(bound = "T: Serialize, for<'lt> T: Deserialize<'lt>")]
18pub enum Event<T>
19where
20    T: Serialize + for<'lt> Deserialize<'lt> + Clone + Debug,
21{
22    /// Add operation is used to record a component `T` to an [`XvcStore<T>`]
23    Add {
24        /// The unique key
25        entity: XvcEntity,
26        /// The serializable component
27        value: T,
28    },
29    /// Remove operation is used when a value is deleted or updated.
30    /// In an update, a subsequent [Event::Add] is added.
31    Remove {
32        /// The key for the component.
33        /// Unlike [Event::Add] this doesn't need `T`.
34        entity: XvcEntity,
35    },
36}
37
38/// A series of [Event] values.
39#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
40#[serde(bound = "T: Serialize, for<'lt> T: Deserialize<'lt>")]
41pub struct EventLog<T>(Vec<Event<T>>)
42where
43    T: Serialize + for<'lt> Deserialize<'lt> + Clone + Debug;
44
45impl<T> Default for EventLog<T>
46where
47    T: Serialize + for<'lt> Deserialize<'lt> + Clone + Debug,
48{
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl<T> EventLog<T>
55where
56    T: Serialize + for<'lt> Deserialize<'lt> + Clone + Debug,
57{
58    /// Create an empty event log
59    pub fn new() -> Self {
60        Self(Vec::new())
61    }
62
63    /// Create an event log already initialized
64    pub fn from_events(events: Vec<Event<T>>) -> Self {
65        Self(events)
66    }
67
68    /// Convert the log to a Json array containing ([XvcEntity], `T`) values.
69    /// This is used to record [XvcStore] to files.
70    pub fn to_json(&self) -> Result<JsonValue> {
71        serde_json::to_value(&self.0).map_err(|e| Error::JsonError { source: e }.warn())
72    }
73
74    /// Load the logs from a Json file.
75    pub fn from_json(json_str: &str) -> Result<Self> {
76        serde_json::from_str(json_str).map_err(|e| Error::JsonError { source: e }.warn())
77    }
78
79    /// Loads the event log from a Json file.
80    pub fn from_file(path: &Path) -> Result<Self> {
81        match fs::read_to_string(path) {
82            Ok(contents) => Self::from_json(&contents),
83            Err(err) => Err(Error::IoError { source: err }),
84        }
85    }
86
87    /// Records the event log to a Json file.
88    pub fn to_file(&self, path: &Path) -> Result<()> {
89        let json_str = self.to_json()?.to_string();
90        fs::write(path, json_str).map_err(|source| Error::IoError { source })
91    }
92
93    /// Loads a set of Json files from a directory after sorting them.
94    /// File contents are merged in a single event log.
95    pub fn from_dir(dir: &Path) -> Result<Self> {
96        let files = sorted_files(dir)?;
97        let merged = files
98            .iter()
99            .map(|f| {
100                Self::from_file(f)
101                    .unwrap_or_else(|_| panic!("Error reading event log: {}", f.to_string_lossy()))
102            })
103            .fold(Self::new(), |mut merged, new| {
104                merged.0.extend(new.0);
105                merged
106            });
107        Ok(merged)
108    }
109
110    /// Records an event log to a single file in the given directory.
111    /// The file name uses [timestamp] to make this file as the last file in a sorted list.
112    pub fn to_dir(&self, dir: &Path) -> Result<()> {
113        if !self.is_empty() {
114            if !dir.exists() {
115                fs::create_dir_all(dir)?;
116            }
117            let path = dir.join(format!("{}.json", timestamp()));
118            let json_str = self.to_json()?.to_string();
119            fs::write(path, json_str).map_err(|source| Error::IoError { source })
120        } else {
121            Ok(())
122        }
123    }
124
125    /// Converts the event log to a [MessagePack](https://msgpack.org/index.html) list
126    pub fn to_msgpack(&self) -> Result<Vec<u8>> {
127        let mut value = Vec::new();
128        match self.serialize(&mut rmp_serde::Serializer::new(&mut value)) {
129            Ok(_) => Ok(value),
130            Err(source) => Err(Error::MsgPackEncodeError { source }.warn()),
131        }
132    }
133
134    /// Converts the event log from a [MessagePack](https://msgpack.org/index.html) list
135    pub fn from_msgpack(msgpack_val: &[u8]) -> Result<Self> {
136        let cursor = io::Cursor::new(msgpack_val);
137        let mut deser = rmp_serde::decode::Deserializer::new(cursor);
138        let val = Deserialize::deserialize(&mut deser);
139        match val {
140            Ok(md) => Ok(md),
141            Err(source) => Err(Error::MsgPackDecodeError { source }.warn()),
142        }
143    }
144
145    /// Adds an [Event] to the log, either an [Event::Add] or an [Event::Remove].
146    pub fn push_event(&mut self, event: Event<T>) {
147        self.0.push(event)
148    }
149}
150
151use std::ops::Deref;
152
153impl<T> Deref for EventLog<T>
154where
155    T: Serialize + for<'lt> Deserialize<'lt> + Clone + Debug,
156{
157    type Target = Vec<Event<T>>;
158
159    fn deref(&self) -> &Self::Target {
160        &self.0
161    }
162}