Skip to main content

runmat_runtime/workspace/
session.rs

1use runmat_thread_local::runmat_thread_local;
2use runmat_value::Value;
3use std::cell::RefCell;
4use std::collections::HashMap;
5
6/// Named session variables and legacy bytecode-keyed persistent storage.
7///
8/// Slot-to-local synchronization belongs to the executor. This state owns only
9/// values whose lifetime and identity cross an individual VM frame.
10#[derive(Debug, Default)]
11pub struct SessionVariableState {
12    globals: HashMap<String, Value>,
13    persistent_slots: HashMap<(String, usize), Value>,
14    persistent_names: HashMap<(String, String), Value>,
15}
16
17runmat_thread_local! {
18    static LEGACY_SESSION_VARIABLES: RefCell<SessionVariableState> = RefCell::new(SessionVariableState::default());
19}
20
21fn with_state<R>(operation: impl FnOnce(&SessionVariableState) -> R) -> R {
22    if let Some(context) = crate::context::legacy::active() {
23        return operation(&context.state().session_variables.borrow());
24    }
25    LEGACY_SESSION_VARIABLES.with(|state| operation(&state.borrow()))
26}
27
28fn with_state_mut<R>(operation: impl FnOnce(&mut SessionVariableState) -> R) -> R {
29    if let Some(context) = crate::context::legacy::active() {
30        return operation(&mut context.state().session_variables.borrow_mut());
31    }
32    LEGACY_SESSION_VARIABLES.with(|state| operation(&mut state.borrow_mut()))
33}
34
35pub fn global_names() -> Vec<String> {
36    with_state(|state| {
37        let mut names = state
38            .globals
39            .keys()
40            .filter(|name| !name.starts_with("var_"))
41            .cloned()
42            .collect::<Vec<_>>();
43        names.sort();
44        names
45    })
46}
47
48pub fn global_value(name: &str) -> Option<Value> {
49    with_state(|state| state.globals.get(name).cloned())
50}
51
52/// Store a global through its semantic name.
53///
54/// Executor-local slot numbers are deliberately not part of this API. Native,
55/// bytecode, and browser hosts can therefore share one session global without
56/// agreeing on a frame layout.
57pub fn store_global_named(name: &str, value: Value) {
58    with_state_mut(|state| {
59        state.globals.insert(name.to_string(), value);
60    });
61}
62
63pub fn roots() -> Vec<Value> {
64    with_state(|state| {
65        state
66            .globals
67            .values()
68            .chain(state.persistent_slots.values())
69            .chain(state.persistent_names.values())
70            .cloned()
71            .collect()
72    })
73}
74
75pub fn update_global_slot(index: usize, alias: Option<&str>, value: &Value) {
76    with_state_mut(|state| {
77        let slot_key = format!("var_{index}");
78        if state.globals.contains_key(&slot_key) {
79            state.globals.insert(slot_key, value.clone());
80        }
81        if let Some(alias) = alias {
82            state.globals.insert(alias.to_string(), value.clone());
83        }
84    });
85}
86
87pub fn global_slot_value(index: usize) -> Option<Value> {
88    global_value(&format!("var_{index}"))
89}
90
91pub fn bind_global_slot(index: usize, name: &str) {
92    with_state_mut(|state| {
93        if let Some(value) = state.globals.get(name).cloned() {
94            state.globals.insert(format!("var_{index}"), value);
95        }
96    });
97}
98
99pub fn persistent_slot_value(function: &str, index: usize) -> Option<Value> {
100    with_state(|state| {
101        state
102            .persistent_slots
103            .get(&(function.to_string(), index))
104            .cloned()
105    })
106}
107
108pub fn persistent_named_value(function: &str, name: &str) -> Option<Value> {
109    with_state(|state| {
110        state
111            .persistent_names
112            .get(&(function.to_string(), name.to_string()))
113            .cloned()
114    })
115}
116
117pub fn update_persistent_slot(function: &str, index: usize, value: &Value) {
118    with_state_mut(|state| {
119        let key = (function.to_string(), index);
120        if state.persistent_slots.contains_key(&key) {
121            state.persistent_slots.insert(key, value.clone());
122        }
123    });
124}
125
126pub fn store_persistent_slot(function: &str, index: usize, value: Value) {
127    with_state_mut(|state| {
128        state
129            .persistent_slots
130            .insert((function.to_string(), index), value);
131    });
132}
133
134pub fn store_persistent_named(function: &str, name: &str, value: Value) {
135    with_state_mut(|state| {
136        state
137            .persistent_names
138            .insert((function.to_string(), name.to_string()), value);
139    });
140}
141
142#[doc(hidden)]
143pub fn reset_legacy_state_for_tests() {
144    LEGACY_SESSION_VARIABLES.with(|state| *state.borrow_mut() = SessionVariableState::default());
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use crate::context::RuntimeContext;
151    use crate::execution::RuntimeExecutionService;
152    use futures::executor::block_on;
153    use std::rc::Rc;
154
155    #[test]
156    fn session_variable_state_is_context_isolated() {
157        let first = RuntimeContext::new(Rc::new(RuntimeExecutionService::new()));
158        let second = RuntimeContext::new(Rc::new(RuntimeExecutionService::new()));
159
160        block_on(first.scope(async {
161            update_global_slot(0, Some("answer"), &Value::Num(42.0));
162            assert_eq!(global_value("answer"), Some(Value::Num(42.0)));
163        }));
164        block_on(second.scope(async {
165            assert_eq!(global_value("answer"), None);
166        }));
167    }
168
169    #[test]
170    fn semantic_names_bridge_executor_specific_global_and_persistent_layouts() {
171        let context = RuntimeContext::new(Rc::new(RuntimeExecutionService::new()));
172        block_on(context.scope(async {
173            store_global_named("answer", Value::Num(42.0));
174            assert_eq!(global_value("answer"), Some(Value::Num(42.0)));
175
176            store_persistent_named("counter", "calls", Value::Num(3.0));
177            assert_eq!(
178                persistent_named_value("counter", "calls"),
179                Some(Value::Num(3.0))
180            );
181        }));
182    }
183}