reovim_core/plugin/
loader.rs

1//! Plugin loader with dependency resolution
2
3use std::{
4    any::TypeId,
5    collections::{HashMap, VecDeque},
6    sync::Arc,
7};
8
9use crate::event_bus::EventBus;
10
11use super::{Plugin, PluginContext, PluginId, PluginStateRegistry};
12
13/// Error during plugin loading
14#[derive(Debug)]
15pub enum PluginError {
16    /// A required dependency is missing
17    MissingDependency {
18        /// Plugin that has the missing dependency
19        plugin: PluginId,
20        /// Type name of the missing dependency
21        dependency_name: &'static str,
22    },
23    /// Circular dependency detected
24    CircularDependency(Vec<PluginId>),
25    /// Plugin already loaded
26    AlreadyLoaded(PluginId),
27}
28
29impl std::fmt::Display for PluginError {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            Self::MissingDependency {
33                plugin,
34                dependency_name,
35            } => {
36                write!(
37                    f,
38                    "Plugin '{plugin}' requires dependency '{dependency_name}' which is not loaded"
39                )
40            }
41            Self::CircularDependency(cycle) => {
42                write!(f, "Circular dependency detected: ")?;
43                for (i, id) in cycle.iter().enumerate() {
44                    if i > 0 {
45                        write!(f, " -> ")?;
46                    }
47                    write!(f, "{id}")?;
48                }
49                Ok(())
50            }
51            Self::AlreadyLoaded(id) => {
52                write!(f, "Plugin '{id}' is already loaded")
53            }
54        }
55    }
56}
57
58impl std::error::Error for PluginError {}
59
60/// Plugin loader with dependency resolution
61///
62/// Collects plugins and loads them in dependency order.
63pub struct PluginLoader {
64    /// Registered plugins in registration order
65    plugins: Vec<Box<dyn Plugin>>,
66    /// `TypeId` to plugin index mapping
67    type_map: HashMap<TypeId, usize>,
68    /// `TypeId` to type name mapping for error messages
69    type_names: HashMap<TypeId, &'static str>,
70}
71
72impl Default for PluginLoader {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl PluginLoader {
79    /// Create a new empty plugin loader
80    #[must_use]
81    pub fn new() -> Self {
82        Self {
83            plugins: Vec::new(),
84            type_map: HashMap::new(),
85            type_names: HashMap::new(),
86        }
87    }
88
89    /// Add a plugin to be loaded
90    ///
91    /// Plugins are added in order, but actual loading respects dependencies.
92    pub fn add<P: Plugin>(&mut self, plugin: P) -> &mut Self {
93        let type_id = TypeId::of::<P>();
94        let idx = self.plugins.len();
95        self.type_names.insert(type_id, std::any::type_name::<P>());
96        self.plugins.push(Box::new(plugin));
97        self.type_map.insert(type_id, idx);
98        self
99    }
100
101    /// Add multiple plugins using tuple syntax (like Bevy)
102    ///
103    /// # Example
104    /// ```ignore
105    /// loader.add_plugins((CorePlugin, EditorPlugin, TelescopePlugin));
106    /// ```
107    pub fn add_plugins<T: PluginTuple>(&mut self, plugins: T) -> &mut Self {
108        plugins.add_to(self);
109        self
110    }
111
112    /// Load all plugins in dependency order
113    ///
114    /// # Errors
115    /// Returns error if dependencies are missing or circular.
116    pub fn load(self, ctx: &mut PluginContext) -> Result<(), PluginError> {
117        // Topological sort based on dependencies
118        let order = self.resolve_order()?;
119
120        // Build phase
121        for idx in &order {
122            let plugin = &self.plugins[*idx];
123            let plugin_id = plugin.id();
124            tracing::debug!(plugin = %plugin_id, "Building plugin");
125            ctx.set_current_plugin(plugin_id.to_string());
126            plugin.build(ctx);
127            ctx.clear_current_plugin();
128            ctx.mark_loaded_by_id(self.type_id_for_index(*idx));
129        }
130
131        // Finish phase
132        for idx in &order {
133            let plugin = &self.plugins[*idx];
134            plugin.finish(ctx);
135        }
136
137        Ok(())
138    }
139
140    /// Load all plugins with state registry and event bus integration
141    ///
142    /// This is the preferred method for loading plugins as it enables:
143    /// - Plugin state initialization via `init_state`
144    /// - Event bus subscription via `subscribe`
145    ///
146    /// Returns the loaded plugins for later boot phase execution.
147    ///
148    /// # Errors
149    /// Returns error if dependencies are missing or circular.
150    ///
151    /// # Panics
152    /// Panics if plugin extraction fails (internal invariant violation).
153    pub fn load_with_state(
154        self,
155        ctx: &mut PluginContext,
156        state_registry: &Arc<PluginStateRegistry>,
157        event_bus: &Arc<EventBus>,
158    ) -> Result<Vec<Box<dyn Plugin>>, PluginError> {
159        // Topological sort based on dependencies
160        let order = self.resolve_order()?;
161
162        // Build phase
163        for idx in &order {
164            let plugin = &self.plugins[*idx];
165            let plugin_id = plugin.id();
166            tracing::debug!(plugin = %plugin_id, "Building plugin");
167            ctx.set_current_plugin(plugin_id.to_string());
168            plugin.build(ctx);
169            ctx.clear_current_plugin();
170            ctx.mark_loaded_by_id(self.type_id_for_index(*idx));
171        }
172
173        // Init state phase - after build, before finish
174        for idx in &order {
175            let plugin = &self.plugins[*idx];
176            tracing::debug!(plugin = %plugin.id(), "Initializing plugin state");
177            plugin.init_state(state_registry);
178        }
179
180        // Finish phase
181        for idx in &order {
182            let plugin = &self.plugins[*idx];
183            plugin.finish(ctx);
184        }
185
186        // Subscribe phase - after finish, allows plugins to subscribe to events
187        for idx in &order {
188            let plugin = &self.plugins[*idx];
189            tracing::debug!(plugin = %plugin.id(), "Subscribing plugin to events");
190            plugin.subscribe(event_bus, Arc::clone(state_registry));
191        }
192
193        // Return plugins in load order for boot phase
194        // Convert Vec to array of Options for O(1) extraction by index
195        let mut plugins: Vec<Option<Box<dyn Plugin>>> =
196            self.plugins.into_iter().map(Some).collect();
197        let mut ordered_plugins = Vec::with_capacity(order.len());
198
199        for idx in order {
200            if let Some(plugin) = plugins[idx].take() {
201                ordered_plugins.push(plugin);
202            }
203        }
204
205        Ok(ordered_plugins)
206    }
207
208    /// Resolve plugin loading order using topological sort
209    fn resolve_order(&self) -> Result<Vec<usize>, PluginError> {
210        let n = self.plugins.len();
211        let mut in_degree = vec![0usize; n];
212        let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
213
214        // Build dependency graph
215        for (idx, plugin) in self.plugins.iter().enumerate() {
216            for dep_type_id in plugin.dependencies() {
217                if let Some(&dep_idx) = self.type_map.get(&dep_type_id) {
218                    adj[dep_idx].push(idx);
219                    in_degree[idx] += 1;
220                } else {
221                    let dep_name = self
222                        .type_names
223                        .get(&dep_type_id)
224                        .copied()
225                        .unwrap_or("unknown");
226                    return Err(PluginError::MissingDependency {
227                        plugin: plugin.id(),
228                        dependency_name: dep_name,
229                    });
230                }
231            }
232
233            // Optional dependencies don't require the plugin to be present
234            for dep_type_id in plugin.optional_dependencies() {
235                if let Some(&dep_idx) = self.type_map.get(&dep_type_id) {
236                    adj[dep_idx].push(idx);
237                    in_degree[idx] += 1;
238                }
239            }
240        }
241
242        // Kahn's algorithm for topological sort
243        let mut queue: VecDeque<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
244        let mut result = Vec::with_capacity(n);
245
246        while let Some(idx) = queue.pop_front() {
247            result.push(idx);
248            for &next in &adj[idx] {
249                in_degree[next] -= 1;
250                if in_degree[next] == 0 {
251                    queue.push_back(next);
252                }
253            }
254        }
255
256        if result.len() != n {
257            // Cycle detected - find and report it
258            let cycle = self.find_cycle(&in_degree);
259            return Err(PluginError::CircularDependency(cycle));
260        }
261
262        Ok(result)
263    }
264
265    /// Find `TypeId` for a plugin index
266    fn type_id_for_index(&self, idx: usize) -> TypeId {
267        for (&type_id, &plugin_idx) in &self.type_map {
268            if plugin_idx == idx {
269                return type_id;
270            }
271        }
272        unreachable!("Index should always have a TypeId")
273    }
274
275    /// Find a cycle in the dependency graph for error reporting
276    fn find_cycle(&self, in_degree: &[usize]) -> Vec<PluginId> {
277        // Find nodes still with dependencies (part of a cycle)
278        let mut cycle_nodes: Vec<PluginId> = in_degree
279            .iter()
280            .enumerate()
281            .filter(|&(_, deg)| *deg > 0)
282            .map(|(idx, _)| self.plugins[idx].id())
283            .collect();
284
285        // If we can't find specific nodes, just report all remaining
286        if cycle_nodes.is_empty() {
287            cycle_nodes = self.plugins.iter().map(|p| p.id()).collect();
288        }
289
290        cycle_nodes
291    }
292}
293
294/// Trait for adding multiple plugins at once (tuple support)
295pub trait PluginTuple {
296    /// Add all plugins in this tuple to the loader
297    fn add_to(self, loader: &mut PluginLoader);
298}
299
300// Implement for single plugin
301impl<P: Plugin> PluginTuple for P {
302    fn add_to(self, loader: &mut PluginLoader) {
303        loader.add(self);
304    }
305}
306
307// Implement for tuples of various sizes
308impl<P1: Plugin, P2: Plugin> PluginTuple for (P1, P2) {
309    fn add_to(self, loader: &mut PluginLoader) {
310        loader.add(self.0);
311        loader.add(self.1);
312    }
313}
314
315impl<P1: Plugin, P2: Plugin, P3: Plugin> PluginTuple for (P1, P2, P3) {
316    fn add_to(self, loader: &mut PluginLoader) {
317        loader.add(self.0);
318        loader.add(self.1);
319        loader.add(self.2);
320    }
321}
322
323impl<P1: Plugin, P2: Plugin, P3: Plugin, P4: Plugin> PluginTuple for (P1, P2, P3, P4) {
324    fn add_to(self, loader: &mut PluginLoader) {
325        loader.add(self.0);
326        loader.add(self.1);
327        loader.add(self.2);
328        loader.add(self.3);
329    }
330}
331
332impl<P1: Plugin, P2: Plugin, P3: Plugin, P4: Plugin, P5: Plugin> PluginTuple
333    for (P1, P2, P3, P4, P5)
334{
335    fn add_to(self, loader: &mut PluginLoader) {
336        loader.add(self.0);
337        loader.add(self.1);
338        loader.add(self.2);
339        loader.add(self.3);
340        loader.add(self.4);
341    }
342}
343
344impl<P1: Plugin, P2: Plugin, P3: Plugin, P4: Plugin, P5: Plugin, P6: Plugin> PluginTuple
345    for (P1, P2, P3, P4, P5, P6)
346{
347    fn add_to(self, loader: &mut PluginLoader) {
348        loader.add(self.0);
349        loader.add(self.1);
350        loader.add(self.2);
351        loader.add(self.3);
352        loader.add(self.4);
353        loader.add(self.5);
354    }
355}
356
357impl<P1: Plugin, P2: Plugin, P3: Plugin, P4: Plugin, P5: Plugin, P6: Plugin, P7: Plugin> PluginTuple
358    for (P1, P2, P3, P4, P5, P6, P7)
359{
360    fn add_to(self, loader: &mut PluginLoader) {
361        loader.add(self.0);
362        loader.add(self.1);
363        loader.add(self.2);
364        loader.add(self.3);
365        loader.add(self.4);
366        loader.add(self.5);
367        loader.add(self.6);
368    }
369}
370
371impl<P1: Plugin, P2: Plugin, P3: Plugin, P4: Plugin, P5: Plugin, P6: Plugin, P7: Plugin, P8: Plugin>
372    PluginTuple for (P1, P2, P3, P4, P5, P6, P7, P8)
373{
374    fn add_to(self, loader: &mut PluginLoader) {
375        loader.add(self.0);
376        loader.add(self.1);
377        loader.add(self.2);
378        loader.add(self.3);
379        loader.add(self.4);
380        loader.add(self.5);
381        loader.add(self.6);
382        loader.add(self.7);
383    }
384}
385
386impl<
387    P1: Plugin,
388    P2: Plugin,
389    P3: Plugin,
390    P4: Plugin,
391    P5: Plugin,
392    P6: Plugin,
393    P7: Plugin,
394    P8: Plugin,
395    P9: Plugin,
396> PluginTuple for (P1, P2, P3, P4, P5, P6, P7, P8, P9)
397{
398    fn add_to(self, loader: &mut PluginLoader) {
399        loader.add(self.0);
400        loader.add(self.1);
401        loader.add(self.2);
402        loader.add(self.3);
403        loader.add(self.4);
404        loader.add(self.5);
405        loader.add(self.6);
406        loader.add(self.7);
407        loader.add(self.8);
408    }
409}
410
411impl<
412    P1: Plugin,
413    P2: Plugin,
414    P3: Plugin,
415    P4: Plugin,
416    P5: Plugin,
417    P6: Plugin,
418    P7: Plugin,
419    P8: Plugin,
420    P9: Plugin,
421    P10: Plugin,
422> PluginTuple for (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10)
423{
424    fn add_to(self, loader: &mut PluginLoader) {
425        loader.add(self.0);
426        loader.add(self.1);
427        loader.add(self.2);
428        loader.add(self.3);
429        loader.add(self.4);
430        loader.add(self.5);
431        loader.add(self.6);
432        loader.add(self.7);
433        loader.add(self.8);
434        loader.add(self.9);
435    }
436}