Skip to main content

runmat_vm/runtime/
workspace.rs

1use runmat_builtins::Value;
2use runmat_thread_local::runmat_thread_local;
3use std::cell::RefCell;
4use std::collections::{HashMap, HashSet};
5
6struct WorkspaceState {
7    names: HashMap<String, usize>,
8    assigned: HashSet<String>,
9    idx_to_name: HashMap<usize, String>,
10    data_ptr: *const Value,
11    len: usize,
12}
13
14pub type WorkspaceSnapshot = (HashMap<String, usize>, HashSet<String>);
15
16runmat_thread_local! {
17    static WORKSPACE_STATE: RefCell<Option<WorkspaceState>> = const { RefCell::new(None) };
18    static PENDING_WORKSPACE: RefCell<Option<WorkspaceSnapshot>> = const { RefCell::new(None) };
19    static LAST_WORKSPACE_STATE: RefCell<Option<WorkspaceSnapshot>> = const { RefCell::new(None) };
20    static WORKSPACE_VARS: RefCell<Option<*mut Vec<Value>>> = const { RefCell::new(None) };
21}
22
23pub struct WorkspaceStateGuard;
24
25impl Drop for WorkspaceStateGuard {
26    fn drop(&mut self) {
27        WORKSPACE_STATE.with(|state| {
28            let mut state_mut = state.borrow_mut();
29            if let Some(ws) = state_mut.take() {
30                LAST_WORKSPACE_STATE.with(|slot| {
31                    *slot.borrow_mut() = Some((ws.names, ws.assigned));
32                });
33            }
34        });
35        WORKSPACE_VARS.with(|slot| {
36            slot.borrow_mut().take();
37        });
38    }
39}
40
41pub struct PendingWorkspaceGuard;
42
43impl Drop for PendingWorkspaceGuard {
44    fn drop(&mut self) {
45        PENDING_WORKSPACE.with(|slot| {
46            slot.borrow_mut().take();
47        });
48    }
49}
50
51pub fn push_pending_workspace(
52    names: HashMap<String, usize>,
53    assigned: HashSet<String>,
54) -> PendingWorkspaceGuard {
55    PENDING_WORKSPACE.with(|slot| {
56        *slot.borrow_mut() = Some((names, assigned));
57    });
58    PendingWorkspaceGuard
59}
60
61pub fn take_pending_workspace_state() -> Option<WorkspaceSnapshot> {
62    PENDING_WORKSPACE.with(|slot| slot.borrow_mut().take())
63}
64
65pub fn take_updated_workspace_state() -> Option<WorkspaceSnapshot> {
66    LAST_WORKSPACE_STATE.with(|slot| slot.borrow_mut().take())
67}
68
69pub fn set_workspace_state(
70    names: HashMap<String, usize>,
71    assigned: HashSet<String>,
72    vars: &mut Vec<Value>,
73) -> WorkspaceStateGuard {
74    let idx_to_name: HashMap<usize, String> = names.iter().map(|(k, &v)| (v, k.clone())).collect();
75    WORKSPACE_STATE.with(|state| {
76        *state.borrow_mut() = Some(WorkspaceState {
77            names,
78            assigned,
79            idx_to_name,
80            data_ptr: vars.as_ptr(),
81            len: vars.len(),
82        });
83    });
84    let vars_ptr = vars as *mut Vec<Value>;
85    WORKSPACE_VARS.with(|slot| {
86        *slot.borrow_mut() = Some(vars_ptr);
87    });
88    WorkspaceStateGuard
89}
90
91pub fn refresh_workspace_state(vars: &[Value]) {
92    WORKSPACE_STATE.with(|state| {
93        if let Some(ws) = state.borrow_mut().as_mut() {
94            ws.data_ptr = vars.as_ptr();
95            ws.len = vars.len();
96        }
97    });
98}
99
100pub fn workspace_lookup(name: &str) -> Option<Value> {
101    WORKSPACE_STATE.with(|state| {
102        let state_ref = state.borrow();
103        let ws = state_ref.as_ref()?;
104        let idx = ws.names.get(name)?;
105        if !ws.assigned.contains(name) {
106            return None;
107        }
108        if *idx >= ws.len {
109            return None;
110        }
111        unsafe {
112            let ptr = ws.data_ptr.add(*idx);
113            Some((*ptr).clone())
114        }
115    })
116}
117
118pub fn workspace_slot_assigned(index: usize) -> Option<bool> {
119    WORKSPACE_STATE.with(|state| {
120        let state_ref = state.borrow();
121        let ws = state_ref.as_ref()?;
122        let name = ws.idx_to_name.get(&index)?;
123        Some(ws.assigned.contains(name))
124    })
125}
126
127pub fn workspace_state_available() -> bool {
128    WORKSPACE_STATE.with(|state| state.borrow().is_some())
129}
130
131pub fn workspace_assign(name: &str, value: Value) -> Result<(), String> {
132    let vars_ptr = WORKSPACE_VARS.with(|slot| *slot.borrow());
133    let Some(vars_ptr) = vars_ptr else {
134        return Err("load: workspace state unavailable".to_string());
135    };
136    let vars = unsafe { &mut *vars_ptr };
137    set_workspace_variable(name, value, vars)
138}
139
140pub fn workspace_clear() -> Result<(), String> {
141    let vars_ptr = WORKSPACE_VARS.with(|slot| *slot.borrow());
142    let Some(vars_ptr) = vars_ptr else {
143        return Err("clear: workspace state unavailable".to_string());
144    };
145    let vars = unsafe { &mut *vars_ptr };
146
147    WORKSPACE_STATE.with(|state| {
148        let mut state_mut = state.borrow_mut();
149        let Some(ws) = state_mut.as_mut() else {
150            return Err("clear: workspace state unavailable".to_string());
151        };
152        vars.clear();
153        ws.names.clear();
154        ws.assigned.clear();
155        ws.idx_to_name.clear();
156        ws.data_ptr = vars.as_ptr();
157        ws.len = vars.len();
158        Ok(())
159    })
160}
161
162pub fn workspace_remove(name: &str) -> Result<(), String> {
163    let vars_ptr = WORKSPACE_VARS.with(|slot| *slot.borrow());
164    let Some(vars_ptr) = vars_ptr else {
165        return Err("clear: workspace state unavailable".to_string());
166    };
167    let vars = unsafe { &mut *vars_ptr };
168
169    WORKSPACE_STATE.with(|state| {
170        let mut state_mut = state.borrow_mut();
171        let Some(ws) = state_mut.as_mut() else {
172            return Err("clear: workspace state unavailable".to_string());
173        };
174        if let Some(idx) = ws.names.remove(name) {
175            if idx < vars.len() {
176                vars[idx] = Value::Num(0.0);
177            }
178            ws.assigned.remove(name);
179            ws.idx_to_name.remove(&idx);
180            ws.data_ptr = vars.as_ptr();
181            ws.len = vars.len();
182        }
183        Ok(())
184    })
185}
186
187pub fn workspace_snapshot() -> Vec<(String, Value)> {
188    WORKSPACE_STATE.with(|state| {
189        if let Some(ws) = state.borrow().as_ref() {
190            let mut entries: Vec<(String, Value)> = ws
191                .names
192                .iter()
193                .filter_map(|(name, idx)| {
194                    if *idx >= ws.len {
195                        return None;
196                    }
197                    if !ws.assigned.contains(name) {
198                        return None;
199                    }
200                    unsafe {
201                        let ptr = ws.data_ptr.add(*idx);
202                        Some((name.clone(), (*ptr).clone()))
203                    }
204                })
205                .collect();
206            entries.sort_by(|a, b| a.0.cmp(&b.0));
207            entries
208        } else {
209            Vec::new()
210        }
211    })
212}
213
214pub fn set_workspace_variable(
215    name: &str,
216    value: Value,
217    vars: &mut Vec<Value>,
218) -> Result<(), String> {
219    let mut result = Ok(());
220    WORKSPACE_STATE.with(|state| {
221        let mut state_mut = state.borrow_mut();
222        match state_mut.as_mut() {
223            Some(ws) => {
224                let idx = if let Some(idx) = ws.names.get(name).copied() {
225                    idx
226                } else {
227                    let idx = vars.len();
228                    ws.names.insert(name.to_string(), idx);
229                    ws.idx_to_name.insert(idx, name.to_string());
230                    idx
231                };
232                if idx >= vars.len() {
233                    vars.resize(idx + 1, Value::Num(0.0));
234                }
235                vars[idx] = value;
236                ws.data_ptr = vars.as_ptr();
237                ws.len = vars.len();
238                ws.assigned.insert(name.to_string());
239            }
240            None => {
241                result = Err("load: workspace state unavailable".to_string());
242            }
243        }
244    });
245    result
246}
247
248pub fn ensure_workspace_slot_name(index: usize, name: &str) {
249    WORKSPACE_STATE.with(|state| {
250        if let Some(ws) = state.borrow_mut().as_mut() {
251            ws.names.entry(name.to_string()).or_insert(index);
252            ws.idx_to_name
253                .entry(index)
254                .or_insert_with(|| name.to_string());
255        }
256    });
257}
258
259pub fn mark_workspace_assigned(index: usize) {
260    WORKSPACE_STATE.with(|state| {
261        if let Some(ws) = state.borrow_mut().as_mut() {
262            if let Some(name) = ws.idx_to_name.get(&index).cloned() {
263                ws.assigned.insert(name);
264            }
265        }
266    });
267}