RClass
By default, from the perspective of GameLisp scripts, each rdata is a black box. GameLisp code
can query whether a particular value is an rdata, receive rdata from function calls,
pass rdata as function arguments... and that's about it!
If you prefer your scripting APIs to be a little more object-oriented, you can register an 
RClass ("Rust class") for any Rust type. When an rdata has an associated RClass,
this will cause the rdata to behave like a GameLisp object.
It can have methods and properties; its type can be queried using the 
is? function; and it has full support for operator 
overloading.
To register an RClass, use the RClassBuilder struct. The builder's api is designed to
vaguely resemble a defclass form. We saw this API used earlier, 
for our Texture struct:
#![allow(unused_variables)] fn main() { RClassBuilder::<Texture>::new() .met("width", &Texture::width) .met("height", &Texture::height) .build(); }
You can register an RClass for any 'static Rust type, including types defined by external 
crates. The only restriction is that each RClass must have a unique name. If you have two 
types, one named audio::Clip and one named video::Clip, they can't both be named Clip.
#![allow(unused_variables)] fn main() { glsp::bind_rfn("Command", &Command::new::<String>)?; RClassBuilder::<Command>::new() .met("args", &|command: &mut Command, rest: Rest<String>| { command.args(rest); }) .met("status", &Command::status) .build(); }
(let command (Command "ls"))
(ensure (is? command 'Command))
(.args command "-l" "-a")
; spawn the process and wait for it to complete
(.status command)
Weak Roots
Generally speaking, it's fine to move any 'static type onto the GameLisp heap. However, 
there's one exception.
When a Root smart pointer is pointing to an object, it will entirely prevent that object from 
being deallocated. This means that if any Root pointers are moved onto the garbage-collected heap
(for example, by storing a Root in a struct which is passed to glsp::rdata), memory leaks 
will occur. The Val and RRoot types may contain Roots, so those types should also be kept 
off the heap.
If you need a pointer from one heap allocation to another, you should use Gc instead.
Gc is a weak reference; it doesn't prevent its pointee from being deallocated, and so it
won't cause any memory leaks.
Unfortunately, Gc does require a little bit of supervision. Because Gc pointers are
weakly-rooted, this means that the object they're pointing to could be deallocated at any
time! To prevent this from happening, you'll need to:
- 
Use
RClassBuilder::traceto specify how the garbage collector should visit each of theGcpointers owned by your Rust type. - 
Call
glsp::write_barrierwhen an instance of your Rust type has been mutated, so that the garbage collector can check whether any newGcpointers have been added to it. 
#![allow(unused_variables)] fn main() { //don't do this! struct Colliders { array: Root<Arr> } impl Colliders { fn get(&self) -> Root<Arr> { Root::clone(&self.array) } fn replace(&mut self, new_array: Root<Arr>) { self.array = new_array; } } RClassBuilder::<Colliders>::new() .met("get", &Colliders::get) .met("replace!", &Colliders::replace) .build(); }
#![allow(unused_variables)] fn main() { //instead, do this... struct Colliders { array: Gc<Arr> } impl Colliders { fn get(&self) -> Root<Arr> { self.array.upgrade().unwrap() } fn replace(&mut self, new_array: Root<Arr>) { self.array = new_array.downgrade(); } fn trace(&self, visitor: &mut GcVisitor) { visitor.visit(&self.array); } } RClassBuilder::<Colliders>::new() .met("get", &Colliders::get) .met( "replace!", &|colliders: RRoot<Colliders>, new_array: Root<Arr>| { colliders.borrow_mut().replace(new_array); glsp::write_barrier(&colliders.into_root()); } ) .trace(Colliders::trace) .build(); }
We also provide GcVal as a weakly-rooted alternative to Val, and RGc as a
weakly-rooted alternative to RRoot.
Make sure you don't get caught out when constructing an rfn! If your rfn is a Rust closure
which captures a Root, Val or RRoot, then you're effectively moving a Root
onto the GameLisp heap, which will cause the captured data to leak. (Of course, for an rfn 
which is bound to a global variable, a memory leak might be acceptable.)
#![allow(unused_variables)] fn main() { let primes: Root<Arr> = arr![2, 3, 5, 7, 11]; primes.freeze(); let closure = move || { Root::clone(&primes) }; glsp::bind_rfn("primes", Box::new(closure))?; }