Skip to main content

sim_lib_control/
ops.rs

1use std::sync::Arc;
2
3use sim_kernel::{
4    Args, Callable, ClassRef, Cx, Error, Expr, NumberLiteral, Object, ObjectCompat, RawArgs, Ref,
5    Result, Symbol, Value,
6    control::{
7        ControlAbort, ControlCapture, ControlPrompt, ControlResume, abort, capture,
8        default_control_result_shape, prompt, resume,
9    },
10};
11
12use crate::model::{ContinuationValue, ControlResultValue};
13
14/// A callable runtime object exposing one control primitive.
15///
16/// The core [`ControlFunction`] variants (`prompt`, `capture`, `abort`,
17/// `resume`) are installed by the control lib as `control/*` functions, turning
18/// the kernel control-policy operations into callables the runtime can invoke.
19#[derive(Clone)]
20pub struct ControlFunction {
21    kind: ControlFunctionKind,
22}
23
24#[derive(Clone, Copy)]
25enum ControlFunctionKind {
26    Prompt,
27    Capture,
28    Abort,
29    Resume,
30    PhysicalSensingTrace,
31}
32
33impl ControlFunction {
34    /// Builds the `control/prompt` function, which establishes a prompt.
35    pub fn prompt() -> Self {
36        Self {
37            kind: ControlFunctionKind::Prompt,
38        }
39    }
40
41    /// Builds the `control/capture` function, which captures a continuation.
42    pub fn capture() -> Self {
43        Self {
44            kind: ControlFunctionKind::Capture,
45        }
46    }
47
48    /// Builds the `control/abort` function, which aborts to a prompt.
49    pub fn abort() -> Self {
50        Self {
51            kind: ControlFunctionKind::Abort,
52        }
53    }
54
55    /// Builds the `control/resume` function, which resumes a continuation.
56    pub fn resume() -> Self {
57        Self {
58            kind: ControlFunctionKind::Resume,
59        }
60    }
61
62    /// Builds the deterministic physical-sensing descriptor fixture.
63    pub fn physical_sensing_trace() -> Self {
64        Self {
65            kind: ControlFunctionKind::PhysicalSensingTrace,
66        }
67    }
68
69    /// Returns the `control/*` symbol under which this function is exported.
70    pub fn symbol(&self) -> Symbol {
71        self.kind.symbol()
72    }
73}
74
75impl Object for ControlFunction {
76    fn display(&self, _cx: &mut Cx) -> Result<String> {
77        Ok(format!("#<function {}>", self.kind.symbol()))
78    }
79
80    fn as_any(&self) -> &dyn std::any::Any {
81        self
82    }
83}
84
85impl ObjectCompat for ControlFunction {
86    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
87        cx.resolve_class(&Symbol::qualified("core", "Function"))
88    }
89
90    fn as_callable(&self) -> Option<&dyn Callable> {
91        Some(self)
92    }
93}
94
95impl Callable for ControlFunction {
96    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
97        self.kind.call(cx, args.into_vec())
98    }
99
100    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
101        let values = args
102            .into_exprs()
103            .into_iter()
104            .map(|expr| cx.eval_expr(expr))
105            .collect::<Result<Vec<_>>>()?;
106        self.kind.call(cx, values)
107    }
108}
109
110impl ControlFunctionKind {
111    fn symbol(self) -> Symbol {
112        match self {
113            Self::Prompt => prompt_symbol(),
114            Self::Capture => capture_symbol(),
115            Self::Abort => abort_symbol(),
116            Self::Resume => resume_symbol(),
117            Self::PhysicalSensingTrace => physical_sensing_trace_symbol(),
118        }
119    }
120
121    fn call(self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
122        match self {
123            Self::Prompt => call_prompt(cx, args),
124            Self::Capture => call_capture(cx, args),
125            Self::Abort => call_abort(cx, args),
126            Self::Resume => call_resume(cx, args),
127            Self::PhysicalSensingTrace => call_physical_sensing_trace(cx, args),
128        }
129    }
130}
131
132/// Returns the `control/prompt` symbol.
133pub fn prompt_symbol() -> Symbol {
134    Symbol::qualified("control", "prompt")
135}
136
137/// Returns the `control/capture` symbol.
138pub fn capture_symbol() -> Symbol {
139    Symbol::qualified("control", "capture")
140}
141
142/// Returns the `control/abort` symbol.
143pub fn abort_symbol() -> Symbol {
144    Symbol::qualified("control", "abort")
145}
146
147/// Returns the `control/resume` symbol.
148pub fn resume_symbol() -> Symbol {
149    Symbol::qualified("control", "resume")
150}
151
152/// Returns the `control/physical-sensing-trace` fixture symbol.
153pub fn physical_sensing_trace_symbol() -> Symbol {
154    Symbol::qualified("control", "physical-sensing-trace")
155}
156
157fn call_prompt(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
158    let refs = refs_from_args(cx, args, "control/prompt")?;
159    let [prompt_ref, value_ref] = refs.as_slice() else {
160        return Err(arity_error("control/prompt", "prompt value"));
161    };
162    let prompt_ref = prompt_ref.clone();
163    let value_ref = value_ref.clone();
164    let result = prompt(
165        cx,
166        ControlPrompt::new(
167            prompt_ref,
168            value_ref.clone(),
169            default_control_result_shape(),
170        ),
171        |_cx| Ok(value_ref),
172    )?;
173    control_result_value(cx, result)
174}
175
176fn call_capture(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
177    let multishot = optional_bool_arg(cx, args.get(3))?;
178    let refs = refs_from_args(cx, args.into_iter().take(3).collect(), "control/capture")?;
179    let [prompt_ref, continuation_ref, value_ref] = refs.as_slice() else {
180        return Err(arity_error(
181            "control/capture",
182            "prompt continuation value [multishot]",
183        ));
184    };
185    let mut request = ControlCapture::new(
186        prompt_ref.clone(),
187        continuation_ref.clone(),
188        value_ref.clone(),
189        default_control_result_shape(),
190    );
191    if multishot {
192        request = request.multishot();
193    }
194    let capture_result = capture(cx, request)?;
195    cx.factory().opaque(Arc::new(ContinuationValue::new(
196        continuation_ref.clone(),
197        capture_result,
198        multishot,
199    )))
200}
201
202fn call_abort(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
203    let refs = refs_from_args(cx, args, "control/abort")?;
204    let [prompt_ref, value_ref] = refs.as_slice() else {
205        return Err(arity_error("control/abort", "prompt value"));
206    };
207    let prompt_ref = prompt_ref.clone();
208    let value_ref = value_ref.clone();
209    let result = abort(
210        cx,
211        ControlAbort::new(prompt_ref, value_ref, default_control_result_shape()),
212    )?;
213    control_result_value(cx, result)
214}
215
216fn call_resume(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
217    if args.len() != 2 {
218        return Err(arity_error("control/resume", "continuation value"));
219    }
220    let continuation = continuation_ref(cx, &args[0])?;
221    let value = value_ref(cx, &args[1], "control/resume value")?;
222    let result = resume(
223        cx,
224        ControlResume::new(continuation, value, default_control_result_shape()),
225    )?;
226    control_result_value(cx, result)
227}
228
229fn call_physical_sensing_trace(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
230    if !args.is_empty() {
231        return Err(arity_error(
232            "control/physical-sensing-trace",
233            "no arguments",
234        ));
235    }
236    cx.factory().expr(physical_sensing_trace_expr())
237}
238
239fn physical_sensing_trace_expr() -> Expr {
240    list(vec![
241        sym("physical-sensing-trace"),
242        list(vec![sym("id"), sym("a30-021-physical-sensing")]),
243        list(vec![
244            sym("fixture"),
245            list(vec![sym("source"), sym("synthetic-sensor-stream")]),
246            list(vec![sym("media"), sym("copied-no")]),
247            list(vec![sym("device"), sym("live-device-none")]),
248        ]),
249        list(vec![
250            sym("sensor-stream"),
251            list(vec![sym("runner"), sym("fake-sensor-stream")]),
252            list(vec![
253                sym("frame"),
254                sym("1"),
255                sym("position"),
256                sym("22"),
257                sym("velocity"),
258                sym("3"),
259            ]),
260            list(vec![
261                sym("frame"),
262                sym("2"),
263                sym("position"),
264                sym("24"),
265                sym("velocity"),
266                sym("2"),
267            ]),
268            list(vec![
269                sym("frame"),
270                sym("3"),
271                sym("position"),
272                sym("26"),
273                sym("velocity"),
274                sym("1"),
275            ]),
276        ]),
277        list(vec![
278            sym("temporal-average"),
279            list(vec![sym("window"), sym("3")]),
280            list(vec![sym("position"), sym("24")]),
281            list(vec![sym("velocity"), sym("2")]),
282        ]),
283        list(vec![
284            sym("controller"),
285            list(vec![sym("kind"), sym("proportional")]),
286            list(vec![sym("setpoint"), sym("30")]),
287            list(vec![sym("gain"), sym("2")]),
288            list(vec![sym("deadband"), sym("2")]),
289            list(vec![sym("hysteresis"), sym("enabled")]),
290        ]),
291        list(vec![
292            sym("control-output"),
293            list(vec![sym("error"), sym("6")]),
294            list(vec![sym("command"), sym("increase-12")]),
295            list(vec![sym("clamped"), sym("no")]),
296            list(vec![sym("next-state"), sym("approach-setpoint")]),
297        ]),
298        list(vec![sym("answer"), sym("increase-actuator-by-12")]),
299        list(vec![
300            sym("effect-ledger"),
301            list(vec![
302                sym("effect"),
303                sym("read-fake-sensor-stream"),
304                sym("deterministic"),
305            ]),
306            list(vec![
307                sym("effect"),
308                sym("average-window-three"),
309                sym("pass"),
310            ]),
311            list(vec![sym("effect"), sym("apply-deadband"), sym("active")]),
312            list(vec![
313                sym("effect"),
314                sym("emit-control-output"),
315                sym("increase-12"),
316            ]),
317        ]),
318    ])
319}
320
321fn list(items: Vec<Expr>) -> Expr {
322    Expr::List(items)
323}
324
325fn sym(name: &str) -> Expr {
326    if name.as_bytes().iter().all(u8::is_ascii_digit) {
327        return Expr::Number(NumberLiteral {
328            domain: Symbol::qualified("numbers", "i64"),
329            canonical: name.to_owned(),
330        });
331    }
332    Expr::Symbol(Symbol::new(name))
333}
334
335fn refs_from_args(cx: &mut Cx, args: Vec<Value>, context: &'static str) -> Result<Vec<Ref>> {
336    args.iter()
337        .map(|value| value_ref(cx, value, context))
338        .collect()
339}
340
341fn continuation_ref(cx: &mut Cx, value: &Value) -> Result<Ref> {
342    if let Some(continuation) = value.object().downcast_ref::<ContinuationValue>() {
343        return Ok(continuation.continuation().clone());
344    }
345    value_ref(cx, value, "control continuation")
346}
347
348fn value_ref(cx: &mut Cx, value: &Value, context: &'static str) -> Result<Ref> {
349    if let Some(result) = value.object().downcast_ref::<ControlResultValue>() {
350        return Ok(result.reference().clone());
351    }
352    let expr = value.object().as_expr(cx)?;
353    match expr {
354        Expr::Symbol(symbol) => Ok(Ref::Symbol(symbol)),
355        _ => Err(Error::TypeMismatch {
356            expected: context,
357            found: "non-ref value",
358        }),
359    }
360}
361
362fn optional_bool_arg(cx: &mut Cx, value: Option<&Value>) -> Result<bool> {
363    let Some(value) = value else {
364        return Ok(false);
365    };
366    match value.object().as_expr(cx)? {
367        Expr::Bool(value) => Ok(value),
368        _ => Err(Error::TypeMismatch {
369            expected: "bool",
370            found: "non-bool",
371        }),
372    }
373}
374
375fn control_result_value(cx: &mut Cx, reference: Ref) -> Result<Value> {
376    cx.factory()
377        .opaque(Arc::new(ControlResultValue::new(reference)))
378}
379
380fn arity_error(function: &'static str, expected: &'static str) -> Error {
381    Error::Eval(format!("{function} expects {expected}"))
382}