immutable_components/
immutable_components.rs

1//! This example demonstrates immutable components.
2
3use 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};
13use 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)]
18pub 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)]
28pub struct MyImmutableComponent(bool);
29
30fn 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)]
59pub struct Name(pub &'static str);
60
61/// This index allows for O(1) lookups of an [`Entity`] by its [`Name`].
62#[derive(Resource, Default)]
63struct NameIndex {
64    name_to_entity: HashMap<Name, Entity>,
65}
66
67impl 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.
77fn 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.
92fn 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
103fn 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)]
135fn 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                )
158            };
159
160            (name, size, descriptor)
161        })
162        .map(|(name, size, descriptor)| {
163            let component_id = world.register_component_with_descriptor(descriptor);
164
165            (name, size, component_id)
166        })
167        .collect::<Vec<(&str, usize, ComponentId)>>();
168
169    // Now that our components are registered, let's add them to an entity
170    let mut entity = world.spawn_empty();
171
172    for (_name, size, component_id) in &my_registered_components {
173        // We're just storing some zeroes for the sake of demonstration.
174        let data = core::iter::repeat_n(0, *size).collect::<Vec<u8>>();
175
176        OwningPtr::make(data, |ptr| {
177            // SAFETY:
178            // - ComponentId has been taken from the same world
179            // - Array is created to the layout specified in the world
180            unsafe {
181                entity.insert_by_id(*component_id, ptr);
182            }
183        });
184    }
185
186    for (_name, _size, component_id) in &my_registered_components {
187        // With immutable components, we can read the values...
188        assert!(entity.get_by_id(*component_id).is_ok());
189
190        // ...but we cannot gain a mutable reference.
191        assert!(entity.get_mut_by_id(*component_id).is_err());
192
193        // Instead, you must either remove or replace the value.
194    }
195}
196
197fn main() {
198    App::new()
199        .add_systems(Startup, demo_1)
200        .add_systems(Startup, demo_2)
201        .add_systems(Startup, demo_3)
202        .run();
203}