#![allow(clippy::new_without_default)]
use anyhow::Result;
use persy::PersyId;
use serde::{Deserialize, Serialize};
use crate::pool::{ComponentPool, ComponentStorage, PageMap};
use std::{collections::HashMap, fs::remove_file, path::Path};
pub mod persistence;
pub mod pool;
pub mod query;
#[derive(Clone, Serialize, Deserialize, Debug)]
struct Entity;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct EntityID(pub u32);
pub struct QMI {
next_entity_id: EntityID,
pub component_storage: HashMap<String, Box<dyn ComponentStorage>>,
db: persy::Persy,
}
impl QMI {
pub fn open<P: AsRef<Path>>(path: P, new: bool) -> Result<Self> {
let path_ref = path.as_ref();
if new {
let _ = remove_file(path_ref);
}
let path_exists = path_ref.exists();
let db = {
if !path_exists {
persy::Persy::create(path_ref)?;
}
persy::Persy::open(path_ref, persy::Config::default())?
};
let mut db_tx = db.begin()?;
if !db.exists_index("qmi_index")? {
db_tx.create_index::<String, PersyId>("qmi_index", persy::ValueMode::Replace)?;
}
if !db.exists_segment("qmi_data")? {
db_tx.create_segment("qmi_data")?;
}
let next_entity_id_key = "next_entity_id".to_string();
let next_entity_pid = db_tx
.get::<String, persy::PersyId>("qmi_index", &next_entity_id_key)?
.next();
let mut next_entity_id = EntityID(0);
if let Some(pid) = next_entity_pid {
let bytes = db_tx.read("qmi_data", &pid)?.unwrap();
next_entity_id = EntityID(u32::from_le_bytes(bytes.as_slice().try_into()?));
}
db_tx.commit()?;
Ok(Self {
db,
next_entity_id,
component_storage: HashMap::new(),
})
}
pub fn spawn(&mut self) -> EntityID {
let id = self.next_entity_id;
self.next_entity_id.0 += 1;
self.insert(id, Entity);
id
}
pub fn insert<T>(&mut self, entity: EntityID, component: T)
where
T: 'static + Serialize + for<'de> Deserialize<'de>,
{
let type_name = std::any::type_name::<T>().rsplit("::").next().unwrap();
if !self.component_storage.contains_key(type_name) {
self.component_storage.insert(
type_name.to_string(),
Box::new(ComponentPool::<T> {
dense_data: Vec::new(),
dense_entities: Vec::new(),
sparse_map: PageMap::new(),
}),
);
}
let pool = self
.component_storage
.get_mut(type_name)
.unwrap()
.as_any_mut()
.downcast_mut::<ComponentPool<T>>()
.unwrap();
if let Some(id) = pool.sparse_map.get(entity.0 as usize) {
pool.dense_data[id] = component;
return;
}
let id = pool.dense_data.len();
pool.sparse_map.insert(entity.0 as usize, id);
pool.dense_data.push(component);
pool.dense_entities.push(entity);
}
pub fn save(&mut self) -> Result<()> {
let mut db_tx = self.db.begin()?;
let next_entity_id_bytes = self.next_entity_id.0.to_le_bytes();
let next_entity_id_key = "next_entity_id".to_string();
if let Some(pid) = db_tx
.get::<String, persy::PersyId>("qmi_index", &next_entity_id_key)?
.next()
{
db_tx.update("qmi_data", &pid, &next_entity_id_bytes)?;
} else {
let persy_id = db_tx.insert("qmi_data", &next_entity_id_bytes)?;
db_tx.put("qmi_index", next_entity_id_key, persy_id)?;
}
for (type_name, storage) in &self.component_storage {
let pool_bytes = storage.serialize()?;
let index_key = type_name.rsplit("::").next().unwrap().to_string();
if let Some(persy_id) = db_tx
.get::<String, persy::PersyId>("qmi_index", &index_key)?
.next()
{
db_tx.update("qmi_data", &persy_id, &pool_bytes)?;
} else {
let persy_id = db_tx.insert("qmi_data", &pool_bytes)?;
db_tx.put("qmi_index", index_key, persy_id)?;
}
}
db_tx.prepare()?.commit()?;
Ok(())
}
pub fn load<T>(&mut self) -> Result<(), Box<dyn std::error::Error>>
where
T: 'static + serde::Serialize + for<'de> serde::Deserialize<'de>,
{
let mut db_tx = self.db.begin()?;
let type_name = std::any::type_name::<T>().to_string();
let index_key = type_name.rsplit("::").next().unwrap().to_string();
if let Some(persy_id) = db_tx
.get::<String, persy::PersyId>("qmi_index", &index_key)?
.next()
{
let bytes = db_tx
.read("qmi_data", &persy_id)?
.expect("Index pointed to a missing component data record.");
let reconstructed_pool = ComponentPool::<T>::deserialize(&bytes)
.unwrap_or_else(|e| panic!("Failed to deserialize pool {}: {}", type_name, e));
self.component_storage
.insert(index_key, Box::new(reconstructed_pool));
} else {
let fresh_pool = ComponentPool::<T> {
dense_data: Vec::new(),
dense_entities: Vec::new(),
sparse_map: PageMap::new(),
};
self.component_storage
.insert(index_key, Box::new(fresh_pool));
}
db_tx.prepare()?.commit()?;
Ok(())
}
}