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_assign(name: &str, value: Value) -> Result<(), String> {
119    let vars_ptr = WORKSPACE_VARS.with(|slot| *slot.borrow());
120    let Some(vars_ptr) = vars_ptr else {
121        return Err("load: workspace state unavailable".to_string());
122    };
123    let vars = unsafe { &mut *vars_ptr };
124    set_workspace_variable(name, value, vars)
125}
126
127pub fn workspace_clear() -> Result<(), String> {
128    let vars_ptr = WORKSPACE_VARS.with(|slot| *slot.borrow());
129    let Some(vars_ptr) = vars_ptr else {
130        return Err("clear: workspace state unavailable".to_string());
131    };
132    let vars = unsafe { &mut *vars_ptr };
133
134    WORKSPACE_STATE.with(|state| {
135        let mut state_mut = state.borrow_mut();
136        let Some(ws) = state_mut.as_mut() else {
137            return Err("clear: workspace state unavailable".to_string());
138        };
139        vars.clear();
140        ws.names.clear();
141        ws.assigned.clear();
142        ws.idx_to_name.clear();
143        ws.data_ptr = vars.as_ptr();
144        ws.len = vars.len();
145        Ok(())
146    })
147}
148
149pub fn workspace_remove(name: &str) -> Result<(), String> {
150    let vars_ptr = WORKSPACE_VARS.with(|slot| *slot.borrow());
151    let Some(vars_ptr) = vars_ptr else {
152        return Err("clear: workspace state unavailable".to_string());
153    };
154    let vars = unsafe { &mut *vars_ptr };
155
156    WORKSPACE_STATE.with(|state| {
157        let mut state_mut = state.borrow_mut();
158        let Some(ws) = state_mut.as_mut() else {
159            return Err("clear: workspace state unavailable".to_string());
160        };
161        if let Some(idx) = ws.names.remove(name) {
162            if idx < vars.len() {
163                vars[idx] = Value::Num(0.0);
164            }
165            ws.assigned.remove(name);
166            ws.idx_to_name.remove(&idx);
167            ws.data_ptr = vars.as_ptr();
168            ws.len = vars.len();
169        }
170        Ok(())
171    })
172}
173
174pub fn workspace_snapshot() -> Vec<(String, Value)> {
175    WORKSPACE_STATE.with(|state| {
176        if let Some(ws) = state.borrow().as_ref() {
177            let mut entries: Vec<(String, Value)> = ws
178                .names
179                .iter()
180                .filter_map(|(name, idx)| {
181                    if *idx >= ws.len {
182                        return None;
183                    }
184                    if !ws.assigned.contains(name) {
185                        return None;
186                    }
187                    unsafe {
188                        let ptr = ws.data_ptr.add(*idx);
189                        Some((name.clone(), (*ptr).clone()))
190                    }
191                })
192                .collect();
193            entries.sort_by(|a, b| a.0.cmp(&b.0));
194            entries
195        } else {
196            Vec::new()
197        }
198    })
199}
200
201pub fn set_workspace_variable(
202    name: &str,
203    value: Value,
204    vars: &mut Vec<Value>,
205) -> Result<(), String> {
206    let mut result = Ok(());
207    WORKSPACE_STATE.with(|state| {
208        let mut state_mut = state.borrow_mut();
209        match state_mut.as_mut() {
210            Some(ws) => {
211                let idx = if let Some(idx) = ws.names.get(name).copied() {
212                    idx
213                } else {
214                    let idx = vars.len();
215                    ws.names.insert(name.to_string(), idx);
216                    ws.idx_to_name.insert(idx, name.to_string());
217                    idx
218                };
219                if idx >= vars.len() {
220                    vars.resize(idx + 1, Value::Num(0.0));
221                }
222                vars[idx] = value;
223                ws.data_ptr = vars.as_ptr();
224                ws.len = vars.len();
225                ws.assigned.insert(name.to_string());
226            }
227            None => {
228                result = Err("load: workspace state unavailable".to_string());
229            }
230        }
231    });
232    result
233}
234
235pub fn ensure_workspace_slot_name(index: usize, name: &str) {
236    WORKSPACE_STATE.with(|state| {
237        if let Some(ws) = state.borrow_mut().as_mut() {
238            ws.names.entry(name.to_string()).or_insert(index);
239            ws.idx_to_name
240                .entry(index)
241                .or_insert_with(|| name.to_string());
242        }
243    });
244}
245
246pub fn mark_workspace_assigned(index: usize) {
247    WORKSPACE_STATE.with(|state| {
248        if let Some(ws) = state.borrow_mut().as_mut() {
249            if let Some(name) = ws.idx_to_name.get(&index).cloned() {
250                ws.assigned.insert(name);
251            }
252        }
253    });
254}