use std::sync::Arc;
#[cfg(debug_assertions)]
use crate::ExtractionMetadata;
use crate::{Acquirable, Extractable, entity::EntityData};
#[cfg(debug_assertions)]
struct HandlerMetadata {
base_type: &'static str,
concrete_type: &'static str,
signature: String,
}
struct TypeErasedFn<Args, Return> {
#[allow(clippy::type_complexity)]
caller: Box<dyn Fn(&Arc<EntityData>, Args) -> Return + Send + Sync>,
#[cfg(debug_assertions)]
metadata: HandlerMetadata,
}
impl<Args, Return> TypeErasedFn<Args, Return> {
pub fn new<Base, Concrete>(
func: impl Fn(&Acquirable<Concrete>, Args) -> Return + Send + Sync + 'static,
) -> Self
where
Base: Extractable,
Concrete: Extractable,
{
let caller = move |data: &Arc<EntityData>, args: Args| -> Return {
#[allow(clippy::expect_used)]
let entity = data
.extract::<Concrete>()
.expect("Handler type mismatch - this is a bug in ComponentHandler");
func(&entity, args)
};
Self {
caller: Box::new(caller),
#[cfg(debug_assertions)]
metadata: HandlerMetadata {
base_type: std::any::type_name::<Base>(),
concrete_type: std::any::type_name::<Concrete>(),
signature: format!(
"Fn(&Acquirable<{}>, {}) -> {}",
std::any::type_name::<Concrete>(),
std::any::type_name::<Args>(),
std::any::type_name::<Return>()
),
},
}
}
pub fn call<E: Extractable>(&self, entity: &Acquirable<E>, args: Args) -> Return {
(self.caller)(&entity.inner, args)
}
}
unsafe impl<Args, Return> Send for TypeErasedFn<Args, Return> {}
unsafe impl<Args, Return> Sync for TypeErasedFn<Args, Return> {}
pub struct ComponentHandler<Base: Extractable, Args = (), Return = ()> {
function: TypeErasedFn<Args, Return>,
_marker: std::marker::PhantomData<Base>,
}
impl<Base: Extractable, Args, Return> ComponentHandler<Base, Args, Return> {
pub fn for_type<Concrete: Extractable>(
func: impl Fn(&Acquirable<Concrete>, Args) -> Return + Send + Sync + 'static,
) -> Self {
#[cfg(debug_assertions)]
Self::validate_type_relationship::<Concrete>();
Self {
function: TypeErasedFn::new::<Base, Concrete>(func),
_marker: std::marker::PhantomData,
}
}
#[cfg(debug_assertions)]
fn validate_type_relationship<Concrete: Extractable>() {
if !can_extract::<Concrete, Base>() {
panic!(
"\n╔════════════════════════════════════════════════════════════╗\n\
║ ComponentHandler Type Mismatch ║\n\
╠════════════════════════════════════════════════════════════╣\n\
║ Base type: {:<44}║\n\
║ Concrete type: {:<44}║\n\
╠════════════════════════════════════════════════════════════╣\n\
║ The concrete type must contain the base type in its ║\n\
║ extraction metadata. Did you forget #[extractable(...)]? ║\n\
║ ║\n\
║ Example: ║\n\
║ #[derive(Extractable)] ║\n\
║ #[extractable(entity)] // <-- Add this! ║\n\
║ pub struct Player {{ ║\n\
║ pub entity: Entity, ║\n\
║ }} ║\n\
╚════════════════════════════════════════════════════════════╝\n",
std::any::type_name::<Base>(),
std::any::type_name::<Concrete>()
);
}
}
pub fn call<E: Extractable>(&self, entity: &Acquirable<E>, args: Args) -> Return {
#[cfg(debug_assertions)]
self.validate_call::<E>();
self.function.call(entity, args)
}
#[cfg(debug_assertions)]
fn validate_call<E: Extractable>(&self) {
if !can_extract::<E, Base>() {
panic!(
"\n╔════════════════════════════════════════════════════════════╗\n\
║ ComponentHandler Call Mismatch ║\n\
╠════════════════════════════════════════════════════════════╣\n\
║ Expected base: {:<44}║\n\
║ Actual type: {:<44}║\n\
║ Handler for: {:<44}║\n\
╠════════════════════════════════════════════════════════════╣\n\
║ The entity type must be extractable as the base type. ║\n\
╚════════════════════════════════════════════════════════════╝\n",
std::any::type_name::<Base>(),
std::any::type_name::<E>(),
self.function.metadata.concrete_type
);
}
}
#[cfg(debug_assertions)]
pub fn debug_info(&self) -> String {
format!(
"ComponentHandler<{}> for {} (signature: {})",
self.function.metadata.base_type,
self.function.metadata.concrete_type,
self.function.metadata.signature
)
}
}
impl<Base: Extractable, Args, Return> std::fmt::Debug for ComponentHandler<Base, Args, Return> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("ComponentHandler");
#[cfg(debug_assertions)]
{
debug.field("base_type", &self.function.metadata.base_type);
debug.field("concrete_type", &self.function.metadata.concrete_type);
debug.field("signature", &self.function.metadata.signature);
}
#[cfg(not(debug_assertions))]
debug.field("function", &"<type erased>");
debug.finish()
}
}
#[cfg(debug_assertions)]
fn search_metadata(list: &[ExtractionMetadata], target: std::any::TypeId) -> bool {
for metadata in list {
match metadata {
ExtractionMetadata::Target { type_id, .. } => {
if *type_id == target {
return true;
}
}
ExtractionMetadata::Nested {
type_id, nested, ..
} => {
if *type_id == target || search_metadata(nested, target) {
return true;
}
}
}
}
false
}
#[cfg(debug_assertions)]
fn can_extract<Concrete: Extractable, Base: Extractable>() -> bool {
let base_type_id = std::any::TypeId::of::<Base>();
search_metadata(Concrete::METADATA_LIST, base_type_id)
}