1#![warn(missing_docs)]
9#![forbid(unsafe_code)]
10pub mod event;
11pub mod hstore;
12pub mod r11store;
13pub mod r1nstore;
14pub mod rmnstore;
15pub mod storable;
16pub mod vstore;
17pub mod xvcstore;
18
19use rand::{rngs, RngCore, SeedableRng};
20use std::fmt;
21use std::fs;
22use std::path::Path;
23use std::path::PathBuf;
24use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
25use std::sync::Once;
26use std::time::SystemTime;
27use std::time::UNIX_EPOCH;
28
29use serde::{Deserialize, Serialize};
30
31use crate::error::{Error as XvcError, Result};
32
33#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
44pub struct XvcEntity(u64, u64);
45
46impl fmt::Display for XvcEntity {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 write!(f, "({}, {})", self.0, self.1)
49 }
50}
51
52impl From<(u64, u64)> for XvcEntity {
53 fn from(e: (u64, u64)) -> Self {
54 Self(e.0, e.1)
55 }
56}
57
58impl From<u128> for XvcEntity {
59 fn from(e: u128) -> Self {
60 Self((e >> 64) as u64, e as u64)
61 }
62}
63
64impl From<XvcEntity> for u128 {
65 fn from(e: XvcEntity) -> u128 {
66 ((e.0 as u128) << 64) | (e.1 as u128)
67 }
68}
69
70impl From<XvcEntity> for (u64, u64) {
71 fn from(e: XvcEntity) -> (u64, u64) {
72 (e.0, e.1)
73 }
74}
75
76#[derive(Debug)]
84pub struct XvcEntityGenerator {
85 counter: AtomicU64,
87 random: u64,
90 dirty: AtomicBool,
92}
93
94static INIT: Once = Once::new();
95
96pub fn load_generator(dir: &Path) -> Result<XvcEntityGenerator> {
103 let mut gen: Result<XvcEntityGenerator> = Err(XvcError::CanInitializeOnlyOnce {
104 object: "XvcEntityGenerator".to_string(),
105 });
106 INIT.call_once(|| gen = XvcEntityGenerator::load(dir));
107 gen
108}
109
110pub fn init_generator() -> Result<XvcEntityGenerator> {
115 let mut gen: Result<XvcEntityGenerator> = Err(XvcError::CanInitializeOnlyOnce {
116 object: "XvcEntityGenerator".to_string(),
117 });
118
119 INIT.call_once(|| gen = Ok(XvcEntityGenerator::new(1)));
120 gen
121}
122
123impl Iterator for XvcEntityGenerator {
124 type Item = XvcEntity;
125
126 fn next(&mut self) -> Option<Self::Item> {
127 Some(self.next_element())
128 }
129}
130
131impl XvcEntityGenerator {
132 fn new(start: u64) -> XvcEntityGenerator {
133 let counter = AtomicU64::new(start);
134 let mut rng = rngs::StdRng::from_entropy();
135 let init_random = rng.next_u64();
136 let dirty = AtomicBool::new(true);
139 Self {
140 dirty,
141 counter,
142 random: init_random,
143 }
144 }
145
146 pub fn next_element(&self) -> XvcEntity {
148 self.dirty.store(true, Ordering::SeqCst);
149 XvcEntity(self.counter.fetch_add(1, Ordering::SeqCst), self.random)
150 }
151
152 fn load(dir: &Path) -> Result<XvcEntityGenerator> {
153 let path = most_recent_file(dir)?;
154 match path {
155 Some(path) => {
156 let current_val = fs::read_to_string(path)?.parse::<u64>()?;
157 let counter = AtomicU64::new(current_val);
159 let mut rng = rngs::StdRng::from_entropy();
160 let init_random = rng.next_u64();
161 let dirty = AtomicBool::new(false);
164 Ok(Self {
165 dirty,
166 counter,
167 random: init_random,
168 })
169 }
170 None => Err(XvcError::CannotRestoreEntityCounter {
171 path: dir.as_os_str().to_owned(),
172 }),
173 }
174 }
175
176 pub fn save(&self, dir: &Path) -> Result<()> {
180 if self.dirty.load(Ordering::SeqCst) {
181 if !dir.exists() {
182 fs::create_dir_all(dir)?;
183 }
184 let path = dir.join(timestamp());
185 fs::write(path, format!("{}", self.counter.load(Ordering::SeqCst)))?;
186 self.dirty.store(false, Ordering::SeqCst);
188 }
189 Ok(())
190 }
191}
192
193pub fn timestamp() -> String {
196 let now = SystemTime::now();
197 let since = now
198 .duration_since(UNIX_EPOCH)
199 .expect("Time went backwards!");
200 format!("{}", since.as_micros())
201}
202
203pub fn sorted_files(dir: &Path) -> Result<Vec<PathBuf>> {
208 if dir.exists() {
209 let mut files: Vec<PathBuf> = fs::read_dir(dir)?
210 .filter_map(|e| match e {
211 Ok(e) => Some(e.path()),
212 Err(_) => None,
213 })
214 .collect();
215
216 files.sort_unstable();
217 Ok(files)
218 } else {
219 fs::create_dir_all(dir)?;
220 Ok(vec![])
221 }
222}
223
224pub fn most_recent_file(dir: &Path) -> Result<Option<PathBuf>> {
228 if !dir.exists() {
229 return Ok(None);
230 }
231
232 let files = sorted_files(dir)?;
233
234 if files.is_empty() {
235 Ok(None)
236 } else {
237 Ok(files.last().cloned())
238 }
239}
240
241#[macro_export]
242macro_rules! persist {
257 ( $t:ty, $desc:literal ) => {
258 impl $crate::Storable for $t {
259 fn type_description() -> String {
260 $desc.to_string()
261 }
262 }
263 };
264}
265
266#[cfg(test)]
267mod tests {
268
269 use std::{thread::sleep, time::Duration};
270
271 use super::*;
272 use log::LevelFilter;
273 use rand;
274 use tempdir::TempDir;
275 use xvc_logging::setup_logging;
276
277 #[test]
278 fn test_init() -> Result<()> {
279 let gen = init_generator()?;
280 assert_eq!(gen.counter.load(Ordering::SeqCst), 1);
281 assert_eq!(gen.next_element().0, 1);
282 assert_eq!(gen.next_element().0, 2);
283 let gen2 = init_generator();
284 assert!(matches!(gen2, Err(XvcError::CanInitializeOnlyOnce { .. })));
285 Ok(())
286 }
287
288 #[test]
289 fn test_load() -> Result<()> {
290 setup_logging(Some(LevelFilter::Trace), None);
291 let tempdir = TempDir::new("test-xvc-ecs")?;
292 let gen_dir = tempdir.path().join("entity-gen");
293 fs::create_dir_all(&gen_dir)?;
294 let r: u64 = rand::random();
295 let gen_file_1 = gen_dir.join(timestamp());
296 fs::write(gen_file_1, format!("{}", r))?;
297 sleep(Duration::from_millis(1));
298 let gen_file_2 = gen_dir.join(timestamp());
299 fs::write(gen_file_2, format!("{}", r + 1000))?;
300 sleep(Duration::from_millis(1));
301 let gen_file_3 = gen_dir.join(timestamp());
302 fs::write(gen_file_3, format!("{}", r + 2000))?;
303 let gen = XvcEntityGenerator::load(&gen_dir)?;
304 assert_eq!(gen.counter.load(Ordering::SeqCst), r + 2000);
305 assert_eq!(gen.next_element().0, (r + 2000));
306 assert_eq!(gen.next_element().0, (r + 2001));
307 assert_eq!(gen.next_element().0, (r + 2002));
308 gen.save(&gen_dir)?;
309 let new_val = fs::read_to_string(most_recent_file(&gen_dir)?.unwrap())?.parse::<u64>()?;
310 assert_eq!(new_val, r + 2003);
311 Ok(())
312 }
313
314 #[test]
317 fn test_multi_save() -> Result<()> {
318 setup_logging(Some(LevelFilter::Trace), None);
319 let tempdir = TempDir::new("test-xvc-ecs")?;
320 let gen_dir = tempdir.path().join("entity-gen");
321 fs::create_dir_all(&gen_dir)?;
322 let gen = XvcEntityGenerator::new(10);
324 gen.save(&gen_dir)?;
325 assert!(sorted_files(&gen_dir)?.len() == 1);
327 gen.save(&gen_dir)?;
329 assert!(sorted_files(&gen_dir)?.len() == 1);
330 let _e = gen.next_element();
332 gen.save(&gen_dir)?;
333 assert!(sorted_files(&gen_dir)?.len() == 2);
334
335 let gen2 = XvcEntityGenerator::load(&gen_dir)?;
336 gen.save(&gen_dir)?;
338 assert!(sorted_files(&gen_dir)?.len() == 2);
339 let _e = gen2.next_element();
341 gen2.save(&gen_dir)?;
342 assert!(sorted_files(&gen_dir)?.len() == 3);
343 gen2.save(&gen_dir)?;
345 gen2.save(&gen_dir)?;
346 gen2.save(&gen_dir)?;
347 gen2.save(&gen_dir)?;
348 assert!(sorted_files(&gen_dir)?.len() == 3);
349
350 Ok(())
351 }
352
353 #[test]
354 fn test_from_to() -> Result<()> {
355 let e1 = XvcEntity(1, 2);
356 let u1: u128 = e1.into();
357 let e2 = XvcEntity::from(u1);
358 assert_eq!(e1, e2);
359 Ok(())
360 }
361}