furmint-runtime 0.1.0

Main package of furmint game engine providing higher level API
Documentation
//! # Plugins
//! This module provides a simple trait for creating plugins within the furmint engine.
//! Plugins allow extending the capabilities of the engine and also are an essential piece of it,
//! as plugins are the backbone for most of the engine's features.
//!
//! An example is the `furmint_graphics::plugin::GraphicsPlugin` plugin, which provides the most important
//! part of the engine, the graphics.
//!
//! ## [`Plugin::update`]
//! This function is called every tick (60 TPS) in the runtime **synchronously**, so if:
//! - your plugin is blocking or does heavy computations and/or
//! - needs to run independently of the runner thread <br/>
//!
//! consider facilitating the usage of channels and threads.
#![warn(missing_docs)]

use crate::app::{Stage, SystemRegistration, push_system_registration};
use crate::events::AppEvent;
use furmint_registry::{ComponentFactory, ComponentRegistry};
use specs::prelude::Resource;
use specs::{Component, World, WorldExt};
use std::collections::HashMap;
use std::error::Error;
use std::sync::mpsc::Sender;

/// Plugin build context
pub struct PluginBuildContext<'a> {
    /// ECS world
    pub(crate) world: &'a mut World,
    /// ECS Dispatcher config, from app builder
    pub(crate) dispatcher_config: &'a mut HashMap<Stage, Vec<SystemRegistration>>,
    /// ECS Component registry
    pub(crate) component_registry: &'a mut ComponentRegistry,
    /// App event sender
    pub(crate) event_sender: Sender<AppEvent>,
}

/// Plugin update context
pub struct PluginUpdateContext<'a> {
    /// ECS world
    pub world: &'a mut World,
    /// Time taken by tick
    pub delta_time: f64,
}

/// A trait that all plugins must implement to be inserted into `App`
///
/// # Example
/// ```
/// use std::collections::HashMap;
/// use specs::{DispatcherBuilder, World};
/// use specs::prelude::{ResourceId, Resource};
/// use specs::shred::cell::AtomicRefCell;
/// use std::boxed::Box;
/// use std::error::Error;
///
/// use furmint_runtime::plugins::{Plugin, PluginBuildContext, PluginUpdateContext};
///
/// struct MyCoolPlugin;
///
/// impl Plugin for MyCoolPlugin {
///     fn build(
///         &mut self,
///         _ctx: &mut PluginBuildContext<'_>
///     ) -> Result<(), Box<dyn Error>> {
///         // initialize your plugin here
///         Ok(())
///     }
///
///     fn update(&mut self, _ctx: &mut PluginUpdateContext<'_>) -> Result<(), Box<dyn Error>> {
///         // update your plugin state
///         Ok(())
///     }
///
///     fn name(&self) -> &'static str {
///         "my_cool_plugin"
///     }
/// }
/// ```
pub trait Plugin {
    /// Prepare/initialize the plugin, return a result of the initialization.
    ///
    /// # Arguments
    /// * `dispatcher_builder`: a mutable reference to [`specs::DispatcherBuilder`] so the plugin can register its own systems
    /// * `world`: a mutable reference to [`World`] so the plugin can register components, insert resources, e.t.c.
    fn build(&mut self, ctx: &mut PluginBuildContext<'_>) -> Result<(), Box<dyn Error>>;
    /// Update the plugin state, return a result of this
    fn update(&mut self, ctx: &mut PluginUpdateContext<'_>) -> Result<(), Box<dyn Error>>;
    /// Shutdown the plugin on exit
    fn shutdown(&mut self) -> Result<(), Box<dyn Error>> {
        Ok(())
    }
    /// Return the plugin's name; must be unique and not change
    fn name(&self) -> &'static str;
}

impl PluginBuildContext<'_> {
    /// Register a system to insert into the dispatcher. Systems will be run in the exact order of registration, and
    /// they will be assigned name corresponding to its module path, e.g. `game::something::MySystem`.
    pub fn register_system<S>(&mut self, stage: Stage, sys: S)
    where
        for<'a> S: specs::System<'a> + Send + 'static,
    {
        push_system_registration(
            self.dispatcher_config
                .get_mut(&stage)
                .expect("stage config missing"),
            sys,
            &[],
        );
    }

    /// Register a system to insert into the dispatcher with dependencies. Systems will be run in the exact order of registration, and
    /// they will be assigned name corresponding to its module path, e.g. `game::something::MySystem`.
    pub fn register_system_deps<S>(&mut self, stage: Stage, sys: S, deps: &'static [&'static str])
    where
        for<'a> S: specs::System<'a> + Send + 'static,
    {
        push_system_registration(
            self.dispatcher_config
                .get_mut(&stage)
                .expect("stage config missing"),
            sys,
            deps,
        );
    }

    /// Insert a resource into ECS world
    pub fn insert_resource(&mut self, resource: impl Resource) {
        self.world.insert(resource);
    }

    /// Register a component in the ECS
    pub fn with_component<C: Component, F: ComponentFactory + 'static>(
        &mut self,
        name: &'static str,
        component_factory: F,
    ) where
        <C as Component>::Storage: Default,
    {
        self.world.register::<C>();
        self.component_registry
            .register(name, Box::new(component_factory));
    }

    /// Get an app event sender
    pub fn event_sender(&self) -> Sender<AppEvent> {
        self.event_sender.clone()
    }
}

impl<'a> PluginUpdateContext<'a> {
    pub(crate) fn new(delta_time: f64, world: &'a mut World) -> Self {
        Self { delta_time, world }
    }
}