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}