Skip to main content

bevy_ecs/system/
access.rs

1use alloc::borrow::Cow;
2use bevy_utils::prelude::ShortName;
3
4use crate::{
5    query::{AccessConflicts, FilteredAccess, FilteredAccessSet},
6    system::SystemMeta,
7};
8
9/// Represents the access a [`System`] requires to the [`World`].
10///
11/// [`System`]: crate::system::System
12/// [`World`]: crate::world::World
13#[derive(Clone, Debug, Default, PartialEq, Eq)]
14pub enum SystemAccess {
15    /// The system does not access the world at all.
16    #[default]
17    None,
18    /// The system requires shared access to the world, which means it can run
19    /// in parallel with other systems that also require shared access, as long
20    /// as they don't require exclusive access to the same components.
21    Shared(FilteredAccessSet),
22    /// The system requires exclusive access to the world, meaning no other
23    /// [`Shared`] or [`Exclusive`] system can run in parallel with it.
24    ///
25    /// [`Shared`]: SystemAccess::Shared
26    /// [`Exclusive`]: SystemAccess::Exclusive
27    Exclusive,
28}
29
30impl SystemAccess {
31    /// Returns true if the system does not access the world at all, so it can run
32    /// in parallel with any other system.
33    pub fn is_none(&self) -> bool {
34        matches!(self, Self::None)
35    }
36
37    /// Returns true if the system requires shared access to the world, which
38    /// means it can run in parallel with other systems that also require shared
39    /// access, as long as they don't require exclusive access to the same components.
40    pub fn is_shared(&self) -> bool {
41        matches!(self, Self::Shared(_))
42    }
43
44    /// Returns true if the system requires exclusive access to the world.
45    pub fn is_exclusive(&self) -> bool {
46        matches!(self, Self::Exclusive)
47    }
48
49    /// Returns true if this system's access is compatible with the other
50    /// system's access, such that they can run in parallel without conflicting
51    /// with each other.
52    pub fn is_compatible(&self, other: &Self) -> bool {
53        match (self, other) {
54            (Self::None, _) | (_, Self::None) => true,
55            (Self::Shared(access), Self::Shared(other_access)) => {
56                access.is_compatible(other_access)
57            }
58            (Self::Exclusive, _) | (_, Self::Exclusive) => false,
59        }
60    }
61
62    /// Returns the conflicts between the system's current access and the
63    /// provided access.
64    ///
65    /// - If the system currently has [`None`] access, it will return an empty
66    ///   [`AccessConflicts`] (i.e. no conflicts).
67    /// - If the system currently has [`Shared`] access, the conflicts between
68    ///   individual components or resources will be returned.
69    /// - If the system currently has [`Exclusive`] access, it will return
70    ///   [`AccessConflicts::All`].
71    ///
72    /// [`None`]: SystemAccess::None
73    /// [`Shared`]: SystemAccess::Shared
74    /// [`Exclusive`]: SystemAccess::Exclusive
75    pub fn get_conflicts_single(&self, filtered_access: &FilteredAccess) -> AccessConflicts {
76        match self {
77            SystemAccess::None => AccessConflicts::empty(),
78            SystemAccess::Shared(access) => access.get_conflicts_single(filtered_access),
79            SystemAccess::Exclusive => AccessConflicts::All,
80        }
81    }
82
83    /// Returns the conflicts between the system's current access and another
84    /// system's access.
85    ///
86    /// - If either system has [`None`] access, it will return an empty
87    ///   [`AccessConflicts`] (i.e. no conflicts).
88    /// - If both systems have [`Shared`] access, the conflicts between
89    ///   individual components or resources will be returned.
90    /// - If either system has [`Exclusive`] access, it will return
91    ///   [`AccessConflicts::All`].
92    ///
93    /// [`None`]: SystemAccess::None
94    /// [`Shared`]: SystemAccess::Shared
95    /// [`Exclusive`]: SystemAccess::Exclusive
96    pub fn get_conflicts(&self, other: &Self) -> AccessConflicts {
97        match (self, other) {
98            (SystemAccess::None, _) | (_, SystemAccess::None) => AccessConflicts::empty(),
99            (SystemAccess::Shared(access), SystemAccess::Shared(other_access)) => {
100                access.get_conflicts(other_access)
101            }
102            (SystemAccess::Exclusive, _) | (_, SystemAccess::Exclusive) => AccessConflicts::All,
103        }
104    }
105
106    /// Converts the system's current access into a [`FilteredAccessSet`].
107    pub fn to_filtered_access_set(&self) -> Cow<'_, FilteredAccessSet> {
108        match self {
109            Self::None => Cow::Owned(FilteredAccessSet::new()),
110            Self::Shared(access) => Cow::Borrowed(access),
111            Self::Exclusive => {
112                let mut access_set = FilteredAccessSet::new();
113                let mut access = FilteredAccess::default();
114                access.write_all();
115                access_set.add(access);
116                Cow::Owned(access_set)
117            }
118        }
119    }
120
121    /// Marks the system as requiring shared access to the world, which means it
122    /// can run in parallel with other systems that also require shared access,
123    /// as long as they don't require exclusive access to the same components.
124    ///
125    /// The provided type `T` is used for error reporting in case of access conflicts;
126    /// it should be the type of the system parameter that is requesting shared access.
127    ///
128    /// # Panics
129    ///
130    /// If the system already has exclusive access, this method will panic with
131    /// an error message indicating the conflict.
132    ///
133    /// # Examples
134    ///
135    /// ## Only metadata access required
136    ///
137    /// If a system parameter requires access to world metadata but not any
138    /// particular components, you may call this function without modifying the
139    /// returned [`FilteredAccessSet`]:
140    ///
141    /// ```rust,no_run
142    /// # use bevy_ecs::system::{SystemAccess, SystemMeta, SystemParam, SystemParamValidationError};
143    /// # use bevy_ecs::world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId};
144    /// # use bevy_ecs::change_detection::Tick;
145    /// pub struct MyWorldId(WorldId);
146    ///
147    /// // SAFETY: World metadata is registered and accessed.
148    /// unsafe impl SystemParam for MyWorldId {
149    ///     type State = ();
150    ///     type Item<'world, 'state> = MyWorldId;
151    ///
152    ///     fn init_state(_: &mut World) -> Self::State {}
153    ///
154    ///     fn init_access(
155    ///         _state: &Self::State,
156    ///         system_meta: &mut SystemMeta,
157    ///         system_access: &mut SystemAccess,
158    ///         _world: &mut World,
159    ///     ) {
160    ///         system_access.require_shared_access::<Self>(system_meta);
161    ///     }
162    ///
163    ///     unsafe fn get_param<'world, 'state>(
164    ///         _: &'state mut Self::State,
165    ///         _: &SystemMeta,
166    ///         world: UnsafeWorldCell<'world>,
167    ///         _: Tick,
168    ///     ) -> Result<Self::Item<'world, 'state>, SystemParamValidationError> {
169    ///         Ok(MyWorldId(world.id()))
170    ///     }
171    /// }
172    /// ```
173    pub fn require_shared_access<T>(&mut self, system_meta: &SystemMeta) -> &mut FilteredAccessSet {
174        match self {
175            this @ Self::None => {
176                *this = Self::Shared(FilteredAccessSet::new());
177                if let Self::Shared(access) = this {
178                    access
179                } else {
180                    unreachable!()
181                }
182            }
183            Self::Shared(access) => access,
184            Self::Exclusive => panic!(
185                "error[B0002]: {} in system {} conflicts with a previous system parameter.",
186                ShortName::of::<T>(),
187                system_meta.name()
188            ),
189        }
190    }
191
192    /// Marks the system as requiring exclusive access to the world, meaning no
193    /// other [`Shared`] or [`Exclusive`] system can run in parallel with it.
194    ///
195    /// The provided type `T` is used for error reporting in case of access conflicts;
196    /// it should be the type of the system parameter that is requesting exclusive access.
197    ///
198    /// # Panics
199    ///
200    /// If the system already has shared or exclusive access, this method will
201    /// panic with an error message indicating the conflict.
202    ///
203    /// [`Shared`]: SystemAccess::Shared
204    /// [`Exclusive`]: SystemAccess::Exclusive
205    pub fn require_exclusive_access<T>(&mut self, system_meta: &SystemMeta) {
206        if !matches!(self, Self::None) {
207            panic!(
208                "error[B0002]: {} in system {} conflicts with a previous system parameter.",
209                ShortName::of::<T>(),
210                system_meta.name()
211            );
212        }
213        *self = Self::Exclusive;
214    }
215
216    /// Attempts to add the provided [`FilteredAccess`] to the system's current access.
217    ///
218    /// - If the system currently has [`None`] access, it will change this access
219    ///   to [`Shared`] and add the provided [`FilteredAccess`].
220    /// - If the system currently has [`Shared`] access, it will check for conflicts
221    ///   with the provided [`FilteredAccess`]. If there are no conflicts, it will
222    ///   add the provided [`FilteredAccess`]. If there are conflicts, it will return
223    ///   an [`AccessConflicts`] error.
224    /// - If the system currently has [`Exclusive`] access, it will error
225    ///   with [`AccessConflicts::All`].
226    ///
227    /// # Errors
228    ///
229    /// If there are conflicts with the system's current access, an
230    /// [`AccessConflicts`] error will be returned.
231    ///
232    /// [`None`]: SystemAccess::None
233    /// [`Shared`]: SystemAccess::Shared
234    /// [`Exclusive`]: SystemAccess::Exclusive
235    pub fn try_add(&mut self, filtered_access: FilteredAccess) -> Result<(), AccessConflicts> {
236        let conflicts = self.get_conflicts_single(&filtered_access);
237        self.ensure_filtered_access(filtered_access);
238        if conflicts.is_empty() {
239            Ok(())
240        } else {
241            Err(conflicts)
242        }
243    }
244
245    /// Marks the system as requiring access to certain or all components or
246    /// resources in the [`World`].
247    ///
248    /// [`World`]: crate::world::World
249    pub fn ensure_filtered_access(&mut self, filtered_access: FilteredAccess) {
250        self.ensure_metadata_access();
251        // If the system is `Exclusive` then we already have access to everything,
252        // so we don't need to add anything.
253        if let Self::Shared(access) = self {
254            access.add(filtered_access);
255        }
256    }
257
258    /// Marks the system as requiring access to [`World`] metadata
259    /// (e.g. [`Archetypes`], [`Components`], etc.).
260    ///
261    /// [`World`]: crate::world::World
262    /// [`Archetypes`]: crate::archetype::Archetypes
263    /// [`Components`]: crate::component::Components
264    pub fn ensure_metadata_access(&mut self) {
265        // If the system is `Shared` or `Exclusive` then we already have access
266        // to metadata, so we don't need to add anything.
267        if let Self::None = self {
268            *self = Self::Shared(FilteredAccessSet::new());
269        }
270    }
271
272    /// Merges the provided [`SystemAccess`] into the system's current access.
273    pub fn extend(&mut self, other: Self) {
274        match (&mut *self, other) {
275            (_, Self::None) | (Self::Exclusive, _) => {
276                // Do nothing: nothing was added, or already at maximum access level
277            }
278            (Self::None, Self::Shared(other_access)) => {
279                // Upgrade self to Shared with the other access
280                *self = Self::Shared(other_access);
281            }
282            (Self::Shared(access), Self::Shared(other_access)) => {
283                // Merge the other access into self
284                access.extend(other_access);
285            }
286            (_, Self::Exclusive) => {
287                // Upgrade self to Exclusive
288                *self = Self::Exclusive;
289            }
290        }
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use crate::{
297        component::ComponentIds,
298        query::{FilteredAccess, FilteredAccessSet},
299        system::{SystemAccess, SystemMeta},
300    };
301
302    #[test]
303    fn check_default_access() {
304        let mut access = SystemAccess::default();
305
306        assert_eq!(access, SystemAccess::None);
307        assert!(access.is_none());
308        assert_ne!(access, SystemAccess::Shared(FilteredAccessSet::default()));
309        assert!(!access.is_shared());
310        assert_ne!(access, SystemAccess::Exclusive);
311        assert!(!access.is_exclusive());
312
313        access.ensure_metadata_access();
314
315        assert!(access.is_shared());
316        assert_eq!(access, SystemAccess::Shared(FilteredAccessSet::default()));
317    }
318
319    #[test]
320    fn check_shared_access() {
321        let mut access = SystemAccess::Shared(FilteredAccessSet::default());
322
323        assert_ne!(access, SystemAccess::None);
324        assert!(!access.is_none());
325        assert!(access.is_shared());
326        assert_ne!(access, SystemAccess::Exclusive);
327        assert!(!access.is_exclusive());
328
329        access.ensure_metadata_access();
330
331        assert!(access.is_shared());
332
333        access.ensure_filtered_access(FilteredAccess::default());
334
335        assert!(access.is_shared());
336    }
337
338    #[test]
339    fn check_exclusive_access() {
340        let mut access = SystemAccess::Exclusive;
341
342        assert_ne!(access, SystemAccess::None);
343        assert!(!access.is_none());
344        assert_ne!(access, SystemAccess::Shared(FilteredAccessSet::default()));
345        assert!(!access.is_shared());
346        assert!(access.is_exclusive());
347
348        access.ensure_metadata_access();
349
350        assert!(access.is_exclusive());
351
352        access.ensure_filtered_access(FilteredAccess::default());
353
354        assert!(access.is_exclusive());
355    }
356
357    #[test]
358    fn check_compatibility() {
359        let mut ids = ComponentIds::default();
360        let access_none = SystemAccess::None;
361        let access_shared = SystemAccess::Shared({
362            let mut set = FilteredAccessSet::default();
363            set.add_unfiltered_component_read(ids.next_mut());
364            set
365        });
366        let access_exclusive = SystemAccess::Exclusive;
367
368        assert!(access_none.is_compatible(&access_none));
369        assert!(access_none.is_compatible(&access_shared));
370        assert!(access_none.is_compatible(&access_exclusive));
371
372        assert!(access_shared.is_compatible(&access_none));
373        assert!(access_shared.is_compatible(&access_shared));
374        assert!(!access_shared.is_compatible(&access_exclusive));
375
376        assert!(access_exclusive.is_compatible(&access_none));
377        assert!(!access_exclusive.is_compatible(&access_shared));
378        assert!(!access_exclusive.is_compatible(&access_exclusive));
379    }
380
381    #[test]
382    fn conflict_reporting() {
383        let mut ids = ComponentIds::default();
384        let access_none = SystemAccess::None;
385        let access_shared = SystemAccess::Shared({
386            let mut set = FilteredAccessSet::default();
387            set.add_unfiltered_component_read(ids.next_mut());
388            set
389        });
390        let access_exclusive = SystemAccess::Exclusive;
391
392        assert!(access_none.get_conflicts(&access_none).is_empty());
393        assert!(access_none.get_conflicts(&access_shared).is_empty());
394        assert!(access_none.get_conflicts(&access_exclusive).is_empty());
395
396        assert!(access_shared.get_conflicts(&access_none).is_empty());
397        assert!(access_shared.get_conflicts(&access_shared).is_empty());
398        assert_eq!(
399            access_shared.get_conflicts(&access_exclusive),
400            crate::query::AccessConflicts::All
401        );
402
403        assert!(access_exclusive.get_conflicts(&access_none).is_empty());
404        assert_eq!(
405            access_exclusive.get_conflicts(&access_shared),
406            crate::query::AccessConflicts::All
407        );
408        assert_eq!(
409            access_exclusive.get_conflicts(&access_exclusive),
410            crate::query::AccessConflicts::All
411        );
412    }
413
414    #[test]
415    #[should_panic]
416    fn require_shared_access_panics_on_exclusive() {
417        let mut access = SystemAccess::Exclusive;
418        access.require_shared_access::<()>(&SystemMeta::new::<()>());
419    }
420
421    #[test]
422    #[should_panic]
423    fn require_exclusive_access_panics_on_shared() {
424        let mut access = SystemAccess::Shared(FilteredAccessSet::default());
425        access.require_exclusive_access::<()>(&SystemMeta::new::<()>());
426    }
427
428    #[test]
429    #[should_panic]
430    fn require_exclusive_access_panics_on_exclusive() {
431        let mut access = SystemAccess::Exclusive;
432        access.require_exclusive_access::<()>(&SystemMeta::new::<()>());
433    }
434
435    #[test]
436    fn try_add_returns_correctly() {
437        let mut access = SystemAccess::None;
438        let filtered_access = FilteredAccess::default();
439
440        assert!(access.try_add(filtered_access.clone()).is_ok());
441        assert!(access.is_shared());
442
443        let mut access_shared = SystemAccess::Shared(FilteredAccessSet::default());
444        assert!(access_shared.try_add(filtered_access.clone()).is_ok());
445
446        let mut access_exclusive = SystemAccess::Exclusive;
447        assert!(access_exclusive.try_add(filtered_access).is_err());
448    }
449
450    #[test]
451    fn conversion_to_access_sets() {
452        let mut ids = ComponentIds::default();
453        let id_1 = ids.next_mut();
454
455        let access_none = SystemAccess::None;
456        let access_shared = SystemAccess::Shared({
457            let mut set = FilteredAccessSet::default();
458            set.add_unfiltered_component_read(id_1);
459            set
460        });
461        let access_exclusive = SystemAccess::Exclusive;
462
463        assert_eq!(
464            access_none.to_filtered_access_set().into_owned(),
465            FilteredAccessSet::new()
466        );
467        assert_eq!(access_shared.to_filtered_access_set().into_owned(), {
468            let mut set = FilteredAccessSet::default();
469            set.add_unfiltered_component_read(id_1);
470            set
471        });
472        assert_eq!(access_exclusive.to_filtered_access_set().into_owned(), {
473            let mut set = FilteredAccessSet::new();
474            let mut access = FilteredAccess::default();
475            access.write_all();
476            set.add(access);
477            set
478        });
479    }
480
481    #[test]
482    fn extending_access() {
483        let mut ids = ComponentIds::default();
484        let mut access = SystemAccess::default();
485
486        let access_none = SystemAccess::None;
487        let access_shared = SystemAccess::Shared({
488            let mut set = FilteredAccessSet::default();
489            set.add_unfiltered_component_read(ids.next_mut());
490            set
491        });
492        let access_exclusive = SystemAccess::Exclusive;
493
494        access.extend(access_none.clone());
495        assert_eq!(access, SystemAccess::None);
496
497        access.extend(access_shared.clone());
498        assert_eq!(access, access_shared);
499
500        access.extend(access_exclusive.clone());
501        assert_eq!(access, access_exclusive);
502    }
503}