Skip to content

Instantly share code, notes, and snippets.

@Skrylar
Created February 3, 2014 06:47
Show Gist options
  • Save Skrylar/8779719 to your computer and use it in GitHub Desktop.
Save Skrylar/8779719 to your computer and use it in GitHub Desktop.
Just some research in to doing Morphic-like GUIs in Rust.
#[crate_id = "mu1"];
#[crate_type = "bin"];
use std::cast::transmute;
use std::cell::RefCell;
use std::libc::size_t;
use std::rc::{Rc, Weak};
static ErrAlreadyParented: &'static str = "Morph already has a parent.";
static ErrNoSelfReference: &'static str = "Morph did not have a valid self reference.";
pub type MorphPtr = ~Morph:;
pub type MorphBox = Rc<~RefCell<MorphPtr>>;
pub type MorphWeakBox = Weak<~RefCell<MorphPtr>>;
pub type MorphVec = ~[MorphBox];
// Allows a raw pointer comparison; if two pointers to the same Morph
// are the same, we are talking about the same morph.
impl Eq for MorphPtr {
fn eq(&self, other: &MorphPtr) -> bool {
let a: size_t = unsafe { transmute( self ) };
let b: size_t = unsafe { transmute( other ) };
a == b
}
}
pub trait Morph {
fn parent(&self) -> Option<MorphWeakBox>;
fn add_child (&mut self , child: MorphBox);
fn remove_child (&mut self , child: MorphBox);
fn clear_children (&mut self);
fn child_count (&self) -> uint;
fn xxx_set_parent(&mut self, x: Option<MorphWeakBox>);
fn xxx_set_self_reference(&mut self, x: MorphWeakBox);
}
pub struct TestFrob {
priv parent : Option<MorphWeakBox>,
priv self_ref : Option<MorphWeakBox>,
priv children : MorphVec,
}
impl Morph for TestFrob {
fn parent(&self) -> Option<MorphWeakBox> {
self.parent.clone()
}
fn xxx_set_parent(&mut self, x: Option<MorphWeakBox>) {
self.parent = x;
}
fn add_child(&mut self , child: MorphBox) {
/* unbox and work with this child object */ {
let mut cr = child.borrow().borrow_mut();
let c = cr.get();
match c.parent() {
// parents are present, check if we need to paddle someone
Some(x) => {
match x.upgrade() {
// parent reference is good, panic
Some(_) => fail!(ErrAlreadyParented),
// creepy times
_ => {}
}
}
// no parent, we're good (NB: this doesn't sound creepy at all)
_ => {}
}
// dispense a self reference, if we have one
match self.self_ref.as_ref() {
Some(x) => c.xxx_set_parent(Some(x.clone())),
None => fail!(ErrNoSelfReference)
}
}
// now put the box in our child list
self.children.push(child);
}
fn remove_child(&mut self , child: MorphBox) {
match self.children.iter().position(|x| x == &child) {
None => {/* nothing to do, not one of our children */},
Some(pos) => {
/* remove from our list of children */
self.children.remove(pos);
/* clear the parent reference */ {
let mut br = child.borrow().borrow_mut();
let b = br.get();
b.xxx_set_parent(None);
}
}
}
}
fn child_count(&self) -> uint {
self.children.len()
}
fn clear_children(&mut self) {
'spring : loop {
match self.children.pop() {
Some(x) => {
let mut br = x.borrow().borrow_mut();
let br = br.get();
br.xxx_set_parent(None);
}
None => break 'spring, // Woohoo, spring break!
}
}
}
fn xxx_set_self_reference(&mut self, x: MorphWeakBox) {
self.self_ref = Some(x)
}
}
impl TestFrob {
pub fn new() -> MorphBox {
// Produce the frob, we'll finish it later.
let this = TestFrob {
children: ~[],
parent: None,
self_ref: None
};
// XXX unbounded boxes are not compatible with @, says dbaupp
let this_box = Rc::new(~RefCell::new(~this as MorphPtr));
/* produce the self reference */ {
let j = this_box.clone().downgrade();
let mut xr = this_box.borrow().borrow_mut();
let x = xr.get();
x.xxx_set_self_reference(j);
}
// return the finished item
return this_box
}
}
#[unsafe_destructor]
impl Drop for TestFrob {
fn drop(&mut self) {
println!("Frob dropped");
}
}
// NB: Test builds don't include main.
#[allow(dead_code)]
fn main() {
let a = TestFrob::new();
let b = TestFrob::new();
let master = TestFrob::new();
{
let mut xr = master.borrow().borrow_mut();
let x = xr.get();
x.add_child(a);
x.add_child(b);
}
}
#[test]
fn adding_children() {
let a = TestFrob::new();
let b = TestFrob::new();
let master = TestFrob::new();
{
let mut xr = master.borrow().borrow_mut();
let x = xr.get();
assert!(x.child_count() == 0);
x.add_child(a);
assert!(x.child_count() == 1);
x.add_child(b);
assert!(x.child_count() == 2);
}
}
#[test]
fn removing_children() {
let a = TestFrob::new();
let b = TestFrob::new();
let master = TestFrob::new();
{
let mut xr = master.borrow().borrow_mut();
let x = xr.get();
assert!(x.child_count() == 0);
x.add_child(a.clone());
assert!(x.child_count() == 1);
x.add_child(b.clone());
assert!(x.child_count() == 2);
x.remove_child(b.clone());
assert!(x.child_count() == 1);
// try removing something already removed
x.remove_child(b);
assert!(x.child_count() == 1);
x.remove_child(a);
assert!(x.child_count() == 0);
}
}
#[test]
fn clearing_children() {
let a = TestFrob::new();
let b = TestFrob::new();
let master = TestFrob::new();
{
let mut xr = master.borrow().borrow_mut();
let x = xr.get();
assert!(x.child_count() == 0);
x.add_child(a.clone());
assert!(x.child_count() == 1);
x.add_child(b.clone());
assert!(x.child_count() == 2);
x.clear_children();
assert!(x.child_count() == 0);
}
}
How do we prevent infinite loops in the hierarchy?
: Insert a "parent" field which is a weak reference to the parent; the
: weak reference will ensure the parent is not kept alive by the child,
: though it will keep the Rc unit around until the tree is harvested.
Creating new RCs derps with the reference counting
Keep the primary RC along with the parent, so it can mint new ones
We can't set the self-reference after minting the box
: Once the box is made, we now are dealing with the Morph
: trait and no longer have direct access to the widget's
: fields. This makes dealing with the object severely
: inconvenient since we cannot do widget-specific things after
: boxing up the morph.
Add an internal use method to the trait
: Solves the problem in the immediate term, but does add
: "magic" routines that users are instructed not to touch.
Can we make our own smart pointer to solve this problem?
Does having a weak reference to yourself prevent collection?
: No, Frobs are still destroyed when their strong references
: are lost. Since they hold their own weak references the Rc
: should be getting destroyed when the strong references
: perform the proper cleanup routines.
Will we have to engage in unsafe pointer jiggery?
: No, holding a weak reference to yourself appears to be safe.
Managing parent/child relationships
Parents
: We just use weak references to parents, which are handed out by
: the parent when they are parented.
Children
Adding
: We check if there is no existing parent, and throw a fit if
: they are already parented. Add the child object to our list
: of children.
Removal
: Conceptually we should be able to just go through the list,
: unbox everything, and do a pointer-based comparison.
Can't compare two &~Thing's
Rc implements Eq by checking *borrow on both Rcs.
Implement the child removal using strong Rcs?
: Doesn't help us all that much; we still need to
: implement our own comparator to do the pointer
: checking. This used to be covered by 'ptr_eq'
: but that was removed some time ago :/
Can we implement our own Eq to make this possible?
: This is what we have to do; implement equality
: between two unbounded Morph trait pointers as a
: pointer equality check. Now we can be sure that as
: long as silliness with Rcs does not occur, we are
: able to add and remove children by their ref boxes.
Clearing
: Just go through every item and drop the parent; it should be
: cheap and simple to unshift/pop until the vector is empty.
How can we test basic Morph behaviors?
Hierarchy testing
Check for failures
: Make sure failure conditions are triggered properly.
TODO how do we expect fail! in rust test?
Do we really need failure conditions?
: We might not need to run fail! simply because it causes the
: GUI to break. We could consider returning a boolean instead,
: which will allow for a bit more gracefulness.
Child count
: Lets us see if adding/removing widgets to the tree had any
: effect.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment