Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Component Lifecycle Hooks and a Deferred World #10756

Merged
merged 46 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
779eabe
Initial hooks implementation
james-j-obrien Nov 24, 2023
6ae319e
Perf improvements, archetype flags
james-j-obrien Nov 25, 2023
5c47028
Refactoring
james-j-obrien Nov 25, 2023
cdd1ee0
Improve command application logic
james-j-obrien Nov 25, 2023
21cd650
Simplify command application to get recursive ordering
james-j-obrien Nov 25, 2023
4e08655
First docs and safety comment pass
james-j-obrien Nov 26, 2023
b620001
Miri pass
james-j-obrien Nov 27, 2023
9b76944
Add missing cfg(test)
james-j-obrien Nov 27, 2023
924fb5d
Tidy iterator chains
james-j-obrien Nov 27, 2023
d23d1b2
Cleanup
james-j-obrien Nov 27, 2023
9b490ad
CI pass
james-j-obrien Nov 27, 2023
b7e5462
Re-generate README.md
james-j-obrien Nov 27, 2023
a4d164a
Add missing safety comment
james-j-obrien Nov 27, 2023
78207c3
Simplify asserts
james-j-obrien Nov 27, 2023
a7b5f28
Relax restrictions on register_component
james-j-obrien Nov 27, 2023
7ab60b9
Update example comments
james-j-obrien Nov 27, 2023
ae26886
API improvements, simplify some usages of unsafe
james-j-obrien Nov 30, 2023
b61f62a
Rebase main.
james-j-obrien Nov 30, 2023
4d1c719
Move command queue's off the function stack to prevent overflows
james-j-obrien Dec 2, 2023
3bef2ce
Add method to register hooks in component trait definition
james-j-obrien Dec 2, 2023
233fd30
Remove most instances of unsafe in DeferredWorld
james-j-obrien Dec 4, 2023
ce47702
Merge main, revert to UnsafeWorldCell, incorporate other feedback
james-j-obrien Dec 12, 2023
cdde4d2
Simplify code in BundleInserter
james-j-obrien Dec 12, 2023
298c598
Address non-complex feedback
james-j-obrien Dec 14, 2023
2e09bd4
Merge branch 'main' into component-hooks
james-j-obrien Dec 14, 2023
d800e7b
Merge main
james-j-obrien Dec 14, 2023
642fb43
Address soundness concerns
james-j-obrien Dec 14, 2023
95cfbe3
Improve docs
james-j-obrien Jan 16, 2024
ee8a9a4
Merge main
james-j-obrien Jan 16, 2024
55b73fd
Merge main
james-j-obrien Jan 16, 2024
6c948d4
Merge main
james-j-obrien Jan 30, 2024
22ed176
Incorporate feedback, fix command application
james-j-obrien Feb 2, 2024
9bc868b
Merge branch 'main' into component-hooks
james-j-obrien Feb 2, 2024
e92c745
Fix example metadata, simplify flush_commands
james-j-obrien Feb 2, 2024
fe4fca3
Remove missing doc link
james-j-obrien Feb 2, 2024
1c2b188
Improve documentation
james-j-obrien Feb 4, 2024
a1a1eeb
Merge main
james-j-obrien Feb 4, 2024
0022c7c
Improve handling of command queue buffers
james-j-obrien Feb 11, 2024
bf5a6fe
Merge branch 'main' into component-hooks
james-j-obrien Feb 11, 2024
9091800
Merge main
james-j-obrien Feb 20, 2024
629d694
Address soundness concern, improve safety comments
james-j-obrien Mar 1, 2024
c794d50
Merge main
james-j-obrien Mar 1, 2024
d55830a
Get hit by the new spelling callout
james-j-obrien Mar 1, 2024
10978e6
Merge branch 'main' into component-hooks
james-j-obrien Mar 1, 2024
5b9f2ba
Wrap unsafe operations in additional unsafe blocks, take archetype fl…
james-j-obrien Mar 1, 2024
492bbb4
Get got again
james-j-obrien Mar 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
73 changes: 65 additions & 8 deletions crates/bevy_ecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -107,7 +107,7 @@ impl ArchetypeId {
}
}

#[derive(Copy, Clone)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub(crate) enum ComponentStatus {
Added,
Mutated,
Expand Down Expand Up @@ -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]*.
Expand All @@ -310,20 +322,26 @@ 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)>,
sparse_set_components: impl Iterator<Item = (ComponentId, ArchetypeComponentId)>,
) -> 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
james-j-obrien marked this conversation as resolved.
Show resolved Hide resolved
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,
Expand All @@ -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,
Expand All @@ -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,
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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>,
Expand All @@ -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),
Expand Down
Loading