Skip to content

Commit

Permalink
A bit of clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
mattkleiny committed Jul 17, 2024
1 parent 985cd8d commit 3727cdb
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 52 deletions.
172 changes: 129 additions & 43 deletions crates/common/src/abstractions/objects.rs
Original file line number Diff line number Diff line change
@@ -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<T: Trace + 'static> {
/// Represents a garbage-collected object with a potentially reified type.
pub struct Object<T: ?Sized = dyn Any> {
entry: GC<ObjectEntry>,
_phantom: std::marker::PhantomData<T>,
}

/// 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<T: 'static> Object<T> {
/// Creates a new object with the given value.
pub fn new(_value: T) -> Self {
todo!()
}

#[inline(always)]
pub fn cast<U: ?Sized>(self) -> Object<U> {
Object {
entry: self.entry,
_phantom: std::marker::PhantomData,
}
}
}

impl<T: ?Sized> Clone for Object<T> {
fn clone(&self) -> Self {
Self {
entry: self.entry.clone(),
_phantom: std::marker::PhantomData,
}
}
}

impl<T: ?Sized> PartialEq for Object<T> {
fn eq(&self, other: &Self) -> bool {
self.entry.as_ptr() == other.entry.as_ptr()
}
}

impl<T: ?Sized> Hash for Object<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.entry.as_ptr().hash(state);
}
}

impl<T: ?Sized> Deref for Object<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
todo!()
}
}

impl<T: ?Sized> DerefMut for Object<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
todo!()
}
}

impl<T: ?Sized> Debug for Object<T> {
/// 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<T: Trace> {
index: ObjectIndex,
_phantom: std::marker::PhantomData<T>,
}
Expand All @@ -22,7 +101,9 @@ impl<T: Trace + 'static> GC<T> {
_phantom: std::marker::PhantomData,
}
}
}

impl<T: Trace> GC<T> {
/// Returns a reference to the inner value.
pub fn as_ref(&self) -> &T {
GarbageCollector::instance().get(self.index).unwrap()
Expand Down Expand Up @@ -55,58 +136,54 @@ impl<T: Trace> Clone for GC<T> {
}
}

impl<T: Trace + Debug + 'static> Debug for GC<T> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl<T: Trace + Debug> Debug for GC<T> {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
self.as_ref().fmt(formatter)
}
}

impl<T: Trace + 'static> Deref for GC<T> {
impl<T: Trace> Deref for GC<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
self.as_ref()
}
}

impl<T: Trace + 'static> DerefMut for GC<T> {
impl<T: Trace> DerefMut for GC<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut()
}
}

impl<T: Trace + 'static> Drop for GC<T> {
impl<T: Trace> Drop for GC<T> {
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<T>
/// 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<T: Trace> 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
}
}
Expand All @@ -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<Arena<ObjectIndex, GarbageCollectorEntry>>,
}

Expand All @@ -148,7 +229,7 @@ unsafe impl Sync for GarbageCollector {}

impl GarbageCollector {
/// Allocates a new object in the garbage collector.
fn allocate<T: Trace + 'static>(&self, value: T) -> ObjectIndex {
pub fn allocate<T: Trace + 'static>(&self, value: T) -> ObjectIndex {
let mut entries = self.entries.lock().unwrap();
let allocation = Box::leak(Box::new(value));

Expand All @@ -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();

Expand All @@ -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();

Expand All @@ -182,15 +263,15 @@ impl GarbageCollector {
}

/// Dereferences an object index to a reference.
fn get<T: Trace + 'static>(&self, index: ObjectIndex) -> Option<&T> {
pub fn get<T: Trace>(&self, index: ObjectIndex) -> Option<&T> {
let entries = self.entries.lock().unwrap();
let entry = entries.get(index)?;

Some(unsafe { &*(entry.object as *const T) })
}

/// Dereferences an object index to a mutable reference.
fn get_mut<T: Trace + 'static>(&self, index: ObjectIndex) -> Option<&mut T> {
pub fn get_mut<T: Trace>(&self, index: ObjectIndex) -> Option<&mut T> {
let entries = self.entries.lock().unwrap();
let entry = entries.get(index)?;

Expand All @@ -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);

Expand All @@ -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);

Expand Down
3 changes: 2 additions & 1 deletion crates/common/src/abstractions/variant.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand Down Expand Up @@ -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`].
Expand Down
3 changes: 3 additions & 0 deletions crates/common/src/io/formats/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
3 changes: 3 additions & 0 deletions crates/common/src/io/formats/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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("[")?;
Expand Down
2 changes: 1 addition & 1 deletion crates/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
8 changes: 4 additions & 4 deletions crates/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions crates/macros/src/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
)*
}
}
Expand Down
3 changes: 3 additions & 0 deletions crates/scripting/src/lang/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ impl From<ScriptValue> for JsValue {
JsValue::Int(value.b as i32),
JsValue::Int(value.a as i32),
]),
Variant::Object(_value) => {
todo!("Object conversion not implemented")
}
}
}
}
Expand Down

0 comments on commit 3727cdb

Please sign in to comment.