Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ecs/component_hooks.rs
6592 views
1
//! This example illustrates the different ways you can employ component lifecycle hooks.
2
//!
3
//! Whenever possible, prefer using Bevy's change detection or Events for reacting to component changes.
4
//! Events generally offer better performance and more flexible integration into Bevy's systems.
5
//! Hooks are useful to enforce correctness but have limitations (only one hook per component,
6
//! less ergonomic than events).
7
//!
8
//! Here are some cases where components hooks might be necessary:
9
//!
10
//! - Maintaining indexes: If you need to keep custom data structures (like a spatial index) in
11
//! sync with the addition/removal of components.
12
//!
13
//! - Enforcing structural rules: When you have systems that depend on specific relationships
14
//! between components (like hierarchies or parent-child links) and need to maintain correctness.
15
16
use bevy::{
17
ecs::component::{Mutable, StorageType},
18
ecs::lifecycle::{ComponentHook, HookContext},
19
prelude::*,
20
};
21
use std::collections::HashMap;
22
23
#[derive(Debug)]
24
/// Hooks can also be registered during component initialization by
25
/// using [`Component`] derive macro:
26
/// ```no_run
27
/// #[derive(Component)]
28
/// #[component(on_add = ..., on_insert = ..., on_replace = ..., on_remove = ...)]
29
/// ```
30
struct MyComponent(KeyCode);
31
32
impl Component for MyComponent {
33
const STORAGE_TYPE: StorageType = StorageType::Table;
34
type Mutability = Mutable;
35
36
/// Hooks can also be registered during component initialization by
37
/// implementing the associated method
38
fn on_add() -> Option<ComponentHook> {
39
// We don't have an `on_add` hook so we'll just return None.
40
// Note that this is the default behavior when not implementing a hook.
41
None
42
}
43
}
44
45
#[derive(Resource, Default, Debug, Deref, DerefMut)]
46
struct MyComponentIndex(HashMap<KeyCode, Entity>);
47
48
#[derive(BufferedEvent)]
49
struct MyEvent;
50
51
fn main() {
52
App::new()
53
.add_plugins(DefaultPlugins)
54
.add_systems(Startup, setup)
55
.add_systems(Update, trigger_hooks)
56
.init_resource::<MyComponentIndex>()
57
.add_event::<MyEvent>()
58
.run();
59
}
60
61
fn setup(world: &mut World) {
62
// In order to register component hooks the component must:
63
// - not be currently in use by any entities in the world
64
// - not already have a hook of that kind registered
65
// This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast
66
world
67
.register_component_hooks::<MyComponent>()
68
// There are 4 component lifecycle hooks: `on_add`, `on_insert`, `on_replace` and `on_remove`
69
// A hook has 2 arguments:
70
// - a `DeferredWorld`, this allows access to resource and component data as well as `Commands`
71
// - a `HookContext`, this provides access to the following contextual information:
72
// - the entity that triggered the hook
73
// - the component id of the triggering component, this is mostly used for dynamic components
74
// - the location of the code that caused the hook to trigger
75
//
76
// `on_add` will trigger when a component is inserted onto an entity without it
77
.on_add(
78
|mut world,
79
HookContext {
80
entity,
81
component_id,
82
caller,
83
..
84
}| {
85
// You can access component data from within the hook
86
let value = world.get::<MyComponent>(entity).unwrap().0;
87
println!(
88
"{component_id:?} added to {entity} with value {value:?}{}",
89
caller
90
.map(|location| format!("due to {location}"))
91
.unwrap_or_default()
92
);
93
// Or access resources
94
world
95
.resource_mut::<MyComponentIndex>()
96
.insert(value, entity);
97
// Or send events
98
world.write_event(MyEvent);
99
},
100
)
101
// `on_insert` will trigger when a component is inserted onto an entity,
102
// regardless of whether or not it already had it and after `on_add` if it ran
103
.on_insert(|world, _| {
104
println!("Current Index: {:?}", world.resource::<MyComponentIndex>());
105
})
106
// `on_replace` will trigger when a component is inserted onto an entity that already had it,
107
// and runs before the value is replaced.
108
// Also triggers when a component is removed from an entity, and runs before `on_remove`
109
.on_replace(|mut world, context| {
110
let value = world.get::<MyComponent>(context.entity).unwrap().0;
111
world.resource_mut::<MyComponentIndex>().remove(&value);
112
})
113
// `on_remove` will trigger when a component is removed from an entity,
114
// since it runs before the component is removed you can still access the component data
115
.on_remove(
116
|mut world,
117
HookContext {
118
entity,
119
component_id,
120
caller,
121
..
122
}| {
123
let value = world.get::<MyComponent>(entity).unwrap().0;
124
println!(
125
"{component_id:?} removed from {entity} with value {value:?}{}",
126
caller
127
.map(|location| format!("due to {location}"))
128
.unwrap_or_default()
129
);
130
// You can also issue commands through `.commands()`
131
world.commands().entity(entity).despawn();
132
},
133
);
134
}
135
136
fn trigger_hooks(
137
mut commands: Commands,
138
keys: Res<ButtonInput<KeyCode>>,
139
index: Res<MyComponentIndex>,
140
) {
141
for (key, entity) in index.iter() {
142
if !keys.pressed(*key) {
143
commands.entity(*entity).remove::<MyComponent>();
144
}
145
}
146
for key in keys.get_just_pressed() {
147
commands.spawn(MyComponent(*key));
148
}
149
}
150
151