Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_app/src/plugin_group.rs
6595 views
1
use crate::{App, AppError, Plugin};
2
use alloc::{
3
boxed::Box,
4
string::{String, ToString},
5
vec::Vec,
6
};
7
use bevy_platform::collections::hash_map::Entry;
8
use bevy_utils::TypeIdMap;
9
use core::any::TypeId;
10
use log::{debug, warn};
11
12
/// A macro for generating a well-documented [`PluginGroup`] from a list of [`Plugin`] paths.
13
///
14
/// Every plugin must implement the [`Default`] trait.
15
///
16
/// # Example
17
///
18
/// ```
19
/// # use bevy_app::*;
20
/// #
21
/// # mod velocity {
22
/// # use bevy_app::*;
23
/// # #[derive(Default)]
24
/// # pub struct VelocityPlugin;
25
/// # impl Plugin for VelocityPlugin { fn build(&self, _: &mut App) {} }
26
/// # }
27
/// #
28
/// # mod collision {
29
/// # pub mod capsule {
30
/// # use bevy_app::*;
31
/// # #[derive(Default)]
32
/// # pub struct CapsuleCollisionPlugin;
33
/// # impl Plugin for CapsuleCollisionPlugin { fn build(&self, _: &mut App) {} }
34
/// # }
35
/// # }
36
/// #
37
/// # #[derive(Default)]
38
/// # pub struct TickratePlugin;
39
/// # impl Plugin for TickratePlugin { fn build(&self, _: &mut App) {} }
40
/// #
41
/// # mod features {
42
/// # use bevy_app::*;
43
/// # #[derive(Default)]
44
/// # pub struct ForcePlugin;
45
/// # impl Plugin for ForcePlugin { fn build(&self, _: &mut App) {} }
46
/// # }
47
/// #
48
/// # mod web {
49
/// # use bevy_app::*;
50
/// # #[derive(Default)]
51
/// # pub struct WebCompatibilityPlugin;
52
/// # impl Plugin for WebCompatibilityPlugin { fn build(&self, _: &mut App) {} }
53
/// # }
54
/// #
55
/// # mod audio {
56
/// # use bevy_app::*;
57
/// # #[derive(Default)]
58
/// # pub struct AudioPlugins;
59
/// # impl PluginGroup for AudioPlugins {
60
/// # fn build(self) -> PluginGroupBuilder {
61
/// # PluginGroupBuilder::start::<Self>()
62
/// # }
63
/// # }
64
/// # }
65
/// #
66
/// # mod internal {
67
/// # use bevy_app::*;
68
/// # #[derive(Default)]
69
/// # pub struct InternalPlugin;
70
/// # impl Plugin for InternalPlugin { fn build(&self, _: &mut App) {} }
71
/// # }
72
/// #
73
/// plugin_group! {
74
/// /// Doc comments and annotations are supported: they will be added to the generated plugin
75
/// /// group.
76
/// #[derive(Debug)]
77
/// pub struct PhysicsPlugins {
78
/// // If referencing a plugin within the same module, you must prefix it with a colon `:`.
79
/// :TickratePlugin,
80
/// // If referencing a plugin within a different module, there must be three colons `:::`
81
/// // between the final module and the plugin name.
82
/// collision::capsule:::CapsuleCollisionPlugin,
83
/// velocity:::VelocityPlugin,
84
/// // If you feature-flag a plugin, it will be automatically documented. There can only be
85
/// // one automatically documented feature flag, and it must be first. All other
86
/// // `#[cfg()]` attributes must be wrapped by `#[custom()]`.
87
/// #[cfg(feature = "external_forces")]
88
/// features:::ForcePlugin,
89
/// // More complicated `#[cfg()]`s and annotations are not supported by automatic doc
90
/// // generation, in which case you must wrap it in `#[custom()]`.
91
/// #[custom(cfg(target_arch = "wasm32"))]
92
/// web:::WebCompatibilityPlugin,
93
/// // You can nest `PluginGroup`s within other `PluginGroup`s, you just need the
94
/// // `#[plugin_group]` attribute.
95
/// #[plugin_group]
96
/// audio:::AudioPlugins,
97
/// // You can hide plugins from documentation. Due to macro limitations, hidden plugins
98
/// // must be last.
99
/// #[doc(hidden)]
100
/// internal:::InternalPlugin
101
/// }
102
/// /// You may add doc comments after the plugin group as well. They will be appended after
103
/// /// the documented list of plugins.
104
/// }
105
/// ```
106
#[macro_export]
107
macro_rules! plugin_group {
108
{
109
$(#[$group_meta:meta])*
110
$vis:vis struct $group:ident {
111
$(
112
$(#[cfg(feature = $plugin_feature:literal)])?
113
$(#[custom($plugin_meta:meta)])*
114
$($plugin_path:ident::)* : $plugin_name:ident
115
),*
116
$(
117
$(,)?$(
118
#[plugin_group]
119
$(#[cfg(feature = $plugin_group_feature:literal)])?
120
$(#[custom($plugin_group_meta:meta)])*
121
$($plugin_group_path:ident::)* : $plugin_group_name:ident
122
),+
123
)?
124
$(
125
$(,)?$(
126
#[doc(hidden)]
127
$(#[cfg(feature = $hidden_plugin_feature:literal)])?
128
$(#[custom($hidden_plugin_meta:meta)])*
129
$($hidden_plugin_path:ident::)* : $hidden_plugin_name:ident
130
),+
131
)?
132
133
$(,)?
134
}
135
$($(#[doc = $post_doc:literal])+)?
136
} => {
137
$(#[$group_meta])*
138
///
139
$(#[doc = concat!(
140
" - [`", stringify!($plugin_name), "`](" $(, stringify!($plugin_path), "::")*, stringify!($plugin_name), ")"
141
$(, " - with feature `", $plugin_feature, "`")?
142
)])*
143
$($(#[doc = concat!(
144
" - [`", stringify!($plugin_group_name), "`](" $(, stringify!($plugin_group_path), "::")*, stringify!($plugin_group_name), ")"
145
$(, " - with feature `", $plugin_group_feature, "`")?
146
)])+)?
147
$(
148
///
149
$(#[doc = $post_doc])+
150
)?
151
$vis struct $group;
152
153
impl $crate::PluginGroup for $group {
154
fn build(self) -> $crate::PluginGroupBuilder {
155
let mut group = $crate::PluginGroupBuilder::start::<Self>();
156
157
$(
158
$(#[cfg(feature = $plugin_feature)])?
159
$(#[$plugin_meta])*
160
{
161
const _: () = {
162
const fn check_default<T: Default>() {}
163
check_default::<$($plugin_path::)*$plugin_name>();
164
};
165
166
group = group.add(<$($plugin_path::)*$plugin_name>::default());
167
}
168
)*
169
$($(
170
$(#[cfg(feature = $plugin_group_feature)])?
171
$(#[$plugin_group_meta])*
172
{
173
const _: () = {
174
const fn check_default<T: Default>() {}
175
check_default::<$($plugin_group_path::)*$plugin_group_name>();
176
};
177
178
group = group.add_group(<$($plugin_group_path::)*$plugin_group_name>::default());
179
}
180
)+)?
181
$($(
182
$(#[cfg(feature = $hidden_plugin_feature)])?
183
$(#[$hidden_plugin_meta])*
184
{
185
const _: () = {
186
const fn check_default<T: Default>() {}
187
check_default::<$($hidden_plugin_path::)*$hidden_plugin_name>();
188
};
189
190
group = group.add(<$($hidden_plugin_path::)*$hidden_plugin_name>::default());
191
}
192
)+)?
193
194
group
195
}
196
}
197
};
198
}
199
200
/// Combines multiple [`Plugin`]s into a single unit.
201
///
202
/// If you want an easier, but slightly more restrictive, method of implementing this trait, you
203
/// may be interested in the [`plugin_group!`] macro.
204
pub trait PluginGroup: Sized {
205
/// Configures the [`Plugin`]s that are to be added.
206
fn build(self) -> PluginGroupBuilder;
207
/// Configures a name for the [`PluginGroup`] which is primarily used for debugging.
208
fn name() -> String {
209
core::any::type_name::<Self>().to_string()
210
}
211
/// Sets the value of the given [`Plugin`], if it exists
212
fn set<T: Plugin>(self, plugin: T) -> PluginGroupBuilder {
213
self.build().set(plugin)
214
}
215
}
216
217
struct PluginEntry {
218
plugin: Box<dyn Plugin>,
219
enabled: bool,
220
}
221
222
impl PluginGroup for PluginGroupBuilder {
223
fn build(self) -> PluginGroupBuilder {
224
self
225
}
226
}
227
228
/// Facilitates the creation and configuration of a [`PluginGroup`].
229
///
230
/// Provides a build ordering to ensure that [`Plugin`]s which produce/require a [`Resource`](bevy_ecs::resource::Resource)
231
/// are built before/after dependent/depending [`Plugin`]s. [`Plugin`]s inside the group
232
/// can be disabled, enabled or reordered.
233
pub struct PluginGroupBuilder {
234
group_name: String,
235
plugins: TypeIdMap<PluginEntry>,
236
order: Vec<TypeId>,
237
}
238
239
impl PluginGroupBuilder {
240
/// Start a new builder for the [`PluginGroup`].
241
pub fn start<PG: PluginGroup>() -> Self {
242
Self {
243
group_name: PG::name(),
244
plugins: Default::default(),
245
order: Default::default(),
246
}
247
}
248
249
/// Checks if the [`PluginGroupBuilder`] contains the given [`Plugin`].
250
pub fn contains<T: Plugin>(&self) -> bool {
251
self.plugins.contains_key(&TypeId::of::<T>())
252
}
253
254
/// Returns `true` if the [`PluginGroupBuilder`] contains the given [`Plugin`] and it's enabled.
255
pub fn enabled<T: Plugin>(&self) -> bool {
256
self.plugins
257
.get(&TypeId::of::<T>())
258
.is_some_and(|e| e.enabled)
259
}
260
261
/// Finds the index of a target [`Plugin`].
262
fn index_of<Target: Plugin>(&self) -> Option<usize> {
263
self.order
264
.iter()
265
.position(|&ty| ty == TypeId::of::<Target>())
266
}
267
268
// Insert the new plugin as enabled, and removes its previous ordering if it was
269
// already present
270
fn upsert_plugin_state<T: Plugin>(&mut self, plugin: T, added_at_index: usize) {
271
self.upsert_plugin_entry_state(
272
TypeId::of::<T>(),
273
PluginEntry {
274
plugin: Box::new(plugin),
275
enabled: true,
276
},
277
added_at_index,
278
);
279
}
280
281
// Insert the new plugin entry as enabled, and removes its previous ordering if it was
282
// already present
283
fn upsert_plugin_entry_state(
284
&mut self,
285
key: TypeId,
286
plugin: PluginEntry,
287
added_at_index: usize,
288
) {
289
if let Some(entry) = self.plugins.insert(key, plugin) {
290
if entry.enabled {
291
warn!(
292
"You are replacing plugin '{}' that was not disabled.",
293
entry.plugin.name()
294
);
295
}
296
if let Some(to_remove) = self
297
.order
298
.iter()
299
.enumerate()
300
.find(|(i, ty)| *i != added_at_index && **ty == key)
301
.map(|(i, _)| i)
302
{
303
self.order.remove(to_remove);
304
}
305
}
306
}
307
308
/// Sets the value of the given [`Plugin`], if it exists.
309
///
310
/// # Panics
311
///
312
/// Panics if the [`Plugin`] does not exist.
313
pub fn set<T: Plugin>(self, plugin: T) -> Self {
314
self.try_set(plugin).unwrap_or_else(|_| {
315
panic!(
316
"{} does not exist in this PluginGroup",
317
core::any::type_name::<T>(),
318
)
319
})
320
}
321
322
/// Tries to set the value of the given [`Plugin`], if it exists.
323
///
324
/// If the given plugin doesn't exist returns self and the passed in [`Plugin`].
325
pub fn try_set<T: Plugin>(mut self, plugin: T) -> Result<Self, (Self, T)> {
326
match self.plugins.entry(TypeId::of::<T>()) {
327
Entry::Occupied(mut entry) => {
328
entry.get_mut().plugin = Box::new(plugin);
329
330
Ok(self)
331
}
332
Entry::Vacant(_) => Err((self, plugin)),
333
}
334
}
335
336
/// Adds the plugin [`Plugin`] at the end of this [`PluginGroupBuilder`]. If the plugin was
337
/// already in the group, it is removed from its previous place.
338
// This is not confusing, clippy!
339
#[expect(
340
clippy::should_implement_trait,
341
reason = "This does not emulate the `+` operator, but is more akin to pushing to a stack."
342
)]
343
pub fn add<T: Plugin>(mut self, plugin: T) -> Self {
344
let target_index = self.order.len();
345
self.order.push(TypeId::of::<T>());
346
self.upsert_plugin_state(plugin, target_index);
347
self
348
}
349
350
/// Attempts to add the plugin [`Plugin`] at the end of this [`PluginGroupBuilder`].
351
///
352
/// If the plugin was already in the group the addition fails.
353
pub fn try_add<T: Plugin>(self, plugin: T) -> Result<Self, (Self, T)> {
354
if self.contains::<T>() {
355
return Err((self, plugin));
356
}
357
358
Ok(self.add(plugin))
359
}
360
361
/// Adds a [`PluginGroup`] at the end of this [`PluginGroupBuilder`]. If the plugin was
362
/// already in the group, it is removed from its previous place.
363
pub fn add_group(mut self, group: impl PluginGroup) -> Self {
364
let Self {
365
mut plugins, order, ..
366
} = group.build();
367
368
for plugin_id in order {
369
self.upsert_plugin_entry_state(
370
plugin_id,
371
plugins.remove(&plugin_id).unwrap(),
372
self.order.len(),
373
);
374
375
self.order.push(plugin_id);
376
}
377
378
self
379
}
380
381
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`.
382
///
383
/// If the plugin was already the group, it is removed from its previous place.
384
///
385
/// # Panics
386
///
387
/// Panics if `Target` is not already in this [`PluginGroupBuilder`].
388
pub fn add_before<Target: Plugin>(self, plugin: impl Plugin) -> Self {
389
self.try_add_before_overwrite::<Target, _>(plugin)
390
.unwrap_or_else(|_| {
391
panic!(
392
"Plugin does not exist in group: {}.",
393
core::any::type_name::<Target>()
394
)
395
})
396
}
397
398
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`.
399
///
400
/// If the plugin was already in the group the add fails. If there isn't a plugin
401
/// of type `Target` in the group the plugin we're trying to insert is returned.
402
pub fn try_add_before<Target: Plugin, Insert: Plugin>(
403
self,
404
plugin: Insert,
405
) -> Result<Self, (Self, Insert)> {
406
if self.contains::<Insert>() {
407
return Err((self, plugin));
408
}
409
410
self.try_add_before_overwrite::<Target, _>(plugin)
411
}
412
413
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`.
414
///
415
/// If the plugin was already in the group, it is removed from its previous places.
416
/// If there isn't a plugin of type `Target` in the group the plugin we're trying to insert
417
/// is returned.
418
pub fn try_add_before_overwrite<Target: Plugin, Insert: Plugin>(
419
mut self,
420
plugin: Insert,
421
) -> Result<Self, (Self, Insert)> {
422
let Some(target_index) = self.index_of::<Target>() else {
423
return Err((self, plugin));
424
};
425
426
self.order.insert(target_index, TypeId::of::<Insert>());
427
self.upsert_plugin_state(plugin, target_index);
428
Ok(self)
429
}
430
431
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`.
432
///
433
/// If the plugin was already the group, it is removed from its previous place.
434
///
435
/// # Panics
436
///
437
/// Panics if `Target` is not already in this [`PluginGroupBuilder`].
438
pub fn add_after<Target: Plugin>(self, plugin: impl Plugin) -> Self {
439
self.try_add_after_overwrite::<Target, _>(plugin)
440
.unwrap_or_else(|_| {
441
panic!(
442
"Plugin does not exist in group: {}.",
443
core::any::type_name::<Target>()
444
)
445
})
446
}
447
448
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`.
449
///
450
/// If the plugin was already in the group the add fails. If there isn't a plugin
451
/// of type `Target` in the group the plugin we're trying to insert is returned.
452
pub fn try_add_after<Target: Plugin, Insert: Plugin>(
453
self,
454
plugin: Insert,
455
) -> Result<Self, (Self, Insert)> {
456
if self.contains::<Insert>() {
457
return Err((self, plugin));
458
}
459
460
self.try_add_after_overwrite::<Target, _>(plugin)
461
}
462
463
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`.
464
///
465
/// If the plugin was already in the group, it is removed from its previous places.
466
/// If there isn't a plugin of type `Target` in the group the plugin we're trying to insert
467
/// is returned.
468
pub fn try_add_after_overwrite<Target: Plugin, Insert: Plugin>(
469
mut self,
470
plugin: Insert,
471
) -> Result<Self, (Self, Insert)> {
472
let Some(target_index) = self.index_of::<Target>() else {
473
return Err((self, plugin));
474
};
475
476
let target_index = target_index + 1;
477
478
self.order.insert(target_index, TypeId::of::<Insert>());
479
self.upsert_plugin_state(plugin, target_index);
480
Ok(self)
481
}
482
483
/// Enables a [`Plugin`].
484
///
485
/// [`Plugin`]s within a [`PluginGroup`] are enabled by default. This function is used to
486
/// opt back in to a [`Plugin`] after [disabling](Self::disable) it. If there are no plugins
487
/// of type `T` in this group, it will panic.
488
pub fn enable<T: Plugin>(mut self) -> Self {
489
let plugin_entry = self
490
.plugins
491
.get_mut(&TypeId::of::<T>())
492
.expect("Cannot enable a plugin that does not exist.");
493
plugin_entry.enabled = true;
494
self
495
}
496
497
/// Disables a [`Plugin`], preventing it from being added to the [`App`] with the rest of the
498
/// [`PluginGroup`]. The disabled [`Plugin`] keeps its place in the [`PluginGroup`], so it can
499
/// still be used for ordering with [`add_before`](Self::add_before) or
500
/// [`add_after`](Self::add_after), or it can be [re-enabled](Self::enable). If there are no
501
/// plugins of type `T` in this group, it will panic.
502
pub fn disable<T: Plugin>(mut self) -> Self {
503
let plugin_entry = self
504
.plugins
505
.get_mut(&TypeId::of::<T>())
506
.expect("Cannot disable a plugin that does not exist.");
507
plugin_entry.enabled = false;
508
self
509
}
510
511
/// Consumes the [`PluginGroupBuilder`] and [builds](Plugin::build) the contained [`Plugin`]s
512
/// in the order specified.
513
///
514
/// # Panics
515
///
516
/// Panics if one of the plugin in the group was already added to the application.
517
#[track_caller]
518
pub fn finish(mut self, app: &mut App) {
519
for ty in &self.order {
520
if let Some(entry) = self.plugins.remove(ty)
521
&& entry.enabled
522
{
523
debug!("added plugin: {}", entry.plugin.name());
524
if let Err(AppError::DuplicatePlugin { plugin_name }) =
525
app.add_boxed_plugin(entry.plugin)
526
{
527
panic!(
528
"Error adding plugin {} in group {}: plugin was already added in application",
529
plugin_name,
530
self.group_name
531
);
532
}
533
}
534
}
535
}
536
}
537
538
/// A plugin group which doesn't do anything. Useful for examples:
539
/// ```
540
/// # use bevy_app::prelude::*;
541
/// use bevy_app::NoopPluginGroup as MinimalPlugins;
542
///
543
/// fn main(){
544
/// App::new().add_plugins(MinimalPlugins).run();
545
/// }
546
/// ```
547
#[doc(hidden)]
548
pub struct NoopPluginGroup;
549
550
impl PluginGroup for NoopPluginGroup {
551
fn build(self) -> PluginGroupBuilder {
552
PluginGroupBuilder::start::<Self>()
553
}
554
}
555
556
#[cfg(test)]
557
mod tests {
558
use alloc::vec;
559
use core::{any::TypeId, fmt::Debug};
560
561
use super::PluginGroupBuilder;
562
use crate::{App, NoopPluginGroup, Plugin, PluginGroup};
563
564
#[derive(Default)]
565
struct PluginA;
566
impl Plugin for PluginA {
567
fn build(&self, _: &mut App) {}
568
}
569
570
#[derive(Default)]
571
struct PluginB;
572
impl Plugin for PluginB {
573
fn build(&self, _: &mut App) {}
574
}
575
576
#[derive(Default)]
577
struct PluginC;
578
impl Plugin for PluginC {
579
fn build(&self, _: &mut App) {}
580
}
581
582
#[derive(PartialEq, Debug)]
583
struct PluginWithData(u32);
584
impl Plugin for PluginWithData {
585
fn build(&self, _: &mut App) {}
586
}
587
588
fn get_plugin<T: Debug + 'static>(group: &PluginGroupBuilder, id: TypeId) -> &T {
589
group.plugins[&id]
590
.plugin
591
.as_any()
592
.downcast_ref::<T>()
593
.unwrap()
594
}
595
596
#[test]
597
fn contains() {
598
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
599
.add(PluginA)
600
.add(PluginB);
601
602
assert!(group.contains::<PluginA>());
603
assert!(!group.contains::<PluginC>());
604
605
let group = group.disable::<PluginA>();
606
607
assert!(group.enabled::<PluginB>());
608
assert!(!group.enabled::<PluginA>());
609
}
610
611
#[test]
612
fn basic_ordering() {
613
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
614
.add(PluginA)
615
.add(PluginB)
616
.add(PluginC);
617
618
assert_eq!(
619
group.order,
620
vec![
621
TypeId::of::<PluginA>(),
622
TypeId::of::<PluginB>(),
623
TypeId::of::<PluginC>(),
624
]
625
);
626
}
627
628
#[test]
629
fn add_before() {
630
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
631
.add(PluginA)
632
.add(PluginB)
633
.add_before::<PluginB>(PluginC);
634
635
assert_eq!(
636
group.order,
637
vec![
638
TypeId::of::<PluginA>(),
639
TypeId::of::<PluginC>(),
640
TypeId::of::<PluginB>(),
641
]
642
);
643
}
644
645
#[test]
646
fn try_add_before() {
647
let group = PluginGroupBuilder::start::<NoopPluginGroup>().add(PluginA);
648
649
let Ok(group) = group.try_add_before::<PluginA, _>(PluginC) else {
650
panic!("PluginA wasn't in group");
651
};
652
653
assert_eq!(
654
group.order,
655
vec![TypeId::of::<PluginC>(), TypeId::of::<PluginA>(),]
656
);
657
658
assert!(group.try_add_before::<PluginA, _>(PluginC).is_err());
659
}
660
661
#[test]
662
#[should_panic(
663
expected = "Plugin does not exist in group: bevy_app::plugin_group::tests::PluginB."
664
)]
665
fn add_before_nonexistent() {
666
PluginGroupBuilder::start::<NoopPluginGroup>()
667
.add(PluginA)
668
.add_before::<PluginB>(PluginC);
669
}
670
671
#[test]
672
fn add_after() {
673
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
674
.add(PluginA)
675
.add(PluginB)
676
.add_after::<PluginA>(PluginC);
677
678
assert_eq!(
679
group.order,
680
vec![
681
TypeId::of::<PluginA>(),
682
TypeId::of::<PluginC>(),
683
TypeId::of::<PluginB>(),
684
]
685
);
686
}
687
688
#[test]
689
fn try_add_after() {
690
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
691
.add(PluginA)
692
.add(PluginB);
693
694
let Ok(group) = group.try_add_after::<PluginA, _>(PluginC) else {
695
panic!("PluginA wasn't in group");
696
};
697
698
assert_eq!(
699
group.order,
700
vec![
701
TypeId::of::<PluginA>(),
702
TypeId::of::<PluginC>(),
703
TypeId::of::<PluginB>(),
704
]
705
);
706
707
assert!(group.try_add_after::<PluginA, _>(PluginC).is_err());
708
}
709
710
#[test]
711
#[should_panic(
712
expected = "Plugin does not exist in group: bevy_app::plugin_group::tests::PluginB."
713
)]
714
fn add_after_nonexistent() {
715
PluginGroupBuilder::start::<NoopPluginGroup>()
716
.add(PluginA)
717
.add_after::<PluginB>(PluginC);
718
}
719
720
#[test]
721
fn add_overwrite() {
722
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
723
.add(PluginA)
724
.add(PluginWithData(0x0F))
725
.add(PluginC);
726
727
let id = TypeId::of::<PluginWithData>();
728
assert_eq!(
729
get_plugin::<PluginWithData>(&group, id),
730
&PluginWithData(0x0F)
731
);
732
733
let group = group.add(PluginWithData(0xA0));
734
735
assert_eq!(
736
get_plugin::<PluginWithData>(&group, id),
737
&PluginWithData(0xA0)
738
);
739
assert_eq!(
740
group.order,
741
vec![
742
TypeId::of::<PluginA>(),
743
TypeId::of::<PluginC>(),
744
TypeId::of::<PluginWithData>(),
745
]
746
);
747
748
let Ok(group) = group.try_add_before_overwrite::<PluginA, _>(PluginWithData(0x01)) else {
749
panic!("PluginA wasn't in group");
750
};
751
assert_eq!(
752
get_plugin::<PluginWithData>(&group, id),
753
&PluginWithData(0x01)
754
);
755
assert_eq!(
756
group.order,
757
vec![
758
TypeId::of::<PluginWithData>(),
759
TypeId::of::<PluginA>(),
760
TypeId::of::<PluginC>(),
761
]
762
);
763
764
let Ok(group) = group.try_add_after_overwrite::<PluginA, _>(PluginWithData(0xdeadbeef))
765
else {
766
panic!("PluginA wasn't in group");
767
};
768
assert_eq!(
769
get_plugin::<PluginWithData>(&group, id),
770
&PluginWithData(0xdeadbeef)
771
);
772
assert_eq!(
773
group.order,
774
vec![
775
TypeId::of::<PluginA>(),
776
TypeId::of::<PluginWithData>(),
777
TypeId::of::<PluginC>(),
778
]
779
);
780
}
781
782
#[test]
783
fn readd() {
784
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
785
.add(PluginA)
786
.add(PluginB)
787
.add(PluginC)
788
.add(PluginB);
789
790
assert_eq!(
791
group.order,
792
vec![
793
TypeId::of::<PluginA>(),
794
TypeId::of::<PluginC>(),
795
TypeId::of::<PluginB>(),
796
]
797
);
798
}
799
800
#[test]
801
fn readd_before() {
802
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
803
.add(PluginA)
804
.add(PluginB)
805
.add(PluginC)
806
.add_before::<PluginB>(PluginC);
807
808
assert_eq!(
809
group.order,
810
vec![
811
TypeId::of::<PluginA>(),
812
TypeId::of::<PluginC>(),
813
TypeId::of::<PluginB>(),
814
]
815
);
816
}
817
818
#[test]
819
fn readd_after() {
820
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
821
.add(PluginA)
822
.add(PluginB)
823
.add(PluginC)
824
.add_after::<PluginA>(PluginC);
825
826
assert_eq!(
827
group.order,
828
vec![
829
TypeId::of::<PluginA>(),
830
TypeId::of::<PluginC>(),
831
TypeId::of::<PluginB>(),
832
]
833
);
834
}
835
836
#[test]
837
fn add_basic_subgroup() {
838
let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
839
.add(PluginA)
840
.add(PluginB);
841
842
let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
843
.add_group(group_a)
844
.add(PluginC);
845
846
assert_eq!(
847
group_b.order,
848
vec![
849
TypeId::of::<PluginA>(),
850
TypeId::of::<PluginB>(),
851
TypeId::of::<PluginC>(),
852
]
853
);
854
}
855
856
#[test]
857
fn add_conflicting_subgroup() {
858
let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
859
.add(PluginA)
860
.add(PluginC);
861
862
let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
863
.add(PluginB)
864
.add(PluginC);
865
866
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
867
.add_group(group_a)
868
.add_group(group_b);
869
870
assert_eq!(
871
group.order,
872
vec![
873
TypeId::of::<PluginA>(),
874
TypeId::of::<PluginB>(),
875
TypeId::of::<PluginC>(),
876
]
877
);
878
}
879
880
plugin_group! {
881
#[derive(Default)]
882
struct PluginGroupA {
883
:PluginA
884
}
885
}
886
plugin_group! {
887
#[derive(Default)]
888
struct PluginGroupB {
889
:PluginB
890
}
891
}
892
plugin_group! {
893
struct PluginGroupC {
894
:PluginC
895
#[plugin_group]
896
:PluginGroupA,
897
#[plugin_group]
898
:PluginGroupB,
899
}
900
}
901
#[test]
902
fn construct_nested_plugin_groups() {
903
PluginGroupC {}.build();
904
}
905
}
906
907