use std::{any::TypeId, fmt};
use gc_arena::{barrier::Write, Collect, Gc, Mutation, Root, Rootable};
#[derive(Collect)]
#[collect(no_drop)]
pub struct AnyValue<'gc, M: 'gc>(Gc<'gc, Header<M>>);
impl<'gc, M> fmt::Debug for AnyValue<'gc, M>
where
M: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("AnyValue")
.field("metadata", self.metadata())
.field("type_id", &(self.type_id()))
.finish()
}
}
#[derive(Collect)]
#[collect(no_drop)]
struct Header<M> {
metadata: M,
type_id: TypeId,
}
#[derive(Collect)]
#[collect(no_drop)]
#[repr(C)]
struct Value<M, V> {
header: Header<M>,
data: V,
}
impl<'gc, M> Copy for AnyValue<'gc, M> {}
impl<'gc, M> Clone for AnyValue<'gc, M> {
fn clone(&self) -> Self {
*self
}
}
impl<'gc, M> AnyValue<'gc, M> {
pub fn new<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: Header {
metadata,
type_id: TypeId::of::<R>(),
},
data,
},
);
Self(unsafe { Gc::cast::<Header<M>>(val) })
}
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), Header, metadata)
}
pub fn as_ptr(&self) -> *const () {
Gc::as_ptr(self.0) as *const ()
}
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().data)
} 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 = AnyValue::new::<Rootable![A<'_>]>(mc, 1i32, A(Gc::new(mc, 5)));
let any2 = AnyValue::new::<Rootable![B<'_>]>(mc, 2i32, B(Gc::new(mc, 6)));
let any3 = AnyValue::new::<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());
})
}
}