furmint-registry 0.1.0

Registry abstractions for `furmint`
Documentation
//! # Component registry
//! This crate provides required trait, struct and a macro to store and decode components from scenes
#![warn(missing_docs)]

pub extern crate ron;

use ron::Value;
use specs::{Entity, LazyUpdate, World};
use std::collections::HashMap;

/// All components must have a factory implementing this trait to be able created from a scene
/// config.
///
/// You might want to use the [`impl_component_factory`] macro.
pub trait ComponentFactory: Send + Sync {
    /// Build a component from `value` and insert it into `entity` of `world`
    fn insert(&self, world: &mut World, entity: Entity, value: &Value);
    /// Build a component from `value` and lazily insert it into `entity` of `world`
    fn insert_lazy(&self, lazy: &LazyUpdate, entity: Entity, value: &Value);
    /// Component name
    fn name() -> &'static str
    where
        Self: Sized;
}

/// Registry of component name - component factory
pub struct ComponentRegistry {
    map: HashMap<String, Box<dyn ComponentFactory>>,
}

impl ComponentRegistry {
    /// Creates a new instance of [`ComponentRegistry`]
    pub fn new() -> Self {
        Self {
            map: HashMap::new(),
        }
    }

    /// Registers a component factory
    pub fn register(&mut self, name: &str, f: Box<dyn ComponentFactory>) {
        self.map.insert(name.to_string(), f);
    }

    /// Fetches a component factory by name
    pub fn get(&self, name: &str) -> Option<&dyn ComponentFactory> {
        self.map.get(name).map(|v| &**v)
    }
}

impl Default for ComponentRegistry {
    fn default() -> Self {
        Self::new()
    }
}

#[macro_export]
/// Create and implement a component factory for a component.
///
/// # Arguments
/// * `factory` - arbitrary factory name (tho component name + Factory is recommended)
/// * `component` - the component itself
///
/// # Example
/// ```ignore
/// impl_component_factory!(SpriteFactory, Sprite);
/// ```
macro_rules! impl_component_factory {
    ($factory:ident, $component:path, $name:expr) => {
        #[derive(Debug, Default)]
        pub struct $factory;

        impl $crate::ComponentFactory for $factory {
            fn insert(&self, world: &mut specs::World, entity: specs::Entity, value: &ron::Value) {
                match ron::Value::into_rust::<$component>(value.clone()) {
                    Ok(component) => {
                        if let Err(error) = world
                            .write_storage::<$component>()
                            .insert(entity, component)
                        {
                            log::error!(
                                "failed to insert component {} for entity {:?}: {}",
                                stringify!($component),
                                entity,
                                error
                            );
                        }
                    }

                    Err(error) => {
                        log::error!(
                            "failed to construct component {} for entity {:?}: {}",
                            stringify!($component),
                            entity,
                            error
                        );
                    }
                }
            }

            fn insert_lazy(
                &self,
                lazy: &specs::LazyUpdate,
                entity: specs::Entity,
                value: &ron::Value,
            ) {
                match ron::Value::into_rust::<$component>(value.clone()) {
                    Ok(component) => {
                        lazy.insert(entity, component);
                    }

                    Err(error) => {
                        log::error!(
                            "failed to construct component {} for entity {:?}: {}",
                            stringify!($component),
                            entity,
                            error
                        );
                    }
                }
            }

            fn name() -> &'static str {
                $name
            }
        }
    };
}