#![doc(hidden)]
use crate::core::*;
use crate::sys;
use core::ffi::c_char;
#[cfg(feature = "std")]
extern crate std;
extern crate alloc;
use alloc::{
ffi::CString,
format,
string::{String, ToString},
vec::Vec,
};
const ECS_GENERATION_MASK: u64 = u32::MAX as u64;
#[inline(always)]
pub fn ecs_entity_id_combine(lo: u64, hi: u64) -> u64 {
(hi << 32) | (lo & ECS_GENERATION_MASK)
}
#[inline(always)]
pub fn ecs_pair(rel: u64, target: u64) -> u64 {
ECS_PAIR | ecs_entity_id_combine(target, rel)
}
pub fn ecs_is_pair(entity: impl Into<Id>) -> bool {
entity.into() & RUST_ecs_id_FLAGS_MASK == ECS_PAIR
}
#[inline(always)]
pub fn ecs_dependson(entity: u64) -> u64 {
ecs_pair(ECS_DEPENDS_ON, entity)
}
#[inline(always)]
#[expect(dead_code, reason = "possibly used in the future")]
pub(crate) fn ecs_has_pair(
world: *const sys::ecs_world_t,
entity: impl Into<Entity>,
first: impl Into<Entity>,
second: impl Into<Entity>,
) -> bool {
unsafe {
sys::ecs_has_id(
world,
*entity.into(),
ecs_pair(*first.into(), *second.into()),
)
}
}
#[inline(always)]
pub(crate) fn ecs_add_pair(
world: *mut sys::ecs_world_t,
entity: impl Into<Entity>,
first: impl Into<Entity>,
second: impl Into<Entity>,
) {
unsafe {
sys::ecs_add_id(
world,
*entity.into(),
ecs_pair(*first.into(), *second.into()),
);
};
}
#[inline(always)]
pub fn ecs_first<'a>(e: impl IntoId, world: impl WorldProvider<'a>) -> Entity {
let world = world.world();
let id = (*e.into_id(world)) & RUST_ECS_COMPONENT_MASK;
Entity(ecs_entity_id_high(id))
}
#[inline(always)]
pub fn ecs_second<'a>(e: impl IntoId, world: impl WorldProvider<'a>) -> Entity {
let world = world.world();
Entity(ecs_entity_id_low(Entity(*e.into_id(world))))
}
#[inline(always)]
pub fn ecs_entity_id_low(value: impl Into<Entity>) -> u64 {
*value.into() as u32 as u64
}
#[inline(always)]
pub fn ecs_entity_id_high(value: impl Into<Entity>) -> u64 {
*value.into() >> 32
}
pub fn type_name_cstring<T>() -> CString {
CString::new(core::any::type_name::<T>()).unwrap()
}
#[derive(Debug, Clone)]
pub enum OnlyTypeName {
NonGeneric(&'static str),
Generic(String),
}
impl OnlyTypeName {
pub fn as_str(&self) -> &str {
match self {
OnlyTypeName::NonGeneric(name) => name,
OnlyTypeName::Generic(name) => name,
}
}
}
impl PartialEq for OnlyTypeName {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<&str> for OnlyTypeName {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<String> for OnlyTypeName {
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
#[inline(always)]
pub fn get_type_name_without_scope<T: ComponentId>() -> OnlyTypeName {
ecs_assert!(
!T::IS_GENERIC,
FlecsErrorCode::InvalidParameter,
"get_only_type_name() cannot be used with generic types"
);
let name = T::name();
OnlyTypeName::NonGeneric(name.split("::").last().unwrap_or(name))
}
#[inline(always)]
pub fn get_type_name_without_scope_generic<T>() -> OnlyTypeName {
fn split_top_level(s: &str) -> Vec<&str> {
let mut parts = Vec::new();
let mut depth = 0;
let mut start = 0;
for (i, c) in s.char_indices() {
match c {
'<' => depth += 1,
'>' => depth -= 1,
',' if depth == 0 => {
parts.push(&s[start..i]);
start = i + 1;
}
_ => {}
}
}
parts.push(&s[start..]);
parts
}
fn strip_paths(name: &str) -> String {
if let Some(lt) = name.find('<') {
let base = &name[..lt];
let args = &name[lt + 1..name.len() - 1]; let base_name = base.rsplit("::").next().unwrap_or(base);
let args_strs = split_top_level(args);
let stripped_args: Vec<String> = args_strs
.into_iter()
.map(|arg| strip_paths(arg.trim()))
.collect();
format!("{}<{}>", base_name, stripped_args.join(", "))
} else {
name.rsplit("::").next().unwrap_or(name).to_string()
}
}
let full = core::any::type_name::<T>();
OnlyTypeName::Generic(strip_paths(full))
}
#[inline(always)]
pub const fn is_empty_type<T>() -> bool {
core::mem::size_of::<T>() == 0
}
pub fn ecs_record_to_row(row: u32) -> i32 {
(row & sys::ECS_ROW_MASK) as i32
}
pub(crate) fn set_helper<T: ComponentId>(
world: *mut sys::ecs_world_t,
entity: u64,
value: T,
id: u64,
) {
const {
assert!(
core::mem::size_of::<T>() != 0,
"cannot set zero-sized-type / tag components"
);
};
unsafe {
if T::NEEDS_DROP && sys::ecs_has_id(world, entity, id) {
assign_helper(world, entity, value, id);
return;
}
let res = sys::ecs_cpp_set(
world,
entity,
id,
&value as *const _ as *const _,
const { core::mem::size_of::<T>() },
);
let comp = res.ptr as *mut T;
core::ptr::write(comp, value);
if res.call_modified {
sys::ecs_modified_id(world, entity, id);
}
}
}
pub(crate) fn assign_helper<T: ComponentId>(
world: *mut sys::ecs_world_t,
entity: sys::ecs_entity_t,
value: T,
id: sys::ecs_id_t,
) {
ecs_assert!(
core::mem::size_of::<T>() != 0,
FlecsErrorCode::InvalidParameter,
"operation invalid for empty type"
);
let res = unsafe {
sys::ecs_cpp_assign(
world,
entity,
id,
&value as *const _ as *const _,
core::mem::size_of::<T>(),
)
};
let dst = unsafe { &mut *(res.ptr as *mut T) };
unsafe {
core::ptr::drop_in_place(dst);
core::ptr::write(dst, value);
}
if res.call_modified {
unsafe { sys::ecs_modified_id(world, entity, id) };
}
}
#[inline(always)]
pub fn strip_generation(entity: impl Into<Entity>) -> u64 {
unsafe { sys::ecs_strip_generation(*entity.into()) }
}
#[inline(always)]
pub fn get_generation(entity: impl Into<Entity>) -> u32 {
(*(entity.into() & sys::ECS_GENERATION_MASK) >> 32) as u32
}
#[inline(always)]
pub(crate) unsafe fn flecs_field_at<T>(it: *const sys::ecs_iter_t, index: i8, row: i32) -> *mut T {
unsafe {
let size = core::mem::size_of::<T>();
sys::ecs_field_at_w_size(it, size, index, row) as *mut T
}
}
#[expect(dead_code, reason = "possibly used in the future")]
pub(crate) fn type_to_oper<T: OperType>() -> OperKind {
T::OPER
}
pub fn ecs_bit_set(flags: &mut u32, bit: u32) {
*flags |= bit;
}
pub fn ecs_bit_clear(flags: &mut u32, bit: u32) {
*flags &= !bit;
}
pub fn ecs_bit_cond(flags: &mut u32, bit: u32, cond: bool) {
if cond {
ecs_bit_set(flags, bit);
} else {
ecs_bit_clear(flags, bit);
}
}
#[expect(dead_code, reason = "possibly used in the future")]
pub(crate) fn copy_and_allocate_c_char_from_rust_str(data: &str) -> *mut c_char {
ecs_assert!(
data.is_ascii(),
FlecsErrorCode::InvalidParameter,
"string must be ascii"
);
let bytes = data.as_bytes();
let len = bytes.len() + 1; let memory_c_str = unsafe { sys::ecs_os_api.malloc_.unwrap()(len as i32) } as *mut u8;
for (i, &byte) in bytes.iter().enumerate() {
unsafe {
memory_c_str.add(i).write(byte);
}
}
unsafe { memory_c_str.add(bytes.len()).write(0) };
memory_c_str as *mut c_char
}
#[cfg(feature = "std")]
#[expect(dead_code, reason = "possibly used in the future")]
pub(crate) unsafe fn print_c_string(c_string: *const c_char) {
unsafe {
assert!(!c_string.is_null(), "Null pointer passed to print_c_string");
let c_str = core::ffi::CStr::from_ptr(c_string);
#[allow(clippy::print_stdout)]
match c_str.to_str() {
Ok(s) => println!("{s}"),
Err(_) => println!("Failed to convert C string to Rust string"),
}
}
}
pub(crate) fn strip_prefix_str_raw<'a>(str: &'a str, prefix: &str) -> Option<&'a str> {
let str_bytes = str.as_bytes();
let prefix_bytes = prefix.as_bytes();
if str_bytes.starts_with(prefix_bytes) {
Some(
core::str::from_utf8(&str_bytes[prefix_bytes.len()..])
.expect("error: pass valid utf strings"),
)
} else {
None
}
}
pub(crate) fn check_add_id_validity(world: *const sys::ecs_world_t, id: u64) {
let is_valid_id = unsafe { sys::ecs_id_is_valid(world, id) };
if !is_valid_id {
panic!("Id is not a valid component, pair or entity.");
}
let is_not_tag = unsafe { sys::ecs_get_typeid(world, id) != 0 };
if is_not_tag {
assert!(
has_default_hook(world, id),
"Id is not a zero-sized type (ZST) such as a Tag or Entity or does not implement the Default hook for a non ZST type. Default hooks are automatically implemented if the type has a Default trait."
);
}
}
#[inline(never)]
pub(crate) fn has_default_hook(world: *const sys::ecs_world_t, id: u64) -> bool {
let hooks = unsafe { sys::ecs_get_hooks_id(world, id) };
let ctor_hooks =
unsafe { (*hooks).ctor }.expect("ctor hook is always implemented, either in Rust or C");
#[cfg(target_family = "wasm")]
type ExternDefaultCtorFn =
unsafe extern "C" fn(*mut core::ffi::c_void, i32, *const sys::ecs_type_info_t);
#[cfg(not(target_family = "wasm"))]
type ExternDefaultCtorFn =
unsafe extern "C-unwind" fn(*mut core::ffi::c_void, i32, *const sys::ecs_type_info_t);
!core::ptr::fn_addr_eq(ctor_hooks, sys::flecs_default_ctor as ExternDefaultCtorFn)
}
pub fn debug_separate_archetype_types_into_strings(archetype: &Archetype) -> Vec<String> {
let mut result = Vec::with_capacity(archetype.count());
let mut skip_next = false; let archetype_str = archetype
.to_string()
.unwrap_or_else(|| "empty entity | no components".to_string());
if archetype.count() == 0 {
return vec![archetype_str];
}
let parts: Vec<&str> = archetype_str.split(',').map(str::trim).collect();
let ids = archetype.as_slice();
let mut i_ids = 0;
for i in 0..parts.len() {
if skip_next {
skip_next = false;
continue;
}
let part = parts[i];
let id = ids[i_ids];
if part.starts_with('(') {
let combined = format!("{part}, {} : {id}", parts[i + 1]);
result.push(combined);
skip_next = true; } else {
result.push(format!("{part} : {id}"));
}
i_ids += 1;
}
result
}
#[cfg(test)]
mod tests {
use super::get_type_name_without_scope_generic;
struct MyStruct;
#[allow(dead_code)] enum MyEnum {
A,
B,
}
#[test]
fn simple_type() {
assert_eq!(get_type_name_without_scope_generic::<i32>(), "i32");
assert_eq!(get_type_name_without_scope_generic::<bool>(), "bool");
}
#[test]
fn single_generic() {
assert_eq!(
get_type_name_without_scope_generic::<Vec<String>>(),
"Vec<String>"
);
assert_eq!(
get_type_name_without_scope_generic::<Option<u8>>(),
"Option<u8>"
);
}
#[test]
fn multi_generic() {
assert_eq!(
get_type_name_without_scope_generic::<Result<i32, f64>>(),
"Result<i32, f64>"
);
}
#[test]
fn nested_generics() {
type Deep = Option<Result<Vec<MyStruct>, MyEnum>>;
assert_eq!(
get_type_name_without_scope_generic::<Deep>(),
"Option<Result<Vec<MyStruct>, MyEnum>>"
);
}
#[test]
fn custom_struct_and_enum() {
assert_eq!(
get_type_name_without_scope_generic::<MyStruct>(),
"MyStruct"
);
assert_eq!(get_type_name_without_scope_generic::<MyEnum>(), "MyEnum");
}
#[test]
fn pointer_and_reference() {
assert_eq!(get_type_name_without_scope_generic::<&str>(), "&str");
assert_eq!(
get_type_name_without_scope_generic::<*const i32>(),
"*const i32"
);
}
mod outer {
pub mod inner {
pub struct Deep;
pub struct Wrap<T>(pub T);
#[allow(dead_code)] pub enum E {
A,
B,
}
}
}
mod a {
pub mod b {
pub mod c {
pub struct Z;
}
}
}
#[test]
fn nested_modules_simple() {
assert_eq!(
get_type_name_without_scope_generic::<outer::inner::Deep>(),
"Deep"
);
assert_eq!(get_type_name_without_scope_generic::<a::b::c::Z>(), "Z");
assert_eq!(
get_type_name_without_scope_generic::<outer::inner::E>(),
"E"
);
}
#[test]
fn nested_modules_with_generics() {
type T1 = outer::inner::Wrap<outer::inner::Deep>;
type T2 = outer::inner::Wrap<outer::inner::Wrap<outer::inner::Deep>>;
assert_eq!(get_type_name_without_scope_generic::<T1>(), "Wrap<Deep>");
assert_eq!(
get_type_name_without_scope_generic::<T2>(),
"Wrap<Wrap<Deep>>"
);
}
#[test]
fn long_std_path_nested_generics() {
type LongNested = ::alloc::collections::BTreeMap<String, Vec<Vec<String>>>;
assert_eq!(
get_type_name_without_scope_generic::<LongNested>(),
"BTreeMap<String, Vec<Vec<String>>>"
);
}
}