docs/bevy/examples/ecs/immutable_components.rs 7.2 K · 205 lines · raw · history

1 //! This example demonstrates immutable components.
2
3 use bevy::{
4 ecs::{
5 component::{ComponentCloneBehavior, ComponentDescriptor, ComponentId, StorageType},
6 lifecycle::HookContext,
7 world::DeferredWorld,
8 },
9 platform::collections::HashMap,
10 prelude::*,
11 ptr::OwningPtr,
12 };
13 use core::alloc::Layout;
14
15 /// This component is mutable, the default case. This is indicated by components
16 /// implementing [`Component`] where [`Component::Mutability`] is [`Mutable`](bevy::ecs::component::Mutable).
17 #[derive(Component)]
18 pub struct MyMutableComponent(bool);
19
20 /// This component is immutable. Once inserted into the ECS, it can only be viewed,
21 /// or removed. Replacement is also permitted, as this is equivalent to removal
22 /// and insertion.
23 ///
24 /// Adding the `#[component(immutable)]` attribute prevents the implementation of [`Component<Mutability = Mutable>`]
25 /// in the derive macro.
26 #[derive(Component)]
27 #[component(immutable)]
28 pub struct MyImmutableComponent(bool);
29
30 fn demo_1(world: &mut World) {
31 // Immutable components can be inserted just like mutable components.
32 let mut entity = world.spawn((MyMutableComponent(false), MyImmutableComponent(false)));
33
34 // But where mutable components can be mutated...
35 let mut my_mutable_component = entity.get_mut::<MyMutableComponent>().unwrap();
36 my_mutable_component.0 = true;
37
38 // ...immutable ones cannot. The below fails to compile as `MyImmutableComponent`
39 // is declared as immutable.
40 // let mut my_immutable_component = entity.get_mut::<MyImmutableComponent>().unwrap();
41
42 // Instead, you could take or replace the immutable component to update its value.
43 let mut my_immutable_component = entity.take::<MyImmutableComponent>().unwrap();
44 my_immutable_component.0 = true;
45 entity.insert(my_immutable_component);
46 }
47
48 /// This is an example of a component like [`Name`](bevy::prelude::Name), but immutable.
49 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Component, Reflect)]
50 #[reflect(Hash, Component)]
51 #[component(
52 immutable,
53 // Since this component is immutable, we can fully capture all mutations through
54 // these component hooks. This allows for keeping other parts of the ECS synced
55 // to a component's value at all times.
56 on_insert = on_insert_name,
57 on_discard = on_discard_name,
58 )]
59 pub struct Name(pub &'static str);
60
61 /// This index allows for O(1) lookups of an [`Entity`] by its [`Name`].
62 #[derive(Resource, Default)]
63 struct NameIndex {
64 name_to_entity: HashMap<Name, Entity>,
65 }
66
67 impl NameIndex {
68 fn get_entity(&self, name: &'static str) -> Option<Entity> {
69 self.name_to_entity.get(&Name(name)).copied()
70 }
71 }
72
73 /// When a [`Name`] is inserted, we will add it to our [`NameIndex`].
74 ///
75 /// Since all mutations to [`Name`] are captured by hooks, we know it is not currently
76 /// inserted in the index, and its value will not change without triggering a hook.
77 fn on_insert_name(mut world: DeferredWorld<'_>, HookContext { entity, .. }: HookContext) {
78 let Some(&name) = world.entity(entity).get::<Name>() else {
79 unreachable!("Insert hook guarantees `Name` is available on entity")
80 };
81 let Some(mut index) = world.get_resource_mut::<NameIndex>() else {
82 return;
83 };
84
85 index.name_to_entity.insert(name, entity);
86 }
87
88 /// When a [`Name`] is removed or replaced, remove it from our [`NameIndex`].
89 ///
90 /// Since all mutations to [`Name`] are captured by hooks, we know it is currently
91 /// inserted in the index.
92 fn on_discard_name(mut world: DeferredWorld<'_>, HookContext { entity, .. }: HookContext) {
93 let Some(&name) = world.entity(entity).get::<Name>() else {
94 unreachable!("Discard hook guarantees `Name` is available on entity")
95 };
96 let Some(mut index) = world.get_resource_mut::<NameIndex>() else {
97 return;
98 };
99
100 index.name_to_entity.remove(&name);
101 }
102
103 fn demo_2(world: &mut World) {
104 // Setup our name index
105 world.init_resource::<NameIndex>();
106
107 // Spawn some entities!
108 let alyssa = world.spawn(Name("Alyssa")).id();
109 let javier = world.spawn(Name("Javier")).id();
110
111 // Check our index
112 let index = world.resource::<NameIndex>();
113
114 assert_eq!(index.get_entity("Alyssa"), Some(alyssa));
115 assert_eq!(index.get_entity("Javier"), Some(javier));
116
117 // Changing the name of an entity is also fully capture by our index
118 world.entity_mut(javier).insert(Name("Steven"));
119
120 // Javier changed their name to Steven
121 let steven = javier;
122
123 // Check our index
124 let index = world.resource::<NameIndex>();
125
126 assert_eq!(index.get_entity("Javier"), None);
127 assert_eq!(index.get_entity("Steven"), Some(steven));
128 }
129
130 /// This example demonstrates how to work with _dynamic_ immutable components.
131 #[expect(
132 unsafe_code,
133 reason = "Unsafe code is needed to work with dynamic components"
134 )]
135 fn demo_3(world: &mut World) {
136 // This is a list of dynamic components we will create.
137 // The first item is the name of the component, and the second is the size
138 // in bytes.
139 let my_dynamic_components = [("Foo", 1), ("Bar", 2), ("Baz", 4)];
140
141 // This pipeline takes our component descriptions, registers them, and gets
142 // their ComponentId's.
143 let my_registered_components = my_dynamic_components
144 .into_iter()
145 .map(|(name, size)| {
146 // SAFETY:
147 // - No drop command is required
148 // - The component will store [u8; size], which is Send + Sync
149 let descriptor = unsafe {
150 ComponentDescriptor::new_with_layout(
151 name.to_string(),
152 StorageType::Table,
153 Layout::array::<u8>(size).unwrap(),
154 None,
155 false,
156 false,
157 ComponentCloneBehavior::Default,
158 None,
159 )
160 };
161
162 (name, size, descriptor)
163 })
164 .map(|(name, size, descriptor)| {
165 let component_id = world.register_component_with_descriptor(descriptor);
166
167 (name, size, component_id)
168 })
169 .collect::<Vec<(&str, usize, ComponentId)>>();
170
171 // Now that our components are registered, let's add them to an entity
172 let mut entity = world.spawn_empty();
173
174 for (_name, size, component_id) in &my_registered_components {
175 // We're just storing some zeroes for the sake of demonstration.
176 let data = core::iter::repeat_n(0, *size).collect::<Vec<u8>>();
177
178 OwningPtr::make(data, |ptr| {
179 // SAFETY:
180 // - ComponentId has been taken from the same world
181 // - Array is created to the layout specified in the world
182 unsafe {
183 entity.insert_by_id(*component_id, ptr);
184 }
185 });
186 }
187
188 for (_name, _size, component_id) in &my_registered_components {
189 // With immutable components, we can read the values...
190 assert!(entity.get_by_id(*component_id).is_ok());
191
192 // ...but we cannot gain a mutable reference.
193 assert!(entity.get_mut_by_id(*component_id).is_err());
194
195 // Instead, you must either remove or replace the value.
196 }
197 }
198
199 fn main() {
200 App::new()
201 .add_systems(Startup, demo_1)
202 .add_systems(Startup, demo_2)
203 .add_systems(Startup, demo_3)
204 .run();
205 }