1use std::collections::HashMap;
2use std::time::{Duration, Instant};
3
4use crate::engine::ecs::{ComponentId, IntentValue, RxWorld, SignalEmitter, World};
5use crate::engine::graphics::render_assets::RenderAssets;
6use crate::scripting::object::{HeapHandle, MaterializedCE, Value};
7use crate::scripting::world_evaluator::{
8 EvalRequest, EvalResponse, HostCallKind, HostValue, MeowMeowEvaluator, eval_mms_fn,
9 eval_module_source,
10};
11
12#[derive(Debug, Default)]
14pub struct EvalOutput {
15 pub intents: Vec<IntentValue>,
16 pub errors: Vec<String>,
17}
18
19#[derive(Debug, Clone)]
20pub struct LoadedMmsModule {
21 pub named_exports: HashMap<String, Value>,
22 pub sequence: Vec<MaterializedCE>,
23 pub heap: HeapHandle,
24 pub source_path: Option<String>,
25}
26
27impl LoadedMmsModule {
28 pub fn named_export(&self, name: &str) -> Option<&Value> {
29 self.named_exports.get(name)
30 }
31}
32
33pub struct MeowMeowRunner;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum ModuleFactoryEvalMode {
42 Template,
43 Live,
44}
45
46fn headers_to_value(headers: &[(String, String)]) -> Value {
47 Value::Map(
48 headers
49 .iter()
50 .map(|(name, value)| (name.clone(), Value::String(value.clone())))
51 .collect(),
52 )
53}
54
55pub(crate) fn event_arg_value(signal: &crate::engine::ecs::Signal) -> Value {
56 match signal.event.as_ref() {
57 Some(crate::engine::ecs::EventSignal::DataEvent { name, .. }) => {
58 Value::String(name.clone())
59 }
60 Some(crate::engine::ecs::EventSignal::XrButtonDown {
61 hand,
62 control,
63 value,
64 ..
65 })
66 | Some(crate::engine::ecs::EventSignal::XrButtonUp {
67 hand,
68 control,
69 value,
70 ..
71 })
72 | Some(crate::engine::ecs::EventSignal::XrButtonChanged {
73 hand,
74 control,
75 value,
76 ..
77 }) => Value::Map(HashMap::from([
78 ("hand".to_string(), Value::String(format!("{hand:?}"))),
79 ("control".to_string(), Value::String(format!("{control:?}"))),
80 ("value".to_string(), Value::Number(*value as f64)),
81 ])),
82 Some(crate::engine::ecs::EventSignal::XrAxisChanged {
83 hand,
84 control,
85 value,
86 ..
87 }) => Value::Map(HashMap::from([
88 ("hand".to_string(), Value::String(format!("{hand:?}"))),
89 ("control".to_string(), Value::String(format!("{control:?}"))),
90 (
91 "value".to_string(),
92 Value::Array(vec![
93 Value::Number(value[0] as f64),
94 Value::Number(value[1] as f64),
95 ]),
96 ),
97 ])),
98 Some(crate::engine::ecs::EventSignal::TextInputChanged { text, caret, .. }) => {
99 Value::Map(HashMap::from([
100 ("text".to_string(), Value::String(text.clone())),
101 ("caret".to_string(), Value::Number(*caret as f64)),
102 ]))
103 }
104 Some(crate::engine::ecs::EventSignal::HttpRequest {
105 request_id,
106 method,
107 path,
108 query,
109 url,
110 headers,
111 body_text,
112 remote_addr,
113 }) => Value::Map(HashMap::from([
114 ("request_id".to_string(), Value::Number(*request_id as f64)),
115 ("method".to_string(), Value::String(method.clone())),
116 ("path".to_string(), Value::String(path.clone())),
117 (
118 "query".to_string(),
119 query
120 .as_ref()
121 .map(|query| Value::String(query.clone()))
122 .unwrap_or(Value::Null),
123 ),
124 ("url".to_string(), Value::String(url.clone())),
125 ("target".to_string(), Value::String(url.clone())),
126 ("headers".to_string(), headers_to_value(headers)),
127 ("body_text".to_string(), Value::String(body_text.clone())),
128 (
129 "remote_addr".to_string(),
130 remote_addr
131 .as_ref()
132 .map(|addr| Value::String(addr.clone()))
133 .unwrap_or(Value::Null),
134 ),
135 ])),
136 Some(crate::engine::ecs::EventSignal::HttpResponse {
137 request_id,
138 status,
139 ok,
140 headers,
141 body_text,
142 url,
143 }) => Value::Map(HashMap::from([
144 ("request_id".to_string(), Value::Number(*request_id as f64)),
145 ("status".to_string(), Value::Number(*status as f64)),
146 ("ok".to_string(), Value::Bool(*ok)),
147 ("headers".to_string(), headers_to_value(headers)),
148 ("body_text".to_string(), Value::String(body_text.clone())),
149 ("url".to_string(), Value::String(url.clone())),
150 ])),
151 Some(crate::engine::ecs::EventSignal::HttpError {
152 request_id,
153 phase,
154 message,
155 url,
156 bind_addr,
157 }) => Value::Map(HashMap::from([
158 (
159 "request_id".to_string(),
160 request_id
161 .map(|request_id| Value::Number(request_id as f64))
162 .unwrap_or(Value::Null),
163 ),
164 ("phase".to_string(), Value::String(phase.clone())),
165 ("message".to_string(), Value::String(message.clone())),
166 (
167 "url".to_string(),
168 url.as_ref()
169 .map(|url| Value::String(url.clone()))
170 .unwrap_or(Value::Null),
171 ),
172 (
173 "bind_addr".to_string(),
174 bind_addr
175 .as_ref()
176 .map(|bind_addr| Value::String(bind_addr.clone()))
177 .unwrap_or(Value::Null),
178 ),
179 ])),
180 _ => Value::Null,
181 }
182}
183
184impl MeowMeowRunner {
185 pub fn eval(source: &str) -> EvalOutput {
194 Self::eval_impl(source, None, Duration::from_secs(2))
195 }
196
197 pub fn eval_with_timeout(source: &str, timeout: Duration) -> EvalOutput {
199 Self::eval_impl(source, None, timeout)
200 }
201
202 pub fn eval_with_path(source: &str, path: &str) -> EvalOutput {
204 Self::eval_impl(source, Some(path), Duration::from_secs(2))
205 }
206
207 pub fn eval_file(path: &str) -> EvalOutput {
209 Self::eval_file_with_timeout(path, Duration::from_secs(2))
210 }
211
212 pub fn eval_file_with_timeout(path: &str, timeout: Duration) -> EvalOutput {
214 match std::fs::read_to_string(path) {
215 Ok(source) => Self::eval_impl(&source, Some(path), timeout),
216 Err(e) => {
217 let mut output = EvalOutput::default();
218 output
219 .errors
220 .push(format!("cannot read file '{}': {}", path, e));
221 output
222 }
223 }
224 }
225
226 pub fn load_module_source(
227 source: &str,
228 source_path: Option<&str>,
229 ) -> Result<LoadedMmsModule, String> {
230 let module = match eval_module_source(source, source_path)? {
231 Value::Module {
232 named,
233 sequence,
234 heap,
235 } => Ok(LoadedMmsModule {
236 named_exports: named,
237 sequence,
238 heap,
239 source_path: source_path.map(|s| s.to_string()),
240 }),
241 other => Err(format!(
242 "load_module_source: expected module result, got {:?}",
243 other
244 )),
245 }?;
246 Ok(module)
247 }
248
249 pub fn load_module_file(path: &str) -> Result<LoadedMmsModule, String> {
250 let source = std::fs::read_to_string(path)
251 .map_err(|e| format!("cannot read module '{}': {}", path, e))?;
252 Self::load_module_source(&source, Some(path))
253 }
254
255 pub fn call_mms_module_fn(
256 module: &LoadedMmsModule,
257 name: &str,
258 args: Vec<Value>,
259 channels: Option<&mut crate::scripting::world_evaluator::EvalChannels>,
260 world_host: Option<&mut World>,
261 emit: Option<&mut dyn SignalEmitter>,
262 ) -> Result<Value, String> {
263 let Some(export) = module.named_export(name) else {
264 return Err(format!("call_mms_module_fn: export '{}' not found", name));
265 };
266 if !matches!(export, Value::Function { .. }) {
267 return Err(format!(
268 "call_mms_module_fn: export '{}' is not a function",
269 name
270 ));
271 }
272 eval_mms_fn(export, args, channels, world_host, emit)
273 }
274
275 pub fn materialize_mms_module_component(
276 module: &LoadedMmsModule,
277 name: &str,
278 args: Vec<Value>,
279 world_host: Option<&mut World>,
280 emit: Option<&mut dyn SignalEmitter>,
281 ) -> Result<MaterializedCE, String> {
282 Self::materialize_mms_module_component_in_mode(
283 module,
284 name,
285 args,
286 world_host,
287 emit,
288 ModuleFactoryEvalMode::Template,
289 )
290 }
291
292 pub fn materialize_mms_module_component_in_mode(
293 module: &LoadedMmsModule,
294 name: &str,
295 args: Vec<Value>,
296 world_host: Option<&mut World>,
297 emit: Option<&mut dyn SignalEmitter>,
298 mode: ModuleFactoryEvalMode,
299 ) -> Result<MaterializedCE, String> {
300 match mode {
301 ModuleFactoryEvalMode::Template => {}
302 ModuleFactoryEvalMode::Live => {
303 return Err(
304 "materialize_mms_module_component_in_mode: live mode does not return a stable MaterializedCE; use a spawn/instantiate helper instead".to_string()
305 )
306 }
307 }
308 let _ = world_host;
309 let _ = emit;
310 let value = Self::call_mms_module_fn(module, name, args, None, None, None)?;
311 let Value::ComponentExpr(component_expr) = value else {
312 return Err(format!(
313 "materialize_mms_module_component: export '{}' did not return a component tree",
314 name
315 ));
316 };
317 Ok(*component_expr)
318 }
319
320 pub fn materialize_mms_module_component_from_file(
321 path: &str,
322 name: &str,
323 args: Vec<Value>,
324 world_host: Option<&mut World>,
325 emit: Option<&mut dyn SignalEmitter>,
326 ) -> Result<MaterializedCE, String> {
327 let module = Self::load_module_file(path)?;
328 Self::materialize_mms_module_component(&module, name, args, world_host, emit)
329 }
330
331 pub fn spawn_mms_module_component_uninitialized(
332 module: &LoadedMmsModule,
333 name: &str,
334 args: Vec<Value>,
335 world: &mut World,
336 emit: &mut dyn SignalEmitter,
337 ) -> Result<ComponentId, String> {
338 Self::spawn_mms_module_component_uninitialized_with_assets(
339 module, name, args, world, None, emit,
340 )
341 }
342
343 pub fn spawn_mms_module_component_uninitialized_with_assets(
344 module: &LoadedMmsModule,
345 name: &str,
346 args: Vec<Value>,
347 world: &mut World,
348 render_assets: Option<&mut RenderAssets>,
349 emit: &mut dyn SignalEmitter,
350 ) -> Result<ComponentId, String> {
351 Self::spawn_mms_module_component_value(
352 module,
353 name,
354 args,
355 None,
356 world,
357 render_assets,
358 emit,
359 false,
360 )
361 }
362
363 pub fn spawn_mms_module_component_uninitialized_from_file(
364 path: &str,
365 name: &str,
366 args: Vec<Value>,
367 world: &mut World,
368 emit: &mut dyn SignalEmitter,
369 ) -> Result<ComponentId, String> {
370 let module = Self::load_module_file(path)?;
371 Self::spawn_mms_module_component_uninitialized(&module, name, args, world, emit)
372 }
373
374 pub fn spawn_mms_module_component(
375 module: &LoadedMmsModule,
376 name: &str,
377 args: Vec<Value>,
378 parent: Option<ComponentId>,
379 world: &mut World,
380 emit: &mut dyn SignalEmitter,
381 ) -> Result<ComponentId, String> {
382 Self::spawn_mms_module_component_value(module, name, args, parent, world, None, emit, true)
383 }
384
385 pub fn spawn_mms_module_component_from_file(
386 path: &str,
387 name: &str,
388 args: Vec<Value>,
389 parent: Option<ComponentId>,
390 world: &mut World,
391 emit: &mut dyn SignalEmitter,
392 ) -> Result<ComponentId, String> {
393 let module = Self::load_module_file(path)?;
394 Self::spawn_mms_module_component(&module, name, args, parent, world, emit)
395 }
396
397 fn spawn_mms_module_component_value(
398 module: &LoadedMmsModule,
399 name: &str,
400 args: Vec<Value>,
401 parent: Option<ComponentId>,
402 world: &mut World,
403 mut render_assets: Option<&mut RenderAssets>,
404 emit: &mut dyn SignalEmitter,
405 initialize: bool,
406 ) -> Result<ComponentId, String> {
407 let value = Self::eval_mms_module_component_live(
408 module,
409 name,
410 args,
411 world,
412 render_assets.as_deref_mut(),
413 emit,
414 )?;
415 match value {
416 Value::ComponentObject { id, .. } => {
417 if let Some(p) = parent {
418 world
419 .add_child(p, id)
420 .map_err(|e| format!("attach live module component failed: {e}"))?;
421 }
422 if initialize {
423 let should_init = parent.map(|p| world.is_initialized(p)).unwrap_or(true);
424 if should_init {
425 world.init_component_tree(id, emit);
426 }
427 }
428 Ok(id)
429 }
430 Value::ComponentExpr(component_expr) => {
431 if let Some(render_assets) = render_assets.as_deref_mut() {
432 crate::scripting::component_registry::with_live_render_assets(
433 render_assets,
434 || {
435 if initialize {
436 crate::scripting::component_registry::spawn_tree(
437 &component_expr,
438 parent,
439 world,
440 emit,
441 )
442 } else {
443 crate::scripting::component_registry::spawn_tree_uninitialized(
444 &component_expr,
445 world,
446 emit,
447 )
448 }
449 },
450 )
451 } else if initialize {
452 crate::scripting::component_registry::spawn_tree(
453 &component_expr,
454 parent,
455 world,
456 emit,
457 )
458 } else {
459 crate::scripting::component_registry::spawn_tree_uninitialized(
460 &component_expr,
461 world,
462 emit,
463 )
464 }
465 }
466 other => Err(format!(
467 "spawn_mms_module_component: export '{}' did not return a component tree, got {:?}",
468 name, other
469 )),
470 }
471 }
472
473 fn eval_mms_module_component_live(
474 module: &LoadedMmsModule,
475 name: &str,
476 args: Vec<Value>,
477 world: &mut World,
478 render_assets: Option<&mut RenderAssets>,
479 emit: &mut dyn SignalEmitter,
480 ) -> Result<Value, String> {
481 if let Some(render_assets) = render_assets {
482 crate::scripting::component_registry::with_live_render_assets(render_assets, || {
483 Self::call_mms_module_fn(module, name, args, None, Some(world), Some(emit))
484 })
485 } else {
486 Self::call_mms_module_fn(module, name, args, None, Some(world), Some(emit))
487 }
488 }
489
490 pub fn eval_with_world(
498 source: &str,
499 world: &mut World,
500 rx: &mut RxWorld,
501 emit: &mut dyn SignalEmitter,
502 ) -> EvalOutput {
503 Self::eval_with_world_at_path(source, None, world, rx, emit)
504 }
505
506 pub fn eval_with_world_at_path(
509 source: &str,
510 source_path: Option<&str>,
511 world: &mut World,
512 rx: &mut RxWorld,
513 emit: &mut dyn SignalEmitter,
514 ) -> EvalOutput {
515 Self::eval_with_world_and_assets_at_path(source, source_path, world, rx, None, emit)
516 }
517
518 pub fn eval_with_world_and_assets(
520 source: &str,
521 world: &mut World,
522 rx: &mut RxWorld,
523 render_assets: &mut RenderAssets,
524 emit: &mut dyn SignalEmitter,
525 ) -> EvalOutput {
526 Self::eval_with_world_and_assets_at_path(source, None, world, rx, Some(render_assets), emit)
527 }
528
529 pub fn eval_with_world_and_assets_at_path(
532 source: &str,
533 source_path: Option<&str>,
534 world: &mut World,
535 rx: &mut RxWorld,
536 mut render_assets: Option<&mut RenderAssets>,
537 emit: &mut dyn SignalEmitter,
538 ) -> EvalOutput {
539 let mut handle = MeowMeowEvaluator::spawn(64);
540 handle
541 .requests
542 .push(EvalRequest::EvalScript {
543 source: source.to_string(),
544 source_path: source_path.map(|s| s.to_string()),
545 })
546 .expect("MeowMeowRunner: push EvalScript");
547 handle
548 .requests
549 .push(EvalRequest::Shutdown)
550 .expect("MeowMeowRunner: push Shutdown");
551
552 let mut output = EvalOutput::default();
553 let deadline = Instant::now() + Duration::from_secs(5);
554
555 loop {
556 match handle.responses.pop() {
557 Ok(EvalResponse::Intent(iv)) => output.intents.push(iv),
558 Ok(EvalResponse::Error { message }) => output.errors.push(message),
559 Ok(EvalResponse::ParsedOk { .. }) => {}
560 Ok(EvalResponse::SnippetComplete { .. }) => {}
561 Ok(EvalResponse::NavigationComplete { .. } | EvalResponse::ReplReset) => {}
562 Ok(EvalResponse::ShutdownAck) => break,
563 Ok(EvalResponse::HostCall { id, kind }) => {
564 let reply = match kind {
565 HostCallKind::Spawn(ce) => {
566 let result = if let Some(render_assets) = render_assets.as_deref_mut() {
567 crate::scripting::component_registry::with_live_render_assets(
568 render_assets,
569 || {
570 crate::scripting::component_registry::spawn_tree(
571 &ce, None, world, emit,
572 )
573 },
574 )
575 } else {
576 crate::scripting::component_registry::spawn_tree(
577 &ce, None, world, emit,
578 )
579 };
580 match result {
581 Ok(component_id) => HostValue::ComponentId(component_id),
582 Err(e) => {
583 output.errors.push(format!("HostCall::Spawn error: {e}"));
584 HostValue::Null
585 }
586 }
587 }
588 HostCallKind::Register(ce) => {
589 let result = if let Some(render_assets) = render_assets.as_deref_mut() {
590 crate::scripting::component_registry::with_live_render_assets(
591 render_assets,
592 || {
593 crate::scripting::component_registry::spawn_tree_uninitialized(
594 &ce, world, emit,
595 )
596 },
597 )
598 } else {
599 crate::scripting::component_registry::spawn_tree_uninitialized(
600 &ce, world, emit,
601 )
602 };
603 match result {
604 Ok(component_id) => HostValue::ComponentId(component_id),
605 Err(e) => {
606 output.errors.push(format!("HostCall::Register error: {e}"));
607 HostValue::Null
608 }
609 }
610 }
611 HostCallKind::Attach { parent, child } => {
612 if let Some(p) = parent {
613 if let Err(e) = world.add_child(p, child) {
614 output.errors.push(format!("HostCall::Attach error: {e}"));
615 }
616 }
617 world.init_component_tree(child, emit);
619 HostValue::Null
620 }
621 HostCallKind::Query {
622 selector,
623 scope,
624 multiple,
625 } => {
626 let roots: Vec<crate::engine::ecs::ComponentId> = match scope {
627 Some(id) => vec![id],
628 None => world
629 .all_components()
630 .filter(|&id| world.parent_of(id).is_none())
631 .collect(),
632 };
633 let mut all_ids: Vec<crate::engine::ecs::ComponentId> = Vec::new();
634 for r in roots {
635 if multiple {
636 all_ids.extend(world.find_all_components(r, &selector));
637 } else if let Some(found) = world.find_component(r, &selector) {
638 all_ids.push(found);
639 break;
640 }
641 }
642 if multiple {
643 let list = all_ids
644 .into_iter()
645 .filter_map(|id| {
646 world.component_name(id).map(|t| (id, t.to_string()))
647 })
648 .collect();
649 HostValue::ComponentList(list)
650 } else {
651 match all_ids.into_iter().next() {
652 Some(id) => match world.component_name(id) {
653 Some(t) => HostValue::Component {
654 id,
655 component_type: t.to_string(),
656 },
657 None => HostValue::Null,
658 },
659 None => HostValue::Null,
660 }
661 }
662 }
663 HostCallKind::RegisterHandler {
664 scope,
665 signal_kind,
666 name,
667 handler,
668 } => {
669 let callback =
670 move |world: &mut World,
671 emit: &mut dyn SignalEmitter,
672 signal: &crate::engine::ecs::Signal| {
673 let arg = event_arg_value(signal);
674 if let Err(e) = eval_mms_fn(
675 &handler,
676 vec![arg],
677 None,
678 Some(world),
679 Some(emit),
680 ) {
681 eprintln!("[mms] handler error: {e}");
682 }
683 };
684 if let Some(name) = name {
685 rx.add_handler_closure_named(
686 signal_kind,
687 scope,
688 Some(name),
689 callback,
690 );
691 } else {
692 rx.add_handler_closure(signal_kind, scope, callback);
693 }
694 HostValue::Null
695 }
696 HostCallKind::AudioClipInstance {
697 source,
698 start_beat,
699 stop_beat,
700 } => {
701 use crate::engine::ecs::component::AudioClipComponent;
702 match world.get_component_by_id_as::<AudioClipComponent>(source) {
703 Some(src) => {
704 let mut c = AudioClipComponent::instance_of(src);
705 if let Some(sb) = start_beat {
706 c.start_beat = sb;
707 }
708 if let Some(eb) = stop_beat {
709 c.stop_beat = Some(eb);
710 }
711 let id = world.add_component(c);
712 HostValue::ComponentId(id)
713 }
714 None => {
715 output.errors.push(
716 "HostCall::AudioClipInstance: source is not an AudioClip"
717 .to_string(),
718 );
719 HostValue::Null
720 }
721 }
722 }
723 HostCallKind::InvokeComponentMethod {
724 id,
725 component_type,
726 method,
727 args,
728 } => match crate::scripting::component_method_registry::invoke_component_method(
729 world,
730 id,
731 &component_type,
732 &method,
733 &args,
734 |intent| output.intents.push(intent),
735 ) {
736 Ok(value) => match value {
737 Value::Null => HostValue::Null,
738 Value::ComponentObject { id, component_type } => {
739 HostValue::Component { id, component_type }
740 }
741 other => {
742 output.errors.push(format!(
743 "HostCall::InvokeComponentMethod returned unsupported value: {:?}",
744 other
745 ));
746 HostValue::Null
747 }
748 },
749 Err(e) => {
750 output
751 .errors
752 .push(format!("HostCall::InvokeComponentMethod error: {e}"));
753 HostValue::Null
754 }
755 },
756 HostCallKind::ReplTree { .. }
757 | HostCallKind::ReplDump { .. }
758 | HostCallKind::ReplHelp
759 | HostCallKind::ReplClear => HostValue::Null,
760 };
761 let _ = handle
762 .requests
763 .push(EvalRequest::HostCallResult { id, value: reply });
764 }
765 Err(rtrb::PopError::Empty) => {
766 if Instant::now() > deadline {
767 output
768 .errors
769 .push("MeowMeowRunner: timed out waiting for evaluator".into());
770 break;
771 }
772 std::thread::yield_now();
773 }
774 }
775 }
776
777 handle.shutdown_and_join();
778 output
779 }
780
781 fn eval_impl(source: &str, source_path: Option<&str>, timeout: Duration) -> EvalOutput {
782 let mut handle = MeowMeowEvaluator::spawn(64);
783
784 handle
785 .requests
786 .push(EvalRequest::EvalScript {
787 source: source.to_string(),
788 source_path: source_path.map(|s| s.to_string()),
789 })
790 .expect("MeowMeowRunner: push EvalScript");
791 handle
792 .requests
793 .push(EvalRequest::Shutdown)
794 .expect("MeowMeowRunner: push Shutdown");
795
796 let mut output = EvalOutput::default();
797 let deadline = Instant::now() + timeout;
798
799 loop {
800 match handle.responses.pop() {
801 Ok(EvalResponse::Intent(iv)) => output.intents.push(iv),
802 Ok(EvalResponse::Error { message }) => output.errors.push(message),
803 Ok(EvalResponse::ParsedOk { .. }) => {}
804 Ok(EvalResponse::SnippetComplete { .. }) => {}
805 Ok(EvalResponse::NavigationComplete { .. } | EvalResponse::ReplReset) => {}
806 Ok(EvalResponse::ShutdownAck) => break,
807 Ok(EvalResponse::HostCall { id, .. }) => {
810 let _ = handle.requests.push(EvalRequest::HostCallResult {
811 id,
812 value: HostValue::Null,
813 });
814 }
815 Err(rtrb::PopError::Empty) => {
816 if Instant::now() > deadline {
817 output
818 .errors
819 .push("MeowMeowRunner: timed out waiting for evaluator".into());
820 break;
821 }
822 std::thread::yield_now();
823 }
824 }
825 }
826
827 handle.shutdown_and_join();
828 output
829 }
830}