bevy_ecs/change_detection/
maybe_location.rs

1#[cfg(feature = "bevy_reflect")]
2use bevy_reflect::Reflect;
3use core::{
4    marker::PhantomData,
5    ops::{Deref, DerefMut},
6    panic::Location,
7};
8
9/// A value that contains a `T` if the `track_location` feature is enabled,
10/// and is a ZST if it is not.
11///
12/// The overall API is similar to [`Option`], but whether the value is `Some` or `None` is set at compile
13/// time and is the same for all values.
14///
15/// If the `track_location` feature is disabled, then all functions on this type that return
16/// an `MaybeLocation` will have an empty body and should be removed by the optimizer.
17///
18/// This allows code to be written that will be checked by the compiler even when the feature is disabled,
19/// but that will be entirely removed during compilation.
20#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
21#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
22pub struct MaybeLocation<T: ?Sized = &'static Location<'static>> {
23    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
24    marker: PhantomData<T>,
25    #[cfg(feature = "track_location")]
26    value: T,
27}
28
29impl<T: core::fmt::Display> core::fmt::Display for MaybeLocation<T> {
30    fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31        #[cfg(feature = "track_location")]
32        {
33            self.value.fmt(_f)?;
34        }
35        Ok(())
36    }
37}
38
39impl<T> MaybeLocation<T> {
40    /// Constructs a new `MaybeLocation` that wraps the given value.
41    ///
42    /// This may only accept `Copy` types,
43    /// since it needs to drop the value if the `track_location` feature is disabled,
44    /// and non-`Copy` types cannot be dropped in `const` context.
45    /// Use [`new_with`][Self::new_with] if you need to construct a non-`Copy` value.
46    ///
47    /// # See also
48    /// - [`new_with`][Self::new_with] to initialize using a closure.
49    /// - [`new_with_flattened`][Self::new_with_flattened] to initialize using a closure that returns an `Option<MaybeLocation<T>>`.
50    #[inline]
51    pub const fn new(_value: T) -> Self
52    where
53        T: Copy,
54    {
55        Self {
56            #[cfg(feature = "track_location")]
57            value: _value,
58            marker: PhantomData,
59        }
60    }
61
62    /// Constructs a new `MaybeLocation` that wraps the result of the given closure.
63    ///
64    /// # See also
65    /// - [`new`][Self::new] to initialize using a value.
66    /// - [`new_with_flattened`][Self::new_with_flattened] to initialize using a closure that returns an `Option<MaybeLocation<T>>`.
67    #[inline]
68    pub fn new_with(_f: impl FnOnce() -> T) -> Self {
69        Self {
70            #[cfg(feature = "track_location")]
71            value: _f(),
72            marker: PhantomData,
73        }
74    }
75
76    /// Maps an `MaybeLocation<T> `to `MaybeLocation<U>` by applying a function to a contained value.
77    #[inline]
78    pub fn map<U>(self, _f: impl FnOnce(T) -> U) -> MaybeLocation<U> {
79        MaybeLocation {
80            #[cfg(feature = "track_location")]
81            value: _f(self.value),
82            marker: PhantomData,
83        }
84    }
85
86    /// Converts a pair of `MaybeLocation` values to an `MaybeLocation` of a tuple.
87    #[inline]
88    pub fn zip<U>(self, _other: MaybeLocation<U>) -> MaybeLocation<(T, U)> {
89        MaybeLocation {
90            #[cfg(feature = "track_location")]
91            value: (self.value, _other.value),
92            marker: PhantomData,
93        }
94    }
95
96    /// Returns the contained value or a default.
97    /// If the `track_location` feature is enabled, this always returns the contained value.
98    /// If it is disabled, this always returns `T::Default()`.
99    #[inline]
100    pub fn unwrap_or_default(self) -> T
101    where
102        T: Default,
103    {
104        self.into_option().unwrap_or_default()
105    }
106
107    /// Converts an `MaybeLocation` to an [`Option`] to allow run-time branching.
108    /// If the `track_location` feature is enabled, this always returns `Some`.
109    /// If it is disabled, this always returns `None`.
110    #[inline]
111    pub fn into_option(self) -> Option<T> {
112        #[cfg(feature = "track_location")]
113        {
114            Some(self.value)
115        }
116        #[cfg(not(feature = "track_location"))]
117        {
118            None
119        }
120    }
121}
122
123impl<T> MaybeLocation<Option<T>> {
124    /// Constructs a new `MaybeLocation` that wraps the result of the given closure.
125    /// If the closure returns `Some`, it unwraps the inner value.
126    ///
127    /// # See also
128    /// - [`new`][Self::new] to initialize using a value.
129    /// - [`new_with`][Self::new_with] to initialize using a closure.
130    #[inline]
131    pub fn new_with_flattened(_f: impl FnOnce() -> Option<MaybeLocation<T>>) -> Self {
132        Self {
133            #[cfg(feature = "track_location")]
134            value: _f().map(|value| value.value),
135            marker: PhantomData,
136        }
137    }
138
139    /// Transposes a `MaybeLocation` of an [`Option`] into an [`Option`] of a `MaybeLocation`.
140    ///
141    /// This can be useful if you want to use the `?` operator to exit early
142    /// if the `track_location` feature is enabled but the value is not found.
143    ///
144    /// If the `track_location` feature is enabled,
145    /// this returns `Some` if the inner value is `Some`
146    /// and `None` if the inner value is `None`.
147    ///
148    /// If it is disabled, this always returns `Some`.
149    ///
150    /// # Example
151    ///
152    /// ```
153    /// # use bevy_ecs::{change_detection::MaybeLocation, world::World};
154    /// # use core::panic::Location;
155    /// #
156    /// # fn test() -> Option<()> {
157    /// let mut world = World::new();
158    /// let entity = world.spawn(()).id();
159    /// let location: MaybeLocation<Option<&'static Location<'static>>> =
160    ///     world.entities().entity_get_spawned_or_despawned_by(entity);
161    /// let location: MaybeLocation<&'static Location<'static>> = location.transpose()?;
162    /// # Some(())
163    /// # }
164    /// # test();
165    /// ```
166    ///
167    /// # See also
168    ///
169    /// - [`into_option`][Self::into_option] to convert to an `Option<Option<T>>`.
170    ///   When used with [`Option::flatten`], this will have a similar effect,
171    ///   but will return `None` when the `track_location` feature is disabled.
172    #[inline]
173    pub fn transpose(self) -> Option<MaybeLocation<T>> {
174        #[cfg(feature = "track_location")]
175        {
176            self.value.map(|value| MaybeLocation {
177                value,
178                marker: PhantomData,
179            })
180        }
181        #[cfg(not(feature = "track_location"))]
182        {
183            Some(MaybeLocation {
184                marker: PhantomData,
185            })
186        }
187    }
188}
189
190impl<T> MaybeLocation<&T> {
191    /// Maps an `MaybeLocation<&T>` to an `MaybeLocation<T>` by copying the contents.
192    #[inline]
193    pub const fn copied(&self) -> MaybeLocation<T>
194    where
195        T: Copy,
196    {
197        MaybeLocation {
198            #[cfg(feature = "track_location")]
199            value: *self.value,
200            marker: PhantomData,
201        }
202    }
203}
204
205impl<T> MaybeLocation<&mut T> {
206    /// Maps an `MaybeLocation<&mut T>` to an `MaybeLocation<T>` by copying the contents.
207    #[inline]
208    pub const fn copied(&self) -> MaybeLocation<T>
209    where
210        T: Copy,
211    {
212        MaybeLocation {
213            #[cfg(feature = "track_location")]
214            value: *self.value,
215            marker: PhantomData,
216        }
217    }
218
219    /// Assigns the contents of an `MaybeLocation<T>` to an `MaybeLocation<&mut T>`.
220    #[inline]
221    pub fn assign(&mut self, _value: MaybeLocation<T>) {
222        #[cfg(feature = "track_location")]
223        {
224            *self.value = _value.value;
225        }
226    }
227}
228
229impl<T: ?Sized> MaybeLocation<T> {
230    /// Converts from `&MaybeLocation<T>` to `MaybeLocation<&T>`.
231    #[inline]
232    pub const fn as_ref(&self) -> MaybeLocation<&T> {
233        MaybeLocation {
234            #[cfg(feature = "track_location")]
235            value: &self.value,
236            marker: PhantomData,
237        }
238    }
239
240    /// Converts from `&mut MaybeLocation<T>` to `MaybeLocation<&mut T>`.
241    #[inline]
242    pub const fn as_mut(&mut self) -> MaybeLocation<&mut T> {
243        MaybeLocation {
244            #[cfg(feature = "track_location")]
245            value: &mut self.value,
246            marker: PhantomData,
247        }
248    }
249
250    /// Converts from `&MaybeLocation<T>` to `MaybeLocation<&T::Target>`.
251    #[inline]
252    pub fn as_deref(&self) -> MaybeLocation<&T::Target>
253    where
254        T: Deref,
255    {
256        MaybeLocation {
257            #[cfg(feature = "track_location")]
258            value: &*self.value,
259            marker: PhantomData,
260        }
261    }
262
263    /// Converts from `&mut MaybeLocation<T>` to `MaybeLocation<&mut T::Target>`.
264    #[inline]
265    pub fn as_deref_mut(&mut self) -> MaybeLocation<&mut T::Target>
266    where
267        T: DerefMut,
268    {
269        MaybeLocation {
270            #[cfg(feature = "track_location")]
271            value: &mut *self.value,
272            marker: PhantomData,
273        }
274    }
275}
276
277impl MaybeLocation {
278    /// Returns the source location of the caller of this function. If that function's caller is
279    /// annotated then its call location will be returned, and so on up the stack to the first call
280    /// within a non-tracked function body.
281    #[inline]
282    #[track_caller]
283    pub const fn caller() -> Self {
284        // Note that this cannot use `new_with`, since `FnOnce` invocations cannot be annotated with `#[track_caller]`.
285        MaybeLocation {
286            #[cfg(feature = "track_location")]
287            value: Location::caller(),
288            marker: PhantomData,
289        }
290    }
291}