runmat_runtime/
workspace.rs1use runmat_value::Value;
2
3pub mod session;
4
5#[cfg(test)]
6use once_cell::sync::Lazy;
7#[cfg(test)]
8use std::sync::Mutex;
9
10type AssignFn = fn(&str, Value) -> Result<(), String>;
13type ClearFn = fn() -> Result<(), String>;
14type RemoveFn = fn(&str) -> Result<(), String>;
15
16#[derive(Clone, Copy, Debug)]
17pub struct WorkspaceResolver {
18 pub lookup: fn(&str) -> Option<Value>,
19 pub snapshot: fn() -> Vec<(String, Value)>,
20 pub globals: fn() -> Vec<String>,
21 pub assign: Option<AssignFn>,
22 pub clear: Option<ClearFn>,
23 pub remove: Option<RemoveFn>,
24}
25
26mod resolver_storage {
27 use super::WorkspaceResolver;
28
29 pub(super) fn set(resolver: WorkspaceResolver) {
30 imp::set(resolver)
31 }
32
33 pub(super) fn with<R>(f: impl FnOnce(Option<&WorkspaceResolver>) -> R) -> R {
34 imp::with(f)
35 }
36
37 #[cfg(test)]
38 mod imp {
39 use super::WorkspaceResolver;
40 use std::cell::RefCell;
41
42 thread_local! {
47 static RESOLVER: RefCell<Option<WorkspaceResolver>> = const { RefCell::new(None) };
48 }
49
50 pub(super) fn set(resolver: WorkspaceResolver) {
51 RESOLVER.with(|slot| {
52 *slot.borrow_mut() = Some(resolver);
53 });
54 }
55
56 pub(super) fn with<R>(f: impl FnOnce(Option<&WorkspaceResolver>) -> R) -> R {
57 RESOLVER.with(|slot| {
58 let guard = slot.borrow();
59 f(guard.as_ref())
60 })
61 }
62 }
63
64 #[cfg(not(test))]
65 mod imp {
66 use super::WorkspaceResolver;
67 use once_cell::sync::Lazy;
68 use std::sync::RwLock;
69
70 static RESOLVER: Lazy<RwLock<Option<WorkspaceResolver>>> = Lazy::new(|| RwLock::new(None));
71
72 pub(super) fn set(resolver: WorkspaceResolver) {
73 let mut guard = RESOLVER
74 .write()
75 .unwrap_or_else(|poison| poison.into_inner());
76 *guard = Some(resolver);
77 }
78
79 pub(super) fn with<R>(f: impl FnOnce(Option<&WorkspaceResolver>) -> R) -> R {
80 let guard = RESOLVER.read().unwrap_or_else(|poison| poison.into_inner());
81 f(guard.as_ref())
82 }
83 }
84}
85
86#[cfg(test)]
87static TEST_WORKSPACE_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
88
89pub fn register_workspace_resolver(resolver: WorkspaceResolver) {
92 if let Some(context) = crate::context::legacy::active() {
93 context.state().workspace.replace(Some(resolver));
94 }
95 resolver_storage::set(resolver);
99}
100
101pub fn lookup(name: &str) -> Option<Value> {
103 if let Some(context) = crate::context::legacy::active() {
104 if let Some(service) = context.service_ports().workspace() {
105 return service.lookup(name);
106 }
107 if let Some(resolver) = context.state().workspace.borrow().as_ref() {
108 return (resolver.lookup)(name);
109 }
110 }
111 resolver_storage::with(|resolver| resolver.and_then(|r| (r.lookup)(name)))
112}
113
114pub fn snapshot() -> Option<Vec<(String, Value)>> {
117 if let Some(context) = crate::context::legacy::active() {
118 if let Some(service) = context.service_ports().workspace() {
119 return Some(service.snapshot());
120 }
121 if let Some(resolver) = context.state().workspace.borrow().as_ref() {
122 return Some((resolver.snapshot)());
123 }
124 }
125 resolver_storage::with(|resolver| resolver.map(|r| (r.snapshot)()))
126}
127
128pub fn global_names() -> Vec<String> {
130 if let Some(context) = crate::context::legacy::active() {
131 if let Some(service) = context.service_ports().workspace() {
132 return service.global_names();
133 }
134 if let Some(resolver) = context.state().workspace.borrow().as_ref() {
135 return (resolver.globals)();
136 }
137 }
138 resolver_storage::with(|resolver| resolver.map(|r| (r.globals)()).unwrap_or_default())
139}
140
141pub fn assign(name: &str, value: Value) -> Result<(), String> {
142 if let Some(context) = crate::context::legacy::active() {
143 if let Some(service) = context.service_ports().workspace() {
144 return service
145 .assign(name, value)
146 .map_err(|error| error.to_string());
147 }
148 if context.state().workspace.borrow().is_some() {
149 return with_context_resolver(&context, |resolver| {
150 let assign = resolver
151 .assign
152 .ok_or_else(|| "workspace assignment unavailable".to_string())?;
153 assign(name, value)
154 });
155 }
156 }
157 resolver_storage::with(|resolver| {
158 let resolver = resolver.ok_or_else(|| "workspace state unavailable".to_string())?;
159 let assign = resolver
160 .assign
161 .ok_or_else(|| "workspace assignment unavailable".to_string())?;
162 (assign)(name, value)
163 })
164}
165
166pub fn clear() -> Result<(), String> {
167 if let Some(context) = crate::context::legacy::active() {
168 if let Some(service) = context.service_ports().workspace() {
169 return service.clear().map_err(|error| error.to_string());
170 }
171 if context.state().workspace.borrow().is_some() {
172 return with_context_resolver(&context, |resolver| {
173 let clear = resolver
174 .clear
175 .ok_or_else(|| "workspace clearing unavailable".to_string())?;
176 clear()
177 });
178 }
179 }
180 resolver_storage::with(|resolver| {
181 let resolver = resolver.ok_or_else(|| "workspace state unavailable".to_string())?;
182 let clear = resolver
183 .clear
184 .ok_or_else(|| "workspace clearing unavailable".to_string())?;
185 (clear)()
186 })
187}
188
189pub fn remove(name: &str) -> Result<(), String> {
190 if let Some(context) = crate::context::legacy::active() {
191 if let Some(service) = context.service_ports().workspace() {
192 return service.remove(name).map_err(|error| error.to_string());
193 }
194 if context.state().workspace.borrow().is_some() {
195 return with_context_resolver(&context, |resolver| {
196 let remove = resolver
197 .remove
198 .ok_or_else(|| "workspace removal unavailable".to_string())?;
199 remove(name)
200 });
201 }
202 }
203 resolver_storage::with(|resolver| {
204 let resolver = resolver.ok_or_else(|| "workspace state unavailable".to_string())?;
205 let remove = resolver
206 .remove
207 .ok_or_else(|| "workspace removal unavailable".to_string())?;
208 (remove)(name)
209 })
210}
211
212pub fn is_available() -> bool {
214 if let Some(context) = crate::context::legacy::active() {
215 if context.service_ports().workspace().is_some()
216 || context.state().workspace.borrow().is_some()
217 {
218 return true;
219 }
220 }
221 resolver_storage::with(|resolver| resolver.is_some())
222}
223
224fn with_context_resolver<R>(
225 context: &crate::context::RuntimeContext,
226 callback: impl FnOnce(&WorkspaceResolver) -> Result<R, String>,
227) -> Result<R, String> {
228 let resolver = context.state().workspace.borrow();
229 callback(
230 resolver
231 .as_ref()
232 .ok_or_else(|| "workspace state unavailable".to_string())?,
233 )
234}
235
236#[cfg(test)]
237pub(crate) fn test_guard() -> std::sync::MutexGuard<'static, ()> {
238 TEST_WORKSPACE_LOCK
239 .lock()
240 .unwrap_or_else(|poison| poison.into_inner())
241}