use std::{
any::TypeId,
fmt,
hash::{Hash, Hasher},
};
use gc_arena::{barrier::Write, Collect, Gc, Mutation, Root, Rootable};
#[derive(Collect)]
#[collect(no_drop)]
pub struct Any<'gc, M: 'gc = ()>(Gc<'gc, AnyInner<M>>);
#[derive(Collect)]
#[collect(no_drop)]
pub struct AnyInner<M> {
metadata: M,
type_id: TypeId,
}
#[derive(Collect)]
#[collect(no_drop)]
#[repr(C)]
struct Value<M, V> {
header: AnyInner<M>,
value: V,
}
impl<'gc, M> fmt::Debug for Any<'gc, M>
where
M: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Any")
.field("data", &Gc::as_ptr(self.0))
.field("metadata", self.metadata())
.field("type_id", &(self.type_id()))
.finish()
}
}
impl<'gc, M> PartialEq for Any<'gc, M> {
fn eq(&self, other: &Self) -> bool {
Gc::ptr_eq(self.0, other.0)
}
}
impl<'gc, M> Eq for Any<'gc, M> {}
impl<'gc, M> Hash for Any<'gc, M> {
fn hash<H: Hasher>(&self, state: &mut H) {
Gc::as_ptr(self.0).hash(state)
}
}
impl<'gc, M> Copy for Any<'gc, M> {}
impl<'gc, M> Clone for Any<'gc, M> {
fn clone(&self) -> Self {
*self
}
}
impl<'gc, M> Any<'gc, M> {
pub fn new<R>(mc: &Mutation<'gc>, data: Root<'gc, R>) -> Self
where
M: Collect + Default,
R: for<'a> Rootable<'a>,
{
Self::with_metadata::<R>(mc, M::default(), data)
}
pub fn with_metadata<R>(mc: &Mutation<'gc>, metadata: M, data: Root<'gc, R>) -> Self
where
M: Collect,
R: for<'a> Rootable<'a>,
{
let val = Gc::new(
mc,
Value::<M, Root<'gc, R>> {
header: AnyInner {
metadata,
type_id: TypeId::of::<R>(),
},
value: data,
},
);
Self(unsafe { Gc::cast::<AnyInner<M>>(val) })
}
pub fn from_inner(inner: Gc<'gc, AnyInner<M>>) -> Self {
Self(inner)
}
pub fn into_inner(self) -> Gc<'gc, AnyInner<M>> {
self.0
}
pub fn metadata(self) -> &'gc M {
&self.0.as_ref().metadata
}
pub fn write_metadata(self, mc: &Mutation<'gc>) -> &'gc Write<M> {
gc_arena::barrier::field!(Gc::write(mc, self.0), AnyInner, metadata)
}
pub fn type_id(self) -> TypeId {
self.0.type_id
}
pub fn is<R>(self) -> bool
where
R: for<'b> Rootable<'b>,
{
TypeId::of::<R>() == self.0.type_id
}
pub fn downcast<R>(self) -> Option<&'gc Root<'gc, R>>
where
R: for<'b> Rootable<'b>,
{
if TypeId::of::<R>() == self.0.type_id {
let ptr = unsafe { Gc::cast::<Value<M, Root<'gc, R>>>(self.0) };
Some(&ptr.as_ref().value)
} else {
None
}
}
pub fn downcast_write<R>(self, mc: &Mutation<'gc>) -> Option<&'gc Write<Root<'gc, R>>>
where
R: for<'b> Rootable<'b>,
{
let root = self.downcast::<R>()?;
Gc::write(mc, self.0);
Some(unsafe { Write::assume(root) })
}
}
#[cfg(test)]
mod tests {
use gc_arena::rootless_arena;
use super::*;
#[test]
fn test_any_value() {
rootless_arena(|mc| {
#[derive(Collect)]
#[collect(no_drop)]
struct A<'gc>(Gc<'gc, i32>);
#[derive(Collect)]
#[collect(no_drop)]
struct B<'gc>(Gc<'gc, i32>);
#[derive(Collect)]
#[collect(no_drop)]
struct C<'gc>(Gc<'gc, i32>);
let any1 = Any::with_metadata::<Rootable![A<'_>]>(mc, 1i32, A(Gc::new(mc, 5)));
let any2 = Any::with_metadata::<Rootable![B<'_>]>(mc, 2i32, B(Gc::new(mc, 6)));
let any3 = Any::with_metadata::<Rootable![C<'_>]>(mc, 3i32, C(Gc::new(mc, 7)));
assert!(any1.is::<Rootable![A<'_>]>());
assert!(!any1.is::<Rootable![B<'_>]>());
assert!(!any1.is::<Rootable![C<'_>]>());
assert_eq!(*any1.metadata(), 1);
assert_eq!(*any1.downcast::<Rootable![A<'_>]>().unwrap().0, 5);
assert_eq!(*any2.metadata(), 2);
assert_eq!(*any2.downcast::<Rootable![B<'_>]>().unwrap().0, 6);
assert_eq!(*any3.metadata(), 3);
assert_eq!(*any3.downcast::<Rootable![C<'_>]>().unwrap().0, 7);
assert!(any1.downcast::<Rootable![B<'_>]>().is_none());
assert!(any1.downcast::<Rootable![C<'_>]>().is_none());
assert!(any2.downcast::<Rootable![A<'_>]>().is_none());
assert!(any2.downcast::<Rootable![C<'_>]>().is_none());
assert!(any3.downcast::<Rootable![A<'_>]>().is_none());
assert!(any3.downcast::<Rootable![B<'_>]>().is_none());
})
}
}