1use crate::{App, AppError, Plugin};
2use bevy_utils::{tracing::debug, tracing::warn, TypeIdMap};
3use std::any::TypeId;
4
5pub trait PluginGroup: Sized {
7 fn build(self) -> PluginGroupBuilder;
9 fn name() -> String {
11 std::any::type_name::<Self>().to_string()
12 }
13 fn set<T: Plugin>(self, plugin: T) -> PluginGroupBuilder {
15 self.build().set(plugin)
16 }
17}
18
19struct PluginEntry {
20 plugin: Box<dyn Plugin>,
21 enabled: bool,
22}
23
24impl PluginGroup for PluginGroupBuilder {
25 fn build(self) -> PluginGroupBuilder {
26 self
27 }
28}
29
30pub struct PluginGroupBuilder {
35 group_name: String,
36 plugins: TypeIdMap<PluginEntry>,
37 order: Vec<TypeId>,
38}
39
40impl PluginGroupBuilder {
41 pub fn start<PG: PluginGroup>() -> Self {
43 Self {
44 group_name: PG::name(),
45 plugins: Default::default(),
46 order: Default::default(),
47 }
48 }
49
50 fn index_of<Target: Plugin>(&self) -> usize {
52 let index = self
53 .order
54 .iter()
55 .position(|&ty| ty == TypeId::of::<Target>());
56
57 match index {
58 Some(i) => i,
59 None => panic!(
60 "Plugin does not exist in group: {}.",
61 std::any::type_name::<Target>()
62 ),
63 }
64 }
65
66 fn upsert_plugin_state<T: Plugin>(&mut self, plugin: T, added_at_index: usize) {
69 self.upsert_plugin_entry_state(
70 TypeId::of::<T>(),
71 PluginEntry {
72 plugin: Box::new(plugin),
73 enabled: true,
74 },
75 added_at_index,
76 );
77 }
78
79 fn upsert_plugin_entry_state(
82 &mut self,
83 key: TypeId,
84 plugin: PluginEntry,
85 added_at_index: usize,
86 ) {
87 if let Some(entry) = self.plugins.insert(key, plugin) {
88 if entry.enabled {
89 warn!(
90 "You are replacing plugin '{}' that was not disabled.",
91 entry.plugin.name()
92 );
93 }
94 if let Some(to_remove) = self
95 .order
96 .iter()
97 .enumerate()
98 .find(|(i, ty)| *i != added_at_index && **ty == key)
99 .map(|(i, _)| i)
100 {
101 self.order.remove(to_remove);
102 }
103 }
104 }
105
106 pub fn set<T: Plugin>(mut self, plugin: T) -> Self {
112 let entry = self.plugins.get_mut(&TypeId::of::<T>()).unwrap_or_else(|| {
113 panic!(
114 "{} does not exist in this PluginGroup",
115 std::any::type_name::<T>(),
116 )
117 });
118 entry.plugin = Box::new(plugin);
119 self
120 }
121
122 #[allow(clippy::should_implement_trait)]
126 pub fn add<T: Plugin>(mut self, plugin: T) -> Self {
127 let target_index = self.order.len();
128 self.order.push(TypeId::of::<T>());
129 self.upsert_plugin_state(plugin, target_index);
130 self
131 }
132
133 pub fn add_group(mut self, group: impl PluginGroup) -> Self {
136 let Self {
137 mut plugins, order, ..
138 } = group.build();
139
140 for plugin_id in order {
141 self.upsert_plugin_entry_state(
142 plugin_id,
143 plugins.remove(&plugin_id).unwrap(),
144 self.order.len(),
145 );
146
147 self.order.push(plugin_id);
148 }
149
150 self
151 }
152
153 pub fn add_before<Target: Plugin, T: Plugin>(mut self, plugin: T) -> Self {
157 let target_index = self.index_of::<Target>();
158 self.order.insert(target_index, TypeId::of::<T>());
159 self.upsert_plugin_state(plugin, target_index);
160 self
161 }
162
163 pub fn add_after<Target: Plugin, T: Plugin>(mut self, plugin: T) -> Self {
167 let target_index = self.index_of::<Target>() + 1;
168 self.order.insert(target_index, TypeId::of::<T>());
169 self.upsert_plugin_state(plugin, target_index);
170 self
171 }
172
173 pub fn enable<T: Plugin>(mut self) -> Self {
179 let plugin_entry = self
180 .plugins
181 .get_mut(&TypeId::of::<T>())
182 .expect("Cannot enable a plugin that does not exist.");
183 plugin_entry.enabled = true;
184 self
185 }
186
187 pub fn disable<T: Plugin>(mut self) -> Self {
193 let plugin_entry = self
194 .plugins
195 .get_mut(&TypeId::of::<T>())
196 .expect("Cannot disable a plugin that does not exist.");
197 plugin_entry.enabled = false;
198 self
199 }
200
201 #[track_caller]
208 pub fn finish(mut self, app: &mut App) {
209 for ty in &self.order {
210 if let Some(entry) = self.plugins.remove(ty) {
211 if entry.enabled {
212 debug!("added plugin: {}", entry.plugin.name());
213 if let Err(AppError::DuplicatePlugin { plugin_name }) =
214 app.add_boxed_plugin(entry.plugin)
215 {
216 panic!(
217 "Error adding plugin {} in group {}: plugin was already added in application",
218 plugin_name,
219 self.group_name
220 );
221 }
222 }
223 }
224 }
225 }
226}
227
228#[doc(hidden)]
238pub struct NoopPluginGroup;
239
240impl PluginGroup for NoopPluginGroup {
241 fn build(self) -> PluginGroupBuilder {
242 PluginGroupBuilder::start::<Self>()
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::PluginGroupBuilder;
249 use crate::{App, NoopPluginGroup, Plugin};
250
251 struct PluginA;
252 impl Plugin for PluginA {
253 fn build(&self, _: &mut App) {}
254 }
255
256 struct PluginB;
257 impl Plugin for PluginB {
258 fn build(&self, _: &mut App) {}
259 }
260
261 struct PluginC;
262 impl Plugin for PluginC {
263 fn build(&self, _: &mut App) {}
264 }
265
266 #[test]
267 fn basic_ordering() {
268 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
269 .add(PluginA)
270 .add(PluginB)
271 .add(PluginC);
272
273 assert_eq!(
274 group.order,
275 vec![
276 std::any::TypeId::of::<PluginA>(),
277 std::any::TypeId::of::<PluginB>(),
278 std::any::TypeId::of::<PluginC>(),
279 ]
280 );
281 }
282
283 #[test]
284 fn add_after() {
285 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
286 .add(PluginA)
287 .add(PluginB)
288 .add_after::<PluginA, PluginC>(PluginC);
289
290 assert_eq!(
291 group.order,
292 vec![
293 std::any::TypeId::of::<PluginA>(),
294 std::any::TypeId::of::<PluginC>(),
295 std::any::TypeId::of::<PluginB>(),
296 ]
297 );
298 }
299
300 #[test]
301 fn add_before() {
302 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
303 .add(PluginA)
304 .add(PluginB)
305 .add_before::<PluginB, PluginC>(PluginC);
306
307 assert_eq!(
308 group.order,
309 vec![
310 std::any::TypeId::of::<PluginA>(),
311 std::any::TypeId::of::<PluginC>(),
312 std::any::TypeId::of::<PluginB>(),
313 ]
314 );
315 }
316
317 #[test]
318 fn readd() {
319 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
320 .add(PluginA)
321 .add(PluginB)
322 .add(PluginC)
323 .add(PluginB);
324
325 assert_eq!(
326 group.order,
327 vec![
328 std::any::TypeId::of::<PluginA>(),
329 std::any::TypeId::of::<PluginC>(),
330 std::any::TypeId::of::<PluginB>(),
331 ]
332 );
333 }
334
335 #[test]
336 fn readd_after() {
337 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
338 .add(PluginA)
339 .add(PluginB)
340 .add(PluginC)
341 .add_after::<PluginA, PluginC>(PluginC);
342
343 assert_eq!(
344 group.order,
345 vec![
346 std::any::TypeId::of::<PluginA>(),
347 std::any::TypeId::of::<PluginC>(),
348 std::any::TypeId::of::<PluginB>(),
349 ]
350 );
351 }
352
353 #[test]
354 fn readd_before() {
355 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
356 .add(PluginA)
357 .add(PluginB)
358 .add(PluginC)
359 .add_before::<PluginB, PluginC>(PluginC);
360
361 assert_eq!(
362 group.order,
363 vec![
364 std::any::TypeId::of::<PluginA>(),
365 std::any::TypeId::of::<PluginC>(),
366 std::any::TypeId::of::<PluginB>(),
367 ]
368 );
369 }
370
371 #[test]
372 fn add_basic_subgroup() {
373 let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
374 .add(PluginA)
375 .add(PluginB);
376
377 let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
378 .add_group(group_a)
379 .add(PluginC);
380
381 assert_eq!(
382 group_b.order,
383 vec![
384 std::any::TypeId::of::<PluginA>(),
385 std::any::TypeId::of::<PluginB>(),
386 std::any::TypeId::of::<PluginC>(),
387 ]
388 );
389 }
390
391 #[test]
392 fn add_conflicting_subgroup() {
393 let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
394 .add(PluginA)
395 .add(PluginC);
396
397 let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
398 .add(PluginB)
399 .add(PluginC);
400
401 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
402 .add_group(group_a)
403 .add_group(group_b);
404
405 assert_eq!(
406 group.order,
407 vec![
408 std::any::TypeId::of::<PluginA>(),
409 std::any::TypeId::of::<PluginB>(),
410 std::any::TypeId::of::<PluginC>(),
411 ]
412 );
413 }
414}