pub mod error;
pub use hecs::{With, Without};
use crate::error::QmiError;
use hecs::{
Bundle, Component, DynamicBundle, Entity, EntityRef, Query, QueryBorrow, QueryMut,
World as HecsWorld,
};
use persy::{ByteVec, Config as PersyConfig, Persy};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::{any::type_name, collections::HashMap, path::Path};
#[derive(Serialize, Deserialize, Default)]
struct EntityRecord {
components: HashMap<String, Vec<u8>>,
}
pub struct Qmi {
pub(crate) world: HecsWorld,
db: Persy,
}
impl Qmi {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, QmiError> {
let world = HecsWorld::new();
let db = Persy::open_or_create_with(path, PersyConfig::new(), |_| Ok(()))?;
let mut tx = db.begin()?;
if !db.exists_index("entity_records")? {
tx.create_index::<u64, ByteVec>("entity_records", persy::ValueMode::Replace)?;
}
tx.prepare()?.commit()?;
Ok(Self { world, db })
}
pub fn spawn(&mut self, components: impl DynamicBundle) -> Entity {
self.world.spawn(components)
}
pub fn despawn(&mut self, entity: Entity) -> Result<(), QmiError> {
self.world.despawn(entity)?;
Ok(())
}
pub fn insert(
&mut self,
entity: Entity,
components: impl DynamicBundle,
) -> Result<(), QmiError> {
self.world.insert(entity, components)?;
Ok(())
}
pub fn remove<C: Bundle + 'static>(&mut self, entity: Entity) -> Result<(), QmiError> {
self.world.remove::<C>(entity)?;
Ok(())
}
pub fn query<Q: Query>(&self) -> QueryBorrow<'_, Q> {
self.world.query::<Q>()
}
pub fn query_mut<Q: Query>(&mut self) -> QueryMut<'_, Q> {
self.world.query_mut::<Q>()
}
pub fn entity_count(&self) -> u32 {
self.world.len()
}
pub fn entities(&self) -> impl Iterator<Item = EntityRef<'_>> {
self.world.iter()
}
pub fn save<C: Component + Serialize>(&self) -> Result<(), QmiError> {
let type_key = type_name::<C>().to_string();
let index_name = "entity_records";
let mut tx = self.db.begin()?;
if !self.db.exists_index(index_name)? {
tx.create_index::<u64, ByteVec>(index_name, persy::ValueMode::Replace)?;
}
for (entity, component) in self.world.query::<(Entity, &C)>().iter() {
let key = entity.to_bits().get();
let comp_bytes = postcard::to_allocvec(component).unwrap();
let mut record: EntityRecord = self
.db
.get::<u64, ByteVec>(index_name, &key)
.unwrap()
.next()
.map(|bytes| postcard::from_bytes(bytes.as_ref()))
.transpose()
.unwrap()
.unwrap_or_default();
record.components.insert(type_key.clone(), comp_bytes);
let record_bytes = postcard::to_allocvec(&record).unwrap();
tx.put(index_name, key, ByteVec::from(record_bytes))?;
}
tx.prepare()?.commit()?;
Ok(())
}
pub fn load<C: Component + DeserializeOwned>(&mut self) -> Result<(), QmiError> {
let type_key = type_name::<C>();
let index_name = "entity_records";
if !self.db.exists_index(index_name)? {
return Err(QmiError::DbNoSuchIndex);
}
for (key, val_iter) in self.db.range::<u64, ByteVec, _>(index_name, ..).unwrap() {
let entity = match Entity::from_bits(key) {
Some(e) => e,
None => continue,
};
for byte_vec in val_iter {
let record: EntityRecord = match postcard::from_bytes(byte_vec.as_ref()) {
Ok(rec) => rec,
Err(_) => continue,
};
if let Some(payload) = record.components.get(type_key) {
let component: C = postcard::from_bytes(payload).unwrap();
if self.world.contains(entity) {
let _ = self.world.insert_one(entity, component);
} else {
self.world.spawn_at(entity, (component,));
}
}
}
}
Ok(())
}
}