Struct Arena

Source
pub struct Arena<R>
where R: for<'a> Rootable<'a>,
{ /* private fields */ }
Expand description

A generic, garbage collected arena.

Garbage collected arenas allow for isolated sets of garbage collected objects with zero-overhead garbage collected pointers. It provides incremental mark and sweep garbage collection which must be manually triggered outside the mutate method, and works best when units of work inside mutate can be kept relatively small. It is designed primarily to be a garbage collector for scripting language runtimes.

The arena API is able to provide extremely cheap Gc pointers because it is based around “generativity”. During construction and access, the root type is branded by a unique, invariant lifetime 'gc which ensures that Gc pointers must be contained inside the root object hierarchy and cannot escape the arena callbacks or be smuggled inside another arena. This way, the arena can be sure that during mutation, all Gc pointers come from the arena we expect them to come from, and that they’re all either reachable from root or have been allocated during the current mutate call. When not inside the mutate callback, the arena knows that all Gc pointers must be either reachable from root or they are unreachable and safe to collect. In this way, incremental garbage collection can be achieved (assuming “sufficiently small” calls to mutate) that is both extremely safe and zero overhead vs what you would write in C with raw pointers and manually ensuring that invariants are held.

Implementations§

Source§

impl<R> Arena<R>
where R: for<'a> Rootable<'a>,

Source

pub fn new<F>(f: F) -> Arena<R>
where F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> <R as Rootable<'gc>>::Root,

Create a new arena with the given garbage collector tuning parameters. You must provide a closure that accepts a &Mutation<'gc> and returns the appropriate root.

Examples found in repository?
examples/basic.rs (lines 85-96)
84fn get_running_project(xml: &str, system: Rc<StdSystem<C>>) -> EnvArena {
85    EnvArena::new(|mc| {
86        let parser = ast::Parser::default();
87        let ast = parser.parse(xml).unwrap();
88        assert_eq!(ast.roles.len(), 1); // this should be handled more elegantly in practice - for the sake of this example, we only allow one role
89
90        let (bytecode, init_info, locs, _) = ByteCode::compile(&ast.roles[0]).unwrap();
91
92        let mut proj = Project::from_init(mc, &init_info, Rc::new(bytecode), Settings::default(), system);
93        proj.input(mc, Input::Start); // this is equivalent to clicking the green flag button
94
95        Env { proj: Gc::new(mc, RefLock::new(proj)), locs }
96    })
97}
Source

pub fn try_new<F, E>(f: F) -> Result<Arena<R>, E>
where F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> Result<<R as Rootable<'gc>>::Root, E>,

Similar to new, but allows for constructor that can fail.

Source

pub fn mutate<F, T>(&self, f: F) -> T
where F: for<'gc> FnOnce(&'gc Mutation<'gc>, &'gc <R as Rootable<'gc>>::Root) -> T,

The primary means of interacting with a garbage collected arena. Accepts a callback which receives a &Mutation<'gc> and a reference to the root, and can return any non garbage collected value. The callback may “mutate” any part of the object graph during this call, but no garbage collection will take place during this method.

Examples found in repository?
examples/basic.rs (lines 139-152)
99fn main() {
100    // read in an xml file whose path is given as a command line argument
101    let args = std::env::args().collect::<Vec<_>>();
102    if args.len() != 2 {
103        panic!("usage: {} [xml file]", &args[0]);
104    }
105    let mut xml = String::new();
106    std::fs::File::open(&args[1]).expect("failed to open file").read_to_string(&mut xml).expect("failed to read file");
107
108    // create a new shared clock and start a thread that updates it at our desired interval
109    let clock = Arc::new(Clock::new(UtcOffset::UTC, Some(Precision::Medium)));
110    let clock_clone = clock.clone();
111    std::thread::spawn(move || loop {
112        std::thread::sleep(CLOCK_INTERVAL);
113        clock_clone.update();
114    });
115
116    // create a custom config for the system - in this simple example we just implement the say/think blocks to print to stdout
117    let config = Config::<C, StdSystem<C>> {
118        request: None,
119        command: Some(Rc::new(|_mc, key, command, _proc| match command {
120            Command::Print { style: _, value } => {
121                if let Some(value) = value {
122                    println!("{value:?}");
123                }
124                key.complete(Ok(())); // any request that you handle must be completed - otherwise the calling process will hang forever
125                CommandStatus::Handled
126            }
127            _ => CommandStatus::UseDefault { key, command }, // anything you don't handle should return the key and command to invoke the default behavior instead
128        })),
129    };
130
131    // initialize our system with all the info we've put together
132    let system = Rc::new(StdSystem::new_sync(CompactString::new(BASE_URL), None, config, clock.clone()));
133    let mut env = get_running_project(&xml, system);
134
135    // begin running the code - these are some helpers to make things more efficient in terms of memory and cpu resources
136    let mut idle_sleeper = IdleAction::new(YIELDS_BEFORE_SLEEP, Box::new(|| std::thread::sleep(IDLE_SLEEP_TIME)));
137    let mut next_collect = clock.read(Precision::Medium) + COLLECT_INTERVAL;
138    loop {
139        env.mutate(|mc, env| {
140            let mut proj = env.proj.borrow_mut(mc);
141            for _ in 0..1024 {
142                // step the virtual machine forward by one bytecode instruction
143                let res = proj.step(mc);
144                if let ProjectStep::Error { error, proc } = &res {
145                    // if we get an error, we can generate an error summary including a stack trace - here we just print out the result
146                    let trace = ErrorSummary::extract(error, proc, &env.locs);
147                    println!("error: {error:?}\ntrace: {trace:?}");
148                }
149                // this takes care of performing thread sleep if we get a bunch of no-ops from proj.step back to back
150                idle_sleeper.consume(&res);
151            }
152        });
153        // if it's time for us to do garbage collection, do it and reset the next collection time
154        if clock.read(Precision::Low) >= next_collect {
155            env.collect_all();
156            next_collect = clock.read(Precision::Medium) + COLLECT_INTERVAL;
157        }
158    }
159}
Source

pub fn mutate_root<F, T>(&mut self, f: F) -> T
where F: for<'gc> FnOnce(&'gc Mutation<'gc>, &'gc mut <R as Rootable<'gc>>::Root) -> T,

An alternative version of Arena::mutate which allows mutating the root set, at the cost of an extra write barrier.

Source

pub fn map_root<R2>( self, f: impl for<'gc> FnOnce(&'gc Mutation<'gc>, <R as Rootable<'gc>>::Root) -> <R2 as Rootable<'gc>>::Root, ) -> Arena<R2>
where R2: for<'a> Rootable<'a>,

Source

pub fn try_map_root<R2, E>( self, f: impl for<'gc> FnOnce(&'gc Mutation<'gc>, <R as Rootable<'gc>>::Root) -> Result<<R2 as Rootable<'gc>>::Root, E>, ) -> Result<Arena<R2>, E>
where R2: for<'a> Rootable<'a>,

Source

pub fn metrics(&self) -> &Metrics

Source

pub fn collection_phase(&self) -> CollectionPhase

Source

pub fn collect_debt(&mut self)

Run incremental garbage collection until the allocation debt is <= 0.0.

There is no minimum unit of work enforced here, so it may be faster to only call this method when the allocation debt is above some threshold.

This method will always return at least once when collection enters the Sleeping phase, i.e. it will never transition from the Collecting phase to the Marking phase without returning in-between.

Source

pub fn mark_debt(&mut self) -> Option<MarkedArena<'_, R>>

Run only the marking part of incremental garbage collection until allocation debt is <= 0.0.

This does not transition collection past the Marked phase. Does nothing if the collection phase is Marked or Collecting, otherwise acts like Arena::collect_debt.

Source

pub fn collect_all(&mut self)

Run the current garbage collection cycle to completion, stopping once garbage collection has restarted in the sleep phase. If the collector is currently in the sleep phase, this restarts the collection and performs a full collection before transitioning back to the sleep phase.

Examples found in repository?
examples/basic.rs (line 155)
99fn main() {
100    // read in an xml file whose path is given as a command line argument
101    let args = std::env::args().collect::<Vec<_>>();
102    if args.len() != 2 {
103        panic!("usage: {} [xml file]", &args[0]);
104    }
105    let mut xml = String::new();
106    std::fs::File::open(&args[1]).expect("failed to open file").read_to_string(&mut xml).expect("failed to read file");
107
108    // create a new shared clock and start a thread that updates it at our desired interval
109    let clock = Arc::new(Clock::new(UtcOffset::UTC, Some(Precision::Medium)));
110    let clock_clone = clock.clone();
111    std::thread::spawn(move || loop {
112        std::thread::sleep(CLOCK_INTERVAL);
113        clock_clone.update();
114    });
115
116    // create a custom config for the system - in this simple example we just implement the say/think blocks to print to stdout
117    let config = Config::<C, StdSystem<C>> {
118        request: None,
119        command: Some(Rc::new(|_mc, key, command, _proc| match command {
120            Command::Print { style: _, value } => {
121                if let Some(value) = value {
122                    println!("{value:?}");
123                }
124                key.complete(Ok(())); // any request that you handle must be completed - otherwise the calling process will hang forever
125                CommandStatus::Handled
126            }
127            _ => CommandStatus::UseDefault { key, command }, // anything you don't handle should return the key and command to invoke the default behavior instead
128        })),
129    };
130
131    // initialize our system with all the info we've put together
132    let system = Rc::new(StdSystem::new_sync(CompactString::new(BASE_URL), None, config, clock.clone()));
133    let mut env = get_running_project(&xml, system);
134
135    // begin running the code - these are some helpers to make things more efficient in terms of memory and cpu resources
136    let mut idle_sleeper = IdleAction::new(YIELDS_BEFORE_SLEEP, Box::new(|| std::thread::sleep(IDLE_SLEEP_TIME)));
137    let mut next_collect = clock.read(Precision::Medium) + COLLECT_INTERVAL;
138    loop {
139        env.mutate(|mc, env| {
140            let mut proj = env.proj.borrow_mut(mc);
141            for _ in 0..1024 {
142                // step the virtual machine forward by one bytecode instruction
143                let res = proj.step(mc);
144                if let ProjectStep::Error { error, proc } = &res {
145                    // if we get an error, we can generate an error summary including a stack trace - here we just print out the result
146                    let trace = ErrorSummary::extract(error, proc, &env.locs);
147                    println!("error: {error:?}\ntrace: {trace:?}");
148                }
149                // this takes care of performing thread sleep if we get a bunch of no-ops from proj.step back to back
150                idle_sleeper.consume(&res);
151            }
152        });
153        // if it's time for us to do garbage collection, do it and reset the next collection time
154        if clock.read(Precision::Low) >= next_collect {
155            env.collect_all();
156            next_collect = clock.read(Precision::Medium) + COLLECT_INTERVAL;
157        }
158    }
159}
Source

pub fn mark_all(&mut self) -> Option<MarkedArena<'_, R>>

Runs all of the remaining marking part of the current garbage collection cycle.

Similarly to Arena::mark_debt, this does not transition collection past the Marked phase, and does nothing if the collector is currently in the Marked phase or the Collecting phase.

Auto Trait Implementations§

§

impl<R> Freeze for Arena<R>
where <R as Rootable<'static>>::Root: Freeze,

§

impl<R> !RefUnwindSafe for Arena<R>

§

impl<R> !Send for Arena<R>

§

impl<R> !Sync for Arena<R>

§

impl<R> Unpin for Arena<R>
where <R as Rootable<'static>>::Root: Unpin,

§

impl<R> !UnwindSafe for Arena<R>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,