Component Lifecycle Hooks and a Deferred World (#10756)

# Objective

- Provide a reliable and performant mechanism to allows users to keep
components synchronized with external sources: closing/opening sockets,
updating indexes, debugging etc.
- Implement a generic mechanism to provide mutable access to the world
without allowing structural changes; this will not only be used here but
is a foundational piece for observers, which are key for a performant
implementation of relations.

## Solution

- Implement a new type `DeferredWorld` (naming is not important,
`StaticWorld` is also suitable) that wraps a world pointer and prevents
user code from making any structural changes to the ECS; spawning
entities, creating components, initializing resources etc.
- Add component lifecycle hooks `on_add`, `on_insert` and `on_remove`
that can be assigned callbacks in user code.

---

## Changelog
- Add new `DeferredWorld` type.
- Add new world methods: `register_component::<T>` and
`register_component_with_descriptor`. These differ from `init_component`
in that they provide mutable access to the created `ComponentInfo` but
will panic if the component is already in any archetypes. These
restrictions serve two purposes:
1. Prevent users from defining hooks for components that may already
have associated hooks provided in another plugin. (a use case better
served by observers)
2. Ensure that when an `Archetype` is created it gets the appropriate
flags to early-out when triggering hooks.
- Add methods to `ComponentInfo`: `on_add`, `on_insert` and `on_remove`
to be used to register hooks of the form `fn(DeferredWorld, Entity,
ComponentId)`
- Modify `BundleInserter`, `BundleSpawner` and `EntityWorldMut` to
trigger component hooks when appropriate.
- Add bit flags to `Archetype` indicating whether or not any contained
components have each type of hook, this can be expanded for other flags
as needed.
- Add `component_hooks` example to illustrate usage. Try it out! It's
fun to mash keys.

## Safety
The changes to component insertion, removal and deletion involve a large
amount of unsafe code and it's fair for that to raise some concern. I
have attempted to document it as clearly as possible and have confirmed
that all the hooks examples are accepted by `cargo miri` as not causing
any undefined behavior. The largest issue is in ensuring there are no
outstanding references when passing a `DeferredWorld` to the hooks which
requires some use of raw pointers (as was already happening to some
degree in those places) and I have taken some time to ensure that is the
case but feel free to let me know if I've missed anything.

## Performance
These changes come with a small but measurable performance cost of
between 1-5% on `add_remove` benchmarks and between 1-3% on `insert`
benchmarks. One consideration to be made is the existence of the current
`RemovedComponents` which is on average more costly than the addition of
`on_remove` hooks due to the early-out, however hooks doesn't completely
remove the need for `RemovedComponents` as there is a chance you want to
respond to the removal of a component that already has an `on_remove`
hook defined in another plugin, so I have not removed it here. I do
intend to deprecate it with the introduction of observers in a follow up
PR.

## Discussion Questions
- Currently `DeferredWorld` implements `Deref` to `&World` which makes
sense conceptually, however it does cause some issues with rust-analyzer
providing autocomplete for `&mut World` references which is annoying.
There are alternative implementations that may address this but involve
more code churn so I have attempted them here. The other alternative is
to not implement `Deref` at all but that leads to a large amount of API
duplication.
- `DeferredWorld`, `StaticWorld`, something else?
- In adding support for hooks to `EntityWorldMut` I encountered some
unfortunate difficulties with my desired API. If commands are flushed
after each call i.e. `world.spawn() // flush commands .insert(A) //
flush commands` the entity may be despawned while `EntityWorldMut` still
exists which is invalid. An alternative was then to add
`self.world.flush_commands()` to the drop implementation for
`EntityWorldMut` but that runs into other problems for implementing
functions like `into_unsafe_entity_cell`. For now I have implemented a
`.flush()` which will flush the commands and consume `EntityWorldMut` or
users can manually run `world.flush_commands()` after using
`EntityWorldMut`.
- In order to allowing querying on a deferred world we need
implementations of `WorldQuery` to not break our guarantees of no
structural changes through their `UnsafeWorldCell`. All our
implementations do this, but there isn't currently any safety
documentation specifying what is or isn't allowed for an implementation,
just for the caller, (they also shouldn't be aliasing components they
didn't specify access for etc.) is that something we should start doing?
(see 10752)

Please check out the example `component_hooks` or the tests in
`bundle.rs` for usage examples. I will continue to expand this
description as I go.

See #10839 for a more ergonomic API built on top of this one that isn't
subject to the same restrictions and supports `SystemParam` dependency
injection.
This commit is contained in:
James O'Brien 2024-03-01 06:59:22 -08:00 committed by GitHub
parent bcdca068ad
commit 94ff123d7f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1513 additions and 519 deletions

View file

@ -1392,6 +1392,17 @@ description = "Change detection on components"
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "component_hooks"
path = "examples/ecs/component_hooks.rs"
doc-scrape-examples = true
[package.metadata.example.component_hooks]
name = "Component Hooks"
description = "Define component hooks to manage component lifecycle events"
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "custom_schedule"
path = "examples/ecs/custom_schedule.rs"

View file

@ -22,6 +22,7 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_ecs_macros = { path = "macros", version = "0.14.0-dev" }
bitflags = "2.3"
concurrent-queue = "2.4.0"
fixedbitset = "0.4.2"
rustc-hash = "1.1"

View file

@ -21,7 +21,7 @@
use crate::{
bundle::BundleId,
component::{ComponentId, StorageType},
component::{ComponentId, Components, StorageType},
entity::{Entity, EntityLocation},
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow},
};
@ -107,7 +107,7 @@ impl ArchetypeId {
}
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub(crate) enum ComponentStatus {
Added,
Mutated,
@ -298,6 +298,18 @@ struct ArchetypeComponentInfo {
archetype_component_id: ArchetypeComponentId,
}
bitflags::bitflags! {
/// Flags used to keep track of metadata about the component in this [`Archetype`]
///
/// Used primarily to early-out when there are no [`ComponentHook`] registered for any contained components.
#[derive(Clone, Copy)]
pub(crate) struct ArchetypeFlags: u32 {
const ON_ADD_HOOK = (1 << 0);
const ON_INSERT_HOOK = (1 << 1);
const ON_REMOVE_HOOK = (1 << 2);
}
}
/// Metadata for a single archetype within a [`World`].
///
/// For more information, see the *[module level documentation]*.
@ -310,10 +322,12 @@ pub struct Archetype {
edges: Edges,
entities: Vec<ArchetypeEntity>,
components: ImmutableSparseSet<ComponentId, ArchetypeComponentInfo>,
flags: ArchetypeFlags,
}
impl Archetype {
pub(crate) fn new(
components: &Components,
id: ArchetypeId,
table_id: TableId,
table_components: impl Iterator<Item = (ComponentId, ArchetypeComponentId)>,
@ -321,9 +335,13 @@ impl Archetype {
) -> Self {
let (min_table, _) = table_components.size_hint();
let (min_sparse, _) = sparse_set_components.size_hint();
let mut components = SparseSet::with_capacity(min_table + min_sparse);
let mut flags = ArchetypeFlags::empty();
let mut archetype_components = SparseSet::with_capacity(min_table + min_sparse);
for (component_id, archetype_component_id) in table_components {
components.insert(
// SAFETY: We are creating an archetype that includes this component so it must exist
let info = unsafe { components.get_info_unchecked(component_id) };
info.update_archetype_flags(&mut flags);
archetype_components.insert(
component_id,
ArchetypeComponentInfo {
storage_type: StorageType::Table,
@ -333,7 +351,10 @@ impl Archetype {
}
for (component_id, archetype_component_id) in sparse_set_components {
components.insert(
// SAFETY: We are creating an archetype that includes this component so it must exist
let info = unsafe { components.get_info_unchecked(component_id) };
info.update_archetype_flags(&mut flags);
archetype_components.insert(
component_id,
ArchetypeComponentInfo {
storage_type: StorageType::SparseSet,
@ -345,8 +366,9 @@ impl Archetype {
id,
table_id,
entities: Vec::new(),
components: components.into_immutable(),
components: archetype_components.into_immutable(),
edges: Default::default(),
flags,
}
}
@ -356,6 +378,12 @@ impl Archetype {
self.id
}
/// Fetches the flags for the archetype.
#[inline]
pub(crate) fn flags(&self) -> ArchetypeFlags {
self.flags
}
/// Fetches the archetype's [`Table`] ID.
///
/// [`Table`]: crate::storage::Table
@ -542,6 +570,24 @@ impl Archetype {
pub(crate) fn clear_entities(&mut self) {
self.entities.clear();
}
/// Returns true if any of the components in this archetype have `on_add` hooks
#[inline]
pub(crate) fn has_on_add(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_ADD_HOOK)
}
/// Returns true if any of the components in this archetype have `on_insert` hooks
#[inline]
pub(crate) fn has_on_insert(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK)
}
/// Returns true if any of the components in this archetype have `on_remove` hooks
#[inline]
pub(crate) fn has_on_remove(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK)
}
}
/// The next [`ArchetypeId`] in an [`Archetypes`] collection.
@ -624,7 +670,15 @@ impl Archetypes {
by_components: Default::default(),
archetype_component_count: 0,
};
archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new());
// SAFETY: Empty archetype has no components
unsafe {
archetypes.get_id_or_insert(
&Components::default(),
TableId::empty(),
Vec::new(),
Vec::new(),
);
}
archetypes
}
@ -717,8 +771,10 @@ impl Archetypes {
///
/// # Safety
/// [`TableId`] must exist in tables
pub(crate) fn get_id_or_insert(
/// `table_components` and `sparse_set_components` must exist in `components`
pub(crate) unsafe fn get_id_or_insert(
&mut self,
components: &Components,
table_id: TableId,
table_components: Vec<ComponentId>,
sparse_set_components: Vec<ComponentId>,
@ -744,6 +800,7 @@ impl Archetypes {
let sparse_set_archetype_components =
(sparse_start..*archetype_component_count).map(ArchetypeComponentId);
archetypes.push(Archetype::new(
components,
id,
table_id,
table_components.into_iter().zip(table_archetype_components),

File diff suppressed because it is too large Load diff

View file

@ -2,10 +2,12 @@
use crate::{
self as bevy_ecs,
archetype::ArchetypeFlags,
change_detection::MAX_CHANGE_AGE,
entity::Entity,
storage::{SparseSetIndex, Storages},
system::{Local, Resource, SystemParam},
world::{FromWorld, World},
world::{DeferredWorld, FromWorld, World},
};
pub use bevy_ecs_macros::Component;
use bevy_ptr::{OwningPtr, UnsafeCellDeref};
@ -152,6 +154,10 @@ pub trait Component: Send + Sync + 'static {
/// A marker type indicating the storage type used for this component.
/// This must be either [`TableStorage`] or [`SparseStorage`].
type Storage: ComponentStorage;
/// Called when registering this component, allowing mutable access to it's [`ComponentInfo`].
/// This is currently used for registering hooks.
fn init_component_info(_info: &mut ComponentInfo) {}
}
/// Marker type for components stored in a [`Table`](crate::storage::Table).
@ -203,11 +209,83 @@ pub enum StorageType {
SparseSet,
}
/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`
pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId);
/// Lifecycle hooks for a given [`Component`], stored in it's [`ComponentInfo`]
#[derive(Debug, Clone, Default)]
pub struct ComponentHooks {
pub(crate) on_add: Option<ComponentHook>,
pub(crate) on_insert: Option<ComponentHook>,
pub(crate) on_remove: Option<ComponentHook>,
}
impl ComponentHooks {
/// Register a [`ComponentHook`] that will be run when this component is added to an entity.
/// An `on_add` hook will always be followed by `on_insert`.
///
/// Will panic if the component already has an `on_add` hook
pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_add(hook)
.expect("Component id: {:?}, already has an on_add hook")
}
/// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert`
/// An `on_insert` hook will run even if the entity already has the component unlike `on_add`,
/// `on_insert` also always runs after any `on_add` hooks.
///
/// Will panic if the component already has an `on_insert` hook
pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_insert(hook)
.expect("Component id: {:?}, already has an on_insert hook")
}
/// Register a [`ComponentHook`] that will be run when this component is removed from an entity.
/// Despawning an entity counts as removing all of it's components.
///
/// Will panic if the component already has an `on_remove` hook
pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_remove(hook)
.expect("Component id: {:?}, already has an on_remove hook")
}
/// Fallible version of [`Self::on_add`].
/// Returns `None` if the component already has an `on_add` hook.
pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_add.is_some() {
return None;
}
self.on_add = Some(hook);
Some(self)
}
/// Fallible version of [`Self::on_insert`].
/// Returns `None` if the component already has an `on_insert` hook.
pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_insert.is_some() {
return None;
}
self.on_insert = Some(hook);
Some(self)
}
/// Fallible version of [`Self::on_remove`].
/// Returns `None` if the component already has an `on_remove` hook.
pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_remove.is_some() {
return None;
}
self.on_remove = Some(hook);
Some(self)
}
}
/// Stores metadata for a type of component or resource stored in a specific [`World`].
#[derive(Debug, Clone)]
pub struct ComponentInfo {
id: ComponentId,
descriptor: ComponentDescriptor,
hooks: ComponentHooks,
}
impl ComponentInfo {
@ -263,7 +341,30 @@ impl ComponentInfo {
/// Create a new [`ComponentInfo`].
pub(crate) fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self {
ComponentInfo { id, descriptor }
ComponentInfo {
id,
descriptor,
hooks: ComponentHooks::default(),
}
}
/// Update the given flags to include any [`ComponentHook`] registered to self
#[inline]
pub(crate) fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) {
if self.hooks().on_add.is_some() {
flags.insert(ArchetypeFlags::ON_ADD_HOOK);
}
if self.hooks().on_insert.is_some() {
flags.insert(ArchetypeFlags::ON_INSERT_HOOK);
}
if self.hooks().on_remove.is_some() {
flags.insert(ArchetypeFlags::ON_REMOVE_HOOK);
}
}
/// Provides a reference to the collection of hooks associated with this [`Component`]
pub fn hooks(&self) -> &ComponentHooks {
&self.hooks
}
}
@ -474,7 +575,13 @@ impl Components {
..
} = self;
*indices.entry(type_id).or_insert_with(|| {
Components::init_component_inner(components, storages, ComponentDescriptor::new::<T>())
let index = Components::init_component_inner(
components,
storages,
ComponentDescriptor::new::<T>(),
);
T::init_component_info(&mut components[index.index()]);
index
})
}
@ -551,6 +658,11 @@ impl Components {
unsafe { self.components.get_unchecked(id.0) }
}
#[inline]
pub(crate) fn get_hooks_mut(&mut self, id: ComponentId) -> Option<&mut ComponentHooks> {
self.components.get_mut(id.0).map(|info| &mut info.hooks)
}
/// Type-erased equivalent of [`Components::component_id()`].
#[inline]
pub fn get_id(&self, type_id: TypeId) -> Option<ComponentId> {

View file

@ -1067,7 +1067,7 @@ mod tests {
fn reserve_and_spawn() {
let mut world = World::default();
let e = world.entities().reserve_entity();
world.flush();
world.flush_entities();
let mut e_mut = world.entity_mut(e);
e_mut.insert(A(0));
assert_eq!(e_mut.get::<A>().unwrap(), &A(0));
@ -1550,7 +1550,7 @@ mod tests {
let e1 = world_a.spawn(A(1)).id();
let e2 = world_a.spawn(A(2)).id();
let e3 = world_a.entities().reserve_entity();
world_a.flush();
world_a.flush_entities();
let world_a_max_entities = world_a.entities().len();
world_b.entities.reserve_entities(world_a_max_entities);

View file

@ -112,12 +112,12 @@ impl CommandQueue {
}
}
/// Execute the queued [`Command`]s in the world.
/// Execute the queued [`Command`]s in the world after applying any commands in the world's internal queue.
/// This clears the queue.
#[inline]
pub fn apply(&mut self, world: &mut World) {
// flush the previously queued entities
world.flush();
world.flush_entities();
self.apply_or_drop_queued(Some(world));
}
@ -131,39 +131,85 @@ impl CommandQueue {
let bytes_range = self.bytes.as_mut_ptr_range();
// Pointer that will iterate over the entries of the buffer.
let mut cursor = bytes_range.start;
let cursor = bytes_range.start;
let end = bytes_range.end;
// Reset the buffer, so it can be reused after this function ends.
// In the loop below, ownership of each command will be transferred into user code.
// SAFETY: `set_len(0)` is always valid.
unsafe { self.bytes.set_len(0) };
while cursor < bytes_range.end {
// SAFETY: The cursor is either at the start of the buffer, or just after the previous command.
// Since we know that the cursor is in bounds, it must point to the start of a new command.
let meta = unsafe { cursor.cast::<CommandMeta>().read_unaligned() };
// Advance to the bytes just after `meta`, which represent a type-erased command.
// SAFETY: For most types of `Command`, the pointer immediately following the metadata
// is guaranteed to be in bounds. If the command is a zero-sized type (ZST), then the cursor
// might be 1 byte past the end of the buffer, which is safe.
cursor = unsafe { cursor.add(std::mem::size_of::<CommandMeta>()) };
// Construct an owned pointer to the command.
// SAFETY: It is safe to transfer ownership out of `self.bytes`, since the call to `set_len(0)` above
// guarantees that nothing stored in the buffer will get observed after this function ends.
// `cmd` points to a valid address of a stored command, so it must be non-null.
let cmd = unsafe {
OwningPtr::<Unaligned>::new(std::ptr::NonNull::new_unchecked(cursor.cast()))
};
// SAFETY: The data underneath the cursor must correspond to the type erased in metadata,
// since they were stored next to each other by `.push()`.
// For ZSTs, the type doesn't matter as long as the pointer is non-null.
let size = unsafe { (meta.consume_command_and_get_size)(cmd, world.as_deref_mut()) };
// Advance the cursor past the command. For ZSTs, the cursor will not move.
// At this point, it will either point to the next `CommandMeta`,
// or the cursor will be out of bounds and the loop will end.
// SAFETY: The address just past the command is either within the buffer,
// or 1 byte past the end, so this addition will not overflow the pointer's allocation.
cursor = unsafe { cursor.add(size) };
// Create a stack for the command queue's we will be applying as commands may queue additional commands.
// This is preferred over recursion to avoid stack overflows.
let mut resolving_commands = vec![(cursor, end)];
// Take ownership of any additional buffers so they are not free'd uintil they are iterated.
let mut buffers = Vec::new();
// Add any commands in the world's internal queue to the top of the stack.
if let Some(world) = &mut world {
if !world.command_queue.is_empty() {
let mut bytes = std::mem::take(&mut world.command_queue.bytes);
let bytes_range = bytes.as_mut_ptr_range();
resolving_commands.push((bytes_range.start, bytes_range.end));
buffers.push(bytes);
}
}
while let Some((mut cursor, mut end)) = resolving_commands.pop() {
while cursor < end {
// SAFETY: The cursor is either at the start of the buffer, or just after the previous command.
// Since we know that the cursor is in bounds, it must point to the start of a new command.
let meta = unsafe { cursor.cast::<CommandMeta>().read_unaligned() };
// Advance to the bytes just after `meta`, which represent a type-erased command.
// SAFETY: For most types of `Command`, the pointer immediately following the metadata
// is guaranteed to be in bounds. If the command is a zero-sized type (ZST), then the cursor
// might be 1 byte past the end of the buffer, which is safe.
cursor = unsafe { cursor.add(std::mem::size_of::<CommandMeta>()) };
// Construct an owned pointer to the command.
// SAFETY: It is safe to transfer ownership out of `self.bytes`, since the call to `set_len(0)` above
// guarantees that nothing stored in the buffer will get observed after this function ends.
// `cmd` points to a valid address of a stored command, so it must be non-null.
let cmd = unsafe {
OwningPtr::<Unaligned>::new(std::ptr::NonNull::new_unchecked(cursor.cast()))
};
// SAFETY: The data underneath the cursor must correspond to the type erased in metadata,
// since they were stored next to each other by `.push()`.
// For ZSTs, the type doesn't matter as long as the pointer is non-null.
let size =
unsafe { (meta.consume_command_and_get_size)(cmd, world.as_deref_mut()) };
// Advance the cursor past the command. For ZSTs, the cursor will not move.
// At this point, it will either point to the next `CommandMeta`,
// or the cursor will be out of bounds and the loop will end.
// SAFETY: The address just past the command is either within the buffer,
// or 1 byte past the end, so this addition will not overflow the pointer's allocation.
cursor = unsafe { cursor.add(size) };
if let Some(world) = &mut world {
// If the command we just applied generated more commands we must apply those first
if !world.command_queue.is_empty() {
// If our current list of commands isn't complete push it to the `resolving_commands` stack to be applied after
if cursor < end {
resolving_commands.push((cursor, end));
}
let mut bytes = std::mem::take(&mut world.command_queue.bytes);
// Start applying the new queue
let bytes_range = bytes.as_mut_ptr_range();
cursor = bytes_range.start;
end = bytes_range.end;
// Store our buffer so it is not dropped;
buffers.push(bytes);
}
}
}
// Re-use last buffer to avoid re-allocation
if let (Some(world), Some(buffer)) = (&mut world, buffers.pop()) {
world.command_queue.bytes = buffer;
// SAFETY: `set_len(0)` is always valid.
unsafe { world.command_queue.bytes.set_len(0) };
}
}
}
@ -171,6 +217,12 @@ impl CommandQueue {
pub fn append(&mut self, other: &mut CommandQueue) {
self.bytes.append(&mut other.bytes);
}
/// Returns false if there are any commands in the queue
#[inline]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
}
impl Drop for CommandQueue {

View file

@ -110,6 +110,7 @@ where
);
let out = self.func.run(world, input, params);
world.flush_commands();
let change_tick = world.change_tick.get_mut();
self.system_meta.last_run.set(*change_tick);
*change_tick = change_tick.wrapping_add(1);

View file

@ -0,0 +1,317 @@
use std::ops::Deref;
use crate::{
change_detection::MutUntyped,
component::ComponentId,
entity::Entity,
event::{Event, EventId, Events, SendBatchIds},
prelude::{Component, QueryState},
query::{QueryData, QueryFilter},
system::{Commands, Query, Resource},
};
use super::{
unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell},
EntityMut, Mut, World,
};
/// A [`World`] reference that disallows structural ECS changes.
/// This includes initializing resources, registering components or spawning entities.
pub struct DeferredWorld<'w> {
// SAFETY: Implementors must not use this reference to make structural changes
world: UnsafeWorldCell<'w>,
}
impl<'w> Deref for DeferredWorld<'w> {
type Target = World;
fn deref(&self) -> &Self::Target {
// SAFETY: Structural changes cannot be made through &World
unsafe { self.world.world() }
}
}
impl<'w> UnsafeWorldCell<'w> {
/// Turn self into a [`DeferredWorld`]
///
/// # Safety
/// Caller must ensure there are no outstanding mutable references to world and no
/// outstanding references to the world's command queue, resource or component data
#[inline]
pub unsafe fn into_deferred(self) -> DeferredWorld<'w> {
DeferredWorld { world: self }
}
}
impl<'w> From<&'w mut World> for DeferredWorld<'w> {
fn from(world: &'w mut World) -> DeferredWorld<'w> {
DeferredWorld {
world: world.as_unsafe_world_cell(),
}
}
}
impl<'w> DeferredWorld<'w> {
/// Creates a [`Commands`] instance that pushes to the world's command queue
#[inline]
pub fn commands(&mut self) -> Commands {
// SAFETY: &mut self ensure that there are no outstanding accesses to the queue
let queue = unsafe { self.world.get_command_queue() };
Commands::new_from_entities(queue, self.world.entities())
}
/// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type.
/// Returns `None` if the `entity` does not have a [`Component`] of the given type.
#[inline]
pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<Mut<T>> {
// SAFETY: &mut self ensure that there are no outstanding accesses to the component
unsafe { self.world.get_entity(entity)?.get_mut() }
}
/// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`.
/// This will panic if the `entity` does not exist. Use [`Self::get_entity_mut`] if you want
/// to check for entity existence instead of implicitly panic-ing.
#[inline]
#[track_caller]
pub fn entity_mut(&mut self, entity: Entity) -> EntityMut {
#[inline(never)]
#[cold]
#[track_caller]
fn panic_no_entity(entity: Entity) -> ! {
panic!("Entity {entity:?} does not exist");
}
match self.get_entity_mut(entity) {
Some(entity) => entity,
None => panic_no_entity(entity),
}
}
/// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`.
/// Returns [`None`] if the `entity` does not exist.
/// Instead of unwrapping the value returned from this function, prefer [`Self::entity_mut`].
#[inline]
pub fn get_entity_mut(&mut self, entity: Entity) -> Option<EntityMut> {
let location = self.entities.get(entity)?;
// SAFETY: `entity` exists and `location` is that entity's location
Some(unsafe { EntityMut::new(UnsafeEntityCell::new(self.world, entity, location)) })
}
/// Returns [`Query`] for the given [`QueryState`], which is used to efficiently
/// run queries on the [`World`] by storing and reusing the [`QueryState`].
///
/// # Panics
/// If state is from a different world then self
#[inline]
pub fn query<'s, D: QueryData, F: QueryFilter>(
&'w mut self,
state: &'s mut QueryState<D, F>,
) -> Query<'w, 's, D, F> {
state.validate_world(self.world.id());
state.update_archetypes(self);
// SAFETY: We ran validate_world to ensure our state matches
unsafe {
let world_cell = self.world;
Query::new(
world_cell,
state,
world_cell.last_change_tick(),
world_cell.change_tick(),
)
}
}
/// Gets a mutable reference to the resource of the given type
///
/// # Panics
///
/// Panics if the resource does not exist.
/// Use [`get_resource_mut`](DeferredWorld::get_resource_mut) instead if you want to handle this case.
#[inline]
#[track_caller]
pub fn resource_mut<R: Resource>(&mut self) -> Mut<'_, R> {
match self.get_resource_mut() {
Some(x) => x,
None => panic!(
"Requested resource {} does not exist in the `World`.
Did you forget to add it using `app.insert_resource` / `app.init_resource`?
Resources are also implicitly added via `app.add_event`,
and can be added by plugins.",
std::any::type_name::<R>()
),
}
}
/// Gets a mutable reference to the resource of the given type if it exists
#[inline]
pub fn get_resource_mut<R: Resource>(&mut self) -> Option<Mut<'_, R>> {
// SAFETY: &mut self ensure that there are no outstanding accesses to the resource
unsafe { self.world.get_resource_mut() }
}
/// Gets a mutable reference to the non-send resource of the given type, if it exists.
///
/// # Panics
///
/// Panics if the resource does not exist.
/// Use [`get_non_send_resource_mut`](World::get_non_send_resource_mut) instead if you want to handle this case.
///
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
#[inline]
#[track_caller]
pub fn non_send_resource_mut<R: 'static>(&mut self) -> Mut<'_, R> {
match self.get_non_send_resource_mut() {
Some(x) => x,
None => panic!(
"Requested non-send resource {} does not exist in the `World`.
Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`?
Non-send resources can also be be added by plugins.",
std::any::type_name::<R>()
),
}
}
/// Gets a mutable reference to the non-send resource of the given type, if it exists.
/// Otherwise returns `None`.
///
/// # Panics
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
#[inline]
pub fn get_non_send_resource_mut<R: 'static>(&mut self) -> Option<Mut<'_, R>> {
// SAFETY: &mut self ensure that there are no outstanding accesses to the resource
unsafe { self.world.get_non_send_resource_mut() }
}
/// Sends an [`Event`].
/// This method returns the [ID](`EventId`) of the sent `event`,
/// or [`None`] if the `event` could not be sent.
#[inline]
pub fn send_event<E: Event>(&mut self, event: E) -> Option<EventId<E>> {
self.send_event_batch(std::iter::once(event))?.next()
}
/// Sends the default value of the [`Event`] of type `E`.
/// This method returns the [ID](`EventId`) of the sent `event`,
/// or [`None`] if the `event` could not be sent.
#[inline]
pub fn send_event_default<E: Event + Default>(&mut self) -> Option<EventId<E>> {
self.send_event(E::default())
}
/// Sends a batch of [`Event`]s from an iterator.
/// This method returns the [IDs](`EventId`) of the sent `events`,
/// or [`None`] if the `event` could not be sent.
#[inline]
pub fn send_event_batch<E: Event>(
&mut self,
events: impl IntoIterator<Item = E>,
) -> Option<SendBatchIds<E>> {
let Some(mut events_resource) = self.get_resource_mut::<Events<E>>() else {
bevy_utils::tracing::error!(
"Unable to send event `{}`\n\tEvent must be added to the app with `add_event()`\n\thttps://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event ",
std::any::type_name::<E>()
);
return None;
};
Some(events_resource.send_batch(events))
}
/// Gets a pointer to the resource with the id [`ComponentId`] if it exists.
/// The returned pointer may be used to modify the resource, as long as the mutable borrow
/// of the [`World`] is still valid.
///
/// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only
/// use this in cases where the actual types are not known at compile time.**
#[inline]
pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
// SAFETY: &mut self ensure that there are no outstanding accesses to the resource
unsafe { self.world.get_resource_mut_by_id(component_id) }
}
/// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists.
/// The returned pointer may be used to modify the resource, as long as the mutable borrow
/// of the [`World`] is still valid.
///
/// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only
/// use this in cases where the actual types are not known at compile time.**
///
/// # Panics
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
#[inline]
pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
// SAFETY: &mut self ensure that there are no outstanding accesses to the resource
unsafe { self.world.get_non_send_resource_mut_by_id(component_id) }
}
/// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`].
/// Returns `None` if the `entity` does not have a [`Component`] of the given type.
///
/// **You should prefer to use the typed API [`World::get_mut`] where possible and only
/// use this in cases where the actual types are not known at compile time.**
#[inline]
pub fn get_mut_by_id(
&mut self,
entity: Entity,
component_id: ComponentId,
) -> Option<MutUntyped<'_>> {
// SAFETY: &mut self ensure that there are no outstanding accesses to the resource
unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) }
}
/// Triggers all `on_add` hooks for [`ComponentId`] in target.
///
/// # Safety
/// Caller must ensure [`ComponentId`] in target exist in self.
#[inline]
pub(crate) unsafe fn trigger_on_add(
&mut self,
entity: Entity,
targets: impl Iterator<Item = ComponentId>,
) {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_add {
hook(DeferredWorld { world: self.world }, entity, component_id);
}
}
}
/// Triggers all `on_insert` hooks for [`ComponentId`] in target.
///
/// # Safety
/// Caller must ensure [`ComponentId`] in target exist in self.
#[inline]
pub(crate) unsafe fn trigger_on_insert(
&mut self,
entity: Entity,
targets: impl Iterator<Item = ComponentId>,
) {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_insert {
hook(DeferredWorld { world: self.world }, entity, component_id);
}
}
}
/// Triggers all `on_remove` hooks for [`ComponentId`] in target.
///
/// # Safety
/// Caller must ensure [`ComponentId`] in target exist in self.
#[inline]
pub(crate) unsafe fn trigger_on_remove(
&mut self,
entity: Entity,
targets: impl Iterator<Item = ComponentId>,
) {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_remove {
hook(DeferredWorld { world: self.world }, entity, component_id);
}
}
}
}

View file

@ -1,6 +1,6 @@
use crate::{
archetype::{Archetype, ArchetypeId, Archetypes},
bundle::{Bundle, BundleInfo, BundleInserter, DynamicBundle},
bundle::{Bundle, BundleId, BundleInfo, BundleInserter, DynamicBundle},
change_detection::MutUntyped,
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
entity::{Entities, Entity, EntityLocation},
@ -10,7 +10,6 @@ use crate::{
world::{Mut, World},
};
use bevy_ptr::{OwningPtr, Ptr};
use bevy_utils::tracing::debug;
use std::{any::TypeId, marker::PhantomData};
use thiserror::Error;
@ -653,23 +652,10 @@ impl<'w> EntityWorldMut<'w> {
/// This will overwrite any previous value(s) of the same component type.
pub fn insert<T: Bundle>(&mut self, bundle: T) -> &mut Self {
let change_tick = self.world.change_tick();
let bundle_info = self
.world
.bundles
.init_info::<T>(&mut self.world.components, &mut self.world.storages);
let mut bundle_inserter = bundle_info.get_bundle_inserter(
&mut self.world.entities,
&mut self.world.archetypes,
&self.world.components,
&mut self.world.storages,
self.location.archetype_id,
change_tick,
);
let mut bundle_inserter =
BundleInserter::new::<T>(self.world, self.location.archetype_id, change_tick);
// SAFETY: location matches current entity. `T` matches `bundle_info`
unsafe {
self.location = bundle_inserter.insert(self.entity, self.location, bundle);
}
self.location = unsafe { bundle_inserter.insert(self.entity, self.location, bundle) };
self
}
@ -689,17 +675,16 @@ impl<'w> EntityWorldMut<'w> {
component: OwningPtr<'_>,
) -> &mut Self {
let change_tick = self.world.change_tick();
let bundle_id = self
.world
.bundles
.init_component_info(&self.world.components, component_id);
let storage_type = self.world.bundles.get_storage_unchecked(bundle_id);
let bundles = &mut self.world.bundles;
let components = &mut self.world.components;
let (bundle_info, storage_type) = bundles.init_component_info(components, component_id);
let bundle_inserter = bundle_info.get_bundle_inserter(
&mut self.world.entities,
&mut self.world.archetypes,
&self.world.components,
&mut self.world.storages,
let bundle_inserter = BundleInserter::new_with_id(
self.world,
self.location.archetype_id,
bundle_id,
change_tick,
);
@ -708,9 +693,8 @@ impl<'w> EntityWorldMut<'w> {
self.entity,
self.location,
Some(component).into_iter(),
Some(storage_type).into_iter(),
Some(storage_type).iter().cloned(),
);
self
}
@ -732,17 +716,16 @@ impl<'w> EntityWorldMut<'w> {
iter_components: I,
) -> &mut Self {
let change_tick = self.world.change_tick();
let bundles = &mut self.world.bundles;
let components = &mut self.world.components;
let (bundle_info, storage_types) = bundles.init_dynamic_info(components, component_ids);
let bundle_inserter = bundle_info.get_bundle_inserter(
&mut self.world.entities,
&mut self.world.archetypes,
&self.world.components,
&mut self.world.storages,
let bundle_id = self
.world
.bundles
.init_dynamic_info(&self.world.components, component_ids);
let mut storage_types =
std::mem::take(self.world.bundles.get_storages_unchecked(bundle_id));
let bundle_inserter = BundleInserter::new_with_id(
self.world,
self.location.archetype_id,
bundle_id,
change_tick,
);
@ -751,9 +734,9 @@ impl<'w> EntityWorldMut<'w> {
self.entity,
self.location,
iter_components,
storage_types.iter().cloned(),
(*storage_types).iter().cloned(),
);
*self.world.bundles.get_storages_unchecked(bundle_id) = std::mem::take(&mut storage_types);
self
}
@ -764,19 +747,18 @@ impl<'w> EntityWorldMut<'w> {
// TODO: BundleRemover?
#[must_use]
pub fn take<T: Bundle>(&mut self) -> Option<T> {
let archetypes = &mut self.world.archetypes;
let storages = &mut self.world.storages;
let components = &mut self.world.components;
let entities = &mut self.world.entities;
let removed_components = &mut self.world.removed_components;
let bundle_info = self.world.bundles.init_info::<T>(components, storages);
let world = &mut self.world;
let storages = &mut world.storages;
let components = &mut world.components;
let bundle_id = world.bundles.init_info::<T>(components, storages);
// SAFETY: We just ensured this bundle exists
let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) };
let old_location = self.location;
// SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid,
// components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T`
let new_archetype_id = unsafe {
remove_bundle_from_archetype(
archetypes,
&mut world.archetypes,
storages,
components,
old_location.archetype_id,
@ -789,8 +771,33 @@ impl<'w> EntityWorldMut<'w> {
return None;
}
let mut bundle_components = bundle_info.components().iter().cloned();
let entity = self.entity;
// SAFETY: Archetypes and Bundles cannot be mutably aliased through DeferredWorld
let (old_archetype, bundle_info, mut deferred_world) = unsafe {
let bundle_info: *const BundleInfo = bundle_info;
let world = world.as_unsafe_world_cell();
(
&world.archetypes()[old_location.archetype_id],
&*bundle_info,
world.into_deferred(),
)
};
if old_archetype.has_on_remove() {
// SAFETY: All components in the archetype exist in world
unsafe {
deferred_world.trigger_on_remove(entity, bundle_info.iter_components());
}
}
let archetypes = &mut world.archetypes;
let storages = &mut world.storages;
let components = &mut world.components;
let entities = &mut world.entities;
let removed_components = &mut world.removed_components;
let entity = self.entity;
let mut bundle_components = bundle_info.iter_components();
// SAFETY: bundle components are iterated in order, which guarantees that the component type
// matches
let result = unsafe {
@ -824,7 +831,6 @@ impl<'w> EntityWorldMut<'w> {
new_archetype_id,
);
}
Some(result)
}
@ -914,49 +920,60 @@ impl<'w> EntityWorldMut<'w> {
}
}
/// Remove the components of `bundle_info` from `entity`, where `self_location` and `old_location`
/// are the location of this entity, and `self_location` is updated to the new location.
/// Remove the components of `bundle` from `entity`.
///
/// SAFETY: `old_location` must be valid and the components in `bundle_info` must exist.
/// SAFETY: The components in `bundle_info` must exist.
#[allow(clippy::too_many_arguments)]
unsafe fn remove_bundle_info(
entity: Entity,
self_location: &mut EntityLocation,
old_location: EntityLocation,
bundle_info: &BundleInfo,
archetypes: &mut Archetypes,
storages: &mut Storages,
components: &Components,
entities: &mut Entities,
removed_components: &mut RemovedComponentEvents,
) {
// SAFETY: `archetype_id` exists because it is referenced in `old_location` which is valid
unsafe fn remove_bundle(&mut self, bundle: BundleId) -> EntityLocation {
let entity = self.entity;
let world = &mut self.world;
let location = self.location;
let bundle_info = world.bundles.get_unchecked(bundle);
// SAFETY: `archetype_id` exists because it is referenced in `location` which is valid
// and components in `bundle_info` must exist due to this functions safety invariants.
let new_archetype_id = unsafe {
remove_bundle_from_archetype(
archetypes,
storages,
components,
old_location.archetype_id,
bundle_info,
true,
)
}
let new_archetype_id = remove_bundle_from_archetype(
&mut world.archetypes,
&mut world.storages,
&world.components,
location.archetype_id,
bundle_info,
true,
)
.expect("intersections should always return a result");
if new_archetype_id == old_location.archetype_id {
return;
if new_archetype_id == location.archetype_id {
return location;
}
let old_archetype = &mut archetypes[old_location.archetype_id];
for component_id in bundle_info.components().iter().cloned() {
// SAFETY: Archetypes and Bundles cannot be mutably aliased through DeferredWorld
let (old_archetype, bundle_info, mut deferred_world) = unsafe {
let bundle_info: *const BundleInfo = bundle_info;
let world = world.as_unsafe_world_cell();
(
&world.archetypes()[location.archetype_id],
&*bundle_info,
world.into_deferred(),
)
};
if old_archetype.has_on_remove() {
// SAFETY: All components in the archetype exist in world
unsafe {
deferred_world.trigger_on_remove(entity, bundle_info.iter_components());
}
}
let old_archetype = &world.archetypes[location.archetype_id];
for component_id in bundle_info.iter_components() {
if old_archetype.contains(component_id) {
removed_components.send(component_id, entity);
world.removed_components.send(component_id, entity);
// Make sure to drop components stored in sparse sets.
// Dense components are dropped later in `move_to_and_drop_missing_unchecked`.
if let Some(StorageType::SparseSet) = old_archetype.get_storage_type(component_id) {
storages
world
.storages
.sparse_sets
.get_mut(component_id)
.unwrap()
@ -967,18 +984,19 @@ impl<'w> EntityWorldMut<'w> {
// SAFETY: `new_archetype_id` is a subset of the components in `old_location.archetype_id`
// because it is created by removing a bundle from these components.
unsafe {
Self::move_entity_from_remove::<true>(
entity,
self_location,
old_location.archetype_id,
old_location,
entities,
archetypes,
storages,
new_archetype_id,
);
}
let mut new_location = location;
Self::move_entity_from_remove::<true>(
entity,
&mut new_location,
location.archetype_id,
location,
&mut world.entities,
&mut world.archetypes,
&mut world.storages,
new_archetype_id,
);
new_location
}
/// Removes any components in the [`Bundle`] from the entity.
@ -986,30 +1004,13 @@ impl<'w> EntityWorldMut<'w> {
/// See [`EntityCommands::remove`](crate::system::EntityCommands::remove) for more details.
// TODO: BundleRemover?
pub fn remove<T: Bundle>(&mut self) -> &mut Self {
let archetypes = &mut self.world.archetypes;
let storages = &mut self.world.storages;
let components = &mut self.world.components;
let entities = &mut self.world.entities;
let removed_components = &mut self.world.removed_components;
let bundle_info = self.world.bundles.init_info::<T>(components, storages);
let old_location = self.location;
// SAFETY: Components exist in `bundle_info` because `Bundles::init_info`
// initializes a `BundleInfo` containing all components of the bundle type `T`.
unsafe {
Self::remove_bundle_info(
self.entity,
&mut self.location,
old_location,
bundle_info,
archetypes,
storages,
components,
entities,
removed_components,
);
}
// initializes a: EntityLocation `BundleInfo` containing all components of the bundle type `T`.
self.location = unsafe { self.remove_bundle(bundle_info) };
self
}
@ -1021,10 +1022,10 @@ impl<'w> EntityWorldMut<'w> {
let archetypes = &mut self.world.archetypes;
let storages = &mut self.world.storages;
let components = &mut self.world.components;
let entities = &mut self.world.entities;
let removed_components = &mut self.world.removed_components;
let retained_bundle_info = self.world.bundles.init_info::<T>(components, storages);
let retained_bundle = self.world.bundles.init_info::<T>(components, storages);
// SAFETY: `retained_bundle` exists as we just initialized it.
let retained_bundle_info = unsafe { self.world.bundles.get_unchecked(retained_bundle) };
let old_location = self.location;
let old_archetype = &mut archetypes[old_location.archetype_id];
@ -1032,28 +1033,11 @@ impl<'w> EntityWorldMut<'w> {
.components()
.filter(|c| !retained_bundle_info.components().contains(c))
.collect::<Vec<_>>();
let remove_bundle_info = self
.world
.bundles
.init_dynamic_info(components, to_remove)
.0;
let remove_bundle = self.world.bundles.init_dynamic_info(components, to_remove);
// SAFETY: Components exist in `remove_bundle_info` because `Bundles::init_dynamic_info`
// SAFETY: Components exist in `remove_bundle` because `Bundles::init_dynamic_info`
// initializes a `BundleInfo` containing all components in the to_remove Bundle.
unsafe {
Self::remove_bundle_info(
self.entity,
&mut self.location,
old_location,
remove_bundle_info,
archetypes,
storages,
components,
entities,
removed_components,
);
}
self.location = unsafe { self.remove_bundle(remove_bundle) };
self
}
@ -1061,9 +1045,28 @@ impl<'w> EntityWorldMut<'w> {
///
/// See [`World::despawn`] for more details.
pub fn despawn(self) {
debug!("Despawning entity {:?}", self.entity);
let world = self.world;
world.flush();
world.flush_entities();
let archetype = &world.archetypes[self.location.archetype_id];
// SAFETY: Archetype cannot be mutably aliased by DeferredWorld
let (archetype, mut deferred_world) = unsafe {
let archetype: *const Archetype = archetype;
let world = world.as_unsafe_world_cell();
(&*archetype, world.into_deferred())
};
if archetype.has_on_remove() {
// SAFETY: All components in the archetype exist in world
unsafe {
deferred_world.trigger_on_remove(self.entity, archetype.components());
}
}
for component_id in archetype.components() {
world.removed_components.send(component_id, self.entity);
}
let location = world
.entities
.free(self.entity)
@ -1072,10 +1075,7 @@ impl<'w> EntityWorldMut<'w> {
let moved_entity;
{
let archetype = &mut world.archetypes[location.archetype_id];
for component_id in archetype.components() {
world.removed_components.send(component_id, self.entity);
}
let archetype = &mut world.archetypes[self.location.archetype_id];
let remove_result = archetype.swap_remove(location.archetype_row);
if let Some(swapped_entity) = remove_result.swapped_entity {
let swapped_location = world.entities.get(swapped_entity).unwrap();
@ -1123,6 +1123,13 @@ impl<'w> EntityWorldMut<'w> {
world.archetypes[moved_location.archetype_id]
.set_entity_table_row(moved_location.archetype_row, table_row);
}
world.flush_commands();
}
/// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`]
pub fn flush(self) -> Entity {
self.world.flush_commands();
self.entity
}
/// Gets read-only access to the world that the current entity belongs to.
@ -2077,7 +2084,7 @@ unsafe fn insert_dynamic_bundle<
I: Iterator<Item = OwningPtr<'a>>,
S: Iterator<Item = StorageType>,
>(
mut bundle_inserter: BundleInserter<'_, '_>,
mut bundle_inserter: BundleInserter<'_>,
entity: Entity,
location: EntityLocation,
components: I,
@ -2185,6 +2192,7 @@ unsafe fn remove_bundle_from_archetype(
}
let new_archetype_id = archetypes.get_id_or_insert(
components,
next_table_id,
next_table_components,
next_sparse_set_components,

View file

@ -1,5 +1,6 @@
//! Defines the [`World`] and APIs for accessing it directly.
mod deferred_world;
mod entity_ref;
pub mod error;
mod spawn_batch;
@ -7,6 +8,7 @@ pub mod unsafe_world_cell;
mod world_cell;
pub use crate::change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD};
pub use deferred_world::DeferredWorld;
pub use entity_ref::{
EntityMut, EntityRef, EntityWorldMut, Entry, FilteredEntityMut, FilteredEntityRef,
OccupiedEntry, VacantEntry,
@ -19,8 +21,8 @@ use crate::{
bundle::{Bundle, BundleInserter, BundleSpawner, Bundles},
change_detection::{MutUntyped, TicksMut},
component::{
Component, ComponentDescriptor, ComponentId, ComponentInfo, ComponentTicks, Components,
Tick,
Component, ComponentDescriptor, ComponentHooks, ComponentId, ComponentInfo, ComponentTicks,
Components, Tick,
},
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation},
event::{Event, EventId, Events, SendBatchIds},
@ -28,7 +30,7 @@ use crate::{
removal_detection::RemovedComponentEvents,
schedule::{Schedule, ScheduleLabel, Schedules},
storage::{ResourceData, Storages},
system::{Res, Resource},
system::{CommandQueue, Commands, Res, Resource},
world::error::TryRunScheduleError,
};
use bevy_ptr::{OwningPtr, Ptr};
@ -77,6 +79,7 @@ pub struct World {
pub(crate) change_tick: AtomicU32,
pub(crate) last_change_tick: Tick,
pub(crate) last_check_tick: Tick,
pub(crate) command_queue: CommandQueue,
}
impl Default for World {
@ -95,6 +98,7 @@ impl Default for World {
change_tick: AtomicU32::new(1),
last_change_tick: Tick::new(0),
last_check_tick: Tick::new(0),
command_queue: CommandQueue::default(),
}
}
}
@ -183,11 +187,39 @@ impl World {
WorldCell::new(self)
}
/// Creates a new [`Commands`] instance that writes to the world's command queue
/// Use [`World::flush_commands`] to apply all queued commands
#[inline]
pub fn commands(&mut self) -> Commands {
Commands::new_from_entities(&mut self.command_queue, &self.entities)
}
/// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it.
pub fn init_component<T: Component>(&mut self) -> ComponentId {
self.components.init_component::<T>(&mut self.storages)
}
/// Returns a mutable reference to the [`ComponentHooks`] for a [`Component`] type.
///
/// Will panic if `T` exists in any archetypes.
pub fn register_component_hooks<T: Component>(&mut self) -> &mut ComponentHooks {
let index = self.init_component::<T>();
assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(index)), "Components hooks cannot be modified if the component already exists in an archetype, use init_component if {} may already be in use", std::any::type_name::<T>());
// SAFETY: We just created this component
unsafe { self.components.get_hooks_mut(index).debug_checked_unwrap() }
}
/// Returns a mutable reference to the [`ComponentHooks`] for a [`Component`] with the given id if it exists.
///
/// Will panic if `id` exists in any archetypes.
pub fn register_component_hooks_by_id(
&mut self,
id: ComponentId,
) -> Option<&mut ComponentHooks> {
assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(id)), "Components hooks cannot be modified if the component already exists in an archetype, use init_component if the component with id {:?} may already be in use", id);
self.components.get_hooks_mut(id)
}
/// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it.
///
/// This method differs from [`World::init_component`] in that it uses a [`ComponentDescriptor`]
@ -422,7 +454,7 @@ impl World {
/// scheme worked out to share an ID space (which doesn't happen by default).
#[inline]
pub fn get_or_spawn(&mut self, entity: Entity) -> Option<EntityWorldMut> {
self.flush();
self.flush_entities();
match self.entities.alloc_at_without_replacement(entity) {
AllocAtWithoutReplacement::Exists(location) => {
// SAFETY: `entity` exists and `location` is that entity's location
@ -676,7 +708,7 @@ impl World {
/// assert_eq!(position.x, 0.0);
/// ```
pub fn spawn_empty(&mut self) -> EntityWorldMut {
self.flush();
self.flush_entities();
let entity = self.entities.alloc();
// SAFETY: entity was just allocated
unsafe { self.spawn_at_empty_internal(entity) }
@ -742,23 +774,13 @@ impl World {
/// assert_eq!(position.x, 2.0);
/// ```
pub fn spawn<B: Bundle>(&mut self, bundle: B) -> EntityWorldMut {
self.flush();
self.flush_entities();
let change_tick = self.change_tick();
let entity = self.entities.alloc();
let entity_location = {
let bundle_info = self
.bundles
.init_info::<B>(&mut self.components, &mut self.storages);
let mut spawner = bundle_info.get_bundle_spawner(
&mut self.entities,
&mut self.archetypes,
&self.components,
&mut self.storages,
change_tick,
);
let mut bundle_spawner = BundleSpawner::new::<B>(self, change_tick);
// SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent
unsafe { spawner.spawn_non_existent(entity, bundle) }
unsafe { bundle_spawner.spawn_non_existent(entity, bundle) }
};
// SAFETY: entity and location are valid, as they were just created above
@ -1534,33 +1556,30 @@ impl World {
I::IntoIter: Iterator<Item = (Entity, B)>,
B: Bundle,
{
self.flush();
self.flush_entities();
let change_tick = self.change_tick();
let bundle_info = self
let bundle_id = self
.bundles
.init_info::<B>(&mut self.components, &mut self.storages);
enum SpawnOrInsert<'a, 'b> {
Spawn(BundleSpawner<'a, 'b>),
Insert(BundleInserter<'a, 'b>, ArchetypeId),
enum SpawnOrInsert<'w> {
Spawn(BundleSpawner<'w>),
Insert(BundleInserter<'w>, ArchetypeId),
}
impl<'a, 'b> SpawnOrInsert<'a, 'b> {
impl<'w> SpawnOrInsert<'w> {
fn entities(&mut self) -> &mut Entities {
match self {
SpawnOrInsert::Spawn(spawner) => spawner.entities,
SpawnOrInsert::Insert(inserter, _) => inserter.entities,
SpawnOrInsert::Spawn(spawner) => spawner.entities(),
SpawnOrInsert::Insert(inserter, _) => inserter.entities(),
}
}
}
let mut spawn_or_insert = SpawnOrInsert::Spawn(bundle_info.get_bundle_spawner(
&mut self.entities,
&mut self.archetypes,
&self.components,
&mut self.storages,
change_tick,
));
// SAFETY: we initialized this bundle_id in `init_info`
let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe {
BundleSpawner::new_with_id(self, bundle_id, change_tick)
});
let mut invalid_entities = Vec::new();
for (entity, bundle) in iter {
@ -1577,14 +1596,15 @@ impl World {
unsafe { inserter.insert(entity, location, bundle) };
}
_ => {
let mut inserter = bundle_info.get_bundle_inserter(
&mut self.entities,
&mut self.archetypes,
&self.components,
&mut self.storages,
location.archetype_id,
change_tick,
);
// SAFETY: we initialized this bundle_id in `init_info`
let mut inserter = unsafe {
BundleInserter::new_with_id(
self,
location.archetype_id,
bundle_id,
change_tick,
)
};
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
unsafe { inserter.insert(entity, location, bundle) };
spawn_or_insert =
@ -1597,13 +1617,9 @@ impl World {
// SAFETY: `entity` is allocated (but non existent), bundle matches inserter
unsafe { spawner.spawn_non_existent(entity, bundle) };
} else {
let mut spawner = bundle_info.get_bundle_spawner(
&mut self.entities,
&mut self.archetypes,
&self.components,
&mut self.storages,
change_tick,
);
// SAFETY: we initialized this bundle_id in `init_info`
let mut spawner =
unsafe { BundleSpawner::new_with_id(self, bundle_id, change_tick) };
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
unsafe { spawner.spawn_non_existent(entity, bundle) };
spawn_or_insert = SpawnOrInsert::Spawn(spawner);
@ -1824,7 +1840,7 @@ impl World {
/// Empties queued entities and adds them to the empty [`Archetype`](crate::archetype::Archetype).
/// This should be called before doing operations that might operate on queued entities,
/// such as inserting a [`Component`].
pub(crate) fn flush(&mut self) {
pub(crate) fn flush_entities(&mut self) {
let empty_archetype = self.archetypes.empty_mut();
let table = &mut self.storages.tables[empty_archetype.table_id()];
// PERF: consider pre-allocating space for flushed entities
@ -1838,6 +1854,16 @@ impl World {
}
}
/// Applies any commands in the world's internal [`CommandQueue`].
/// This does not apply commands from any systems, only those stored in the world.
#[inline]
pub fn flush_commands(&mut self) {
if !self.command_queue.is_empty() {
// `CommandQueue` application always applies commands from the world queue first so this will apply all stored commands
CommandQueue::default().apply(self);
}
}
/// Increments the world's current change tick and returns the old value.
#[inline]
pub fn increment_change_tick(&self) -> Tick {

View file

@ -15,7 +15,7 @@ where
I::Item: Bundle,
{
inner: I,
spawner: BundleSpawner<'w, 'w>,
spawner: BundleSpawner<'w>,
}
impl<'w, I> SpawnBatchIter<'w, I>
@ -27,24 +27,15 @@ where
pub(crate) fn new(world: &'w mut World, iter: I) -> Self {
// Ensure all entity allocations are accounted for so `self.entities` can realloc if
// necessary
world.flush();
world.flush_entities();
let change_tick = world.change_tick();
let (lower, upper) = iter.size_hint();
let length = upper.unwrap_or(lower);
let bundle_info = world
.bundles
.init_info::<I::Item>(&mut world.components, &mut world.storages);
world.entities.reserve(length as u32);
let mut spawner = bundle_info.get_bundle_spawner(
&mut world.entities,
&mut world.archetypes,
&world.components,
&mut world.storages,
change_tick,
);
let mut spawner = BundleSpawner::new::<I::Item>(world, change_tick);
spawner.reserve_storage(length);
Self {
@ -60,7 +51,11 @@ where
I::Item: Bundle,
{
fn drop(&mut self) {
for _ in 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() };
}
}

View file

@ -14,10 +14,10 @@ use crate::{
prelude::Component,
removal_detection::RemovedComponentEvents,
storage::{Column, ComponentSparseSet, Storages},
system::{Res, Resource},
system::{CommandQueue, Res, Resource},
};
use bevy_ptr::Ptr;
use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr};
use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr, ptr::addr_of_mut};
/// Variant of the [`World`] where resource and component accesses take `&self`, and the responsibility to avoid
/// aliasing violations are given to the caller instead of being checked at compile-time by rust's unique XOR shared rule.
@ -590,6 +590,18 @@ impl<'w> UnsafeWorldCell<'w> {
.get(component_id)?
.get_with_ticks()
}
// Returns a mutable reference to the underlying world's [`CommandQueue`].
/// # Safety
/// It is the callers responsibility to ensure that
/// - the [`UnsafeWorldCell`] has permission to access the queue mutably
/// - no mutable references to the queue exist at the same time
pub(crate) unsafe fn get_command_queue(self) -> &'w mut CommandQueue {
// SAFETY:
// - caller ensures there are no existing mutable references
// - caller ensures that we have permission to access the queue
unsafe { &mut *addr_of_mut!((*self.0).command_queue) }
}
}
impl Debug for UnsafeWorldCell<'_> {

View file

@ -228,6 +228,7 @@ Example | Description
Example | Description
--- | ---
[Component Change Detection](../examples/ecs/component_change_detection.rs) | Change detection on components
[Component Hooks](../examples/ecs/component_hooks.rs) | Define component hooks to manage component lifecycle events
[Custom Query Parameters](../examples/ecs/custom_query_param.rs) | Groups commonly used compound queries and query filters into a single type
[Custom Schedule](../examples/ecs/custom_schedule.rs) | Demonstrates how to add custom schedules
[Dynamic ECS](../examples/ecs/dynamic.rs) | Dynamically create components, spawn entities with those components and query those components

View file

@ -0,0 +1,96 @@
//! This examples illustrates the different ways you can employ component lifecycle hooks
use bevy::ecs::component::{ComponentInfo, TableStorage};
use bevy::prelude::*;
use std::collections::HashMap;
#[derive(Debug)]
struct MyComponent(KeyCode);
impl Component for MyComponent {
type Storage = TableStorage;
/// Hooks can also be registered during component initialisation by
/// implementing `init_component_info`
fn init_component_info(_info: &mut ComponentInfo) {
// Register hooks...
}
}
#[derive(Resource, Default, Debug, Deref, DerefMut)]
struct MyComponentIndex(HashMap<KeyCode, Entity>);
#[derive(Event)]
struct MyEvent;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, trigger_hooks)
.init_resource::<MyComponentIndex>()
.add_event::<MyEvent>()
.run();
}
fn setup(world: &mut World) {
// In order to register component hooks the component must:
// - not belong to any created archetypes
// - not already have a hook of that kind registered
// This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast
world
.register_component_hooks::<MyComponent>()
// There are 3 component lifecycle hooks: `on_add`, `on_insert` and `on_remove`
// A hook has 3 arguments:
// - a `DeferredWorld`, this allows access to resource and component data as well as `Commands`
// - the entity that triggered the hook
// - the component id of the triggering component, this is mostly used for dynamic components
//
// `on_add` will trigger when a component is inserted onto an entity without it
.on_add(|mut world, entity, component_id| {
// You can access component data from within the hook
let value = world.get::<MyComponent>(entity).unwrap().0;
println!(
"Component: {:?} added to: {:?} with value {:?}",
component_id, entity, value
);
// Or access resources
world
.resource_mut::<MyComponentIndex>()
.insert(value, entity);
// Or send events
world.send_event(MyEvent);
})
// `on_insert` will trigger when a component is inserted onto an entity,
// regardless of whether or not it already had it and after `on_add` if it ran
.on_insert(|world, _, _| {
println!("Current Index: {:?}", world.resource::<MyComponentIndex>());
})
// `on_remove` will trigger when a component is removed from an entity,
// since it runs before the component is removed you can still access the component data
.on_remove(|mut world, entity, component_id| {
let value = world.get::<MyComponent>(entity).unwrap().0;
println!(
"Component: {:?} removed from: {:?} with value {:?}",
component_id, entity, value
);
world.resource_mut::<MyComponentIndex>().remove(&value);
// You can also issue commands through `.commands()`
world.commands().entity(entity).despawn();
});
}
fn trigger_hooks(
mut commands: Commands,
keys: Res<ButtonInput<KeyCode>>,
index: Res<MyComponentIndex>,
) {
for (key, entity) in index.iter() {
if !keys.pressed(*key) {
commands.entity(*entity).remove::<MyComponent>();
}
}
for key in keys.get_just_pressed() {
commands.spawn(MyComponent(*key));
}
}