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#[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 pub fn prompt() -> Self {
36 Self {
37 kind: ControlFunctionKind::Prompt,
38 }
39 }
40
41 pub fn capture() -> Self {
43 Self {
44 kind: ControlFunctionKind::Capture,
45 }
46 }
47
48 pub fn abort() -> Self {
50 Self {
51 kind: ControlFunctionKind::Abort,
52 }
53 }
54
55 pub fn resume() -> Self {
57 Self {
58 kind: ControlFunctionKind::Resume,
59 }
60 }
61
62 pub fn physical_sensing_trace() -> Self {
64 Self {
65 kind: ControlFunctionKind::PhysicalSensingTrace,
66 }
67 }
68
69 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
132pub fn prompt_symbol() -> Symbol {
134 Symbol::qualified("control", "prompt")
135}
136
137pub fn capture_symbol() -> Symbol {
139 Symbol::qualified("control", "capture")
140}
141
142pub fn abort_symbol() -> Symbol {
144 Symbol::qualified("control", "abort")
145}
146
147pub fn resume_symbol() -> Symbol {
149 Symbol::qualified("control", "resume")
150}
151
152pub 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}