engine/game_objects/
scene_builder.rs

1// SPDX-FileCopyrightText: 2025 Jens Pitkänen <jens.pitkanen@helsinki.fi>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5use core::{
6    any::TypeId,
7    sync::atomic::{AtomicU32, Ordering},
8};
9
10use arrayvec::ArrayVec;
11
12use crate::{allocators::LinearAllocator, collections::FixedVec};
13
14use super::{ComponentColumn, ComponentInfo, ComponentVec, GameObject, GameObjectTable, Scene};
15
16struct GameObjectInfo {
17    component_infos: ComponentVec<ComponentInfo>,
18    game_object_type: TypeId,
19    game_object_count: usize,
20}
21
22#[allow(clippy::large_enum_variant)]
23enum GameObjectInfoLinkedList<'a> {
24    End,
25    Element {
26        next: &'a GameObjectInfoLinkedList<'a>,
27        info: GameObjectInfo,
28    },
29}
30
31impl<'a> IntoIterator for &'a GameObjectInfoLinkedList<'a> {
32    type Item = &'a GameObjectInfo;
33    type IntoIter = GameObjectInfoLinkedListIterator<'a>;
34
35    fn into_iter(self) -> Self::IntoIter {
36        GameObjectInfoLinkedListIterator { next: self }
37    }
38}
39
40struct GameObjectInfoLinkedListIterator<'a> {
41    next: &'a GameObjectInfoLinkedList<'a>,
42}
43
44impl<'a> Iterator for GameObjectInfoLinkedListIterator<'a> {
45    type Item = &'a GameObjectInfo;
46
47    fn next(&mut self) -> Option<Self::Item> {
48        match self.next {
49            GameObjectInfoLinkedList::End => None,
50            GameObjectInfoLinkedList::Element { next, info } => {
51                self.next = next;
52                Some(info)
53            }
54        }
55    }
56}
57
58/// Builder for [`Scene`].
59pub struct SceneBuilder<'a> {
60    game_object_infos: GameObjectInfoLinkedList<'a>,
61}
62
63impl<'a> SceneBuilder<'a> {
64    /// Adds `G` as a game object type and reserves space for a maximum of
65    /// `count` game objects at a time.
66    pub fn with_game_object_type<G: GameObject>(&'a mut self, count: usize) -> SceneBuilder<'a> {
67        SceneBuilder {
68            game_object_infos: GameObjectInfoLinkedList::Element {
69                next: &self.game_object_infos,
70                info: GameObjectInfo {
71                    component_infos: G::component_infos(),
72                    game_object_type: TypeId::of::<G>(),
73                    game_object_count: count,
74                },
75            },
76        }
77    }
78}
79
80impl Scene<'_> {
81    /// Creates a [`SceneBuilder`] which is used to create a [`Scene`].
82    pub fn builder<'a>() -> SceneBuilder<'a> {
83        SceneBuilder {
84            game_object_infos: GameObjectInfoLinkedList::End,
85        }
86    }
87}
88
89impl SceneBuilder<'_> {
90    /// Allocates memory for and creates a [`Scene`], if `arena` has enough
91    /// memory for it.
92    ///
93    /// The memory requirement of a [`Scene`] is the sum of each component's
94    /// size times how many game objects have that component, and possibly
95    /// padding bytes between the per-component allocations. Allocations are
96    /// done on a per-component basis, so multiple game objects using component
97    /// A will simply result in one large allocation for component A that can
98    /// fit all of those game objects' components.
99    ///
100    /// The `temp_arena` allocator is used for small allocations of about 100
101    /// bytes per component, and can be reset after this function is done.
102    pub fn build<'a>(
103        self,
104        arena: &'a LinearAllocator,
105        temp_arena: &LinearAllocator,
106    ) -> Option<Scene<'a>> {
107        profiling::function_scope!();
108
109        // Count how many component types there are across every game object type
110        let mut distinct_components = 0;
111        for (i, infos) in (self.game_object_infos.into_iter())
112            .enumerate()
113            .map(|(i, info)| (i, &info.component_infos))
114        {
115            for (j, component) in infos.iter().enumerate() {
116                let mut already_seen = false;
117                'find_prev: for previous_infos in (self.game_object_infos.into_iter())
118                    .take(i + 1)
119                    .map(|info| &info.component_infos)
120                {
121                    for previous_component in previous_infos.iter().take(j) {
122                        if component.type_id == previous_component.type_id {
123                            already_seen = true;
124                            break 'find_prev;
125                        }
126                    }
127                }
128                if !already_seen {
129                    distinct_components += 1;
130                }
131            }
132        }
133
134        // Count how many components there are in total, for each component type
135        let mut component_alloc_counts =
136            FixedVec::<(&ComponentInfo, usize)>::new(temp_arena, distinct_components)?;
137        for game_object_info in &self.game_object_infos {
138            for component_info in &game_object_info.component_infos {
139                let count = 'count: {
140                    for (existing_info, count) in &mut *component_alloc_counts {
141                        if component_info.type_id == existing_info.type_id {
142                            break 'count count;
143                        }
144                    }
145                    let i = component_alloc_counts.len();
146                    component_alloc_counts
147                        .push((component_info, 0))
148                        .ok()
149                        .unwrap();
150                    &mut component_alloc_counts[i].1
151                };
152
153                *count += game_object_info.game_object_count;
154            }
155        }
156
157        // Allocate the requested amount of memory for each component type
158        let mut component_datas_by_type = FixedVec::new(temp_arena, distinct_components)?;
159        for (component_info, total_count) in &*component_alloc_counts {
160            let data: FixedVec<u8> = FixedVec::with_alignment(
161                arena,
162                component_info.size * *total_count,
163                component_info.alignment,
164            )?;
165            component_datas_by_type
166                .push((component_info.type_id, data))
167                .unwrap();
168        }
169        component_datas_by_type.sort_unstable_by_key(|(type_id, _)| *type_id);
170
171        // Create the game object tables, using the allocations above as the column data vecs
172        let game_object_table_count = self.game_object_infos.into_iter().count();
173        let mut game_object_tables = FixedVec::new(arena, game_object_table_count)?;
174        for GameObjectInfo {
175            component_infos,
176            game_object_type,
177            game_object_count,
178        } in &self.game_object_infos
179        {
180            let mut columns = ArrayVec::new();
181            for component_info in component_infos {
182                let alloc_for_type = {
183                    let i = component_datas_by_type
184                        .binary_search_by_key(&component_info.type_id, |(t, _)| *t)
185                        .unwrap();
186                    &mut component_datas_by_type[i].1
187                };
188                let data_size = *game_object_count * component_info.size;
189
190                columns.push(ComponentColumn {
191                    component_info: *component_info,
192                    data: alloc_for_type.split_off_head(data_size).unwrap(),
193                });
194            }
195
196            let table = GameObjectTable {
197                game_object_type: *game_object_type,
198                columns,
199            };
200            game_object_tables.push(table).ok().unwrap();
201        }
202        game_object_tables.sort_unstable_by_key(|table| table.game_object_type);
203
204        // Create a unique id for the scene
205        static SCENE_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
206        let prev_id = SCENE_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
207        let scene_id = prev_id.checked_add(1).unwrap();
208
209        Some(Scene {
210            id: scene_id,
211            generation: 0,
212            game_object_tables,
213        })
214    }
215}