runmat_runtime/workspace/
session.rs1use runmat_thread_local::runmat_thread_local;
2use runmat_value::Value;
3use std::cell::RefCell;
4use std::collections::HashMap;
5
6#[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
52pub 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}