qmi 0.3.1

An extension of HECS that adds easy entity saving using Persy.
Documentation
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(())
    }
}