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

Generalised ECS reactivity with Observers #10839

Open
wants to merge 108 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
001f900
Initial hooks implementation
james-j-obrien Nov 24, 2023
26edb73
Perf improvements, archetype flags
james-j-obrien Nov 25, 2023
4eb9a80
Refactoring
james-j-obrien Nov 25, 2023
e174cd8
Improve command application logic
james-j-obrien Nov 25, 2023
abc728b
Simplify command application to get recursive ordering
james-j-obrien Nov 25, 2023
06d4db6
First docs and safety comment pass
james-j-obrien Nov 26, 2023
dec4cec
Miri pass
james-j-obrien Nov 27, 2023
6589b9f
Add missing cfg(test)
james-j-obrien Nov 27, 2023
43d7c74
Tidy iterator chains
james-j-obrien Nov 27, 2023
3d55037
Cleanup
james-j-obrien Nov 27, 2023
9754ae9
CI pass
james-j-obrien Nov 27, 2023
d237d0b
Re-generate README.md
james-j-obrien Nov 27, 2023
79bbe67
Add missing safety comment
james-j-obrien Nov 27, 2023
ce371fc
Simplify asserts
james-j-obrien Nov 27, 2023
bbecffa
Relax restrictions on register_component
james-j-obrien Nov 27, 2023
3600bee
Update example comments
james-j-obrien Nov 27, 2023
1ffe76c
Initial implementation
james-j-obrien Nov 30, 2023
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
029f09f
Merge component-hooks
james-j-obrien Nov 30, 2023
388036f
More complete example
james-j-obrien Dec 1, 2023
8b15ee1
Additional comments
james-j-obrien Dec 1, 2023
f374ee8
Fix typo
james-j-obrien Dec 1, 2023
4d1c719
Move command queue's off the function stack to prevent overflows
james-j-obrien Dec 2, 2023
ea80aa0
Merge hooks
james-j-obrien Dec 2, 2023
6682da3
Remove unused code
james-j-obrien Dec 2, 2023
a52837c
Strip more code
james-j-obrien Dec 2, 2023
615e232
Major cleanup
james-j-obrien Dec 2, 2023
a5e5f53
Add data_ptr
james-j-obrien Dec 2, 2023
283c44d
Avoiding spawning entity during world bootstrap
james-j-obrien Dec 2, 2023
91797f6
Move AttachObserver back to SparseSet
james-j-obrien Dec 2, 2023
3bef2ce
Add method to register hooks in component trait definition
james-j-obrien Dec 2, 2023
13240f0
Merge branch 'component-hooks' into observers
james-j-obrien Dec 3, 2023
9cdb86f
Merge hooks, refactor to take advantage
james-j-obrien Dec 3, 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
fa42ed2
Partial FnMut implementation
james-j-obrien Dec 14, 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
1600f4b
Merge component-hooks
james-j-obrien Jan 30, 2024
0dfe890
System refactor
james-j-obrien Jan 30, 2024
940efbe
Clean-up
james-j-obrien Jan 30, 2024
ffecc78
Fix doc tests
james-j-obrien Jan 30, 2024
49ee3eb
System refactor, merge main
james-j-obrien Jan 30, 2024
6c948d4
Merge main
james-j-obrien Jan 30, 2024
961bff7
Remove In<>
james-j-obrien Jan 30, 2024
dcee208
Add bundle generic
james-j-obrien Jan 30, 2024
43f6d43
Minor fixes
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
329544a
Clean up pass: tidy up code, make APIs more consistent, add prelimina…
james-j-obrien Feb 6, 2024
3f9c119
Minor safety doc fix
james-j-obrien Feb 6, 2024
2615958
Merge branch 'main' into observers
james-j-obrien Feb 6, 2024
84c8531
Doc fixes
james-j-obrien Feb 6, 2024
277d301
Allow DeferredWorld as a system parameter, clean up docs
james-j-obrien Feb 6, 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
c8df047
Merge main
james-j-obrien Feb 20, 2024
84ff2c7
Elaborate on ObserverSystem
james-j-obrien Feb 20, 2024
43c5aec
Merge main
james-j-obrien Mar 1, 2024
42de22b
Spelling and typos
james-j-obrien Mar 1, 2024
6b64539
Fix lints
james-j-obrien Mar 1, 2024
8c0f855
Typos
james-j-obrien Mar 1, 2024
de77347
Merge main
james-j-obrien Mar 8, 2024
238aa90
Cleanup unsafe code, simplify types and trait definitions
james-j-obrien Mar 8, 2024
8db24ac
CI Fixes
james-j-obrien Mar 8, 2024
5faba35
Fix bugs, add tests
james-j-obrien Mar 8, 2024
19e55c0
Revamp example
james-j-obrien Mar 8, 2024
4678743
Merge branch 'main' into observers
james-j-obrien Mar 8, 2024
09fc6b7
CI Fixes
james-j-obrien Mar 8, 2024
b025837
Slightly tidy up example
james-j-obrien Mar 8, 2024
4ddbb94
Minor example tidying
james-j-obrien Mar 8, 2024
50c8c71
Minor cleanup
james-j-obrien Apr 27, 2024
df1f0b8
Merge main
james-j-obrien Apr 27, 2024
a6732fe
Satisfy MIRI at the cost of performance
james-j-obrien Apr 27, 2024
4e41128
Merge branch 'main' into observers
james-j-obrien May 7, 2024
ae56005
Merge main, incorporate some feedback
james-j-obrien May 7, 2024
908cdbb
Rebrand ECS events as Triggers
james-j-obrien May 23, 2024
f2b29f8
Apply easy doc improvements
james-j-obrien May 23, 2024
5757352
Merge branch 'main' into observers
james-j-obrien May 23, 2024
9fa8e9f
Merge branch 'observers' of https://github.com/james-j-obrien/bevy in…
james-j-obrien May 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Expand Up @@ -2479,6 +2479,17 @@ description = "Systems run in parallel, but their order isn't always determinist
category = "ECS (Entity Component System)"
wasm = false

[[example]]
name = "observers"
path = "examples/ecs/observers.rs"
doc-scrape-examples = true

[package.metadata.example.observers]
name = "Observers"
description = "Define observers to react to ECS events"
category = "ECS (Entity Component System)"
wasm = false

[[example]]
name = "3d_rotation"
path = "examples/transforms/3d_rotation.rs"
Expand Down
23 changes: 23 additions & 0 deletions crates/bevy_ecs/macros/src/component.rs
Expand Up @@ -65,6 +65,29 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
})
}

pub fn derive_trigger(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();

let storage = storage_path(&bevy_ecs_path, StorageTy::SparseSet);

ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });

let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();

TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
}

impl #bevy_ecs_path::observer::Trigger for #struct_name #type_generics {}
})
}

pub const COMPONENT: &str = "component";
pub const STORAGE: &str = "storage";

Expand Down
20 changes: 20 additions & 0 deletions crates/bevy_ecs/macros/src/lib.rs
Expand Up @@ -74,6 +74,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
.collect::<Vec<_>>();

let mut field_component_ids = Vec::new();
let mut field_get_component_ids = Vec::new();
let mut field_get_components = Vec::new();
let mut field_from_components = Vec::new();
for (((i, field_type), field_kind), field) in field_type
Expand All @@ -87,6 +88,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
field_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids);
});
field_get_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);
});
match field {
Some(field) => {
field_get_components.push(quote! {
Expand Down Expand Up @@ -133,6 +137,13 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
#(#field_component_ids)*
}

fn get_component_ids(
components: &#ecs_path::component::Components,
ids: &mut impl FnMut(Option<#ecs_path::component::ComponentId>)
){
#(#field_get_component_ids)*
}

#[allow(unused_variables, non_snake_case)]
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
where
Expand Down Expand Up @@ -435,6 +446,10 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world);
}

fn queue(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: #path::world::DeferredWorld) {
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::queue(&mut state.state, system_meta, world);
}

unsafe fn get_param<'w, 's>(
state: &'s mut Self::State,
system_meta: &#path::system::SystemMeta,
Expand Down Expand Up @@ -517,6 +532,11 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
component::derive_component(input)
}

#[proc_macro_derive(Trigger)]
pub fn derive_trigger(input: TokenStream) -> TokenStream {
component::derive_trigger(input)
}

#[proc_macro_derive(States)]
pub fn derive_states(input: TokenStream) -> TokenStream {
states::derive_states(input)
Expand Down
50 changes: 47 additions & 3 deletions crates/bevy_ecs/src/archetype.rs
Expand Up @@ -23,6 +23,7 @@ use crate::{
bundle::BundleId,
component::{ComponentId, Components, StorageType},
entity::{Entity, EntityLocation},
observer::Observers,
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow},
};
use std::{
Expand Down Expand Up @@ -119,6 +120,7 @@ pub(crate) struct AddBundle {
/// For each component iterated in the same order as the source [`Bundle`](crate::bundle::Bundle),
/// indicate if the component is newly added to the target archetype or if it already existed
pub bundle_status: Vec<ComponentStatus>,
pub added: Vec<ComponentId>,
}

/// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components
Expand Down Expand Up @@ -202,12 +204,14 @@ impl Edges {
bundle_id: BundleId,
archetype_id: ArchetypeId,
bundle_status: Vec<ComponentStatus>,
added: Vec<ComponentId>,
) {
self.add_bundle.insert(
bundle_id,
AddBundle {
archetype_id,
bundle_status,
added,
},
);
}
Expand Down Expand Up @@ -314,6 +318,9 @@ bitflags::bitflags! {
const ON_ADD_HOOK = (1 << 0);
const ON_INSERT_HOOK = (1 << 1);
const ON_REMOVE_HOOK = (1 << 2);
const ON_ADD_OBSERVER = (1 << 3);
const ON_INSERT_OBSERVER = (1 << 4);
const ON_REMOVE_OBSERVER = (1 << 5);
}
}

Expand All @@ -335,6 +342,7 @@ pub struct Archetype {
impl Archetype {
pub(crate) fn new(
components: &Components,
observers: &Observers,
id: ArchetypeId,
table_id: TableId,
table_components: impl Iterator<Item = (ComponentId, ArchetypeComponentId)>,
Expand All @@ -348,6 +356,7 @@ impl Archetype {
// 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);
observers.update_archetype_flags(component_id, &mut flags);
archetype_components.insert(
component_id,
ArchetypeComponentInfo {
Expand Down Expand Up @@ -580,21 +589,39 @@ impl Archetype {

james-j-obrien marked this conversation as resolved.
Show resolved Hide resolved
/// Returns true if any of the components in this archetype have `on_add` hooks
#[inline]
pub(crate) fn has_on_add(&self) -> bool {
pub fn has_add_hook(&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 {
pub fn has_insert_hook(&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 {
pub fn has_remove_hook(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK)
}

/// Returns true if any of the components in this archetype have at least one `OnAdd` observer
#[inline]
pub fn has_add_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER)
}

/// Returns true if any of the components in this archetype have at least one `OnInsert` observer
#[inline]
pub fn has_insert_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER)
}

/// Returns true if any of the components in this archetype have at least one `OnRemove` observer
#[inline]
pub fn has_remove_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER)
}
}

/// The next [`ArchetypeId`] in an [`Archetypes`] collection.
Expand Down Expand Up @@ -681,6 +708,7 @@ impl Archetypes {
unsafe {
archetypes.get_id_or_insert(
&Components::default(),
&Observers::default(),
TableId::empty(),
Vec::new(),
Vec::new(),
Expand Down Expand Up @@ -782,6 +810,7 @@ impl Archetypes {
pub(crate) unsafe fn get_id_or_insert(
&mut self,
components: &Components,
observers: &Observers,
table_id: TableId,
table_components: Vec<ComponentId>,
sparse_set_components: Vec<ComponentId>,
Expand All @@ -808,6 +837,7 @@ impl Archetypes {
(sparse_start..*archetype_component_count).map(ArchetypeComponentId);
archetypes.push(Archetype::new(
components,
observers,
id,
table_id,
table_components.into_iter().zip(table_archetype_components),
Expand All @@ -832,6 +862,20 @@ impl Archetypes {
archetype.clear_entities();
}
}

pub(crate) fn update_flags(
&mut self,
component_id: ComponentId,
flags: ArchetypeFlags,
set: bool,
) {
// TODO: Refactor component index to speed this up.
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
for archetype in &mut self.archetypes {
if archetype.contains(component_id) {
archetype.flags.set(flags, set);
}
}
}
}

impl Index<RangeFrom<ArchetypeGeneration>> for Archetypes {
Expand Down