intuicio_core/context.rs
1//! The storage a function call runs on.
2//!
3//! See [`Context`].
4use intuicio_data::data_stack::{DataStack, DataStackMode, DataStackRegisterAccess};
5use std::{any::Any, collections::HashMap};
6
7/// Everything a running function can reach, other than the registry.
8///
9/// A context holds three things:
10///
11/// - a **stack**, which carries arguments and results between calls,
12/// - **registers**, the closest thing to local variables,
13/// - **custom data**, a name to value map for anything else a frontend needs
14/// to keep around.
15///
16/// Both stack and registers only ever move data, never copy it. A type that
17/// wants copies has to provide a function that pushes a duplicate itself.
18///
19/// Registers are scoped: every call saves the register count on entry and
20/// drops back down to it on exit, so a function only sees its own registers.
21/// That is what [`Context::store_registers`] and
22/// [`Context::restore_registers`] do, and [`crate::function::Function::invoke`]
23/// calls them for you.
24pub struct Context {
25 stack: DataStack,
26 registers: DataStack,
27 registers_barriers: Vec<usize>,
28 custom: HashMap<String, Box<dyn Any + Send + Sync>>,
29}
30
31impl Context {
32 /// Allocates a context with fixed stack and register capacities, in bytes.
33 ///
34 /// Both are rounded up to a power of two. Nothing grows later, so pick
35 /// sizes that fit the deepest call chain the scripts will make.
36 pub fn new(stack_capacity: usize, registers_capacity: usize) -> Self {
37 Self {
38 stack: DataStack::new(stack_capacity, DataStackMode::Values),
39 registers: DataStack::new(registers_capacity, DataStackMode::Registers),
40 registers_barriers: vec![],
41 custom: Default::default(),
42 }
43 }
44
45 /// Builds a fresh, empty context with the same capacities as this one.
46 ///
47 /// Used to give a worker thread a context of its own.
48 pub fn fork(&self) -> Self {
49 Self::new(self.stack.size(), self.registers.size())
50 }
51
52 /// Returns the stack size in bytes.
53 pub fn stack_capacity(&self) -> usize {
54 self.stack.size()
55 }
56
57 /// Returns the register storage size in bytes.
58 pub fn registers_capacity(&self) -> usize {
59 self.registers.size()
60 }
61
62 /// Returns the value stack, where arguments and results are passed.
63 pub fn stack(&mut self) -> &mut DataStack {
64 &mut self.stack
65 }
66
67 /// Returns the register storage.
68 ///
69 /// Indices used here are absolute. Prefer [`Context::access_register`],
70 /// which counts from the current call's barrier.
71 pub fn registers(&mut self) -> &mut DataStack {
72 &mut self.registers
73 }
74
75 /// Returns stack and registers at once, for moving values between them.
76 pub fn stack_and_registers(&mut self) -> (&mut DataStack, &mut DataStack) {
77 (&mut self.stack, &mut self.registers)
78 }
79
80 /// Marks the current register count, so the next
81 /// [`Context::restore_registers`] drops back down to it.
82 pub fn store_registers(&mut self) {
83 self.registers_barriers
84 .push(self.registers.registers_count());
85 }
86
87 /// Drops every register defined since the matching
88 /// [`Context::store_registers`].
89 pub fn restore_registers(&mut self) {
90 if let Some(count) = self.registers_barriers.pop() {
91 while self.registers.registers_count() > count {
92 self.registers.drop_register();
93 }
94 }
95 }
96
97 /// Returns the stored register counts, one per call currently on the stack.
98 pub fn registers_barriers(&self) -> &[usize] {
99 &self.registers_barriers
100 }
101
102 /// Turns a register index relative to the current call into an absolute one.
103 pub fn absolute_register_index(&self, index: usize) -> usize {
104 self.registers_barriers
105 .last()
106 .map(|count| index + count)
107 .unwrap_or(index)
108 }
109
110 /// Takes a handle to one of the current call's registers.
111 ///
112 /// Returns [`None`] when the index was never defined.
113 pub fn access_register(&'_ mut self, index: usize) -> Option<DataStackRegisterAccess<'_>> {
114 let index = self.absolute_register_index(index);
115 self.registers.access_register(index)
116 }
117
118 /// Reads a value stored under `name`, or returns [`None`] when it is absent
119 /// or of another type.
120 pub fn custom<T: Send + Sync + 'static>(&self, name: &str) -> Option<&T> {
121 self.custom.get(name)?.downcast_ref::<T>()
122 }
123
124 /// Mutable [`Context::custom`].
125 pub fn custom_mut<T: Send + Sync + 'static>(&mut self, name: &str) -> Option<&mut T> {
126 self.custom.get_mut(name)?.downcast_mut::<T>()
127 }
128
129 /// Stores a value under `name`, replacing anything already there.
130 ///
131 /// This is the escape hatch for state a frontend needs but the platform does
132 /// not model, for example a producer that builds a host per worker thread.
133 pub fn set_custom<T: Send + Sync + 'static>(&mut self, name: impl ToString, data: T) {
134 self.custom.insert(name.to_string(), Box::new(data));
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test_async() {
144 fn is_async<T: Send + Sync>() {}
145
146 is_async::<Context>();
147 }
148}