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