/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2026 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use async_lock::Mutex; use darkfi_serial::{async_trait, deserialize, Decodable, Encodable, SerialDecodable, VarInt}; use std::{io::Cursor, sync::Arc}; use zeromq::{Socket, SocketRecv, SocketSend}; use crate::{ app::node::{create_layer, create_vector_art}, error::{Error, Result}, expr::{decompile, Compiler, MachineGlobals, SExprCode, SExprMachine, SExprVal}, gfx::{gfxtag, Renderer}, prop::{PropertyPermission, PropertyType, Role}, scene::{Pimpl, SceneNodeId, SceneNodePtr, SceneNodeType, ScenePath, Slot}, ui::{ get_ui_object3, get_ui_object_ptr, Layer, RedrawTrigger, ShapeVertex, VectorArt, VectorShape, }, ExecutorPtr, }; /// Stop the UI tasks and clear the buffers of a subtree about to be /// removed at runtime, mirroring Window::stop(). Only pimpl types with a /// UIObject mapping are touched; others (Window, Setting, plugins, Null) /// keep their tasks until process exit. fn stop_ui_subtree(node: &SceneNodePtr) { if matches!( node.pimpl(), Pimpl::Layer(_) | Pimpl::ScrollLayer(_) | Pimpl::VectorArt(_) | Pimpl::Text(_) | Pimpl::TextScramble(_) | Pimpl::Edit(_) | Pimpl::Image(_) | Pimpl::Video(_) | Pimpl::Button(_) | Pimpl::EmojiPicker(_) | Pimpl::Shortcut(_) | Pimpl::Menu(_) | Pimpl::TokenTable(_) | Pimpl::Slider(_) | Pimpl::Dropdown(_) ) { get_ui_object3(node).stop(); } for child in node.get_children() { stop_ui_subtree(&child); } } /// Run a freshly compiled expr on a throwaway machine so unknown /// variables (typos) reject the set request instead of failing silently /// on every eval afterwards. The dummy values are irrelevant; only name /// resolution matters and the globals are discarded. `global_names` are /// the variable names the property's real eval can provide. fn check_expr(code: &SExprCode, global_names: &[String]) -> Result<()> { let mut globals: MachineGlobals = vec![]; for name in global_names { globals.push((name.clone(), SExprVal::Float32(1.))); } let mut machine = SExprMachine { globals, stmts: code }; machine.call()?; Ok(()) } const USE_IPV6: bool = true; #[derive(Debug, SerialDecodable)] #[repr(u8)] enum Command { Hello = 0, AddNode = 1, RemoveNode = 9, RenameNode = 23, ScanDangling = 24, LookupNodeId = 12, AddProperty = 11, LinkNode = 2, UnlinkNode = 8, GetInfo = 19, GetChildren = 4, GetParents = 5, GetProperties = 3, GetPropertyValue = 6, SetPropertyValue = 7, GetSignals = 14, RegisterSlot = 15, UnregisterSlot = 16, LookupSlotId = 17, GetSlots = 18, GetMethods = 20, GetMethod = 21, CallMethod = 22, } // Missing calls todo: // GetPropLen // UnsetProperty // SetPropertyNull // PropertyPushNull // PropertyPush // PropertyIsUnset pub struct ZeroMQAdapter { /* // req-reply commands req_socket: zmq::Socket, // We cannot share zmq sockets across threads, and we cannot quickly spawn // pub sockets due to address reuse errors. slot_sender: mpsc::SyncSender<(Vec, Vec)>, slot_recvr: Option, Vec)>>, */ sg_root: SceneNodePtr, renderer: Renderer, redraw: RedrawTrigger, ex: ExecutorPtr, zmq_rep: Mutex, zmq_pub: Mutex, } impl ZeroMQAdapter { pub async fn new( sg_root: SceneNodePtr, renderer: Renderer, redraw: RedrawTrigger, ex: ExecutorPtr, ) -> Arc { let mut zmq_rep = zeromq::RepSocket::new(); if USE_IPV6 { zmq_rep.bind("tcp://[::]:9484").await.unwrap(); } else { zmq_rep.bind("tcp://0.0.0.0:9484").await.unwrap(); } let mut zmq_pub = zeromq::PubSocket::new(); if USE_IPV6 { zmq_pub.bind("tcp://[::]:9485").await.unwrap(); } else { zmq_pub.bind("tcp://0.0.0.0:9485").await.unwrap(); } Arc::new(Self { sg_root, renderer, redraw, ex, zmq_rep: Mutex::new(zmq_rep), zmq_pub: Mutex::new(zmq_pub), }) } pub async fn run(self: Arc) { loop { let req = self.zmq_rep.lock().await.recv().await.unwrap(); assert_eq!(req.len(), 2); let cmd = req.get(0).unwrap().to_vec(); assert_eq!(cmd.len(), 1); let payload = req.get(1).unwrap().to_vec(); let cmd = deserialize(&cmd).unwrap(); debug!(target: "req", "zmq: {:?} {:?}", cmd, payload); let self2 = self.clone(); match self2.process_request(cmd, payload).await { Ok(reply) => { let mut m = zeromq::ZmqMessage::from(vec![0u8]); m.push_back(reply.into()); // [errc:1] [reply] self.zmq_rep.lock().await.send(m).await.unwrap(); } Err(err) => { let errc = err as u8; warn!(target: "req", "errc {}: {}", errc, err); let mut m = zeromq::ZmqMessage::from(vec![errc]); m.push_back(vec![].into()); // [errc:1] [reply] self.zmq_rep.lock().await.send(m).await.unwrap(); } } } } async fn process_request(self: Arc, cmd: Command, payload: Vec) -> Result> { let mut cur = Cursor::new(&payload); let mut reply = vec![]; match cmd { Command::Hello => { debug!(target: "req", "hello()"); assert_eq!(payload.len(), 0); "hello".encode(&mut reply).unwrap(); } Command::GetInfo => { /* let node_id = SceneNodeId::decode(&mut cur).unwrap(); debug!(target: "req", "{:?}({})", cmd, node_id); let node = scene_graph.get_node(node_id).ok_or(Error::NodeNotFound)?; node.name.encode(&mut reply).unwrap(); node.typ.encode(&mut reply).unwrap(); */ } Command::GetChildren => { let node_path: ScenePath = String::decode(&mut cur).unwrap().parse()?; debug!(target: "req", "{cmd:?}({node_path})"); let node = self.sg_root.lookup_node(node_path).ok_or(Error::NodeNotFound)?; let children: Vec<_> = node .get_children() .iter() .map(|node| (node.name.clone(), node.id, node.typ)) .collect(); children.encode(&mut reply).unwrap(); } Command::GetParents => { /* let node_id = SceneNodeId::decode(&mut cur).unwrap(); debug!(target: "req", "{:?}({})", cmd, node_id); let node = scene_graph.get_node(node_id).ok_or(Error::NodeNotFound)?; let parents: Vec<_> = node .parents .iter() .map(|node_inf| (node_inf.name.clone(), node_inf.id, node_inf.typ)) .collect(); parents.encode(&mut reply).unwrap(); */ } Command::GetProperties => { let node_path: ScenePath = String::decode(&mut cur).unwrap().parse()?; debug!(target: "req", "{cmd:?}({node_path})"); let node = self.sg_root.lookup_node(node_path).ok_or(Error::NodeNotFound)?; VarInt(node.props.len() as u64).encode(&mut reply).unwrap(); for prop in &node.props { prop.name.encode(&mut reply).unwrap(); prop.typ.encode(&mut reply).unwrap(); prop.subtype.encode(&mut reply).unwrap(); //prop.defaults.encode(&mut reply).unwrap(); prop.ui_name.encode(&mut reply).unwrap(); prop.desc.encode(&mut reply).unwrap(); prop.is_null_allowed.encode(&mut reply).unwrap(); prop.is_expr_allowed.encode(&mut reply).unwrap(); (prop.array_len as u32).encode(&mut reply).unwrap(); prop.min_val.encode(&mut reply).unwrap(); prop.max_val.encode(&mut reply).unwrap(); prop.enum_items.encode(&mut reply).unwrap(); let depends: Vec<_> = prop .get_depends() .into_iter() .map(|d| (d.i as u32, d.local_name)) .collect(); depends.encode(&mut reply).unwrap(); } } Command::GetPropertyValue => { let node_path: ScenePath = String::decode(&mut cur).unwrap().parse()?; let prop_name = String::decode(&mut cur).unwrap(); debug!(target: "req", "{cmd:?}({node_path}, {prop_name})"); let node = self.sg_root.lookup_node(node_path).ok_or(Error::NodeNotFound)?; let prop = node.get_property(&prop_name).ok_or(Error::PropertyNotFound)?; prop.typ.encode(&mut reply).unwrap(); VarInt(prop.get_len() as u64).encode(&mut reply).unwrap(); for i in 0..prop.get_len() { // Check the raw stored value, since get_value() resolves // exprs to their cached/default value and would never // report the EXPR status. let val = prop.get_raw_value(i)?; if val.is_expr() { 3u8.encode(&mut reply).unwrap(); let expr = prop.get_expr(i)?; decompile(&expr).encode(&mut reply).unwrap(); } else if val.is_unset() { // A null default encodes zero payload bytes, so it // is reported as the NULL status instead of UNSET. // This mirrors the old get_value() semantics, where // an unset index with a null default resolved to // null. let default = prop.defaults.lock().unwrap()[i].clone(); if default.is_null() { 2u8.encode(&mut reply).unwrap(); } else if default.is_expr() { // An unset index resolving through a default // expr reports the decompiled expr source under // the EXPR status. Encoding the SExprCode bytes // as the payload would desync the client, which // expects a payload matching the property type // on the UNSET path. 3u8.encode(&mut reply).unwrap(); let expr = default.as_sexpr().unwrap(); decompile(&expr).encode(&mut reply).unwrap(); } else { 1u8.encode(&mut reply).unwrap(); // Shapes are not serialized on the get path; // the python client shows a "<...>" placeholder. if prop.typ != PropertyType::VectorShape { default.encode(&mut reply).unwrap(); } } } else if val.is_null() { 2u8.encode(&mut reply).unwrap(); } else { 0u8.encode(&mut reply).unwrap(); if prop.typ != PropertyType::VectorShape { val.encode(&mut reply).unwrap(); } } } } Command::SetPropertyValue => { let node_path: ScenePath = String::decode(&mut cur).unwrap().parse()?; let prop_name = String::decode(&mut cur).unwrap(); let prop_i = u32::decode(&mut cur).unwrap() as usize; let prop_type = PropertyType::decode(&mut cur).unwrap(); debug!(target: "req", "{cmd:?}({node_path}, {prop_name}, {prop_i}, {prop_type:?})"); let node = self.sg_root.lookup_node(node_path).ok_or(Error::NodeNotFound)?; let prop = node.get_property(&prop_name).ok_or(Error::PropertyNotFound)?; let atom = &mut self.redraw.make_guard(gfxtag!("ZeroMQAdapter::SetPropertyValue")); match prop_type { PropertyType::Null => { prop.set_null(atom, Role::Root, prop_i)?; } PropertyType::Bool => { let val = bool::decode(&mut cur).unwrap(); prop.set_bool(atom, Role::Root, prop_i, val)?; } PropertyType::Uint32 => { let val = u32::decode(&mut cur).unwrap(); prop.set_u32(atom, Role::Root, prop_i, val)?; } PropertyType::Float32 => { let val = f32::decode(&mut cur).unwrap(); prop.set_f32(atom, Role::Root, prop_i, val)?; } PropertyType::Str => { let val = String::decode(&mut cur).unwrap(); prop.set_str(atom, Role::Root, prop_i, val)?; } PropertyType::Enum => { let val = String::decode(&mut cur).unwrap(); prop.set_enum(atom, Role::Root, prop_i, val)?; } PropertyType::SceneNodeId => { let val = SceneNodeId::decode(&mut cur).unwrap(); prop.set_node_id(atom, Role::Root, prop_i, val)?; } PropertyType::SExpr => { // Exprs are sent as source strings and compiled here. // The netdebug compiler is const-free: only machine // globals (w, h, ...) are available as variables. let expr_str = String::decode(&mut cur).unwrap(); debug!(target: "req", " compiling expr \"{expr_str}\""); let code = Compiler::new().compile(&expr_str)?; // The property's eval site provides its depends // names plus one of the machine global sets in // use (w, h for most rects, parent_*/rect_* for // edit behaves), so accept the union and treat // anything else as a typo. let mut names: Vec = prop.get_depends().into_iter().map(|d| d.local_name).collect(); names.extend( ["w", "h", "parent_w", "parent_h", "rect_w", "rect_h"] .iter() .map(|s| s.to_string()), ); check_expr(&code, &names)?; prop.set_expr(atom, Role::Root, prop_i, code)?; } PropertyType::VectorShape => { // Vertices carry coordinate exprs as source strings, // compiled with the same const-free compiler. The // payload is: vert count varint; per vert: x expr // string, y expr string, 4x f32 color; index count // varint; u16 indices. let cc = Compiler::new(); // Shape verts eval with only the w/h globals. let shape_globals: Vec = ["w", "h"].iter().map(|s| s.to_string()).collect(); let vert_count = VarInt::decode(&mut cur)?.0 as usize; let mut verts = vec![]; for _ in 0..vert_count { let x_src = String::decode(&mut cur)?; let y_src = String::decode(&mut cur)?; let color = [ f32::decode(&mut cur)?, f32::decode(&mut cur)?, f32::decode(&mut cur)?, f32::decode(&mut cur)?, ]; let x = cc.compile(&x_src)?; let y = cc.compile(&y_src)?; check_expr(&x, &shape_globals)?; check_expr(&y, &shape_globals)?; verts.push(ShapeVertex::new(x, y, color)); } let index_count = VarInt::decode(&mut cur)?.0 as usize; let mut indices = vec![]; for _ in 0..index_count { let index = u16::decode(&mut cur)?; if index as usize >= verts.len() { return Err(Error::PropertyWrongIndex) } indices.push(index); } let shape = VectorShape { verts, indices }; prop.set_shape(atom, Role::Root, prop_i, shape)?; } } } Command::AddNode => { let parent_path: ScenePath = String::decode(&mut cur)?.parse()?; let node_name = String::decode(&mut cur)?; let node_type = SceneNodeType::decode(&mut cur)?; debug!(target: "req", "{cmd:?}({parent_path}, {node_name}, {node_type:?})"); let parent = self.sg_root.lookup_node(parent_path).ok_or(Error::NodeNotFound)?; if parent.get_children().iter().any(|c| c.name == node_name) { return Err(Error::NodeSiblingNameConflict) } let renderer = self.renderer.clone(); let redraw = self.redraw.clone(); let node = match node_type { SceneNodeType::Layer => { create_layer(&node_name) .setup(|me| Layer::new(me, renderer.clone(), redraw.clone())) .await } SceneNodeType::VectorArt => { create_vector_art(&node_name) .setup(|me| VectorArt::new(me, renderer.clone(), redraw.clone())) .await } _ => return Err(Error::UnsupportedNodeType), }; // Hold the guard over the link so the triggered pass sees // the attached node. let _atom = self.redraw.make_guard(gfxtag!("ZeroMQAdapter::AddNode")); parent.link(node.clone()); node.id.encode(&mut reply).unwrap(); // Arm the pimpl's OnModify handlers (redraw on property // change) exactly like window-owned nodes. The task keeps a // strong ref so an immediate RemoveNode cannot drop the node // out from under start(). let node2 = node.clone(); let ex2 = self.ex.clone(); self.ex .spawn(async move { let obj = get_ui_object_ptr(&node2); obj.start(ex2).await }) .detach(); } Command::RemoveNode => { let node_path: ScenePath = String::decode(&mut cur)?.parse()?; debug!(target: "req", "{cmd:?}({node_path})"); let node = self.sg_root.lookup_node(node_path).ok_or(Error::NodeNotFound)?; // The scene root has no parent, removal is meaningless. if Arc::ptr_eq(&node, &self.sg_root) { return Err(Error::NodeNotRemovable) } // Tear down the subtree's UI tasks and buffers before // unlinking, mirroring Window::stop(). Pimpl types without // a UIObject mapping (Window, Setting, plugins, ...) keep // their tasks until process exit. stop_ui_subtree(&node); node.unlink(); self.redraw.trigger(); } Command::RenameNode => { /* let node_id = SceneNodeId::decode(&mut cur).unwrap(); let node_name = String::decode(&mut cur).unwrap(); debug!(target: "req", "{:?}({}, {})", cmd, node_id, node_name); scene_graph.rename_node(node_id, node_name)?; */ } Command::ScanDangling => { /* let dangling = scene_graph.scan_dangling(); dangling.encode(&mut reply).unwrap(); */ } Command::LookupNodeId => { /* let node_path: String = deserialize(&payload).unwrap(); debug!(target: "req", "{:?}({})", cmd, node_path); let node_id = scene_graph.lookup_node_id(&node_path).ok_or(Error::NodeNotFound)?; node_id.encode(&mut reply).unwrap(); */ } Command::AddProperty => { /* let node_id = SceneNodeId::decode(&mut cur).unwrap(); let prop_name = String::decode(&mut cur).unwrap(); let prop_type = PropertyType::decode(&mut cur).unwrap(); let prop_subtype = PropertySubType::decode(&mut cur).unwrap(); debug!(target: "req", "{:?}({}, {}, {:?}, {:?}, ...)", cmd, node_id, prop_name, prop_type, prop_subtype); let mut prop = Property::new(prop_name, prop_type, prop_subtype, PropertyPermission::default()); let prop_array_len = u32::decode(&mut cur).unwrap(); prop.set_array_len(prop_array_len as usize); let prop_defaults_is_some = bool::decode(&mut cur).unwrap(); if prop_defaults_is_some { let prop_defaults_len = VarInt::decode(&mut cur).unwrap(); match prop_type { PropertyType::Uint32 => { let mut prop_defaults = vec![]; for _ in 0..prop_defaults_len.0 { prop_defaults.push(u32::decode(&mut cur).unwrap()); } prop.set_defaults_u32(prop_defaults)?; } PropertyType::Float32 => { let mut prop_defaults = vec![]; for _ in 0..prop_defaults_len.0 { prop_defaults.push(f32::decode(&mut cur).unwrap()); } prop.set_defaults_f32(prop_defaults)?; } PropertyType::Str => { let mut prop_defaults = vec![]; for _ in 0..prop_defaults_len.0 { prop_defaults.push(String::decode(&mut cur).unwrap()); } prop.set_defaults_str(prop_defaults)?; } _ => return Err(Error::PropertyWrongType), } } let prop_ui_name = String::decode(&mut cur).unwrap(); let prop_desc = String::decode(&mut cur).unwrap(); let prop_is_null_allowed = bool::decode(&mut cur).unwrap(); let prop_is_expr_allowed = bool::decode(&mut cur).unwrap(); match prop_type { PropertyType::Uint32 => { let min_is_some = bool::decode(&mut cur).unwrap(); let min = if min_is_some { let min = u32::decode(&mut cur).unwrap(); Some(PropertyValue::Uint32(min)) } else { None }; let max_is_some = bool::decode(&mut cur).unwrap(); let max = if max_is_some { let max = u32::decode(&mut cur).unwrap(); Some(PropertyValue::Uint32(max)) } else { None }; prop.min_val = min; prop.max_val = max; } PropertyType::Float32 => { let min_is_some = bool::decode(&mut cur).unwrap(); let min = if min_is_some { let min = f32::decode(&mut cur).unwrap(); Some(PropertyValue::Float32(min)) } else { None }; let max_is_some = bool::decode(&mut cur).unwrap(); let max = if max_is_some { let max = f32::decode(&mut cur).unwrap(); Some(PropertyValue::Float32(max)) } else { None }; prop.min_val = min; prop.max_val = max; } _ => { let min_is_some = bool::decode(&mut cur).unwrap(); if min_is_some { return Err(Error::PropertyWrongType) } let max_is_some = bool::decode(&mut cur).unwrap(); if max_is_some { return Err(Error::PropertyWrongType) } } } let prop_enum_items = Vec::::decode(&mut cur).unwrap(); let node = scene_graph.get_node_mut(node_id).ok_or(Error::NodeNotFound)?; prop.set_ui_text(prop_ui_name, prop_desc); prop.is_null_allowed = prop_is_null_allowed; prop.is_expr_allowed = prop_is_expr_allowed; if !prop_enum_items.is_empty() { prop.set_enum_items(prop_enum_items)?; } node.add_property(prop)?; */ } Command::LinkNode => { /* let child_id = SceneNodeId::decode(&mut cur).unwrap(); let parent_id = SceneNodeId::decode(&mut cur).unwrap(); debug!(target: "req", "{:?}({}, {})", cmd, child_id, parent_id); scene_graph.link(child_id, parent_id)?; */ } Command::UnlinkNode => { /* let child_id = SceneNodeId::decode(&mut cur).unwrap(); let parent_id = SceneNodeId::decode(&mut cur).unwrap(); debug!(target: "req", "{:?}({}, {})", cmd, child_id, parent_id); scene_graph.unlink(child_id, parent_id)?; */ } Command::GetSignals => { let node_path: ScenePath = String::decode(&mut cur).unwrap().parse()?; debug!(target: "req", "{cmd:?}({node_path})"); let node = self.sg_root.lookup_node(node_path).ok_or(Error::NodeNotFound)?; let sigs = node.sigs.read().unwrap(); let sig_names: Vec<_> = sigs.iter().map(|sig| sig.name.clone()).collect(); sig_names.encode(&mut reply).unwrap(); } Command::RegisterSlot => { let node_path: ScenePath = String::decode(&mut cur).unwrap().parse()?; let sig_name = String::decode(&mut cur).unwrap(); let slot_name = String::decode(&mut cur).unwrap(); let user_data = Vec::::decode(&mut cur).unwrap(); debug!(target: "req", "{cmd:?}({node_path}, {sig_name}, {slot_name}, {user_data:?})"); let node = self.sg_root.lookup_node(node_path).ok_or(Error::NodeNotFound)?; let (sendr, recvr) = async_channel::unbounded(); let slot = Slot { name: slot_name, notify: sendr }; // This task will auto-die when the slot is unregistered let self2 = self.clone(); self.ex .spawn(async move { loop { let Ok(signal_data) = recvr.recv().await else { // Die break }; let mut m = zeromq::ZmqMessage::from(signal_data); m.push_back(user_data.clone().into()); self2.zmq_pub.lock().await.send(m).await.unwrap(); } }) .detach(); let slot_id = node.register(&sig_name, slot)?; slot_id.encode(&mut reply).unwrap(); } Command::UnregisterSlot => { /* let node_id = SceneNodeId::decode(&mut cur).unwrap(); let sig_name = String::decode(&mut cur).unwrap(); let slot_id = SlotId::decode(&mut cur).unwrap(); debug!(target: "req", "{:?}({}, {}, {})", cmd, node_id, sig_name, slot_id); let node = scene_graph.get_node_mut(node_id).ok_or(Error::NodeNotFound)?; node.unregister(&sig_name, slot_id)?; */ } Command::LookupSlotId => { /* let node_id = SceneNodeId::decode(&mut cur).unwrap(); let sig_name = String::decode(&mut cur).unwrap(); let slot_name = String::decode(&mut cur).unwrap(); debug!(target: "req", "{:?}({}, {}, {})", cmd, node_id, sig_name, slot_name); let node = scene_graph.get_node(node_id).ok_or(Error::NodeNotFound)?; let signal = node.get_signal(&sig_name).ok_or(Error::SignalNotFound)?; let slot_id = signal.lookup_slot_id(&slot_name).ok_or(Error::SlotNotFound)?; slot_id.encode(&mut reply).unwrap(); */ } Command::GetSlots => { let node_path: ScenePath = String::decode(&mut cur).unwrap().parse()?; let sig_name = String::decode(&mut cur).unwrap(); debug!(target: "req", "{cmd:?}({node_path}, {sig_name})"); let node = self.sg_root.lookup_node(node_path).ok_or(Error::NodeNotFound)?; let signal = node.get_signal(&sig_name).ok_or(Error::SignalNotFound)?; let slots = signal.get_slots(); let slot_names: Vec<_> = slots.iter().map(|(slot_id, slot)| (slot.name.clone(), *slot_id)).collect(); slot_names.encode(&mut reply).unwrap(); } Command::GetMethods => { let node_path: ScenePath = String::decode(&mut cur).unwrap().parse()?; debug!(target: "req", "{cmd:?}({node_path})"); let node = self.sg_root.lookup_node(node_path).ok_or(Error::NodeNotFound)?; let method_names: Vec<_> = node.methods.iter().map(|m| m.name.clone()).collect(); method_names.encode(&mut reply).unwrap(); } Command::GetMethod => { let node_path: ScenePath = String::decode(&mut cur).unwrap().parse()?; let method_name = String::decode(&mut cur).unwrap(); debug!(target: "req", "{:?}({}, {})", cmd, node_path, method_name); let node = self.sg_root.lookup_node(node_path).ok_or(Error::NodeNotFound)?; let method = node.get_method(&method_name).ok_or(Error::MethodNotFound)?; method.args.encode(&mut reply).unwrap(); method.result.encode(&mut reply).unwrap(); } Command::CallMethod => { let node_path: ScenePath = String::decode(&mut cur).unwrap().parse()?; let method_name = String::decode(&mut cur).unwrap(); let arg_data = Vec::::decode(&mut cur).unwrap(); debug!(target: "req", "{cmd:?}({node_path}, {method_name}, ...)"); let node = self.sg_root.lookup_node(node_path).ok_or(Error::NodeNotFound)?; let result = node.call_method(&method_name, arg_data).await?; result.encode(&mut reply).unwrap(); } } Ok(reply) } }