
# Objective Eliminate the need to write `cfg(feature = "track_location")` every time one uses an API that may use location tracking. It's verbose, and a little intimidating. And it requires code outside of `bevy_ecs` that wants to use location tracking needs to either unconditionally enable the feature, or include conditional compilation of its own. It would be good for users to be able to log locations when they are available without needing to add feature flags to their own crates. Reduce the number of cases where code compiles with the `track_location` feature enabled, but not with it disabled, or vice versa. It can be hard to remember to test it both ways! Remove the need to store a `None` in `HookContext` when the `track_location` feature is disabled. ## Solution Create an `MaybeLocation<T>` type that contains a `T` if the `track_location` feature is enabled, and is a ZST if it is not. The overall API is similar to `Option`, but whether the value is `Some` or `None` is set at compile time and is the same for all values. Default `T` to `&'static Location<'static>`, since that is the most common case. Remove all `cfg(feature = "track_location")` blocks outside of the implementation of that type, and instead call methods on it. When `track_location` is disabled, `MaybeLocation` is a ZST and all methods are `#[inline]` and empty, so they should be entirely removed by the compiler. But the code will still be visible to the compiler and checked, so if it compiles with the feature disabled then it should also compile with it enabled, and vice versa. ## Open Questions Where should these types live? I put them in `change_detection` because that's where the existing `MaybeLocation` types were, but we now use these outside of change detection. While I believe that the compiler should be able to remove all of these calls, I have not actually tested anything. If we want to take this approach, what testing is required to ensure it doesn't impact performance? ## Migration Guide Methods like `Ref::changed_by()` that return a `&'static Location<'static>` will now be available even when the `track_location` feature is disabled, but they will return a new `MaybeLocation` type. `MaybeLocation` wraps a `&'static Location<'static>` when the feature is enabled, and is a ZST when the feature is disabled. Existing code that needs a `&Location` can call `into_option().unwrap()` to recover it. Many trait impls are forwarded, so if you only need `Display` then no changes will be necessary. If that code was conditionally compiled, you may instead want to use the methods on `MaybeLocation` to remove the need for conditional compilation. Code that constructs a `Ref`, `Mut`, `Res`, or `ResMut` will now need to provide location information unconditionally. If you are creating them from existing Bevy types, you can obtain a `MaybeLocation` from methods like `Table::get_changed_by_slice_for()` or `ComponentSparseSet::get_with_ticks`. Otherwise, you will need to store a `MaybeLocation` next to your data and use methods like `as_ref()` or `as_mut()` to obtain wrapped references.
108 lines
2.6 KiB
Rust
108 lines
2.6 KiB
Rust
use crate::{
|
|
bundle::{Bundle, BundleSpawner, NoBundleEffect},
|
|
change_detection::MaybeLocation,
|
|
entity::{Entity, EntitySetIterator},
|
|
world::World,
|
|
};
|
|
use core::iter::FusedIterator;
|
|
|
|
/// An iterator that spawns a series of entities and returns the [ID](Entity) of
|
|
/// each spawned entity.
|
|
///
|
|
/// If this iterator is not fully exhausted, any remaining entities will be spawned when this type is dropped.
|
|
pub struct SpawnBatchIter<'w, I>
|
|
where
|
|
I: Iterator,
|
|
I::Item: Bundle,
|
|
{
|
|
inner: I,
|
|
spawner: BundleSpawner<'w>,
|
|
caller: MaybeLocation,
|
|
}
|
|
|
|
impl<'w, I> SpawnBatchIter<'w, I>
|
|
where
|
|
I: Iterator,
|
|
I::Item: Bundle<Effect: NoBundleEffect>,
|
|
{
|
|
#[inline]
|
|
#[track_caller]
|
|
pub(crate) fn new(world: &'w mut World, iter: I, caller: MaybeLocation) -> Self {
|
|
// Ensure all entity allocations are accounted for so `self.entities` can realloc if
|
|
// necessary
|
|
world.flush();
|
|
|
|
let change_tick = world.change_tick();
|
|
|
|
let (lower, upper) = iter.size_hint();
|
|
let length = upper.unwrap_or(lower);
|
|
world.entities.reserve(length as u32);
|
|
|
|
let mut spawner = BundleSpawner::new::<I::Item>(world, change_tick);
|
|
spawner.reserve_storage(length);
|
|
|
|
Self {
|
|
inner: iter,
|
|
spawner,
|
|
caller,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<I> Drop for SpawnBatchIter<'_, I>
|
|
where
|
|
I: Iterator,
|
|
I::Item: Bundle,
|
|
{
|
|
fn drop(&mut self) {
|
|
// Iterate through self in order to spawn remaining bundles.
|
|
for _ in &mut *self {}
|
|
// Apply any commands from those operations.
|
|
// SAFETY: `self.spawner` will be dropped immediately after this call.
|
|
unsafe { self.spawner.flush_commands() };
|
|
}
|
|
}
|
|
|
|
impl<I> Iterator for SpawnBatchIter<'_, I>
|
|
where
|
|
I: Iterator,
|
|
I::Item: Bundle,
|
|
{
|
|
type Item = Entity;
|
|
|
|
fn next(&mut self) -> Option<Entity> {
|
|
let bundle = self.inner.next()?;
|
|
// SAFETY: bundle matches spawner type
|
|
unsafe { Some(self.spawner.spawn(bundle, self.caller).0) }
|
|
}
|
|
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
self.inner.size_hint()
|
|
}
|
|
}
|
|
|
|
impl<I, T> ExactSizeIterator for SpawnBatchIter<'_, I>
|
|
where
|
|
I: ExactSizeIterator<Item = T>,
|
|
T: Bundle,
|
|
{
|
|
fn len(&self) -> usize {
|
|
self.inner.len()
|
|
}
|
|
}
|
|
|
|
impl<I, T> FusedIterator for SpawnBatchIter<'_, I>
|
|
where
|
|
I: FusedIterator<Item = T>,
|
|
T: Bundle,
|
|
{
|
|
}
|
|
|
|
// SAFETY: Newly spawned entities are unique.
|
|
unsafe impl<I: Iterator, T> EntitySetIterator for SpawnBatchIter<'_, I>
|
|
where
|
|
I: FusedIterator<Item = T>,
|
|
T: Bundle,
|
|
{
|
|
}
|