maycoon_core/
plugin.rs

1use crate::app::info::AppInfo;
2use crate::app::update::UpdateManager;
3use crate::config::MayConfig;
4use crate::vgi::VectorGraphicsInterface;
5use maycoon_theme::theme::Theme;
6use rpds::HashTrieMap;
7use std::cell::RefCell;
8use std::ops::DerefMut;
9use std::rc::Rc;
10use std::sync::Arc;
11use std::time::Instant;
12use taffy::{NodeId, TaffyTree};
13use winit::event::WindowEvent;
14use winit::event_loop::{ActiveEventLoop, EventLoop};
15use winit::window::{Window, WindowAttributes};
16
17/// A plugin interface for maycoon applications.
18///
19/// Plugins are used to extend functionality and manipulate the inner state of applications.
20/// Beware that tampering with the application state may cause crashes or other issues if not done correctly.
21///
22/// All functions defined in this trait get called before the app handler logic and therefore can control the application flow.
23pub trait Plugin<T: Theme, V: VectorGraphicsInterface>: 'static {
24    /// The plugin name.
25    ///
26    /// Should be unique among the ecosystem.
27    fn name(&self) -> &'static str;
28
29    /// Called when the plugin is registered using [PluginManager::register].
30    fn on_register(&mut self, _manager: &mut PluginManager<T, V>) {}
31
32    /// Called when the plugin is unregistered using [PluginManager::unregister].
33    fn on_unregister(&mut self, _manager: &mut PluginManager<T, V>) {}
34
35    /// Called right before initializing the [AppHandler](crate::app::handler::AppHandler) and running the event loop.
36    #[inline(always)]
37    fn init(
38        &mut self,
39        _event_loop: &mut EventLoop<()>,
40        _update: &UpdateManager,
41        _window: &mut WindowAttributes,
42        _config: &mut MayConfig<T, V>,
43    ) {
44    }
45
46    /// Called when the application is resumed after being suspended or when it's first started.
47    ///
48    /// Desktop applications typically don't get suspended and this function is only called once,
49    /// while mobile apps can be suspended and resumed.
50    #[inline(always)]
51    fn on_resume(
52        &mut self,
53        _config: &mut MayConfig<T, V>,
54        _scene: &mut V::Scene,
55        _taffy: &mut TaffyTree,
56        _window_node: NodeId,
57        _info: &mut AppInfo,
58        _update: &UpdateManager,
59        _last_update: &mut Instant,
60        _event_loop: &ActiveEventLoop,
61    ) {
62    }
63
64    /// Called right before the application handler tries to update the application
65    /// and figure out what updates to apply.
66    #[inline(always)]
67    fn on_update(
68        &mut self,
69        _config: &mut MayConfig<T, V>,
70        _window: &Arc<Window>,
71        _scene: &mut V::Scene,
72        _taffy: &mut TaffyTree,
73        _window_node: NodeId,
74        _info: &mut AppInfo,
75        _update: &UpdateManager,
76        _last_update: &mut Instant,
77        _event_loop: &ActiveEventLoop,
78    ) {
79    }
80
81    /// Called when a window event is received.
82    #[inline(always)]
83    fn on_window_event(
84        &mut self,
85        _event: &mut WindowEvent,
86        _config: &mut MayConfig<T, V>,
87        _window: &Arc<Window>,
88        _scene: &mut V::Scene,
89        _taffy: &mut TaffyTree,
90        _window_node: NodeId,
91        _info: &mut AppInfo,
92        _update: &UpdateManager,
93        _last_update: &mut Instant,
94        _event_loop: &ActiveEventLoop,
95    ) {
96    }
97
98    /// Called when the application is suspended.
99    #[cold]
100    fn on_suspended(
101        &mut self,
102        _config: &mut MayConfig<T, V>,
103        _scene: &mut V::Scene,
104        _taffy: &mut TaffyTree,
105        _window_node: NodeId,
106        _info: &mut AppInfo,
107        _update: &UpdateManager,
108        _last_update: &mut Instant,
109        _event_loop: &ActiveEventLoop,
110    ) {
111    }
112}
113
114/// A plugin manager for maycoon applications.
115pub struct PluginManager<T: Theme, V: VectorGraphicsInterface> {
116    plugins: HashTrieMap<&'static str, Rc<RefCell<dyn Plugin<T, V>>>>,
117}
118
119impl<T: Theme, V: VectorGraphicsInterface> PluginManager<T, V> {
120    /// Creates a new empty plugin manager.
121    #[inline(always)]
122    pub fn new() -> Self {
123        Self {
124            plugins: HashTrieMap::new(),
125        }
126    }
127
128    /// Registers a new plugin and returns itself.
129    #[inline(always)]
130    pub fn register(mut self, mut plugin: impl Plugin<T, V>) -> Self {
131        plugin.on_register(&mut self);
132
133        Self {
134            plugins: self
135                .plugins
136                .insert(plugin.name(), Rc::new(RefCell::new(plugin))),
137        }
138    }
139
140    /// Unregisters a plugin and returns itself.
141    #[cold]
142    pub fn unregister(mut self, name: &'static str) -> Self {
143        let plugins = self.plugins.clone();
144
145        plugins
146            .get(name)
147            .expect("Plugin not found")
148            .borrow_mut()
149            .on_unregister(&mut self);
150
151        Self {
152            plugins: self.plugins.remove(name),
153        }
154    }
155
156    /// Unregisters all plugins.
157    #[cold]
158    pub fn clear(&mut self) {
159        let plugins = self.plugins.clone();
160
161        for (_, pl) in plugins.iter() {
162            pl.borrow_mut().on_unregister(self);
163        }
164
165        self.plugins = HashTrieMap::new();
166    }
167
168    /// Runs a closure on all plugins.
169    #[inline(always)]
170    pub fn run<F>(&mut self, mut op: F)
171    where
172        F: FnMut(&mut dyn Plugin<T, V>),
173    {
174        for (_, plugin) in self.plugins.iter() {
175            let mut plugin = plugin.borrow_mut();
176
177            op(plugin.deref_mut());
178        }
179    }
180}
181
182impl<T: Theme, V: VectorGraphicsInterface> Default for PluginManager<T, V> {
183    #[inline(always)]
184    fn default() -> Self {
185        Self::new()
186    }
187}
188
189#[cfg(all(test, feature = "test"))]
190mod tests {
191    use crate::plugin::{Plugin, PluginManager};
192    use crate::vgi::dummy::DummyGraphics;
193    use maycoon_theme::theme::dummy::DummyTheme;
194    use std::sync::atomic::{AtomicU32, Ordering};
195
196    static A: AtomicU32 = AtomicU32::new(0);
197    static B: AtomicU32 = AtomicU32::new(0);
198    static C: AtomicU32 = AtomicU32::new(0);
199
200    /// Tests if plugin manager operates correctly on multiple plugins.
201    #[test]
202    fn test_plugin_manager() {
203        let mut plugins = PluginManager::<DummyTheme, DummyGraphics>::new()
204            .register(TestPluginA)
205            .register(TestPluginB)
206            .register(TestPluginC);
207
208        let mut run_a = 0;
209        let mut run_b = 0;
210        let mut run_c = 0;
211
212        plugins.run(|pl| match pl.name() {
213            "Test Plugin A" => run_a += 1,
214            "Test Plugin B" => run_b += 1,
215            "Test Plugin C" => run_c += 1,
216            _ => panic!(),
217        });
218
219        assert_eq!(run_a, 1);
220        assert_eq!(run_b, 1);
221        assert_eq!(run_c, 1);
222
223        plugins
224            .unregister(<TestPluginA as Plugin<DummyTheme, DummyGraphics>>::name(
225                &TestPluginA,
226            ))
227            .unregister(<TestPluginB as Plugin<DummyTheme, DummyGraphics>>::name(
228                &TestPluginB,
229            ))
230            .unregister(<TestPluginC as Plugin<DummyTheme, DummyGraphics>>::name(
231                &TestPluginC,
232            ));
233
234        assert_eq!(A.load(Ordering::Relaxed), 11);
235        assert_eq!(B.load(Ordering::Relaxed), 11);
236        assert_eq!(C.load(Ordering::Relaxed), 11);
237    }
238
239    struct TestPluginA;
240
241    impl Plugin<DummyTheme, DummyGraphics> for TestPluginA {
242        fn name(&self) -> &'static str {
243            "Test Plugin A"
244        }
245
246        fn on_register(&mut self, _manager: &mut PluginManager<DummyTheme, DummyGraphics>) {
247            A.fetch_add(1, Ordering::Relaxed);
248        }
249
250        fn on_unregister(&mut self, _manager: &mut PluginManager<DummyTheme, DummyGraphics>) {
251            A.fetch_add(10, Ordering::Relaxed);
252        }
253    }
254
255    struct TestPluginB;
256
257    impl Plugin<DummyTheme, DummyGraphics> for TestPluginB {
258        fn name(&self) -> &'static str {
259            "Test Plugin B"
260        }
261
262        fn on_register(&mut self, _manager: &mut PluginManager<DummyTheme, DummyGraphics>) {
263            B.fetch_add(1, Ordering::Relaxed);
264        }
265
266        fn on_unregister(&mut self, _manager: &mut PluginManager<DummyTheme, DummyGraphics>) {
267            B.fetch_add(10, Ordering::Relaxed);
268        }
269    }
270
271    struct TestPluginC;
272
273    impl Plugin<DummyTheme, DummyGraphics> for TestPluginC {
274        fn name(&self) -> &'static str {
275            "Test Plugin C"
276        }
277
278        fn on_register(&mut self, _manager: &mut PluginManager<DummyTheme, DummyGraphics>) {
279            C.fetch_add(1, Ordering::Relaxed);
280        }
281
282        fn on_unregister(&mut self, _manager: &mut PluginManager<DummyTheme, DummyGraphics>) {
283            C.fetch_add(10, Ordering::Relaxed);
284        }
285    }
286}