From 3727cdb92cd15f93556c988cf3d14691ec3d264c Mon Sep 17 00:00:00 2001 From: Matt Kleinschafer Date: Wed, 17 Jul 2024 15:20:45 +1000 Subject: [PATCH] A bit of clean up --- crates/common/src/abstractions/objects.rs | 172 ++++++++++++++++------ crates/common/src/abstractions/variant.rs | 3 +- crates/common/src/io/formats/binary.rs | 3 + crates/common/src/io/formats/json.rs | 3 + crates/common/src/lib.rs | 2 +- crates/macros/src/lib.rs | 8 +- crates/macros/src/objects.rs | 6 +- crates/scripting/src/lang/javascript.rs | 3 + 8 files changed, 148 insertions(+), 52 deletions(-) diff --git a/crates/common/src/abstractions/objects.rs b/crates/common/src/abstractions/objects.rs index ccb9444b..b21e9eb0 100644 --- a/crates/common/src/abstractions/objects.rs +++ b/crates/common/src/abstractions/objects.rs @@ -1,15 +1,94 @@ use std::{ - fmt::Debug, + any::Any, + fmt::{Debug, Formatter}, + hash::{Hash, Hasher}, ops::{Deref, DerefMut}, sync::Mutex, }; +use macros::Trace; + use crate::{impl_arena_index, Arena, Singleton}; impl_arena_index!(ObjectIndex, "An index of an object in the garbage collector"); -/// A reference to a garbage-collected object. -pub struct GC { +/// Represents a garbage-collected object with a potentially reified type. +pub struct Object { + entry: GC, + _phantom: std::marker::PhantomData, +} + +/// The internal entry for an object in the garbage collector. +/// +/// This is used to track the object's reference count, and to allow for +/// garbage collection of the object when it is no longer referenced. +#[derive(Trace)] +struct ObjectEntry { + is_marked: bool, + reference: *const [u8], +} + +impl Object { + /// Creates a new object with the given value. + pub fn new(_value: T) -> Self { + todo!() + } + + #[inline(always)] + pub fn cast(self) -> Object { + Object { + entry: self.entry, + _phantom: std::marker::PhantomData, + } + } +} + +impl Clone for Object { + fn clone(&self) -> Self { + Self { + entry: self.entry.clone(), + _phantom: std::marker::PhantomData, + } + } +} + +impl PartialEq for Object { + fn eq(&self, other: &Self) -> bool { + self.entry.as_ptr() == other.entry.as_ptr() + } +} + +impl Hash for Object { + fn hash(&self, state: &mut H) { + self.entry.as_ptr().hash(state); + } +} + +impl Deref for Object { + type Target = T; + + fn deref(&self) -> &Self::Target { + todo!() + } +} + +impl DerefMut for Object { + fn deref_mut(&mut self) -> &mut Self::Target { + todo!() + } +} + +impl Debug for Object { + /// Formats the object as a pointer to the object. + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + write!(formatter, "Object({:p})", self.entry.as_ptr()) + } +} + +/// A smart pointer to a garbage-collected object. +/// +/// The object is automatically deallocated when the last reference is dropped. +pub struct GC { index: ObjectIndex, _phantom: std::marker::PhantomData, } @@ -22,7 +101,9 @@ impl GC { _phantom: std::marker::PhantomData, } } +} +impl GC { /// Returns a reference to the inner value. pub fn as_ref(&self) -> &T { GarbageCollector::instance().get(self.index).unwrap() @@ -55,13 +136,13 @@ impl Clone for GC { } } -impl Debug for GC { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl Debug for GC { + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { self.as_ref().fmt(formatter) } } -impl Deref for GC { +impl Deref for GC { type Target = T; fn deref(&self) -> &Self::Target { @@ -69,44 +150,40 @@ impl Deref for GC { } } -impl DerefMut for GC { +impl DerefMut for GC { fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut() } } -impl Drop for GC { +impl Drop for GC { fn drop(&mut self) { GarbageCollector::instance().decrement_reference(self.index); } } -/// A garbage-collected object. -/// -/// This trait is used to mark objects that are managed by a garbage collector. -/// -/// The garbage collector will automatically free memory when the object is no -/// longer reachable. -/// -/// In order to reference an object outside the garbage collector, use a GC -/// smart pointer. -pub trait Object {} - /// A trait for objects that can be traced by the garbage collector. +/// +/// This trait is used to mark objects as reachable by the garbage collector. +/// When an object is traced, the garbage collector will mark the object as +/// reachable, and will recursively trace any other objects that the object +/// references. pub unsafe trait Trace { /// Traces the object, marking all reachable objects. - fn trace(&self, gc: &mut GarbageCollector); + fn trace(&self, context: &mut TraceContext); } -/// Blanket implementation of the [`Object`] trait all traceable types. -impl Object for T {} +/// Context for tracing objects using [`Trace`]. +pub struct TraceContext { + // TODO: implement me +} /// Implements the [`Trace`] trait for a type that does not contain any cycles. macro_rules! impl_empty_trace { ($type:ty) => { unsafe impl Trace for $type { #[inline(always)] - fn trace(&self, _gc: &mut GarbageCollector) { + fn trace(&self, _: &mut TraceContext) { // no-op } } @@ -120,19 +197,23 @@ impl_empty_trace!(u8); impl_empty_trace!(u16); impl_empty_trace!(u32); impl_empty_trace!(u64); +impl_empty_trace!(usize); impl_empty_trace!(i8); impl_empty_trace!(i16); impl_empty_trace!(i32); impl_empty_trace!(i64); +impl_empty_trace!(isize); impl_empty_trace!(f32); impl_empty_trace!(f64); +impl_empty_trace!(*const [u8]); +impl_empty_trace!(*const dyn Any); /// A simple mark-sweep garbage collector. /// /// This garbage collector uses a simple mark-sweep algorithm to free memory. /// It is not optimized for performance, but is simple to implement. #[derive(Singleton, Default)] -pub struct GarbageCollector { +struct GarbageCollector { entries: Mutex>, } @@ -148,7 +229,7 @@ unsafe impl Sync for GarbageCollector {} impl GarbageCollector { /// Allocates a new object in the garbage collector. - fn allocate(&self, value: T) -> ObjectIndex { + pub fn allocate(&self, value: T) -> ObjectIndex { let mut entries = self.entries.lock().unwrap(); let allocation = Box::leak(Box::new(value)); @@ -159,7 +240,7 @@ impl GarbageCollector { } /// Increments the reference count of an object. - fn increment_reference(&self, index: ObjectIndex) { + pub fn increment_reference(&self, index: ObjectIndex) { let mut entries = self.entries.lock().unwrap(); let entry = entries.get_mut(index).unwrap(); @@ -170,7 +251,7 @@ impl GarbageCollector { /// /// If the reference count reaches zero, the object is removed from the /// garbage collector. - fn decrement_reference(&self, index: ObjectIndex) { + pub fn decrement_reference(&self, index: ObjectIndex) { let mut entries = self.entries.lock().unwrap(); let entry = entries.get_mut(index).unwrap(); @@ -182,7 +263,7 @@ impl GarbageCollector { } /// Dereferences an object index to a reference. - fn get(&self, index: ObjectIndex) -> Option<&T> { + pub fn get(&self, index: ObjectIndex) -> Option<&T> { let entries = self.entries.lock().unwrap(); let entry = entries.get(index)?; @@ -190,7 +271,7 @@ impl GarbageCollector { } /// Dereferences an object index to a mutable reference. - fn get_mut(&self, index: ObjectIndex) -> Option<&mut T> { + pub fn get_mut(&self, index: ObjectIndex) -> Option<&mut T> { let entries = self.entries.lock().unwrap(); let entry = entries.get(index)?; @@ -200,20 +281,24 @@ impl GarbageCollector { #[cfg(test)] mod tests { - use macros::Object; + use macros::Trace; use super::*; - #[derive(Object, Debug)] - pub struct TestObject { - value: u32, + #[derive(Trace, Debug)] + pub struct TestStruct { + value_1: u32, + value_2: f32, } #[test] - fn test_basic_object_allocation_and_free() { - let instance = GC::new(TestObject { value: 100u32 }); + fn test_allocation_and_free() { + let instance = GC::new(TestStruct { + value_1: 100, + value_2: std::f32::consts::PI, + }); - assert_eq!(instance.value, 100u32); + assert_eq!(instance.value_1, 100u32); drop(instance); @@ -223,15 +308,16 @@ mod tests { } #[test] - fn test_object_allocation_and_clone() { - let instance1 = GC::new(TestObject { value: 100u32 }); + fn test_allocation_and_clone() { + let instance1 = GC::new(TestStruct { + value_1: 100, + value_2: std::f32::consts::PI, + }); let mut instance2 = instance1.clone(); - assert_eq!(instance1.value, 100u32); - - instance2.value = 200u32; - - assert_eq!(instance1.value, 200u32); + assert_eq!(instance1.value_1, 100u32); + instance2.value_1 = 200u32; + assert_eq!(instance1.value_1, 200u32); drop(instance1); diff --git a/crates/common/src/abstractions/variant.rs b/crates/common/src/abstractions/variant.rs index e94c985d..f96db9a0 100644 --- a/crates/common/src/abstractions/variant.rs +++ b/crates/common/src/abstractions/variant.rs @@ -1,4 +1,4 @@ -use crate::{Color, Color32, Quat, StringName, Vec2, Vec3, Vec4}; +use crate::{Color, Color32, Object, Quat, StringName, Vec2, Vec3, Vec4}; /// A type that can hold varying different values. /// @@ -28,6 +28,7 @@ pub enum Variant { Quat(Quat), Color(Color), Color32(Color32), + Object(Object), } /// Allows for a type to be converted to a [`Variant`]. diff --git a/crates/common/src/io/formats/binary.rs b/crates/common/src/io/formats/binary.rs index 81d1dac3..9d6d9eca 100644 --- a/crates/common/src/io/formats/binary.rs +++ b/crates/common/src/io/formats/binary.rs @@ -189,6 +189,9 @@ impl FileFormat for BinaryFileFormat { stream.write_u8(value.b)?; stream.write_u8(value.a)?; } + Variant::Object(_value) => { + todo!("Object serialization is not yet supported"); + } } } Chunk::Sequence(sequence) => { diff --git a/crates/common/src/io/formats/json.rs b/crates/common/src/io/formats/json.rs index 0ff68bf3..212b774e 100644 --- a/crates/common/src/io/formats/json.rs +++ b/crates/common/src/io/formats/json.rs @@ -77,6 +77,9 @@ impl FileFormat for JsonFileFormat { Variant::Color32(value) => { stream.write_string(&format!("[{}, {}, {}, {}]", value.r, value.g, value.b, value.a))?; } + Variant::Object(_value) => { + todo!("Object serialization is not yet supported"); + } }, Chunk::Sequence(sequence) => { stream.write_string("[")?; diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index f61acb8b..c3d5878c 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -31,4 +31,4 @@ mod strings; mod utilities; // Re-export macros for use in other crates. -pub use macros::{profiling, Deserialize, Object, Reflect, Serialize, Singleton}; +pub use macros::{profiling, Deserialize, Reflect, Serialize, Singleton, Trace}; diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 984b4c55..7dfed625 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -40,10 +40,10 @@ pub fn derive_singleton(input: TokenStream) -> TokenStream { singleton::impl_singleton(input) } -/// Derives the `Object` trait for a type. -#[proc_macro_derive(Object)] -pub fn derive_object(input: TokenStream) -> TokenStream { - objects::impl_object(input) +/// Derives the `Trace` trait for a type. +#[proc_macro_derive(Trace)] +pub fn derive_trace(input: TokenStream) -> TokenStream { + objects::impl_trace(input) } /// Derives the `ToShaderUniformSet` trait for a type. diff --git a/crates/macros/src/objects.rs b/crates/macros/src/objects.rs index e7ea85b5..a96450ef 100644 --- a/crates/macros/src/objects.rs +++ b/crates/macros/src/objects.rs @@ -2,7 +2,7 @@ use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput}; -pub fn impl_object(input: TokenStream) -> TokenStream { +pub fn impl_trace(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let ident = &input.ident; @@ -16,9 +16,9 @@ pub fn impl_object(input: TokenStream) -> TokenStream { let expanded = quote! { unsafe impl Trace for #ident { - fn trace(&self, _gc: &mut GarbageCollector) { + fn trace(&self, context: &mut TraceContext) { #( - self.#fields.trace(_gc); + self.#fields.trace(context); )* } } diff --git a/crates/scripting/src/lang/javascript.rs b/crates/scripting/src/lang/javascript.rs index bc12e90e..b1bc528f 100644 --- a/crates/scripting/src/lang/javascript.rs +++ b/crates/scripting/src/lang/javascript.rs @@ -113,6 +113,9 @@ impl From for JsValue { JsValue::Int(value.b as i32), JsValue::Int(value.a as i32), ]), + Variant::Object(_value) => { + todo!("Object conversion not implemented") + } } } }