Skip to main content

mittens_engine/scripting/
host.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use meow_meow_script as mms;
5use slotmap::{Key, KeyData};
6
7use crate::engine::ecs::component::AudioClipComponent;
8use crate::engine::ecs::{ComponentId, IntentValue, RxWorld, SignalEmitter, SignalKind, World};
9use crate::engine::graphics::RenderAssets;
10use crate::scripting::object as legacy;
11
12/// Engine implementation of the host-neutral Meow Meow host contract.
13pub struct MittensHost<'a> {
14    pub world: &'a mut World,
15    pub rx: Option<&'a mut RxWorld>,
16    pub render_assets: Option<&'a mut RenderAssets>,
17    pub emit: &'a mut dyn SignalEmitter,
18    pub intents: &'a mut Vec<IntentValue>,
19}
20
21impl<'a> MittensHost<'a> {
22    pub fn new(
23        world: &'a mut World,
24        emit: &'a mut dyn SignalEmitter,
25        intents: &'a mut Vec<IntentValue>,
26    ) -> Self {
27        Self {
28            world,
29            rx: None,
30            render_assets: None,
31            emit,
32            intents,
33        }
34    }
35
36    pub fn with_rx(mut self, rx: &'a mut RxWorld) -> Self {
37        self.rx = Some(rx);
38        self
39    }
40    pub fn with_render_assets(mut self, assets: &'a mut RenderAssets) -> Self {
41        self.render_assets = Some(assets);
42        self
43    }
44
45    pub fn component_handle(id: ComponentId) -> mms::ComponentHandle {
46        mms::ComponentHandle::from_raw(id.data().as_ffi())
47    }
48
49    pub fn component_id(handle: mms::ComponentHandle) -> ComponentId {
50        ComponentId::from(KeyData::from_ffi(handle.into_raw()))
51    }
52
53    fn existing_id(
54        &self,
55        handle: mms::ComponentHandle,
56        operation: &str,
57    ) -> Result<ComponentId, mms::HostError> {
58        let id = Self::component_id(handle);
59        self.world
60            .get_component_record(id)
61            .map(|_| id)
62            .ok_or_else(|| {
63                mms::HostError::failure(
64                    operation,
65                    format!("component handle {handle:?} is stale or foreign"),
66                )
67            })
68    }
69}
70
71impl mms::Host for MittensHost<'_> {
72    fn capabilities(&self) -> mms::HostCapabilities {
73        crate::scripting::component_registry::SUPPORTED_COMPONENT_NAMES
74            .iter()
75            .fold(mms::HostCapabilities::default(), |capabilities, name| {
76                capabilities.supports_component(*name)
77            })
78    }
79
80    fn dispatch(&mut self, request: mms::HostRequest) -> Result<mms::HostResponse, mms::HostError> {
81        use mms::{HostRequest as R, HostResponse as S};
82        match request {
83            R::Emit { tree } => {
84                let component_type = tree.component_type.clone();
85                let response = self.dispatch(R::Spawn { tree })?;
86                let S::Component { handle: native, .. } = response else {
87                    return Err(mms::HostError::failure("emit", "spawn did not return a component"));
88                };
89                Ok(S::Component { handle: native, component_type })
90            }
91            R::RegisterComponent { tree } => {
92                let component_type = tree.component_type.clone();
93                let response = self.dispatch(R::Register { tree })?;
94                let S::Component { handle: native, .. } = response else {
95                    return Err(mms::HostError::failure("register_component", "registration did not return a component"));
96                };
97                Ok(S::Component { handle: native, component_type })
98            }
99            R::CallApi { api_id, .. } => Err(mms::HostError::unsupported(api_id)),
100            R::Spawn { tree } => {
101                let tree = external_tree_to_legacy(tree)?;
102                let result = if let Some(assets) = self.render_assets.as_deref_mut() {
103                    crate::scripting::component_registry::with_live_render_assets(assets, || {
104                        crate::scripting::component_registry::spawn_tree(
105                            &tree, None, self.world, self.emit,
106                        )
107                    })
108                } else {
109                    crate::scripting::component_registry::spawn_tree(
110                        &tree, None, self.world, self.emit,
111                    )
112                };
113                let id = result.map_err(|e| mms::HostError::failure("spawn", e))?;
114                Ok(S::Component {
115                    handle: Self::component_handle(id),
116                    component_type: tree.component_type,
117                })
118            }
119            R::Register { tree } => {
120                let tree = external_tree_to_legacy(tree)?;
121                let result = if let Some(assets) = self.render_assets.as_deref_mut() {
122                    crate::scripting::component_registry::with_live_render_assets(assets, || {
123                        crate::scripting::component_registry::spawn_tree_uninitialized(
124                            &tree, self.world, self.emit,
125                        )
126                    })
127                } else {
128                    crate::scripting::component_registry::spawn_tree_uninitialized(
129                        &tree, self.world, self.emit,
130                    )
131                };
132                let id = result.map_err(|e| mms::HostError::failure("register", e))?;
133                Ok(S::Component {
134                    handle: Self::component_handle(id),
135                    component_type: tree.component_type,
136                })
137            }
138            R::Attach { parent, child } => {
139                let child = self.existing_id(child, "attach")?;
140                if let Some(parent) = parent {
141                    let parent = self.existing_id(parent, "attach")?;
142                    self.world
143                        .add_child(parent, child)
144                        .map_err(|e| mms::HostError::failure("attach", e))?;
145                }
146                self.world.init_component_tree(child, self.emit);
147                Ok(S::Unit)
148            }
149            R::Query {
150                selector,
151                scope,
152                multiple,
153            } => {
154                let roots = if let Some(scope) = scope {
155                    vec![self.existing_id(scope, "query")?]
156                } else {
157                    self.world
158                        .all_components()
159                        .filter(|&id| self.world.parent_of(id).is_none())
160                        .collect()
161                };
162                let mut matches = Vec::new();
163                for root in roots {
164                    if multiple {
165                        matches.extend(self.world.find_all_components(root, &selector));
166                    } else if let Some(id) = self.world.find_component(root, &selector) {
167                        matches.push(id);
168                        break;
169                    }
170                }
171                if multiple {
172                    Ok(S::Components(
173                        matches
174                            .into_iter()
175                            .filter_map(|id| {
176                                self.world
177                                    .component_name(id)
178                                    .map(|ty| (Self::component_handle(id), ty.to_owned()))
179                            })
180                            .collect(),
181                    ))
182                } else if let Some(id) = matches.into_iter().next() {
183                    let component_type = self
184                        .world
185                        .component_name(id)
186                        .unwrap_or("Component")
187                        .to_owned();
188                    Ok(S::Component {
189                        handle: Self::component_handle(id),
190                        component_type,
191                    })
192                } else {
193                    Ok(S::Unit)
194                }
195            }
196            R::InvokeComponentMethod {
197                component,
198                component_type,
199                method,
200                args,
201            } => {
202                let id = self.existing_id(component, "invoke_component_method")?;
203                let args = args
204                    .into_iter()
205                    .map(external_value_to_legacy)
206                    .collect::<Result<Vec<_>, _>>()?;
207                let value = crate::scripting::component_method_registry::invoke_component_method(
208                    self.world,
209                    id,
210                    &component_type,
211                    &method,
212                    &args,
213                    |intent| self.intents.push(intent),
214                )
215                .map_err(|e| mms::HostError::failure("invoke_component_method", e))?;
216                Ok(S::Value(legacy_value_to_external(value)?))
217            }
218            R::AudioClipInstance {
219                source,
220                start_beat,
221                stop_beat,
222            } => {
223                let source = self.existing_id(source, "audio_clip_instance")?;
224                let source = self
225                    .world
226                    .get_component_by_id_as::<AudioClipComponent>(source)
227                    .ok_or_else(|| {
228                        mms::HostError::failure("audio_clip_instance", "source is not an AudioClip")
229                    })?;
230                let mut clip = AudioClipComponent::instance_of(source);
231                if let Some(start) = start_beat {
232                    clip.start_beat = start;
233                }
234                clip.stop_beat = stop_beat;
235                let id = self.world.add_component(clip);
236                Ok(S::Component {
237                    handle: Self::component_handle(id),
238                    component_type: "AudioClip".into(),
239                })
240            }
241            R::RegisterHandler {
242                scope,
243                signal,
244                name,
245                handler,
246            } => {
247                let scope = self.existing_id(scope, "register_handler")?;
248                let kind = signal_kind(&signal).ok_or_else(|| {
249                    mms::HostError::failure(
250                        "register_handler",
251                        format!("unknown signal '{signal}'"),
252                    )
253                })?;
254                let handler = external_value_to_legacy(handler)?;
255                let Some(rx) = self.rx.as_deref_mut() else {
256                    return Err(mms::HostError::unsupported("register_handler"));
257                };
258                let callback =
259                    move |world: &mut World,
260                          emit: &mut dyn SignalEmitter,
261                          signal: &crate::engine::ecs::Signal| {
262                        let arg = crate::scripting::runner::event_arg_value(signal);
263                        if let Err(error) = crate::scripting::world_evaluator::eval_mms_fn(
264                            &handler,
265                            vec![arg],
266                            None,
267                            Some(world),
268                            Some(emit),
269                        ) {
270                            eprintln!("[mms] handler error: {error}");
271                        }
272                    };
273                if let Some(name) = name {
274                    rx.add_handler_closure_named(kind, scope, Some(name), callback);
275                } else {
276                    rx.add_handler_closure(kind, scope, callback);
277                }
278                Ok(S::Unit)
279            }
280            R::AudioOperation {
281                operation,
282                target,
283                args,
284            } => {
285                let targets = target.into_iter().collect();
286                self.dispatch(R::EngineMutation {
287                    operation,
288                    targets,
289                    args,
290                })
291            }
292            R::EngineMutation {
293                operation,
294                targets,
295                args,
296            } => {
297                // The legacy engine still owns its concrete mutation enum. Route
298                // named operations through component-method dispatch where possible.
299                let Some(target) = targets.first().copied() else {
300                    return Err(mms::HostError::failure(
301                        &operation,
302                        "mutation requires a target",
303                    ));
304                };
305                let id = self.existing_id(target, &operation)?;
306                let component_type = self
307                    .world
308                    .component_name(id)
309                    .unwrap_or("Component")
310                    .to_owned();
311                let args = args
312                    .into_iter()
313                    .map(external_value_to_legacy)
314                    .collect::<Result<Vec<_>, _>>()?;
315                let value = crate::scripting::component_method_registry::invoke_component_method(
316                    self.world,
317                    id,
318                    &component_type,
319                    &operation,
320                    &args,
321                    |intent| self.intents.push(intent),
322                )
323                .map_err(|e| mms::HostError::failure(&operation, e))?;
324                Ok(S::Value(legacy_value_to_external(value)?))
325            }
326            R::ReplTree { .. } | R::ReplDump { .. } | R::ReplHelp | R::ReplClear => Ok(S::Unit),
327        }
328    }
329}
330
331fn signal_kind(name: &str) -> Option<SignalKind> {
332    Some(match name {
333        "Click" => SignalKind::Click,
334        "DataEvent" => SignalKind::DataEvent,
335        "CollisionStarted" => SignalKind::CollisionStarted,
336        "CollisionEnded" => SignalKind::CollisionEnded,
337        "DragStart" => SignalKind::DragStart,
338        "DragMove" => SignalKind::DragMove,
339        "DragEnd" => SignalKind::DragEnd,
340        "ParentChanged" => SignalKind::ParentChanged,
341        "RayIntersected" => SignalKind::RayIntersected,
342        "Scrolling" => SignalKind::Scrolling,
343        "TextInputChanged" => SignalKind::TextInputChanged,
344        "TextInputFocusChanged" => SignalKind::TextInputFocusChanged,
345        "SelectionAdded" => SignalKind::SelectionAdded,
346        "SelectionRemoved" => SignalKind::SelectionRemoved,
347        "SelectionChanged" => SignalKind::SelectionChanged,
348        "SelectionCleared" => SignalKind::SelectionCleared,
349        "XrButtonDown" => SignalKind::XrButtonDown,
350        "XrButtonUp" => SignalKind::XrButtonUp,
351        "XrButtonChanged" => SignalKind::XrButtonChanged,
352        "XrAxisChanged" => SignalKind::XrAxisChanged,
353        "HttpRequest" => SignalKind::HttpRequest,
354        "HttpResponse" => SignalKind::HttpResponse,
355        "HttpError" => SignalKind::HttpError,
356        _ => return None,
357    })
358}
359
360fn external_tree_to_legacy(
361    tree: mms::MaterializedCE,
362) -> Result<legacy::MaterializedCE, mms::HostError> {
363    Ok(legacy::MaterializedCE {
364        component_type: tree.component_type,
365        component_property_assignment_only: tree.component_property_assignment_only,
366        ctor_method: tree.ctor_method,
367        ctor_args: tree
368            .ctor_args
369            .into_iter()
370            .map(external_value_to_legacy)
371            .collect::<Result<_, _>>()?,
372        calls: tree
373            .calls
374            .into_iter()
375            .map(|(name, args)| {
376                Ok((
377                    name,
378                    args.into_iter()
379                        .map(external_value_to_legacy)
380                        .collect::<Result<_, _>>()?,
381                ))
382            })
383            .collect::<Result<_, mms::HostError>>()?,
384        named: tree
385            .named
386            .into_iter()
387            .map(|(name, value)| Ok((name, external_value_to_legacy(value)?)))
388            .collect::<Result<_, mms::HostError>>()?,
389        positionals: tree
390            .positionals
391            .into_iter()
392            .map(external_value_to_legacy)
393            .collect::<Result<_, _>>()?,
394        deferred_block: tree
395            .deferred_block
396            .map(|closure| {
397                Ok(legacy::RuntimeClosure {
398                    body: closure.body,
399                    captured_env: Arc::new(
400                        closure
401                            .captured_env
402                            .iter()
403                            .map(|(k, v)| Ok((k.clone(), external_value_to_legacy(v.clone())?)))
404                            .collect::<Result<HashMap<_, _>, mms::HostError>>()?,
405                    ),
406                    heap: legacy::HeapHandle::new(),
407                    analysis: closure.analysis,
408                })
409            })
410            .transpose()?,
411        children: tree
412            .children
413            .into_iter()
414            .map(|child| match child {
415                mms::CeChild::Spawn(tree) => {
416                    Ok(legacy::CeChild::Spawn(external_tree_to_legacy(tree)?))
417                }
418                mms::CeChild::Attach(handle) => {
419                    Ok(legacy::CeChild::Attach(MittensHost::component_id(handle)))
420                }
421            })
422            .collect::<Result<_, mms::HostError>>()?,
423    })
424}
425
426fn external_value_to_legacy(value: mms::Value) -> Result<legacy::Value, mms::HostError> {
427    Ok(match value {
428        mms::Value::Null => legacy::Value::Null,
429        mms::Value::Bool(v) => legacy::Value::Bool(v),
430        mms::Value::Number(v) => legacy::Value::Number(v),
431        mms::Value::String(v) => legacy::Value::String(v),
432        mms::Value::Dimension { value, unit } => legacy::Value::Dimension { value, unit },
433        mms::Value::Array(v) => legacy::Value::Array(
434            v.into_iter()
435                .map(external_value_to_legacy)
436                .collect::<Result<_, _>>()?,
437        ),
438        mms::Value::Map(v) => legacy::Value::Map(
439            v.into_iter()
440                .map(|(k, v)| Ok((k, external_value_to_legacy(v)?)))
441                .collect::<Result<_, mms::HostError>>()?,
442        ),
443        mms::Value::ComponentObject { id, component_type } => legacy::Value::ComponentObject {
444            id: MittensHost::component_id(id),
445            component_type,
446        },
447        mms::Value::Identifier(v) => legacy::Value::Identifier(v),
448        mms::Value::BuiltinTable(kind) => legacy::Value::BuiltinTable(match kind {
449            mms::BuiltinTableKind::Math => legacy::BuiltinTableKind::Math,
450            mms::BuiltinTableKind::MusicNote => legacy::BuiltinTableKind::MusicNote,
451        }),
452        mms::Value::ComponentExpr(tree) => {
453            legacy::Value::ComponentExpr(Box::new(external_tree_to_legacy(*tree)?))
454        }
455        mms::Value::Function {
456            params,
457            body,
458            captured_env,
459            ..
460        } => legacy::Value::Function {
461            params,
462            body,
463            captured_env: Arc::new(
464                captured_env
465                    .iter()
466                    .map(|(k, v)| Ok((k.clone(), external_value_to_legacy(v.clone())?)))
467                    .collect::<Result<_, mms::HostError>>()?,
468            ),
469            heap: legacy::HeapHandle::new(),
470        },
471        mms::Value::Object(id) => legacy::Value::Map(
472            id.with_map(|map| map.clone())
473                .unwrap_or_default()
474                .into_iter()
475                .map(|(k, v)| Ok((k, external_value_to_legacy(v)?)))
476                .collect::<Result<_, mms::HostError>>()?,
477        ),
478        mms::Value::Module {
479            named, sequence, ..
480        } => legacy::Value::Module {
481            named: named
482                .into_iter()
483                .map(|(k, v)| Ok((k, external_value_to_legacy(v)?)))
484                .collect::<Result<_, mms::HostError>>()?,
485            sequence: sequence
486                .into_iter()
487                .map(external_tree_to_legacy)
488                .collect::<Result<_, _>>()?,
489            heap: legacy::HeapHandle::new(),
490        },
491    })
492}
493
494fn legacy_value_to_external(value: legacy::Value) -> Result<mms::Value, mms::HostError> {
495    Ok(match value {
496        legacy::Value::Null => mms::Value::Null,
497        legacy::Value::Bool(v) => mms::Value::Bool(v),
498        legacy::Value::Number(v) => mms::Value::Number(v),
499        legacy::Value::String(v) => mms::Value::String(v),
500        legacy::Value::Dimension { value, unit } => mms::Value::Dimension { value, unit },
501        legacy::Value::Array(v) => mms::Value::Array(
502            v.into_iter()
503                .map(legacy_value_to_external)
504                .collect::<Result<_, _>>()?,
505        ),
506        legacy::Value::Map(v) => mms::Value::Map(
507            v.into_iter()
508                .map(|(k, v)| Ok((k, legacy_value_to_external(v)?)))
509                .collect::<Result<_, mms::HostError>>()?,
510        ),
511        legacy::Value::ComponentObject { id, component_type } => mms::Value::ComponentObject {
512            id: MittensHost::component_handle(id),
513            component_type,
514        },
515        legacy::Value::Identifier(v) => mms::Value::Identifier(v),
516        legacy::Value::BuiltinTable(kind) => mms::Value::BuiltinTable(match kind {
517            legacy::BuiltinTableKind::Math => mms::BuiltinTableKind::Math,
518            legacy::BuiltinTableKind::MusicNote => mms::BuiltinTableKind::MusicNote,
519        }),
520        other => {
521            return Err(mms::HostError::failure(
522                "value_conversion",
523                format!("unsupported engine runtime value: {other:?}"),
524            ));
525        }
526    })
527}
528
529#[cfg(test)]
530mod tests {
531    use super::*;
532
533    #[test]
534    fn component_handles_round_trip_losslessly() {
535        let mut world = World::default();
536        let id = world.add_component(crate::engine::ecs::component::DataComponent::default());
537        assert_eq!(
538            MittensHost::component_id(MittensHost::component_handle(id)),
539            id
540        );
541    }
542}