Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ecs/immutable_components.rs
9308 views
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_replace = on_replace_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_replace_name(mut world: DeferredWorld<'_>, HookContext { entity, .. }: HookContext) {
93
let Some(&name) = world.entity(entity).get::<Name>() else {
94
unreachable!("Replace 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
ComponentCloneBehavior::Default,
157
None,
158
)
159
};
160
161
(name, size, descriptor)
162
})
163
.map(|(name, size, descriptor)| {
164
let component_id = world.register_component_with_descriptor(descriptor);
165
166
(name, size, component_id)
167
})
168
.collect::<Vec<(&str, usize, ComponentId)>>();
169
170
// Now that our components are registered, let's add them to an entity
171
let mut entity = world.spawn_empty();
172
173
for (_name, size, component_id) in &my_registered_components {
174
// We're just storing some zeroes for the sake of demonstration.
175
let data = core::iter::repeat_n(0, *size).collect::<Vec<u8>>();
176
177
OwningPtr::make(data, |ptr| {
178
// SAFETY:
179
// - ComponentId has been taken from the same world
180
// - Array is created to the layout specified in the world
181
unsafe {
182
entity.insert_by_id(*component_id, ptr);
183
}
184
});
185
}
186
187
for (_name, _size, component_id) in &my_registered_components {
188
// With immutable components, we can read the values...
189
assert!(entity.get_by_id(*component_id).is_ok());
190
191
// ...but we cannot gain a mutable reference.
192
assert!(entity.get_mut_by_id(*component_id).is_err());
193
194
// Instead, you must either remove or replace the value.
195
}
196
}
197
198
fn main() {
199
App::new()
200
.add_systems(Startup, demo_1)
201
.add_systems(Startup, demo_2)
202
.add_systems(Startup, demo_3)
203
.run();
204
}
205
206