flecs_ecs 0.2.1

Rust API for the C/CPP flecs ECS library <https://github.com/SanderMertens/flecs>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
#![doc(hidden)]
// Implementation for lifecycle actions.
//
// Considerations on implementation for user experience vs performance.:
//
// 1. Unlike C++, Rust doesn't support compile-time checks for trivial types.
// 2. Current implementation prioritizes simplicity over performance optimizations.
//    - If trivial type registration incurs a significant performance penalty, reconsider this approach.
//
// Challenges:
// - Rust lacks several features for this scenario:
//   a) Trait specialization.
//   b) Compile-time trivial type checks.
//   c) A direct equivalent of `placement_new` from C++.
//      `ptr::write` still constructs the object on the stack and then moves it, barring optimizations.
//
// Potential Solutions:
// - Bypass the need for `placement_new` with a `placement_ctor` function.
//   - Drawback: Each field needs manual setting, which impacts user experience.
//      - example code:
//      ```
//           struct MyType {
//               vec: Vec<i32>,
//           }
//
//           trait PlacementNew {
//               unsafe fn placement_new(ptr: *mut Self);
//           }
//
//           impl PlacementNew for MyType {
//               unsafe fn placement_new(ptr: *mut Self) {
//                   (*ptr).vec = Vec::<i32>::default();
//               }
//           }
//      ```
// - For potential type optimizations, consider:
//   a) Utilizing the `Zeroable` trait and rely on user's proper implementation.
//   b) Implement pseudo-trait specialization, as detailed in:
//      - <http://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html/>
//      - <https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1e548abff8e35b97b25adcacdddaacda/>
//
// possible helpful crates for trait specialization / type specialization:
// - For type casting: <https://crates.io/crates/castaway/>
//
// Note: C does the same, where the user needs to opt in for non trivial types. We can do the same.
// Note2: zerobit pattern

use core::{ffi::c_void, mem::MaybeUninit, ptr};

use crate::core::*;
use crate::sys;

#[cfg(feature = "std")]
extern crate std;

extern crate alloc;
use alloc::boxed::Box;
use flecs_ecs_derive::extern_abi;

#[expect(dead_code, reason = "possibly used in the future")]
#[derive(Default)]
pub(crate) struct RegistersPanicHooks {
    pub(crate) ctor: bool,
    pub(crate) copy: bool,
}

#[expect(dead_code, reason = "possibly used in the future")]
#[extern_abi]
pub(crate) unsafe fn register_panic_hooks_free_ctx(ctx: *mut c_void) {
    let _box = unsafe { Box::from_raw(ctx as *mut RegistersPanicHooks) };
}

pub fn register_lifecycle_actions<T>(type_hooks: &mut sys::ecs_type_hooks_t) {
    //type_hooks.ctor = Some(ctor::<T>);
    type_hooks.dtor = Some(dtor::<T>);
    type_hooks.move_dtor = Some(move_dtor::<T>); //same implementation as ctor_move_dtor

    //type_hooks.move_ctor = Some(move_ctor::<T>);
    type_hooks.ctor_move_dtor = Some(ctor_move_dtor::<T>);

    //TODO we could potentially add an autoamtic check if the type is unmoveable to add
    //a sparse component tag
}

pub fn register_ctor_lifecycle_actions<T: Default>(type_hooks: &mut sys::ecs_type_hooks_t) {
    type_hooks.ctor = Some(ctor::<T>);
    // if let Some(_) = type_hooks.move_dtor {
    //     type_hooks.move_dtor = Some(move_dtor_impls_default::<T>); //same implementation as move_dtor
    // }
}

pub fn register_ctor_panic_lifecycle_actions<T>(type_hooks: &mut sys::ecs_type_hooks_t) {
    type_hooks.ctor = Some(panic_ctor::<T>);
}

pub fn register_copy_lifecycle_action<T: Clone>(type_hooks: &mut sys::ecs_type_hooks_t) {
    type_hooks.copy = Some(copy::<T>);
    type_hooks.copy_ctor = Some(copy_ctor::<T>); //same implementation as copy
}

pub fn register_copy_panic_lifecycle_action<T>(type_hooks: &mut sys::ecs_type_hooks_t) {
    type_hooks.copy = Some(panic_copy::<T>);
    type_hooks.copy_ctor = Some(panic_copy::<T>); //same implementation as copy
}

pub fn register_partial_ord_lifecycle_action<T: core::cmp::PartialOrd>(
    type_hooks: &mut sys::ecs_type_hooks_t,
) {
    type_hooks.cmp = Some(compare::<T>);
}

pub fn register_partial_ord_panic_lifecycle_action<T>(type_hooks: &mut sys::ecs_type_hooks_t) {
    type_hooks.cmp = Some(panic_compare::<T>);
}

pub fn register_partial_eq_lifecycle_action<T: core::cmp::PartialEq>(
    type_hooks: &mut sys::ecs_type_hooks_t,
) {
    type_hooks.equals = Some(equals::<T>);
}

pub fn register_partial_eq_panic_lifecycle_action<T>(type_hooks: &mut sys::ecs_type_hooks_t) {
    type_hooks.equals = Some(panic_equals::<T>);
}

/// Initialize the memory with the default constructor.
///
/// # Arguments
///
/// * `ptr` - pointer to the memory to be initialized
/// * `count` - number of elements to be initialized
/// * `_type_info` - type info for the type to be initialized
#[extern_abi]
fn ctor<T: Default>(ptr: *mut c_void, count: i32, _type_info: *const sys::ecs_type_info_t) {
    // tags and types with size 0 don't need to be initialized
    let size = const { core::mem::size_of::<T>() };
    if size == 0 {
        return;
    }

    ecs_assert!(
        check_type_info::<T>(_type_info),
        FlecsErrorCode::InternalError
    );

    let arr = ptr as *mut MaybeUninit<T>;
    for i in 0..count as usize {
        unsafe {
            // Default construct the value in place
            MaybeUninit::write(&mut *arr.add(i), T::default());
        }
    }
}

/// Runs the destructor for the type.
///
/// # Arguments
///
/// * `ptr` - pointer to the memory to be destructed
/// * `count` - number of elements to be destructed
/// * `_type_info` - type info for the type to be destructed
#[extern_abi]
fn dtor<T>(ptr: *mut c_void, count: i32, _type_info: *const sys::ecs_type_info_t) {
    let size = const { core::mem::size_of::<T>() };
    if size == 0 {
        let arr = ptr as *mut u8; // tags with drop are (usually) modules with size 1 and alignment 1 
        for i in 0..count as isize {
            unsafe {
                let item = arr.offset(i);
                ptr::drop_in_place(item as *mut T);
            }
        }
    } else {
        ecs_assert!(
            check_type_info::<T>(_type_info),
            FlecsErrorCode::InternalError
        );

        let arr = ptr as *mut T;
        for i in 0..count as isize {
            unsafe {
                let item = arr.offset(i);
                ptr::drop_in_place(item);
            }
        }
    }
}

/// This is the generic copy for trivial types
/// It will copy the memory
#[extern_abi]
fn copy<T: Clone>(
    dst_ptr: *mut c_void,
    src_ptr: *const c_void,
    count: i32,
    _type_info: *const sys::ecs_type_info_t,
) {
    ecs_assert!(
        check_type_info::<T>(_type_info),
        FlecsErrorCode::InternalError
    );
    let dst_arr = dst_ptr as *mut T;
    let src_arr = src_ptr as *const T;
    for i in 0..count as isize {
        //this is safe because C manages the memory and we're cloning the internal data
        unsafe {
            let src_value = &*(src_arr.offset(i)); //get value of src
            let dst_value = dst_arr.offset(i); // get ptr to dest
            core::ptr::drop_in_place(dst_value); //calls destructor
            core::ptr::write(dst_value, src_value.clone()); //overwrite the memory of dest with new value
        }
    }
}

/// This is the generic copy for trivial types
/// It will copy the memory
#[extern_abi]
fn copy_ctor<T: Clone>(
    dst_ptr: *mut c_void,
    src_ptr: *const c_void,
    count: i32,
    _type_info: *const sys::ecs_type_info_t,
) {
    ecs_assert!(
        check_type_info::<T>(_type_info),
        FlecsErrorCode::InternalError
    );
    let dst_arr = dst_ptr as *mut T;
    let src_arr = src_ptr as *const T;
    for i in 0..count as isize {
        //this is safe because C manages the memory and we're cloning the internal data
        unsafe {
            let src_value = &*(src_arr.offset(i)); //get value of src
            let dst_value = dst_arr.offset(i); // get ptr to dest
            core::ptr::write(dst_value, src_value.clone()); //overwrite the memory of dest with new value
        }
    }
}

#[extern_abi]
fn panic_ctor<T>(_dst_ptr: *mut c_void, _count: i32, _type_info: *const sys::ecs_type_info_t) {
    panic!(
        "Default is not implemented for type {} which requires drop and it's being used in an operation which calls the constructor",
        core::any::type_name::<T>()
    );
}

#[extern_abi]
fn panic_copy<T>(
    _dst_ptr: *mut c_void,
    _src_ptr: *const c_void,
    _count: i32,
    _type_info: *const sys::ecs_type_info_t,
) {
    panic!(
        "Clone is not implemented for type {} and it's being used in a copy / duplicate operation such as component overriding or duplicating entities / components or prefab copying",
        core::any::type_name::<T>()
    );
}

/// This is the generic move for non-trivial types
/// It will move the memory
#[extern_abi]
fn move_dtor<T>(
    dst_ptr: *mut c_void,
    src_ptr: *mut c_void,
    count: i32,
    _type_info: *const sys::ecs_type_info_t,
) {
    ecs_assert!(
        check_type_info::<T>(_type_info),
        FlecsErrorCode::InternalError
    );
    let dst_arr = dst_ptr as *mut T;
    let src_arr = src_ptr as *mut T;
    for i in 0..count as isize {
        //this is safe because C manages the memory and we are just moving the internal data around
        unsafe {
            let src_value = src_arr.offset(i); //get value of src
            let dst_value = dst_arr.offset(i); // get ptr to dest

            //memcpy the bytes of src to dest
            //src value and dest value point to the same thing
            core::ptr::copy_nonoverlapping(src_value, dst_value, 1);
        }
    }
}

/// This is the generic move for non-trivial types
/// It will move the memory
#[extern_abi]
fn move_dtor_impls_default<T: Default>(
    dst_ptr: *mut c_void,
    src_ptr: *mut c_void,
    count: i32,
    _type_info: *const sys::ecs_type_info_t,
) {
    ecs_assert!(
        check_type_info::<T>(_type_info),
        FlecsErrorCode::InternalError
    );
    let dst_arr = dst_ptr as *mut T;
    let src_arr = src_ptr as *mut T;
    for i in 0..count as isize {
        //this is safe because C manages the memory and we are just moving the internal data around
        unsafe {
            let src_value = src_arr.offset(i); //get value of src
            let dst_value = dst_arr.offset(i); // get ptr to dest

            core::ptr::drop_in_place(dst_value); //calls destructor on dest

            //memcpy the bytes of src to dest
            //src value and dest value point to the same thing
            core::ptr::copy_nonoverlapping(src_value, dst_value, 1);
        }
    }
}

/// a move to from src to dest where src will not be used anymore and dest is in control of the drop.
#[extern_abi]
fn move_ctor<T>(
    dst_ptr: *mut c_void,
    src_ptr: *mut c_void,
    count: i32,
    _type_info: *const sys::ecs_type_info_t,
) {
    ecs_assert!(
        check_type_info::<T>(_type_info),
        FlecsErrorCode::InternalError
    );
    let dst_arr = dst_ptr as *mut T;
    let src_arr = src_ptr as *mut T;
    for i in 0..count as isize {
        //this is safe because src will not get dropped and dst will get dropped
        unsafe {
            // memcpy the bytes from src to dst
            core::ptr::copy_nonoverlapping(src_arr.offset(i), dst_arr.offset(i), 1);
        }
    }
}

#[extern_abi]
fn ctor_move_dtor<T>(
    dst_ptr: *mut c_void,
    src_ptr: *mut c_void,
    count: i32,
    _type_info: *const sys::ecs_type_info_t,
) {
    ecs_assert!(
        check_type_info::<T>(_type_info),
        FlecsErrorCode::InternalError
    );
    let dst_arr = dst_ptr as *mut T;
    let src_arr = src_ptr as *mut T;
    for i in 0..count as isize {
        //this is safe because src will not get dropped and dst will get dropped
        unsafe {
            // memcpy the bytes from src to dst
            core::ptr::copy_nonoverlapping(src_arr.offset(i), dst_arr.offset(i), 1);
        }
    }
}

#[extern_abi]
fn compare<T: core::cmp::PartialOrd>(
    a: *const c_void,
    b: *const c_void,
    _type_info: *const sys::ecs_type_info_t,
) -> i32 {
    ecs_assert!(
        check_type_info::<T>(_type_info),
        FlecsErrorCode::InternalError
    );
    let lhs = unsafe { &*(a as *const T) };
    let rhs = unsafe { &*(b as *const T) };

    if lhs == rhs {
        0
    } else if lhs < rhs {
        -1 //less
    } else {
        1 //greater
    }
}

#[extern_abi]
fn panic_compare<T>(
    _a: *const c_void,
    _b: *const c_void,
    _type_info: *const sys::ecs_type_info_t,
) -> i32 {
    panic!(
        "PartialOrd is not implemented for type {} and it's being used in a comparison operation",
        core::any::type_name::<T>()
    );
}

#[extern_abi]
fn equals<T: core::cmp::PartialEq>(
    a: *const c_void,
    b: *const c_void,
    _type_info: *const sys::ecs_type_info_t,
) -> bool {
    ecs_assert!(
        check_type_info::<T>(_type_info),
        FlecsErrorCode::InternalError
    );
    let lhs = unsafe { &*(a as *const T) };
    let rhs = unsafe { &*(b as *const T) };

    lhs == rhs
}

#[extern_abi]
fn panic_equals<T>(
    _a: *const c_void,
    _b: *const c_void,
    _type_info: *const sys::ecs_type_info_t,
) -> bool {
    panic!(
        "PartialEq is not implemented for type {} and it's being used in an equality operation",
        core::any::type_name::<T>()
    );
}

fn check_type_info<T>(_type_info: *const sys::ecs_type_info_t) -> bool {
    if !_type_info.is_null() {
        unsafe { (*_type_info).size == core::mem::size_of::<T>() as i32 }
    } else {
        true
    }
}