assemble_core/
plugins.rs

1//! Provide a "unified" way of adding plugins to an assemble project
2
3use crate::project::error::ProjectResult;
4
5use crate::utilities::Action;
6
7use parking_lot::RwLock;
8use std::any::type_name;
9use std::collections::{HashMap, HashSet, VecDeque};
10use std::fmt::{Debug, Formatter};
11
12use std::sync::Arc;
13
14pub mod extensions;
15
16/// A plugin to apply to the project. All plugins must implement default.
17pub trait Plugin<T: ?Sized>: Default {
18    /// Apply the plugin
19    fn apply_to(&self, target: &mut T) -> ProjectResult;
20
21    /// The id of the plugin. A plugin of a certain ID can only added once
22    fn plugin_id(&self) -> &str {
23        type_name::<Self>()
24    }
25}
26
27/// Some value that can have plugins applied to it.
28pub trait PluginAware: Sized {
29    /// Apply a plugin to this.
30    fn apply_plugin<P: Plugin<Self>>(&mut self) -> ProjectResult {
31        let manager = &mut self.plugin_manager().clone();
32        manager.apply::<P>(self)
33    }
34
35    /// Gets a reference to the plugin manager for this value.
36    fn plugin_manager(&self) -> &PluginManager<Self>;
37    /// Gets a mutable reference to the plugin manager for this value.
38    fn plugin_manager_mut(&mut self) -> &mut PluginManager<Self>;
39}
40
41/// A struct representing an applied plugin
42pub struct PluginApplied;
43
44type PluginManagerAction<T> = Box<dyn for<'a> FnOnce(&'a mut T) -> ProjectResult + Send + Sync>;
45
46/// Facilities applying plugins and determining which plugins have been applied to
47/// a plugin aware object.
48pub struct PluginManager<T: PluginAware>(Arc<PluginManagerInner<T>>);
49
50impl<T: PluginAware> PluginManager<T> {
51    /// Create a new plugin manager instance
52    #[inline]
53    pub fn new() -> Self {
54        Self::default()
55    }
56
57    /// Check if this manager has a known plugin
58    pub fn has_plugin(&self, id: &str) -> bool {
59        self.0.has_plugin(id)
60    }
61
62    pub fn has_plugin_ty<P: Plugin<T>>(&self) -> bool {
63        self.0.has_plugin_ty::<P>()
64    }
65
66    /// Applies this plugin if it hasn't been applied before
67    pub fn apply<P: Plugin<T>>(&mut self, target: &mut T) -> ProjectResult {
68        self.0.apply::<P>(target)
69    }
70
71    /// Set an action to perform if a plugin has been applied
72    pub fn with_plugin<F: 'static>(&mut self, id: &str, target: &mut T, action: F) -> ProjectResult
73    where
74        T: 'static,
75        for<'a> F: FnOnce(&'a mut T) -> ProjectResult + Send + Sync,
76    {
77        self.0.with_plugin(id, target, action)
78    }
79}
80
81impl<T: PluginAware> Clone for PluginManager<T> {
82    fn clone(&self) -> Self {
83        Self(self.0.clone())
84    }
85}
86
87impl<T: PluginAware> Default for PluginManager<T> {
88    fn default() -> Self {
89        Self(Default::default())
90    }
91}
92
93impl<T: PluginAware> Debug for PluginManager<T> {
94    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
95        f.debug_struct("PluginManager").finish_non_exhaustive()
96    }
97}
98
99struct PluginManagerInner<T: PluginAware> {
100    applied: RwLock<HashSet<String>>,
101    lazy_with_plugins: RwLock<HashMap<String, VecDeque<PluginManagerAction<T>>>>,
102}
103
104impl<T: PluginAware> Default for PluginManagerInner<T> {
105    fn default() -> Self {
106        Self {
107            applied: Default::default(),
108            lazy_with_plugins: Default::default(),
109        }
110    }
111}
112
113impl<T: PluginAware> PluginManagerInner<T> {
114    /// Check if this manager has a known plugin
115    pub fn has_plugin(&self, id: &str) -> bool {
116        self.applied.read().contains(id)
117    }
118
119    pub fn has_plugin_ty<P: Plugin<T>>(&self) -> bool {
120        let plugins = P::default();
121        let id = plugins.plugin_id();
122        self.has_plugin(id)
123    }
124
125    /// Applies this plugin if it hasn't been applied before
126    pub fn apply<P: Plugin<T>>(&self, target: &mut T) -> ProjectResult {
127        let type_name: &str = std::any::type_name::<P>();
128
129        trace!("attempting to apply plugin of type {type_name}");
130
131        let ret = if self.has_plugin_ty::<P>() {
132            trace!("plugin of type {type_name} already applied");
133            Ok(())
134        } else {
135            let plugin = P::default();
136            let id = plugin.plugin_id().to_string();
137            trace!("applying generated plugin of type {type_name} with id {id}");
138            plugin.apply_to(target)?;
139            trace!("added applied plugin id {id}");
140            self.applied.write().insert(id);
141
142            Ok(())
143        };
144        for applied in self.applied.read().clone() {
145            let mut lazy = self.lazy_with_plugins.write();
146            if let Some(actions) = lazy.get_mut(&*applied) {
147                let actions: Vec<_> = actions.drain(..).collect();
148                trace!(
149                    "found {} delayed actions for plugin {} that will now be applied",
150                    actions.len(),
151                    applied
152                );
153                for action in actions {
154                    action.execute(target)?;
155                }
156            }
157        }
158        ret
159    }
160
161    /// Set an action to perform if a plugin has been applied
162    pub fn with_plugin<F: 'static>(&self, id: &str, target: &mut T, action: F) -> ProjectResult
163    where
164        T: 'static,
165        for<'a> F: FnOnce(&'a mut T) -> ProjectResult + Send + Sync,
166    {
167        if self.has_plugin(id) {
168            action.execute(target)
169        } else {
170            let id = id.to_string();
171            self.lazy_with_plugins
172                .write()
173                .entry(id)
174                .or_default()
175                .push_back(Box::new(action)
176                    as Box<
177                        dyn for<'b> FnOnce(&'b mut T) -> ProjectResult + Send + Sync,
178                    >);
179            Ok(())
180        }
181    }
182}
183
184#[derive(Debug, thiserror::Error)]
185pub enum PluginError {
186    #[error("Couldn't create the plugin")]
187    CouldNotCreatePlugin,
188}