immutable_components/
immutable_components.rs

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