/* 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_channel::{Receiver, Sender}; use async_trait::async_trait; use darkfi_serial::{FutAsyncWriteExt, SerialDecodable, SerialEncodable}; use futures::{stream::FuturesUnordered, StreamExt}; use std::{ collections::{HashMap, VecDeque}, fmt, future::Future, str::FromStr, sync::{Arc, OnceLock, RwLock as SyncRwLock, Weak}, }; use crate::{ error::{Error, Result}, plugin, prop::{Property, PropertyAtomicGuard, PropertyPtr, Role}, pubsub::{Publisher, PublisherPtr, Subscription}, ui, }; macro_rules! t { ($($arg:tt)*) => { trace!(target: "scene", $($arg)*); } } pub struct ScenePath(VecDeque); impl> From for ScenePath { fn from(path: S) -> Self { let path: String = path.into(); (&path).parse().expect("invalid ScenePath &str") } } impl fmt::Display for ScenePath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "/")?; for token in &self.0 { write!(f, "{}/", token)?; } Ok(()) } } impl FromStr for ScenePath { type Err = Error; fn from_str(s: &str) -> Result { if s.is_empty() || s.chars().nth(0).unwrap() != '/' { return Err(Error::InvalidScenePath) } if s == "/" { return Ok(ScenePath(VecDeque::new())) } let mut tokens = s.split('/'); // Should start with a / let initial = tokens.next().expect("should not be empty"); if !initial.is_empty() { return Err(Error::InvalidScenePath) } let mut path = VecDeque::new(); for token in tokens { // There should not be any double slashes // if token.is_empty() { return Err(Error::InvalidScenePath) } path.push_back(token.to_string()); } Ok(ScenePath(path)) } } pub type SceneNodePtr = Arc; pub type SceneNodeWeak = Weak; pub type SceneNodeId = u32; #[derive(Debug, Copy, Clone, PartialEq, SerialEncodable, SerialDecodable)] #[repr(u8)] pub enum SceneNodeType { Null = 0, Root = 1, Window = 2, WindowInput = 3, Keyboard = 4, Mouse = 5, Layer = 6, Object = 7, VectorArt = 8, Text = 9, Texture = 10, Fonts = 11, Font = 12, ChatView = 13, Edit = 14, Image = 15, Button = 16, Shortcut = 17, Gesture = 18, EmojiPicker = 19, SettingRoot = 20, Setting = 21, Menu = 22, PluginRoot = 100, Plugin = 101, } pub struct SceneNode { pub name: String, pub id: SceneNodeId, pub typ: SceneNodeType, parent: SyncRwLock>>, children: SyncRwLock>, pub props: Vec, pub sigs: SyncRwLock>, pub methods: Vec, pub pimpl: OnceLock, } impl SceneNode { pub fn root() -> SceneNodePtr { Arc::new(Self::new("", SceneNodeType::Root)) } pub fn new>(name: S, typ: SceneNodeType) -> Self { Self { name: name.into(), id: rand::random(), typ, parent: SyncRwLock::new(None), children: SyncRwLock::new(vec![]), props: vec![], sigs: SyncRwLock::new(vec![]), methods: vec![], pimpl: OnceLock::new(), } } pub async fn setup(self, pimpl_fn: F) -> Arc where F: FnOnce(SceneNodeWeak) -> Fut, Fut: Future, { let self_ = Arc::new(self); let weak_self = Arc::downgrade(&self_); // Initial props for prop in &self_.props { prop.set_parent(weak_self.clone()); } let pimpl = pimpl_fn(weak_self).await; assert_eq!(Arc::strong_count(&self_), 1); self_.pimpl.set(pimpl).unwrap(); self_ } pub fn setup_null(self) -> Arc { let self_ = Arc::new(self); let weak_self = Arc::downgrade(&self_); // Initial props for prop in &self_.props { prop.set_parent(weak_self.clone()); } assert_eq!(Arc::strong_count(&self_), 1); self_.pimpl.set(Pimpl::Null).unwrap(); self_ } pub fn pimpl<'a>(&'a self) -> &'a Pimpl { self.pimpl.get().unwrap() } pub fn link(self: &Arc, child: SceneNodePtr) { let mut childs_parent = child.parent.write().unwrap(); assert!(childs_parent.is_none()); *childs_parent = Some(Arc::downgrade(&self)); drop(childs_parent); let mut children = self.children.write().unwrap(); children.push(child); } pub fn get_children(&self) -> Vec { self.children.read().unwrap().clone() } pub fn lookup_node>(self: &Arc, path: P) -> Option { let path: ScenePath = path.into(); let mut path = path.0; if path.is_empty() { return Some(self.clone()) } let child_name = path.pop_front().unwrap(); for child in self.get_children() { if child.name == child_name { let path = ScenePath(path); return child.lookup_node(path) } } None } fn has_property(&self, name: &str) -> bool { self.props.iter().any(|prop| prop.name == name) } pub fn add_property(&mut self, prop: Property) -> Result<()> { if self.has_property(&prop.name) { return Err(Error::PropertyAlreadyExists) } self.props.push(Arc::new(prop)); Ok(()) } pub fn get_property(&self, name: &str) -> Option { self.props.iter().find(|prop| prop.name == name).map(|prop| prop.clone()) } // Convenience methods pub fn get_property_bool(&self, name: &str) -> Result { self.get_property(name).ok_or(Error::PropertyNotFound)?.get_bool(0) } pub fn get_property_u32(&self, name: &str) -> Result { self.get_property(name).ok_or(Error::PropertyNotFound)?.get_u32(0) } pub fn get_property_f32(&self, name: &str) -> Result { self.get_property(name).ok_or(Error::PropertyNotFound)?.get_f32(0) } pub fn get_property_str(&self, name: &str) -> Result { self.get_property(name).ok_or(Error::PropertyNotFound)?.get_str(0) } pub fn get_property_enum(&self, name: &str) -> Result { self.get_property(name).ok_or(Error::PropertyNotFound)?.get_enum(0) } pub fn get_property_node_id(&self, name: &str) -> Result { self.get_property(name).ok_or(Error::PropertyNotFound)?.get_node_id(0) } // Setters pub fn set_property_bool( &self, atom: &mut PropertyAtomicGuard, role: Role, name: &str, val: bool, ) -> Result<()> { self.get_property(name).ok_or(Error::PropertyNotFound)?.set_bool(atom, role, 0, val) } pub fn set_property_u32( &self, atom: &mut PropertyAtomicGuard, role: Role, name: &str, val: u32, ) -> Result<()> { self.get_property(name).ok_or(Error::PropertyNotFound)?.set_u32(atom, role, 0, val) } pub fn set_property_f32( &self, atom: &mut PropertyAtomicGuard, role: Role, name: &str, val: f32, ) -> Result<()> { self.get_property(name).ok_or(Error::PropertyNotFound)?.set_f32(atom, role, 0, val) } pub fn set_property_str>( &self, atom: &mut PropertyAtomicGuard, role: Role, name: &str, val: S, ) -> Result<()> { self.get_property(name).ok_or(Error::PropertyNotFound)?.set_str(atom, role, 0, val) } pub fn set_property_node_id( &self, atom: &mut PropertyAtomicGuard, role: Role, name: &str, val: SceneNodeId, ) -> Result<()> { self.get_property(name).ok_or(Error::PropertyNotFound)?.set_node_id(atom, role, 0, val) } pub fn set_property_f32_vec( &self, atom: &mut PropertyAtomicGuard, role: Role, name: &str, val: Vec, ) -> Result<()> { self.get_property(name).ok_or(Error::PropertyNotFound)?.set_f32_vec(atom, role, val) } pub fn add_signal>( &mut self, name: S, desc: S, fmt: Vec<(S, S, CallArgType)>, ) -> Result<()> { let name = name.into(); if self.has_signal(&name) { return Err(Error::SignalAlreadyExists) } let fmt = fmt .into_iter() .map(|(n, d, t)| CallArg { name: n.into(), desc: d.into(), typ: t }) .collect(); let mut sigs = self.sigs.write().unwrap(); sigs.push(Arc::new(Signal { name: name.into(), desc: desc.into(), fmt, slots: SyncRwLock::new(HashMap::new()), })); Ok(()) } fn has_signal(&self, name: &str) -> bool { let sigs = self.sigs.read().unwrap(); sigs.iter().any(|sig| sig.name == name) } pub fn get_signal(&self, name: &str) -> Option { let sigs = self.sigs.read().unwrap(); sigs.iter().find(|sig| sig.name == name).cloned() } pub fn register(&self, sig_name: &str, slot: Slot) -> Result { let slot_id = rand::random(); let sig = self.get_signal(sig_name).ok_or(Error::SignalNotFound)?; let mut slots = sig.slots.write().unwrap(); slots.insert(slot_id, slot); Ok(slot_id) } pub fn unregister(&self, sig_name: &str, slot_id: SlotId) -> Result<()> { let sig = self.get_signal(sig_name).ok_or(Error::SignalNotFound)?; let mut slots = sig.slots.write().unwrap(); slots.remove(&slot_id).ok_or(Error::SlotNotFound)?; Ok(()) } pub async fn trigger(&self, sig_name: &str, data: Vec) -> Result<()> { t!("trigger({sig_name}, {data:?}) [node={self:?}]"); let sig = self.get_signal(sig_name).ok_or(Error::SignalNotFound)?; let futures = FuturesUnordered::new(); let slots: Vec<_> = sig.slots.read().unwrap().values().cloned().collect(); // TODO: autoremove failed slots for slot in slots { t!(" triggering {}", slot.name); // Trigger the slot let data = data.clone(); futures.push(async move { slot.notify.send(data).await.is_ok() }); } let success: Vec<_> = futures.collect().await; t!("trigger success: {success:?}"); Ok(()) } pub fn add_method>( &mut self, name: S, args: Vec<(S, S, CallArgType)>, result: Option>, ) -> Result<()> { let name = name.into(); if self.has_method(&name) { return Err(Error::MethodAlreadyExists) } let args = args .into_iter() .map(|(n, d, t)| CallArg { name: n.into(), desc: d.into(), typ: t }) .collect(); let result = match result { Some(result) => Some( result .into_iter() .map(|(n, d, t)| CallArg { name: n.into(), desc: d.into(), typ: t }) .collect(), ), None => None, }; self.methods.push(Method::new(name.into(), args, result)); Ok(()) } fn has_method(&self, name: &str) -> bool { self.methods.iter().any(|sig| sig.name == name) } pub fn get_method(&self, name: &str) -> Option<&Method> { self.methods.iter().find(|method| method.name == name) } pub async fn call_method(&self, name: &str, arg_data: CallData) -> Result> { let method = self.get_method(name).ok_or(Error::MethodNotFound)?; Ok(method.call(arg_data).await) } pub fn subscribe_method_call(&self, name: &str) -> Result { let method = self.get_method(name).ok_or(Error::MethodNotFound)?; let method_sub = method.pubsub.clone().subscribe(); Ok(method_sub) } pub fn get_full_path(&self) -> Option { let subpath = "/".to_string() + &self.name; let Some(parent_weak) = self.parent.read().unwrap().clone() else { return Some(subpath) }; let Some(parent) = parent_weak.upgrade() else { return None }; // Handle root / if parent.typ == SceneNodeType::Root { return Some(subpath) } Some(parent.get_full_path()? + &subpath) } } impl std::fmt::Debug for SceneNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(path) = self.get_full_path() { write!(f, "{path}") } else { write!(f, "{}:{}", self.name, self.id) } } } #[derive(Debug, Clone, SerialEncodable, SerialDecodable)] pub enum CallArgType { Uint32, Uint64, Float32, Bool, Str, Hash, } #[derive(Debug, Clone, SerialEncodable, SerialDecodable)] pub struct CallArg { pub name: String, pub desc: String, pub typ: CallArgType, } pub type CallData = Vec; pub type SlotId = u32; #[derive(Clone)] pub struct Slot { pub name: String, pub notify: Sender, } impl Slot { pub fn new>(name: S) -> (Self, Receiver) { let (notify, recvr) = async_channel::unbounded(); let self_ = Self { name: name.into(), notify }; (self_, recvr) } } type SignalPtr = Arc; pub struct Signal { pub name: String, #[allow(dead_code)] pub desc: String, #[allow(dead_code)] pub fmt: Vec, slots: SyncRwLock>, } #[derive(Clone, Debug)] pub struct MethodCall { pub data: CallData, pub send_res: Option>, } impl MethodCall { fn new(data: CallData, send_res: Option>) -> Self { Self { data, send_res } } } pub type MethodCallSub = Subscription; pub struct Method { pub name: String, pub args: Vec, pub result: Option>, pub pubsub: PublisherPtr, } impl Method { fn new(name: String, args: Vec, result: Option>) -> Self { Self { name, args, result, pubsub: Publisher::new() } } async fn call(&self, data: CallData) -> Option { match &self.result { Some(_) => { let (send_res, recv_res) = async_channel::bounded(1); self.pubsub.notify(MethodCall::new(data, Some(send_res))); Some(recv_res.recv().await.unwrap()) } None => { self.pubsub.notify(MethodCall::new(data, None)); None } } } } pub enum Pimpl { Null, Window(ui::WindowPtr), Layer(ui::LayerPtr), VectorArt(ui::VectorArtPtr), Text(ui::TextPtr), Edit(ui::BaseEditPtr), ChatView(ui::ChatViewPtr), Image(ui::ImagePtr), Video(ui::VideoPtr), Button(ui::ButtonPtr), Shortcut(ui::ShortcutPtr), Gesture(ui::GesturePtr), EmojiPicker(ui::EmojiPickerPtr), Menu(ui::MenuPtr), DarkIrc(plugin::DarkIrcPtr), Fud(plugin::FudPtr), } impl std::fmt::Debug for Pimpl { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Pimpl") } }