Skip to main content

goud_engine/component_ops/
single_ops.rs

1//! Single-entity component operations.
2//!
3//! Contains the `_impl` functions for register, add, remove, has, get, and
4//! get_mut on individual entities.
5
6use std::collections::HashMap;
7
8use crate::context_registry::{get_context_registry, GoudContextId, GOUD_INVALID_CONTEXT_ID};
9use crate::core::error::{set_last_error, GoudError};
10use crate::core::types::{GoudEntityId, GoudResult};
11
12use super::helpers::{context_key, entity_from_ffi};
13use super::storage::{
14    get_component_type_info, get_context_storage_map, register_component_type_internal,
15};
16
17/// Registers a component type with the engine.
18///
19/// # Safety
20///
21/// - `name_ptr` must be a valid pointer to UTF-8 data (or null)
22/// - `name_len` must match the actual string length
23/// - `size` and `align` must match the actual type layout
24pub unsafe fn component_register_type_impl(
25    type_id_hash: u64,
26    name_ptr: *const u8,
27    name_len: usize,
28    size: usize,
29    align: usize,
30) -> bool {
31    if name_ptr.is_null() {
32        set_last_error(GoudError::InvalidState(
33            "Component type name pointer is null".to_string(),
34        ));
35        return false;
36    }
37
38    // SAFETY: Caller guarantees name_ptr points to valid memory of
39    // name_len bytes.
40    let name_slice = std::slice::from_raw_parts(name_ptr, name_len);
41    if std::str::from_utf8(name_slice).is_err() {
42        set_last_error(GoudError::InvalidState(
43            "Component type name is not valid UTF-8".to_string(),
44        ));
45        return false;
46    }
47
48    register_component_type_internal(type_id_hash, size, align)
49}
50
51/// Adds a component to an entity.
52///
53/// # Safety
54///
55/// - `data_ptr` must point to valid component data
56/// - `data_size` must match the registered component size
57pub unsafe fn component_add_impl(
58    context_id: GoudContextId,
59    entity_id: GoudEntityId,
60    type_id_hash: u64,
61    data_ptr: *const u8,
62    data_size: usize,
63) -> GoudResult {
64    if data_ptr.is_null() {
65        return GoudResult::from_error(GoudError::InvalidState(
66            "Component data pointer is null".to_string(),
67        ));
68    }
69
70    let type_info = match get_component_type_info(type_id_hash) {
71        Some(info) => info,
72        None => {
73            return GoudResult::from_error(GoudError::ResourceLoadFailed(format!(
74                "Component type {} not registered",
75                type_id_hash
76            )));
77        }
78    };
79
80    if data_size != type_info.size {
81        return GoudResult::from_error(GoudError::InvalidState(format!(
82            "Component data size mismatch: expected {}, got {}",
83            type_info.size, data_size
84        )));
85    }
86
87    if context_id == GOUD_INVALID_CONTEXT_ID {
88        return GoudResult::from_error(GoudError::InvalidContext);
89    }
90
91    {
92        let registry = get_context_registry().lock().unwrap();
93        let context = match registry.get(context_id) {
94            Some(ctx) => ctx,
95            None => {
96                return GoudResult::from_error(GoudError::InvalidContext);
97            }
98        };
99
100        let entity = entity_from_ffi(entity_id);
101        if !context.world().is_alive(entity) {
102            return GoudResult::from_error(GoudError::EntityNotFound);
103        }
104    }
105
106    let mut storage_map = get_context_storage_map();
107    let map = storage_map.get_or_insert_with(HashMap::new);
108
109    let key = context_key(context_id);
110    let context_storage = map.entry(key).or_default();
111    let storage =
112        context_storage.get_or_create_storage(type_id_hash, type_info.size, type_info.align);
113
114    if storage.insert(entity_id.bits(), data_ptr) {
115        GoudResult::ok()
116    } else {
117        GoudResult::from_error(GoudError::InternalError(
118            "Failed to allocate component storage".to_string(),
119        ))
120    }
121}
122
123/// Removes a component from an entity.
124pub fn component_remove_impl(
125    context_id: GoudContextId,
126    entity_id: GoudEntityId,
127    type_id_hash: u64,
128) -> GoudResult {
129    if get_component_type_info(type_id_hash).is_none() {
130        return GoudResult::from_error(GoudError::ResourceLoadFailed(format!(
131            "Component type {} not registered",
132            type_id_hash
133        )));
134    }
135
136    if context_id == GOUD_INVALID_CONTEXT_ID {
137        return GoudResult::from_error(GoudError::InvalidContext);
138    }
139
140    {
141        let registry = get_context_registry().lock().unwrap();
142        let context = match registry.get(context_id) {
143            Some(ctx) => ctx,
144            None => {
145                return GoudResult::from_error(GoudError::InvalidContext);
146            }
147        };
148
149        let entity = entity_from_ffi(entity_id);
150        if !context.world().is_alive(entity) {
151            return GoudResult::from_error(GoudError::EntityNotFound);
152        }
153    }
154
155    let mut storage_map = get_context_storage_map();
156    let map = match storage_map.as_mut() {
157        Some(m) => m,
158        None => return GoudResult::ok(),
159    };
160
161    let key = context_key(context_id);
162    let context_storage = match map.get_mut(&key) {
163        Some(s) => s,
164        None => return GoudResult::ok(),
165    };
166
167    let storage = match context_storage.get_storage_mut(type_id_hash) {
168        Some(s) => s,
169        None => return GoudResult::ok(),
170    };
171
172    storage.remove(entity_id.bits());
173    GoudResult::ok()
174}
175
176/// Checks if an entity has a specific component.
177pub fn component_has_impl(
178    context_id: GoudContextId,
179    entity_id: GoudEntityId,
180    type_id_hash: u64,
181) -> bool {
182    if get_component_type_info(type_id_hash).is_none() {
183        set_last_error(GoudError::ResourceLoadFailed(format!(
184            "Component type {} not registered",
185            type_id_hash
186        )));
187        return false;
188    }
189
190    if context_id == GOUD_INVALID_CONTEXT_ID {
191        set_last_error(GoudError::InvalidContext);
192        return false;
193    }
194
195    {
196        let registry = get_context_registry().lock().unwrap();
197        let context = match registry.get(context_id) {
198            Some(ctx) => ctx,
199            None => {
200                set_last_error(GoudError::InvalidContext);
201                return false;
202            }
203        };
204
205        let entity = entity_from_ffi(entity_id);
206        if !context.world().is_alive(entity) {
207            return false;
208        }
209    }
210
211    let storage_map = get_context_storage_map();
212    let map = match storage_map.as_ref() {
213        Some(m) => m,
214        None => return false,
215    };
216
217    let key = context_key(context_id);
218    let context_storage = match map.get(&key) {
219        Some(s) => s,
220        None => return false,
221    };
222
223    let storage = match context_storage.get_storage(type_id_hash) {
224        Some(s) => s,
225        None => return false,
226    };
227
228    storage.contains(entity_id.bits())
229}
230
231/// Gets a read-only pointer to a component on an entity.
232pub fn component_get_impl(
233    context_id: GoudContextId,
234    entity_id: GoudEntityId,
235    type_id_hash: u64,
236) -> *const u8 {
237    if get_component_type_info(type_id_hash).is_none() {
238        set_last_error(GoudError::ResourceLoadFailed(format!(
239            "Component type {} not registered",
240            type_id_hash
241        )));
242        return std::ptr::null();
243    }
244
245    if context_id == GOUD_INVALID_CONTEXT_ID {
246        set_last_error(GoudError::InvalidContext);
247        return std::ptr::null();
248    }
249
250    {
251        let registry = get_context_registry().lock().unwrap();
252        let context = match registry.get(context_id) {
253            Some(ctx) => ctx,
254            None => {
255                set_last_error(GoudError::InvalidContext);
256                return std::ptr::null();
257            }
258        };
259
260        let entity = entity_from_ffi(entity_id);
261        if !context.world().is_alive(entity) {
262            set_last_error(GoudError::EntityNotFound);
263            return std::ptr::null();
264        }
265    }
266
267    let storage_map = get_context_storage_map();
268    let map = match storage_map.as_ref() {
269        Some(m) => m,
270        None => return std::ptr::null(),
271    };
272
273    let key = context_key(context_id);
274    let context_storage = match map.get(&key) {
275        Some(s) => s,
276        None => return std::ptr::null(),
277    };
278
279    let storage = match context_storage.get_storage(type_id_hash) {
280        Some(s) => s,
281        None => return std::ptr::null(),
282    };
283
284    storage.get(entity_id.bits())
285}
286
287/// Gets a mutable pointer to a component on an entity.
288pub fn component_get_mut_impl(
289    context_id: GoudContextId,
290    entity_id: GoudEntityId,
291    type_id_hash: u64,
292) -> *mut u8 {
293    if get_component_type_info(type_id_hash).is_none() {
294        set_last_error(GoudError::ResourceLoadFailed(format!(
295            "Component type {} not registered",
296            type_id_hash
297        )));
298        return std::ptr::null_mut();
299    }
300
301    if context_id == GOUD_INVALID_CONTEXT_ID {
302        set_last_error(GoudError::InvalidContext);
303        return std::ptr::null_mut();
304    }
305
306    {
307        let registry = get_context_registry().lock().unwrap();
308        let context = match registry.get(context_id) {
309            Some(ctx) => ctx,
310            None => {
311                set_last_error(GoudError::InvalidContext);
312                return std::ptr::null_mut();
313            }
314        };
315
316        let entity = entity_from_ffi(entity_id);
317        if !context.world().is_alive(entity) {
318            set_last_error(GoudError::EntityNotFound);
319            return std::ptr::null_mut();
320        }
321    }
322
323    let mut storage_map = get_context_storage_map();
324    let map = match storage_map.as_mut() {
325        Some(m) => m,
326        None => return std::ptr::null_mut(),
327    };
328
329    let key = context_key(context_id);
330    let context_storage = match map.get_mut(&key) {
331        Some(s) => s,
332        None => return std::ptr::null_mut(),
333    };
334
335    let storage = match context_storage.get_storage_mut(type_id_hash) {
336        Some(s) => s,
337        None => return std::ptr::null_mut(),
338    };
339
340    storage.get_mut(entity_id.bits())
341}