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
12pub 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 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}