diff --git a/flecs_ecs/src/core/c_types.rs b/flecs_ecs/src/core/c_types.rs index 7c854c7c..232a7256 100644 --- a/flecs_ecs/src/core/c_types.rs +++ b/flecs_ecs/src/core/c_types.rs @@ -369,6 +369,8 @@ impl NotEmptyComponent for EcsComponent {} impl ComponentInfo for EcsComponent { const IS_ENUM: bool = false; const IS_TAG: bool = false; + const IMPLS_CLONE: bool = true; + const IMPLS_DEFAULT: bool = true; } impl ComponentType for EcsComponent {} @@ -413,6 +415,8 @@ impl ComponentId for EcsComponent { impl ComponentInfo for Poly { const IS_ENUM: bool = false; const IS_TAG: bool = false; + const IMPLS_CLONE: bool = true; + const IMPLS_DEFAULT: bool = true; } impl NotEmptyComponent for Poly {} @@ -460,6 +464,8 @@ impl ComponentId for Poly { impl ComponentInfo for TickSource { const IS_TAG: bool = false; const IS_ENUM: bool = false; + const IMPLS_CLONE: bool = true; + const IMPLS_DEFAULT: bool = true; } #[cfg(feature = "flecs_system")] @@ -503,6 +509,8 @@ impl ComponentId for TickSource { impl ComponentInfo for EntityId { const IS_ENUM: bool = false; const IS_TAG: bool = false; + const IMPLS_CLONE: bool = true; + const IMPLS_DEFAULT: bool = false; } impl ComponentId for EntityId { diff --git a/flecs_ecs/src/core/component_registration/helpers.rs b/flecs_ecs/src/core/component_registration/helpers.rs index 34f22777..ecfc085a 100644 --- a/flecs_ecs/src/core/component_registration/helpers.rs +++ b/flecs_ecs/src/core/component_registration/helpers.rs @@ -27,16 +27,14 @@ where } else { 0 }; - - let hooks = if size != 0 && T::NEEDS_DROP { + let mut hooks = Default::default(); + if size != 0 && T::NEEDS_DROP { // Register lifecycle callbacks, but only if the component has a // size and requires initialization of heap memory / needs drop. // Components that don't have a size are tags, and tags don't // require construction/destruction/copy/move's. - T::__register_lifecycle_hooks() - } else { - Default::default() - }; + T::__register_lifecycle_hooks(&mut hooks); + } let type_info: flecs_ecs_sys::ecs_type_info_t = flecs_ecs_sys::ecs_type_info_t { size: size as i32, diff --git a/flecs_ecs/src/core/component_registration/mod.rs b/flecs_ecs/src/core/component_registration/mod.rs index 82482f57..231e97de 100644 --- a/flecs_ecs/src/core/component_registration/mod.rs +++ b/flecs_ecs/src/core/component_registration/mod.rs @@ -1,9 +1,9 @@ mod helpers; mod registration; pub mod registration_traits; -pub mod types; +pub mod registration_types; pub(crate) use helpers::*; pub use registration::*; pub use registration_traits::*; -pub use types::*; +pub use registration_types::*; diff --git a/flecs_ecs/src/core/component_registration/registration_traits.rs b/flecs_ecs/src/core/component_registration/registration_traits.rs index 749d8def..520c7f77 100644 --- a/flecs_ecs/src/core/component_registration/registration_traits.rs +++ b/flecs_ecs/src/core/component_registration/registration_traits.rs @@ -1,8 +1,8 @@ use std::{ffi::CStr, sync::OnceLock}; use crate::core::{ - ConditionalTypeSelector, DefaultCloneDummy, Entity, EntityT, Enum, IdComponent, IdT, IntoWorld, - Struct, TypeHooksT, + ConditionalTypeSelector, Entity, EntityT, Enum, FlecsNoneCloneDummy, FlecsNoneDefaultDummy, + IdComponent, IdT, IntoWorld, Struct, TypeHooksT, }; use super::{ @@ -108,15 +108,15 @@ pub trait ComponentId: Sized + ComponentInfo { // Not public API. #[doc(hidden)] - fn __register_lifecycle_hooks() -> TypeHooksT { - Default::default() - } + fn __register_lifecycle_hooks(mut _type_hooks: &mut TypeHooksT) {} } pub trait ComponentInfo: Sized { const IS_ENUM: bool; const IS_TAG: bool; const NEEDS_DROP: bool = std::mem::needs_drop::(); + const IMPLS_CLONE: bool; + const IMPLS_DEFAULT: bool; } pub trait CachedEnumData: ComponentType + ComponentId { @@ -195,11 +195,15 @@ pub trait CachedEnumData: ComponentType + ComponentId { impl ComponentInfo for &T { const IS_ENUM: bool = T::IS_ENUM; const IS_TAG: bool = T::IS_TAG; + const IMPLS_CLONE: bool = T::IMPLS_CLONE; + const IMPLS_DEFAULT: bool = T::IMPLS_DEFAULT; } impl ComponentInfo for &mut T { const IS_ENUM: bool = T::IS_ENUM; const IS_TAG: bool = T::IS_TAG; + const IMPLS_CLONE: bool = T::IMPLS_CLONE; + const IMPLS_DEFAULT: bool = T::IMPLS_DEFAULT; } impl ComponentId for &T { @@ -222,17 +226,32 @@ impl ComponentId for &mut T { type UnderlyingEnumType = T::UnderlyingEnumType; } -pub trait DefaultCloneType { - type Type: Default + Clone; +pub trait FlecsDefaultType { + type Type: Default; +} + +pub trait FlecsCloneType { + type Type: Clone; +} + +impl FlecsDefaultType for ConditionalTypeSelector { + type Type = FlecsNoneDefaultDummy; +} + +impl FlecsDefaultType for ConditionalTypeSelector +where + T: Default, +{ + type Type = T; } -impl DefaultCloneType for ConditionalTypeSelector { - type Type = DefaultCloneDummy; +impl FlecsCloneType for ConditionalTypeSelector { + type Type = FlecsNoneCloneDummy; } -impl DefaultCloneType for ConditionalTypeSelector +impl FlecsCloneType for ConditionalTypeSelector where - T: Default + Clone, + T: Clone, { type Type = T; } diff --git a/flecs_ecs/src/core/component_registration/types.rs b/flecs_ecs/src/core/component_registration/registration_types.rs similarity index 84% rename from flecs_ecs/src/core/component_registration/types.rs rename to flecs_ecs/src/core/component_registration/registration_types.rs index 38de829d..fd9ca37c 100644 --- a/flecs_ecs/src/core/component_registration/types.rs +++ b/flecs_ecs/src/core/component_registration/registration_types.rs @@ -13,11 +13,14 @@ pub struct Struct; #[derive(Component)] pub enum NoneEnum { - None, + None = 1, } #[derive(Default, Clone)] -pub struct DefaultCloneDummy; +pub struct FlecsNoneDefaultDummy; + +#[derive(Clone)] +pub struct FlecsNoneCloneDummy; pub struct ConditionalTypeSelector { phantom: std::marker::PhantomData, diff --git a/flecs_ecs/src/core/flecs.rs b/flecs_ecs/src/core/flecs.rs index e63b9b66..49aa4962 100644 --- a/flecs_ecs/src/core/flecs.rs +++ b/flecs_ecs/src/core/flecs.rs @@ -17,6 +17,8 @@ macro_rules! create_pre_registered_component { impl ComponentInfo for $struct_name { const IS_ENUM: bool = false; const IS_TAG: bool = true; + const IMPLS_CLONE: bool = false; + const IMPLS_DEFAULT: bool = false; } impl EmptyComponent for $struct_name {} diff --git a/flecs_ecs/src/core/lifecycle_traits.rs b/flecs_ecs/src/core/lifecycle_traits.rs index 7e0a4d46..53f19196 100644 --- a/flecs_ecs/src/core/lifecycle_traits.rs +++ b/flecs_ecs/src/core/lifecycle_traits.rs @@ -50,24 +50,23 @@ use crate::{core::c_types::TypeHooksT, ecs_assert, sys::ecs_type_info_t}; use std::{ffi::c_void, mem::MaybeUninit, ptr}; #[allow(clippy::not_unsafe_ptr_arg_deref)] -pub fn create_lifecycle_actions() -> TypeHooksT { - TypeHooksT { - ctor: Some(generic_ctor::), - dtor: Some(generic_dtor::), - copy: Some(generic_copy::), - move_: Some(generic_move::), - copy_ctor: Some(generic_copy::), //same implementation as copy - move_ctor: Some(generic_move::), //same implementation as move - ctor_move_dtor: Some(generic_ctor_move_dtor::), - move_dtor: Some(generic_ctor_move_dtor::), //same implementation as ctor_move_dtor - on_add: None, - on_set: None, - on_remove: None, - ctx: std::ptr::null_mut(), - binding_ctx: std::ptr::null_mut(), - ctx_free: None, - binding_ctx_free: None, - } +pub fn register_lifecycle_actions(type_hooks: &mut TypeHooksT) { + type_hooks.ctor = Some(generic_ctor::); + type_hooks.dtor = Some(generic_dtor::); + type_hooks.move_ = Some(generic_move::); + type_hooks.move_ctor = Some(generic_move::); //same implementation as move + type_hooks.ctor_move_dtor = Some(generic_ctor_move_dtor::); + type_hooks.move_dtor = Some(generic_ctor_move_dtor::); //same implementation as ctor_move_dtor +} + +pub fn register_copy_lifecycle_action(type_hooks: &mut TypeHooksT) { + type_hooks.copy = Some(generic_copy::); + type_hooks.copy_ctor = Some(generic_copy::); //same implementation as copy +} + +pub fn register_copy_lifecycle_panic_action(type_hooks: &mut TypeHooksT) { + type_hooks.copy = Some(generic_copy_panic::); + type_hooks.copy_ctor = Some(generic_copy_panic::); //same implementation as copy } /// This is the generic constructor for trivial types @@ -133,7 +132,7 @@ extern "C" fn generic_dtor(ptr: *mut c_void, count: i32, _type_info: *const e /// /// * C++ API: `copy_impl` #[doc(alias = "copy_impl")] -extern "C" fn generic_copy( +extern "C" fn generic_copy( dst_ptr: *mut c_void, src_ptr: *const c_void, count: i32, @@ -155,6 +154,22 @@ extern "C" fn generic_copy( } } +/// This is the generic copy for trivial types +/// It will copy the memory +/// +/// # See also +/// +/// * C++ API: `copy_impl` +#[doc(alias = "copy_impl")] +extern "C" fn generic_copy_panic( + _dst_ptr: *mut c_void, + _src_ptr: *const c_void, + _count: i32, + _type_info: *const 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", std::any::type_name::()); +} + /// This is the generic move for non-trivial types /// It will move the memory /// @@ -194,7 +209,7 @@ extern "C" fn generic_move( /// /// * C++ API: `move_ctor_impl` #[doc(alias = "move_ctor_impl")] -extern "C" fn generic_ctor_move_dtor( +extern "C" fn generic_ctor_move_dtor( dst_ptr: *mut c_void, src_ptr: *mut c_void, count: i32, diff --git a/flecs_ecs/src/core/utility/mod.rs b/flecs_ecs/src/core/utility/mod.rs index afe9caba..b4025044 100644 --- a/flecs_ecs/src/core/utility/mod.rs +++ b/flecs_ecs/src/core/utility/mod.rs @@ -2,7 +2,7 @@ mod errors; mod functions; mod log; pub mod traits; -mod types; +pub mod types; pub use errors::*; pub use functions::*; diff --git a/flecs_ecs/src/core/utility/traits/mod.rs b/flecs_ecs/src/core/utility/traits/mod.rs index 50b4a478..02420a69 100644 --- a/flecs_ecs/src/core/utility/traits/mod.rs +++ b/flecs_ecs/src/core/utility/traits/mod.rs @@ -14,6 +14,8 @@ pub use into_world::*; pub use iter::*; pub use reactor::*; +use super::{ImplementsClone, ImplementsDefault}; + #[doc(hidden)] pub mod private { use std::{ffi::c_void, ptr}; @@ -313,3 +315,17 @@ pub mod private { } } } + +pub trait DoesNotImpl { + const IMPLS: bool = false; +} + +impl DoesNotImpl for T {} + +impl ImplementsClone { + pub const IMPLS: bool = true; +} + +impl ImplementsDefault { + pub const IMPLS: bool = true; +} diff --git a/flecs_ecs/src/core/utility/types.rs b/flecs_ecs/src/core/utility/types.rs index 8283e3dc..4f34e5ba 100644 --- a/flecs_ecs/src/core/utility/types.rs +++ b/flecs_ecs/src/core/utility/types.rs @@ -4,7 +4,7 @@ use crate::core::{Entity, IdT, World}; pub type FTime = f32; -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct EntityId(pub IdT); impl EntityId { @@ -223,3 +223,6 @@ impl ObserverEntityBindingCtx { } } } + +pub struct ImplementsClone(std::marker::PhantomData); +pub struct ImplementsDefault(std::marker::PhantomData); diff --git a/flecs_ecs/tests/clone_default_impl.rs b/flecs_ecs/tests/clone_default_impl.rs new file mode 100644 index 00000000..e3d6042a --- /dev/null +++ b/flecs_ecs/tests/clone_default_impl.rs @@ -0,0 +1,69 @@ +use flecs_ecs::core::{ComponentInfo, World}; +use flecs_ecs_derive::Component; + +// normal structs +#[derive(Component)] +struct NoneCloneDefault; + +#[derive(Default, Clone, Component)] +struct CloneDefault; + +#[derive(Clone, Component)] +struct CloneNoDefault; + +#[derive(Default, Component)] +struct DefaultNoClone; + +// drop structs +#[derive(Component, Default)] +struct DefaultNoCloneDrop { + _data: String, +} + +#[derive(Default, Clone, Component)] +struct CloneDefaultDrop { + data: String, +} + +#[test] +fn compile_time_check_impls_clone_default() { + // we do it this way to avoid the warning of constant bools getting optimized away from clippy in test cases. + let none_clone_default = !NoneCloneDefault::IMPLS_CLONE && !NoneCloneDefault::IMPLS_DEFAULT; + let clone_default = CloneDefault::IMPLS_CLONE && CloneDefault::IMPLS_DEFAULT; + let clone_no_default = CloneNoDefault::IMPLS_CLONE && !CloneNoDefault::IMPLS_DEFAULT; + let default_no_clone = !DefaultNoClone::IMPLS_CLONE && DefaultNoClone::IMPLS_DEFAULT; + + assert!(none_clone_default); + assert!(clone_default); + assert!(clone_no_default); + assert!(default_no_clone); +} + +#[test] +fn copy_hook_implemented_for_drop_types() { + let world = World::new(); + let e_orig = world.new_entity().set(CloneDefaultDrop { + data: "data".to_string(), + }); + + let entity_cloned = e_orig.duplicate(true); + let data_orig = &e_orig.get::().unwrap().data; + let data_cloned = &entity_cloned.get::().unwrap().data; + + assert!(*data_orig == *data_cloned); +} + +#[test] +#[should_panic( + expected = "DefaultNoClone does not implement Clone and with a duplicate operation it will panic" +)] +#[ignore = "C asserts that world didn't properly end deferring and aborts +the test & thus the test not registering the panic and does not get marked as passed"] +fn copy_hook_not_implemented_for_drop_types() { + let world = World::new(); + let e_orig = world.new_entity().set(DefaultNoCloneDrop { + _data: "data".to_string(), + }); + + let _entity_cloned = e_orig.duplicate(true); // PANICS +} diff --git a/flecs_ecs/tests/common.rs b/flecs_ecs/tests/common.rs index 7db2c0c5..e73655c1 100644 --- a/flecs_ecs/tests/common.rs +++ b/flecs_ecs/tests/common.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] #![allow(unused_imports)] -use flecs_ecs::macros::Component; +use flecs_ecs::{core::ComponentInfo, macros::Component}; #[cfg(test)] #[ctor::ctor] @@ -106,3 +106,8 @@ impl Drop for Pod { pub struct Template { pub value: T, } + +#[derive(Component, Default)] +pub struct Templatex { + pub value: String, +} diff --git a/flecs_ecs_derive/src/lib.rs b/flecs_ecs_derive/src/lib.rs index a836eee2..ad81b036 100644 --- a/flecs_ecs_derive/src/lib.rs +++ b/flecs_ecs_derive/src/lib.rs @@ -154,11 +154,19 @@ fn impl_cached_component_data_struct( &ONCE_LOCK } - fn __register_lifecycle_hooks() -> flecs_ecs::core::TypeHooksT { + fn __register_lifecycle_hooks(mut type_hooks: &mut flecs_ecs::core::TypeHooksT) { + use flecs_ecs::core::component_registration::registration_traits::ComponentInfo; const NEEDS_DROP: bool = <#name as flecs_ecs::core::component_registration::registration_traits::ComponentInfo>::NEEDS_DROP; - flecs_ecs::core::lifecycle_traits::create_lifecycle_actions::< - as flecs_ecs::core::component_registration::registration_traits::DefaultCloneType>::Type, - >() + const IMPLS_CLONE: bool = #name::IMPLS_CLONE; + flecs_ecs::core::lifecycle_traits::register_lifecycle_actions::< + + as flecs_ecs::core::component_registration::registration_traits::FlecsDefaultType>::Type,>(&mut type_hooks); + + if IMPLS_CLONE { + flecs_ecs::core::lifecycle_traits::register_copy_lifecycle_action::<as flecs_ecs::core::component_registration::registration_traits::FlecsCloneType> ::Type,>(&mut type_hooks); + } else { + flecs_ecs::core::lifecycle_traits::register_copy_lifecycle_panic_action::<#name>(&mut type_hooks); + } } }; @@ -169,6 +177,14 @@ fn impl_cached_component_data_struct( impl #impl_generics flecs_ecs::core::component_registration::registration_traits::ComponentInfo for #name #type_generics #where_clause { const IS_ENUM: bool = false; #is_tag + const IMPLS_CLONE: bool = { + use flecs_ecs::core::utility::traits::DoesNotImpl; + flecs_ecs::core::utility::types::ImplementsClone::<#name #type_generics>::IMPLS + }; + const IMPLS_DEFAULT: bool = { + use flecs_ecs::core::utility::traits::DoesNotImpl; + flecs_ecs::core::utility::types::ImplementsDefault::<#name #type_generics>::IMPLS + }; } }; @@ -354,11 +370,20 @@ fn impl_cached_component_data_enum(ast: &mut syn::DeriveInput) -> TokenStream { &ONCE_LOCK } - fn __register_lifecycle_hooks() -> flecs_ecs::core::TypeHooksT { + fn __register_lifecycle_hooks(mut type_hooks: &mut flecs_ecs::core::TypeHooksT) { const NEEDS_DROP: bool = <#name as flecs_ecs::core::component_registration::registration_traits::ComponentInfo>::NEEDS_DROP; - flecs_ecs::core::lifecycle_traits::create_lifecycle_actions::< - as flecs_ecs::core::component_registration::registration_traits::DefaultCloneType>::Type, - >() + + flecs_ecs::core::lifecycle_traits::register_lifecycle_actions::< + + as flecs_ecs::core::component_registration::registration_traits::FlecsDefaultType>::Type,>(&mut type_hooks); + + if std::any::type_name::< + as flecs_ecs::core::component_registration::registration_traits::FlecsCloneType>::Type,>() + .contains("FlecsNoneCloneDummy") { + flecs_ecs::core::lifecycle_traits::register_copy_lifecycle_panic_action::<#name>(&mut type_hooks); + } else { + flecs_ecs::core::lifecycle_traits::register_copy_lifecycle_action::<as flecs_ecs::core::component_registration::registration_traits::FlecsCloneType> ::Type,>(&mut type_hooks); + } } }; @@ -378,9 +403,18 @@ fn impl_cached_component_data_enum(ast: &mut syn::DeriveInput) -> TokenStream { quote! { impl #impl_generics flecs_ecs::core::ComponentType for #name #type_generics #where_clause {} + impl #impl_generics flecs_ecs::core::component_registration::registration_traits::ComponentInfo for #name #type_generics #where_clause{ const IS_ENUM: bool = true; const IS_TAG: bool = false; + const IMPLS_CLONE: bool = { + use flecs_ecs::core::utility::traits::DoesNotImpl; + flecs_ecs::core::utility::types::ImplementsClone::<#name #type_generics>::IMPLS + }; + const IMPLS_DEFAULT: bool = { + use flecs_ecs::core::utility::traits::DoesNotImpl; + flecs_ecs::core::utility::types::ImplementsDefault::<#name #type_generics>::IMPLS + }; } #component_id @@ -411,16 +445,25 @@ fn generate_component_id_impl(name: &Ident, ty: &Ident, is_struct: bool) -> Toke quote! { impl flecs_ecs::core::component_registration::registration_traits::ComponentId for #name<#ty> { type UnderlyingType = #name<#ty>; - type UnderlyingEnumType = flecs_ecs::core::component_registration::types::NoneEnum; + type UnderlyingEnumType = flecs_ecs::core::component_registration::registration_types::NoneEnum; fn __get_once_lock_data() -> &'static std::sync::OnceLock { static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); &ONCE_LOCK } - fn __register_lifecycle_hooks() -> flecs_ecs::core::TypeHooksT { + fn __register_lifecycle_hooks(mut type_hooks: &mut flecs_ecs::core::TypeHooksT) { const NEEDS_DROP: bool = <#name<#ty> as flecs_ecs::core::component_registration::registration_traits::ComponentInfo>::NEEDS_DROP; - flecs_ecs::core::lifecycle_traits::create_lifecycle_actions::< - > as flecs_ecs::core::component_registration::registration_traits::DefaultCloneType>::Type, - >() + + flecs_ecs::core::lifecycle_traits::register_lifecycle_actions::< + > + as flecs_ecs::core::component_registration::registration_traits::FlecsDefaultType>::Type,>(&mut type_hooks); + + if std::any::type_name::<> + as flecs_ecs::core::component_registration::registration_traits::FlecsCloneType>::Type,>() + .contains("FlecsNoneCloneDummy") { + flecs_ecs::core::lifecycle_traits::register_copy_lifecycle_panic_action::<#name<#ty>>(&mut type_hooks); + } else { + flecs_ecs::core::lifecycle_traits::register_copy_lifecycle_action::<>as flecs_ecs::core::component_registration::registration_traits::FlecsCloneType> ::Type,>(&mut type_hooks); + } } } } @@ -433,11 +476,19 @@ fn generate_component_id_impl(name: &Ident, ty: &Ident, is_struct: bool) -> Toke static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); &ONCE_LOCK } - fn __register_lifecycle_hooks() -> flecs_ecs::core::TypeHooksT { + fn __register_lifecycle_hooks(mut type_hooks: &mut flecs_ecs::core::TypeHooksT) { + use flecs_ecs::core::component_registration::registration_traits::ComponentInfo; const NEEDS_DROP: bool = <#name<#ty> as flecs_ecs::core::component_registration::registration_traits::ComponentInfo>::NEEDS_DROP; - flecs_ecs::core::lifecycle_traits::create_lifecycle_actions::< - > as flecs_ecs::core::component_registration::registration_traits::DefaultCloneType>::Type, - >() + const IMPLS_CLONE: bool = #name<#ty>::IMPLS_CLONE; + flecs_ecs::core::lifecycle_traits::register_lifecycle_actions::< + > + as flecs_ecs::core::component_registration::registration_traits::FlecsDefaultType>::Type,>(&mut type_hooks); + + if IMPLS_CLONE { + flecs_ecs::core::lifecycle_traits::register_copy_lifecycle_action::<>as flecs_ecs::core::component_registration::registration_traits::FlecsCloneType> ::Type,>(&mut type_hooks); + } else { + flecs_ecs::core::lifecycle_traits::register_copy_lifecycle_panic_action::<#name<#ty>>(&mut type_hooks); + } } } }