Skip to main content

furmint_runtime/
plugins.rs

1//! # Plugins
2//! This module provides a simple trait for creating plugins within the furmint engine.
3//! Plugins allow extending the capabilities of the engine and also are an essential piece of it,
4//! as plugins are the backbone for most of the engine's features.
5//!
6//! An example is the `furmint_graphics::plugin::GraphicsPlugin` plugin, which provides the most important
7//! part of the engine, the graphics.
8//!
9//! ## [`Plugin::update`]
10//! This function is called every tick (60 TPS) in the runtime **synchronously**, so if:
11//! - your plugin is blocking or does heavy computations and/or
12//! - needs to run independently of the runner thread <br/>
13//!
14//! consider facilitating the usage of channels and threads.
15#![warn(missing_docs)]
16
17use crate::app::{Stage, SystemRegistration, push_system_registration};
18use crate::events::AppEvent;
19use furmint_registry::{ComponentFactory, ComponentRegistry};
20use specs::prelude::Resource;
21use specs::{Component, World, WorldExt};
22use std::collections::HashMap;
23use std::error::Error;
24use std::sync::mpsc::Sender;
25
26/// Plugin build context
27pub struct PluginBuildContext<'a> {
28    /// ECS world
29    pub(crate) world: &'a mut World,
30    /// ECS Dispatcher config, from app builder
31    pub(crate) dispatcher_config: &'a mut HashMap<Stage, Vec<SystemRegistration>>,
32    /// ECS Component registry
33    pub(crate) component_registry: &'a mut ComponentRegistry,
34    /// App event sender
35    pub(crate) event_sender: Sender<AppEvent>,
36}
37
38/// Plugin update context
39pub struct PluginUpdateContext<'a> {
40    /// ECS world
41    pub world: &'a mut World,
42    /// Time taken by tick
43    pub delta_time: f64,
44}
45
46/// A trait that all plugins must implement to be inserted into `App`
47///
48/// # Example
49/// ```
50/// use std::collections::HashMap;
51/// use specs::{DispatcherBuilder, World};
52/// use specs::prelude::{ResourceId, Resource};
53/// use specs::shred::cell::AtomicRefCell;
54/// use std::boxed::Box;
55/// use std::error::Error;
56///
57/// use furmint_runtime::plugins::{Plugin, PluginBuildContext, PluginUpdateContext};
58///
59/// struct MyCoolPlugin;
60///
61/// impl Plugin for MyCoolPlugin {
62///     fn build(
63///         &mut self,
64///         _ctx: &mut PluginBuildContext<'_>
65///     ) -> Result<(), Box<dyn Error>> {
66///         // initialize your plugin here
67///         Ok(())
68///     }
69///
70///     fn update(&mut self, _ctx: &mut PluginUpdateContext<'_>) -> Result<(), Box<dyn Error>> {
71///         // update your plugin state
72///         Ok(())
73///     }
74///
75///     fn name(&self) -> &'static str {
76///         "my_cool_plugin"
77///     }
78/// }
79/// ```
80pub trait Plugin {
81    /// Prepare/initialize the plugin, return a result of the initialization.
82    ///
83    /// # Arguments
84    /// * `dispatcher_builder`: a mutable reference to [`specs::DispatcherBuilder`] so the plugin can register its own systems
85    /// * `world`: a mutable reference to [`World`] so the plugin can register components, insert resources, e.t.c.
86    fn build(&mut self, ctx: &mut PluginBuildContext<'_>) -> Result<(), Box<dyn Error>>;
87    /// Update the plugin state, return a result of this
88    fn update(&mut self, ctx: &mut PluginUpdateContext<'_>) -> Result<(), Box<dyn Error>>;
89    /// Shutdown the plugin on exit
90    fn shutdown(&mut self) -> Result<(), Box<dyn Error>> {
91        Ok(())
92    }
93    /// Return the plugin's name; must be unique and not change
94    fn name(&self) -> &'static str;
95}
96
97impl PluginBuildContext<'_> {
98    /// Register a system to insert into the dispatcher. Systems will be run in the exact order of registration, and
99    /// they will be assigned name corresponding to its module path, e.g. `game::something::MySystem`.
100    pub fn register_system<S>(&mut self, stage: Stage, sys: S)
101    where
102        for<'a> S: specs::System<'a> + Send + 'static,
103    {
104        push_system_registration(
105            self.dispatcher_config
106                .get_mut(&stage)
107                .expect("stage config missing"),
108            sys,
109            &[],
110        );
111    }
112
113    /// Register a system to insert into the dispatcher with dependencies. Systems will be run in the exact order of registration, and
114    /// they will be assigned name corresponding to its module path, e.g. `game::something::MySystem`.
115    pub fn register_system_deps<S>(&mut self, stage: Stage, sys: S, deps: &'static [&'static str])
116    where
117        for<'a> S: specs::System<'a> + Send + 'static,
118    {
119        push_system_registration(
120            self.dispatcher_config
121                .get_mut(&stage)
122                .expect("stage config missing"),
123            sys,
124            deps,
125        );
126    }
127
128    /// Insert a resource into ECS world
129    pub fn insert_resource(&mut self, resource: impl Resource) {
130        self.world.insert(resource);
131    }
132
133    /// Register a component in the ECS
134    pub fn with_component<C: Component, F: ComponentFactory + 'static>(
135        &mut self,
136        name: &'static str,
137        component_factory: F,
138    ) where
139        <C as Component>::Storage: Default,
140    {
141        self.world.register::<C>();
142        self.component_registry
143            .register(name, Box::new(component_factory));
144    }
145
146    /// Get an app event sender
147    pub fn event_sender(&self) -> Sender<AppEvent> {
148        self.event_sender.clone()
149    }
150}
151
152impl<'a> PluginUpdateContext<'a> {
153    pub(crate) fn new(delta_time: f64, world: &'a mut World) -> Self {
154        Self { delta_time, world }
155    }
156}