Freeze
Fixtures must own their data. The Vm holds them for its whole life, so everything inside must be 'static. This is directly against a very common pattern for a scripting layer: getting mutable access to the wider program in the middle of a frame. That’s what the Freeze trait is for.
A FreezeCell is an empty slot that holds a borrow for the duration of a single closure and takes it back the moment that closure ends. While the borrow is in the cell, natives can reach it. The instant your frame is over, it’s gone.
Let’s wire up an example similar to what we did on the Fixtures page, only this time, let’s say that our script needs to not just read information from the host but write it too.
// rust
use mimas::vm::freeze::{Freeze, FreezeCell};
struct Database(Vec<String>);
type DatabaseCell = FreezeCell<Freeze![&'freeze mut Database]>;
Freeze![&'freeze mut Database] reads as “this cell holds a &mut Database for some lifetime we’ll call 'freeze.” The cell itself is 'static and starts empty, which is exactly what makes it a legal fixture.
// rust
fn main() {
let mut database = Database(Vec::new());
let mut vm = mimas::compile_source(SOURCE).unwrap();
vm.run().unwrap();
let cell = vm.fixture::<DatabaseCell>();
cell.freeze(&mut database, || vm.call_fn("call_me").unwrap());
assert_eq!(database.0.first(), Some(&"hello!".to_string()));
}
Now any native can add to the database, and our script can use it like anything else:
// rust
#[mimas]
fn add_entry(ctx: Ctx, entry: String) {
ctx.fixture::<DatabaseCell>()
.with_mut(|database| {
database.0.push(entry);
})
.unwrap();
}
// mimas
fn call_me() {
add_entry("hello!");
}
A few things worth noticing:
- The fixture is the cell, not the
Database. TheDatabasenever enters the Vm and never needsDefault– the cell is built empty on first access, which is exactly the state it should be in between frames. with_mutreturns aResultbecause the cell might be empty: a native that runs outside any freeze scope gets an error instead of stale data.add_entryjustunwraps it, which is a plain panic of our own choosing; if some of your natives can legitimately run outside a frame, surface a runtime error instead.vm.fixturehands back a handle that is deliberately not borrow-tied to the Vm – that’s what lets us holdcellwhile callingvm.call_fn(a&mutborrow) inside the closure. The cost of that freedom is one rule: drop the handle before the Vm. Declaring it after thevm, like above, gets you that for free.- Need to lend more than one thing per frame? Cells nest:
a.freeze(x, || b.freeze(y, || ...)).
The freeze pattern comes from Catherine West: it originates in piccolo’s freeze module, and our implementation follows the slimmer form another of her projects, fabricator.
A complete, runnable example of this pattern lives at examples/game-loop.