Skip to main content

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}