use smallvec::{smallvec, SmallVec};
use std::any::TypeId;
pub trait DynTrait: 'static {
fn id() -> TypeId {
TypeId::of::<Self>()
}
fn name() -> &'static str {
std::any::type_name::<Self>()
}
}
pub trait Implementor: Sized + 'static {
unsafe fn dyn_traits() -> &'static [VTablePtrWithMeta];
}
impl Implementor for () {
unsafe fn dyn_traits() -> &'static [VTablePtrWithMeta] {
&[]
}
}
pub type VTablePtr = *const ();
#[repr(C)]
pub struct VTable {
pub data: *const (),
pub ptr: VTablePtr,
}
#[derive(Debug, Clone, Copy)]
pub struct VTablePtrWithMeta {
pub ptr: VTablePtr,
pub dyn_trait_id: TypeId,
pub dyn_trait_name: &'static str,
}
unsafe impl Sync for VTablePtrWithMeta {}
unsafe impl Send for VTablePtrWithMeta {}
pub fn vtable_pointer<C: Implementor, T: DynTrait + ?Sized>() -> Option<VTablePtrWithMeta> {
let dyn_trait_id = T::id();
unsafe {
C::dyn_traits()
.iter()
.find(|e| e.dyn_trait_id == dyn_trait_id)
.cloned()
}
}
pub trait MultipleReflectedTraits {
unsafe fn vtable_pointers<C: Implementor>() -> SmallVec<[(TypeId, Option<VTablePtrWithMeta>); 4]>;
}
impl DynTrait for () {}
impl<T: DynTrait> MultipleReflectedTraits for T {
unsafe fn vtable_pointers<C: Implementor>() -> SmallVec<[(TypeId, Option<VTablePtrWithMeta>); 4]>
{
let dyn_trait_id = Self::id();
let dyn_trait_name = Self::name();
let c_vtable_ptr_with_meta = <C as Implementor>::dyn_traits().iter().find_map(|v| {
if dyn_trait_id == v.dyn_trait_id {
assert_eq!(dyn_trait_name, v.dyn_trait_name);
Some(*v)
} else {
None
}
});
smallvec![(dyn_trait_id, c_vtable_ptr_with_meta)]
}
}
macro_rules! multi_implements_impl_for_tuples {
($a:ident,$($x:ident),+) => {
impl<$a: MultipleReflectedTraits, $($x : MultipleReflectedTraits,)+> MultipleReflectedTraits for ($a, $($x,)+){
unsafe fn vtable_pointers<Comp: Implementor>() -> SmallVec<[(TypeId, Option<VTablePtrWithMeta>); 4]> {
let mut a = $a::vtable_pointers::<Comp>();
$(
let o = $x::vtable_pointers::<Comp>();
a.extend(o);
)+
a
}
}
};
}
multi_implements_impl_for_tuples!(A, B);
multi_implements_impl_for_tuples!(A, B, C);
multi_implements_impl_for_tuples!(A, B, C, D);
multi_implements_impl_for_tuples!(A, B, C, D, E);
multi_implements_impl_for_tuples!(A, B, C, D, E, F);
multi_implements_impl_for_tuples!(A, B, C, D, E, F, G);
multi_implements_impl_for_tuples!(A, B, C, D, E, F, G, H);
multi_implements_impl_for_tuples!(A, B, C, D, E, F, G, H, I);
multi_implements_impl_for_tuples!(A, B, C, D, E, F, G, H, I, J);
#[macro_export]
macro_rules! reflect {
($trait:ident) => {
impl DynTrait for dyn $trait {}
};
($component:ident : $($trait:ident),* ) => {
impl Implementor for $component {
unsafe fn dyn_traits() -> &'static [VTablePtrWithMeta]{
use std::sync::OnceLock;
static ONCE: OnceLock<Box<[VTablePtrWithMeta]>> = OnceLock::new();
ONCE.get_or_init(||{
#[allow(invalid_value)]
let uninit: $component =
unsafe { std::mem::MaybeUninit::<$component>::uninit().assume_init() };
let impls = vec![
$(
{
let trait_obj: &dyn $trait = &uninit as &dyn $trait;
let vtable = &trait_obj as *const _ as *const VTable;
VTablePtrWithMeta {
ptr: unsafe { (*vtable).ptr },
dyn_trait_id: std::any::TypeId::of::<dyn $trait>(),
dyn_trait_name: std::any::type_name::<dyn $trait>(),
}
}
),*
];
std::mem::forget(uninit);
impls.into()
})
}
}
};
}
#[cfg(test)]
mod tests {
use crate::{prelude::*, trait_reflection::vtable_pointer};
#[test]
fn test_macros() {
struct Circle;
struct Rect;
struct Point;
pub trait Render {}
reflect!(Render);
pub trait Log {}
reflect!(Log);
pub trait Update {}
reflect!(Update);
impl Render for Circle {}
impl Render for Rect {}
impl Log for Rect {}
impl Render for Point {}
impl Log for Point {}
impl Update for Point {}
reflect!(Circle: Render);
reflect!(Rect: Render, Log);
reflect!(Point: Render, Log, Update);
assert!(vtable_pointer::<Circle, dyn Render>().is_some());
assert!(vtable_pointer::<Circle, dyn Log>().is_none());
assert!(vtable_pointer::<Circle, dyn Update>().is_none());
assert!(vtable_pointer::<Rect, dyn Render>().is_some());
assert!(vtable_pointer::<Rect, dyn Log>().is_some());
assert!(vtable_pointer::<Rect, dyn Update>().is_none());
assert!(vtable_pointer::<Point, dyn Render>().is_some());
assert!(vtable_pointer::<Point, dyn Log>().is_some());
assert!(vtable_pointer::<Point, dyn Update>().is_some());
}
}