Skip to main content

goud_engine/component_ops/
batch_ops.rs

1//! Batch component operations.
2//!
3//! Contains the `_impl` functions for adding, removing, and querying
4//! components across multiple entities at once.
5
6use std::collections::HashMap;
7
8use crate::context_registry::{GoudContextId, GOUD_INVALID_CONTEXT_ID};
9use crate::core::error::{set_last_error, GoudError};
10
11use super::helpers::context_key;
12use super::storage::{get_context_storage_map, get_type_registry};
13
14/// Adds the same component type to multiple entities in a batch.
15///
16/// # Safety
17///
18/// - `entity_ids` must point to valid memory with `count` u64 values
19/// - `data_ptr` must point to `count * component_size` bytes of data
20/// - `component_size` must match the registered component type size
21pub unsafe fn component_add_batch_impl(
22    context_id: GoudContextId,
23    entity_ids: *const u64,
24    count: u32,
25    type_id_hash: u64,
26    data_ptr: *const u8,
27    component_size: usize,
28) -> u32 {
29    if context_id == GOUD_INVALID_CONTEXT_ID {
30        set_last_error(GoudError::InvalidContext);
31        return 0;
32    }
33
34    if entity_ids.is_null() {
35        set_last_error(GoudError::InvalidState(
36            "entity_ids pointer is null".to_string(),
37        ));
38        return 0;
39    }
40
41    if data_ptr.is_null() {
42        set_last_error(GoudError::InvalidState(
43            "data_ptr pointer is null".to_string(),
44        ));
45        return 0;
46    }
47
48    if count == 0 {
49        return 0;
50    }
51
52    let type_info = {
53        let type_registry = get_type_registry();
54        let registry_map = match type_registry.as_ref() {
55            Some(map) => map,
56            None => {
57                set_last_error(GoudError::ResourceNotFound(format!(
58                    "Component type {} not registered",
59                    type_id_hash
60                )));
61                return 0;
62            }
63        };
64        match registry_map.get(&type_id_hash) {
65            Some(info) => info.clone(),
66            None => {
67                set_last_error(GoudError::ResourceNotFound(format!(
68                    "Component type {} not registered",
69                    type_id_hash
70                )));
71                return 0;
72            }
73        }
74    };
75
76    if component_size != type_info.size {
77        set_last_error(GoudError::InvalidState(format!(
78            "Component size mismatch: expected {}, got {}",
79            type_info.size, component_size
80        )));
81        return 0;
82    }
83
84    let mut storage_map = get_context_storage_map();
85    let map = storage_map.get_or_insert_with(HashMap::new);
86
87    let key = context_key(context_id);
88    let context_storage = map.entry(key).or_default();
89    let storage =
90        context_storage.get_or_create_storage(type_id_hash, type_info.size, type_info.align);
91
92    // SAFETY: Caller guarantees entity_ids points to count u64 values.
93    let entity_slice = std::slice::from_raw_parts(entity_ids, count as usize);
94    let mut success_count = 0u32;
95
96    for (i, &entity_bits) in entity_slice.iter().enumerate() {
97        let component_data = data_ptr.add(i * component_size);
98        if storage.insert(entity_bits, component_data) {
99            success_count += 1;
100        }
101    }
102
103    success_count
104}
105
106/// Removes the same component type from multiple entities in a batch.
107///
108/// # Safety
109///
110/// `entity_ids` must point to valid memory with `count` u64 values.
111pub unsafe fn component_remove_batch_impl(
112    context_id: GoudContextId,
113    entity_ids: *const u64,
114    count: u32,
115    type_id_hash: u64,
116) -> u32 {
117    if context_id == GOUD_INVALID_CONTEXT_ID {
118        set_last_error(GoudError::InvalidContext);
119        return 0;
120    }
121
122    if entity_ids.is_null() {
123        set_last_error(GoudError::InvalidState(
124            "entity_ids pointer is null".to_string(),
125        ));
126        return 0;
127    }
128
129    if count == 0 {
130        return 0;
131    }
132
133    {
134        let type_registry = get_type_registry();
135        let registry_map = match type_registry.as_ref() {
136            Some(map) => map,
137            None => {
138                set_last_error(GoudError::ResourceNotFound(format!(
139                    "Component type {} not registered",
140                    type_id_hash
141                )));
142                return 0;
143            }
144        };
145        if !registry_map.contains_key(&type_id_hash) {
146            set_last_error(GoudError::ResourceNotFound(format!(
147                "Component type {} not registered",
148                type_id_hash
149            )));
150            return 0;
151        }
152    }
153
154    let mut storage_map = get_context_storage_map();
155    let map = match storage_map.as_mut() {
156        Some(m) => m,
157        None => return 0,
158    };
159
160    let key = context_key(context_id);
161    let context_storage = match map.get_mut(&key) {
162        Some(s) => s,
163        None => return 0,
164    };
165
166    let storage = match context_storage.get_storage_mut(type_id_hash) {
167        Some(s) => s,
168        None => return 0,
169    };
170
171    // SAFETY: Caller guarantees entity_ids points to count u64 values.
172    let entity_slice = std::slice::from_raw_parts(entity_ids, count as usize);
173    let mut success_count = 0u32;
174
175    for &entity_bits in entity_slice {
176        if storage.remove(entity_bits) {
177            success_count += 1;
178        }
179    }
180
181    success_count
182}
183
184/// Checks if multiple entities have a specific component type.
185///
186/// # Safety
187///
188/// - `entity_ids` must point to valid memory with `count` u64 values
189/// - `out_results` must point to valid memory with `count` u8 values
190pub unsafe fn component_has_batch_impl(
191    context_id: GoudContextId,
192    entity_ids: *const u64,
193    count: u32,
194    type_id_hash: u64,
195    out_results: *mut u8,
196) -> u32 {
197    if context_id == GOUD_INVALID_CONTEXT_ID {
198        set_last_error(GoudError::InvalidContext);
199        return 0;
200    }
201
202    if entity_ids.is_null() {
203        set_last_error(GoudError::InvalidState(
204            "entity_ids pointer is null".to_string(),
205        ));
206        return 0;
207    }
208
209    if out_results.is_null() {
210        set_last_error(GoudError::InvalidState(
211            "out_results pointer is null".to_string(),
212        ));
213        return 0;
214    }
215
216    if count == 0 {
217        return 0;
218    }
219
220    {
221        let type_registry = get_type_registry();
222        let registry_map = match type_registry.as_ref() {
223            Some(map) => map,
224            None => {
225                set_last_error(GoudError::ResourceNotFound(format!(
226                    "Component type {} not registered",
227                    type_id_hash
228                )));
229                return 0;
230            }
231        };
232        if !registry_map.contains_key(&type_id_hash) {
233            set_last_error(GoudError::ResourceNotFound(format!(
234                "Component type {} not registered",
235                type_id_hash
236            )));
237            return 0;
238        }
239    }
240
241    let storage_map = get_context_storage_map();
242
243    let storage_exists = storage_map.as_ref().is_some_and(|map| {
244        let key = context_key(context_id);
245        map.get(&key)
246            .and_then(|cs| cs.get_storage(type_id_hash))
247            .is_some()
248    });
249
250    // SAFETY: Caller guarantees these pointers are valid for count elements.
251    let entity_slice = std::slice::from_raw_parts(entity_ids, count as usize);
252    let results_slice = std::slice::from_raw_parts_mut(out_results, count as usize);
253
254    if !storage_exists {
255        for result in results_slice.iter_mut() {
256            *result = 0;
257        }
258        return count;
259    }
260
261    let map = storage_map.as_ref().unwrap();
262    let key = context_key(context_id);
263    let context_storage = map.get(&key).unwrap();
264    let storage = context_storage.get_storage(type_id_hash).unwrap();
265
266    for (i, &entity_bits) in entity_slice.iter().enumerate() {
267        results_slice[i] = if storage.contains(entity_bits) { 1 } else { 0 };
268    }
269
270    count
271}