#![warn(missing_docs)]
#![forbid(unsafe_code)]
pub mod event;
pub mod hstore;
pub mod r11store;
pub mod r1nstore;
pub mod rmnstore;
pub mod storable;
pub mod vstore;
pub mod xvcstore;
use rand::{rngs, RngCore, SeedableRng};
use std::fmt;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Once;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use serde::{Deserialize, Serialize};
use crate::error::{Error as XvcError, Result};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
pub struct XvcEntity(u64, u64);
impl fmt::Display for XvcEntity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.0, self.1)
}
}
impl From<(u64, u64)> for XvcEntity {
fn from(e: (u64, u64)) -> Self {
Self(e.0, e.1)
}
}
impl From<u128> for XvcEntity {
fn from(e: u128) -> Self {
Self((e >> 64) as u64, e as u64)
}
}
impl From<XvcEntity> for u128 {
fn from(e: XvcEntity) -> u128 {
((e.0 as u128) << 64) | (e.1 as u128)
}
}
impl From<XvcEntity> for (u64, u64) {
fn from(e: XvcEntity) -> (u64, u64) {
(e.0, e.1)
}
}
#[derive(Debug)]
pub struct XvcEntityGenerator {
counter: AtomicU64,
random: u64,
dirty: AtomicBool,
}
static INIT: Once = Once::new();
pub fn load_generator(dir: &Path) -> Result<XvcEntityGenerator> {
let mut generator: Result<XvcEntityGenerator> = Err(XvcError::CanInitializeOnlyOnce {
object: "XvcEntityGenerator".to_string(),
});
INIT.call_once(|| generator = XvcEntityGenerator::load(dir));
generator
}
pub fn init_generator() -> Result<XvcEntityGenerator> {
let mut generator: Result<XvcEntityGenerator> = Err(XvcError::CanInitializeOnlyOnce {
object: "XvcEntityGenerator".to_string(),
});
INIT.call_once(|| generator = Ok(XvcEntityGenerator::new(1)));
generator
}
impl Iterator for XvcEntityGenerator {
type Item = XvcEntity;
fn next(&mut self) -> Option<Self::Item> {
Some(self.next_element())
}
}
impl XvcEntityGenerator {
fn new(start: u64) -> XvcEntityGenerator {
let counter = AtomicU64::new(start);
let mut rng = rngs::StdRng::from_os_rng();
let init_random = rng.next_u64();
let dirty = AtomicBool::new(true);
Self {
dirty,
counter,
random: init_random,
}
}
pub fn next_element(&self) -> XvcEntity {
self.dirty.store(true, Ordering::SeqCst);
XvcEntity(self.counter.fetch_add(1, Ordering::SeqCst), self.random)
}
fn load(dir: &Path) -> Result<XvcEntityGenerator> {
let path = most_recent_file(dir)?;
match path {
Some(path) => {
let current_val = fs::read_to_string(path)?.parse::<u64>()?;
let counter = AtomicU64::new(current_val);
let mut rng = rngs::StdRng::from_os_rng();
let init_random = rng.next_u64();
let dirty = AtomicBool::new(false);
Ok(Self {
dirty,
counter,
random: init_random,
})
}
None => Err(XvcError::CannotRestoreEntityCounter {
path: dir.as_os_str().to_owned(),
}),
}
}
pub fn save(&self, dir: &Path) -> Result<()> {
if self.dirty.load(Ordering::SeqCst) {
if !dir.exists() {
fs::create_dir_all(dir)?;
}
let path = dir.join(timestamp());
fs::write(path, format!("{}", self.counter.load(Ordering::SeqCst)))?;
self.dirty.store(false, Ordering::SeqCst);
}
Ok(())
}
}
pub fn timestamp() -> String {
let now = SystemTime::now();
let since = now
.duration_since(UNIX_EPOCH)
.expect("Time went backwards!");
format!("{}", since.as_micros())
}
pub fn sorted_files(dir: &Path) -> Result<Vec<PathBuf>> {
if dir.exists() {
let mut files: Vec<PathBuf> = fs::read_dir(dir)?
.filter_map(|e| match e {
Ok(e) => Some(e.path()),
Err(_) => None,
})
.collect();
files.sort_unstable();
Ok(files)
} else {
fs::create_dir_all(dir)?;
Ok(vec![])
}
}
pub fn most_recent_file(dir: &Path) -> Result<Option<PathBuf>> {
if !dir.exists() {
return Ok(None);
}
let files = sorted_files(dir)?;
if files.is_empty() {
Ok(None)
} else {
Ok(files.last().cloned())
}
}
#[macro_export]
macro_rules! persist {
( $t:ty, $desc:literal ) => {
impl $crate::Storable for $t {
fn type_description() -> String {
$desc.to_string()
}
}
};
}
#[cfg(test)]
mod tests {
use std::{thread::sleep, time::Duration};
use super::*;
use log::LevelFilter;
use rand;
use tempdir::TempDir;
use xvc_logging::setup_logging;
#[test]
fn test_init() -> Result<()> {
let g = init_generator()?;
assert_eq!(g.counter.load(Ordering::SeqCst), 1);
assert_eq!(g.next_element().0, 1);
assert_eq!(g.next_element().0, 2);
let gen2 = init_generator();
assert!(matches!(gen2, Err(XvcError::CanInitializeOnlyOnce { .. })));
Ok(())
}
#[test]
fn test_load() -> Result<()> {
setup_logging(Some(LevelFilter::Trace), None);
let tempdir = TempDir::new("test-xvc-ecs")?;
let gen_dir = tempdir.path().join("entity-gen");
fs::create_dir_all(&gen_dir)?;
let r: u64 = rand::random();
let gen_file_1 = gen_dir.join(timestamp());
fs::write(gen_file_1, format!("{}", r))?;
sleep(Duration::from_millis(1));
let gen_file_2 = gen_dir.join(timestamp());
fs::write(gen_file_2, format!("{}", r + 1000))?;
sleep(Duration::from_millis(1));
let gen_file_3 = gen_dir.join(timestamp());
fs::write(gen_file_3, format!("{}", r + 2000))?;
let g = XvcEntityGenerator::load(&gen_dir)?;
assert_eq!(g.counter.load(Ordering::SeqCst), r + 2000);
assert_eq!(g.next_element().0, (r + 2000));
assert_eq!(g.next_element().0, (r + 2001));
assert_eq!(g.next_element().0, (r + 2002));
g.save(&gen_dir)?;
let new_val = fs::read_to_string(most_recent_file(&gen_dir)?.unwrap())?.parse::<u64>()?;
assert_eq!(new_val, r + 2003);
Ok(())
}
#[test]
fn test_multi_save() -> Result<()> {
setup_logging(Some(LevelFilter::Trace), None);
let tempdir = TempDir::new("test-xvc-ecs")?;
let gen_dir = tempdir.path().join("entity-gen");
fs::create_dir_all(&gen_dir)?;
let generator = XvcEntityGenerator::new(10);
generator.save(&gen_dir)?;
assert!(sorted_files(&gen_dir)?.len() == 1);
generator.save(&gen_dir)?;
assert!(sorted_files(&gen_dir)?.len() == 1);
let _e = generator.next_element();
generator.save(&gen_dir)?;
assert!(sorted_files(&gen_dir)?.len() == 2);
let gen2 = XvcEntityGenerator::load(&gen_dir)?;
generator.save(&gen_dir)?;
assert!(sorted_files(&gen_dir)?.len() == 2);
let _e = gen2.next_element();
gen2.save(&gen_dir)?;
assert!(sorted_files(&gen_dir)?.len() == 3);
gen2.save(&gen_dir)?;
gen2.save(&gen_dir)?;
gen2.save(&gen_dir)?;
gen2.save(&gen_dir)?;
assert!(sorted_files(&gen_dir)?.len() == 3);
Ok(())
}
#[test]
fn test_from_to() -> Result<()> {
let e1 = XvcEntity(1, 2);
let u1: u128 = e1.into();
let e2 = XvcEntity::from(u1);
assert_eq!(e1, e2);
Ok(())
}
}