qmi 0.1.1

An ECS with too much macro usage
Documentation
#![allow(clippy::new_without_default)]

use anyhow::Result;
use serde::{Deserialize, Serialize};

use crate::storage::{ComponentPool, ComponentStorage};
use std::{collections::HashMap, path::Path};

mod query;
mod storage;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct EntityID(pub u32);

pub struct QMI {
    next_entity_id: EntityID,
    component_storage: HashMap<String, Box<dyn ComponentStorage>>,
    db: persy::Persy,
}

impl QMI {
    pub fn create_or_open<P: AsRef<Path>>(path: P) -> Self {
        let path_ref = path.as_ref();
        let db_result = {
            if !path_ref.exists() {
                persy::Persy::create(path_ref).expect("Could not create QMI db file at path.");
            }
            persy::Persy::open(path_ref, persy::Config::default())
        };
        let db = match db_result {
            Ok(opened_db) => opened_db,
            Err(_) => {
                eprintln!("Database file structure is unreadable. Recreating...");
                let _ = std::fs::remove_file(path_ref);
                persy::Persy::create(path_ref).expect("Could not recreate QMI db file at path.");
                persy::Persy::open(path_ref, persy::Config::default())
                    .expect("Could not open QMI db file at path.")
            }
        };
        let qmi = Self {
            component_storage: HashMap::new(),
            next_entity_id: EntityID(0),
            db,
        };
        if let Err(e) = qmi.ensure_topology() {
            panic!("Critical database topology initialization failure: {:?}", e);
        }
        qmi
    }

    fn ensure_topology(&self) -> anyhow::Result<()> {
        let mut tx = self.db.begin()?;
        let result = (|| -> anyhow::Result<bool> {
            let mut changes = false;
            if !tx.exists_segment("qmi_data")? {
                tx.create_segment("qmi_data")?;
                changes = true;
            }
            if !tx.exists_index("qmi_index")? {
                tx.create_index::<String, persy::PersyId>("qmi_index", persy::ValueMode::Replace)?;
                changes = true;
            }
            Ok(changes)
        })();
        match result {
            Ok(changes) => {
                if changes {
                    tx.prepare()?.commit()?;
                }
            }
            Err(e) => {
                tx.rollback()?;
                return Err(e);
            }
        }
        Ok(())
    }

    pub fn spawn(&mut self) -> EntityID {
        let id = self.next_entity_id;
        self.next_entity_id.0 += 1;
        id
    }

    pub fn insert<T>(&mut self, entity: EntityID, component: T)
    where
        T: 'static + Serialize,
    {
        let type_name = std::any::type_name::<T>();
        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: HashMap::new(),
                }),
            );
        }
        let pool = self
            .component_storage
            .get_mut(type_name)
            .unwrap()
            .as_any_mut()
            .downcast_mut::<ComponentPool<T>>()
            .unwrap();
        if let Some(&idx) = pool.sparse_map.get(&entity) {
            pool.dense_data[idx] = component;
            return;
        }
        let idx = pool.dense_data.len();
        pool.sparse_map.insert(entity, idx);
        pool.dense_data.push(component);
        pool.dense_entities.push(entity);
    }

    fn query_single<T: 'static>(&self) -> impl Iterator<Item = (EntityID, &T)> {
        self.component_storage
            .get(std::any::type_name::<T>())
            .expect("Query failed. ComponentPool does not exist")
            .as_any()
            .downcast_ref::<ComponentPool<T>>()
            .unwrap()
            .iter_entities()
    }
    fn query_mut_single<T: 'static>(&mut self) -> impl Iterator<Item = (EntityID, &mut T)> {
        self.component_storage
            .get_mut(std::any::type_name::<T>())
            .expect("Mutable query failed. ComponentPool does not exist")
            .as_any_mut()
            .downcast_mut::<ComponentPool<T>>()
            .unwrap()
            .iter_entities_mut()
    }
    pub fn save(&mut self) -> Result<()> {
        let mut db_tx = self.db.begin()?;
        let next_entity_id_bytes = postcard::to_allocvec(&self.next_entity_id)?;
        let next_entity_id_key = "next_entity_id".to_string();
        if let Some(persy_id) = db_tx
            .get::<String, persy::PersyId>("qmi_index", &next_entity_id_key)?
            .next()
        {
            db_tx.update("qmi_data", &persy_id, &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 storage_pool in self.component_storage.values() {
            let pool_bytes = storage_pool.serialize()?;
            let type_key = storage_pool.type_name().to_string();
            if let Some(persy_id) = db_tx.get("qmi_index", &type_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", type_key, persy_id)?;
            }
        }
        db_tx.prepare()?.commit()?;
        Ok(())
    }
}