plugin.old.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2025 Dyne.org foundation 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation, either version 3 of the 8 * License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Affero General Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 use darkfi_serial::{Decodable, Encodable}; 20 use std::{ 21 io::Cursor, 22 sync::{mpsc, Arc, Mutex}, 23 thread, 24 time::{Duration, Instant}, 25 }; 26 27 use crate::{ 28 error::Result, 29 prop::{Property, PropertySubType, PropertyType}, 30 py::PythonPlugin, 31 res::{ResourceId, ResourceManager}, 32 scene::{MethodResponseFn, SceneGraph, SceneGraphPtr, SceneNodeId, SceneNodeType}, 33 }; 34 35 pub enum Category { 36 Null, 37 } 38 39 pub enum SubCategory { 40 Null, 41 } 42 43 pub struct SemVer { 44 pub major: u32, 45 pub minor: u32, 46 pub patch: u32, 47 pub pre: String, 48 pub build: String, 49 } 50 51 pub struct PluginMetadata { 52 pub name: String, 53 pub title: String, 54 pub desc: String, 55 pub author: String, 56 pub version: SemVer, 57 58 pub cat: Category, 59 pub subcat: SubCategory, 60 // icon 61 62 // Permissions 63 // whitelisted nodes + props/methods (use * for all) 64 // /window/input/* 65 } 66 67 pub enum PluginEvent { 68 // (signal_data, user_data) 69 RecvSignal((Vec<u8>, Vec<u8>)), 70 } 71 72 pub type PluginInstancePtr = Arc<Mutex<Box<dyn PluginInstance + Send>>>; 73 74 pub trait Plugin { 75 fn metadata(&self) -> PluginMetadata; 76 // Spawns a new context and begins running the plugin in that context 77 fn start(&self) -> Result<PluginInstancePtr>; 78 } 79 80 pub trait PluginInstance { 81 fn update(&mut self, event: PluginEvent) -> Result<()>; 82 } 83 84 enum SentinelMethodEvent { 85 ImportPlugin, 86 StartPlugin(ResourceId), 87 } 88 89 pub struct Sentinel { 90 scene_graph: SceneGraphPtr, 91 plugins: ResourceManager<Box<dyn Plugin>>, 92 insts: ResourceManager<PluginInstancePtr>, 93 94 method_recvr: mpsc::Receiver<(SentinelMethodEvent, SceneNodeId, Vec<u8>, MethodResponseFn)>, 95 method_sender: mpsc::SyncSender<(SentinelMethodEvent, SceneNodeId, Vec<u8>, MethodResponseFn)>, 96 } 97 98 impl Sentinel { 99 pub fn new(scene_graph: SceneGraphPtr) -> Self { 100 // Create /plugin in scene graph 101 // 102 // Methods provided in SceneGraph under /plugin: 103 // 104 // * import_plugin(pycode) 105 106 let mut sg = scene_graph.lock().unwrap(); 107 let (method_sender, method_recvr) = mpsc::sync_channel(100); 108 109 let node = sg.add_node("plugin", SceneNodeType::Plugins); 110 111 let sender = method_sender.clone(); 112 let node_id = node.id; 113 let method_fn = Box::new(move |arg_data, response_fn| { 114 sender.send((SentinelMethodEvent::ImportPlugin, node_id, arg_data, response_fn)); 115 }); 116 node.add_method("import", vec![("pycode", "", PropertyType::Str)], vec![], method_fn); 117 118 sg.link(node_id, SceneGraph::ROOT_ID).unwrap(); 119 drop(sg); 120 121 Self { 122 scene_graph, 123 plugins: ResourceManager::new(), 124 insts: ResourceManager::new(), 125 method_recvr, 126 method_sender, 127 } 128 } 129 130 pub fn run(&mut self) { 131 loop { 132 // Monitor all running plugins 133 // Check last update times 134 // Kill any slowpokes 135 136 // Check any SceneGraph method requests 137 let deadline = Instant::now() + Duration::from_millis(4000); 138 139 let Ok((event, node_id, arg_data, response_fn)) = 140 self.method_recvr.recv_deadline(deadline) 141 else { 142 break 143 }; 144 let res = match event { 145 SentinelMethodEvent::ImportPlugin => self.import_py_plugin(node_id, arg_data), 146 SentinelMethodEvent::StartPlugin(rid) => self.start_plugin(rid, node_id, arg_data), 147 }; 148 response_fn(res); 149 } 150 } 151 152 fn import_py_plugin(&mut self, node_id: SceneNodeId, arg_data: Vec<u8>) -> Result<Vec<u8>> { 153 // Load the python code 154 let mut cur = Cursor::new(&arg_data); 155 let pycode = String::decode(&mut cur).unwrap(); 156 157 let plugin = Box::new(PythonPlugin::new(self.scene_graph.clone(), pycode)); 158 self.import_plugin(plugin)?; 159 160 // This function doesn't return anything 161 // Only success or an Err which is already handled elsewhere 162 Ok(vec![]) 163 } 164 165 fn import_plugin(&mut self, plugin: Box<dyn Plugin>) -> Result<()> { 166 let metadata = plugin.metadata(); 167 let plugin_rid = self.plugins.alloc(plugin); 168 169 let mut scene_graph = self.scene_graph.lock().unwrap(); 170 171 // Create /plugin/foo 172 173 let node = scene_graph.add_node(metadata.name.clone(), SceneNodeType::Plugin); 174 let node_id = node.id; 175 176 // name 177 let mut prop = Property::new("name", PropertyType::Str, PropertySubType::Null); 178 prop.set_str(0, metadata.name); 179 node.add_property(prop).unwrap(); 180 // title 181 let mut prop = Property::new("title", PropertyType::Str, PropertySubType::Null); 182 prop.set_str(0, metadata.title); 183 node.add_property(prop).unwrap(); 184 // desc 185 let mut prop = Property::new("desc", PropertyType::Str, PropertySubType::Null); 186 prop.set_str(0, metadata.desc); 187 node.add_property(prop).unwrap(); 188 // author 189 let mut prop = Property::new("author", PropertyType::Str, PropertySubType::Null); 190 prop.set_str(0, metadata.author); 191 node.add_property(prop).unwrap(); 192 // version 193 let mut prop = Property::new("version", PropertyType::Uint32, PropertySubType::Null); 194 prop.set_array_len(3); 195 prop.set_u32(0, metadata.version.major); 196 prop.set_u32(1, metadata.version.minor); 197 prop.set_u32(2, metadata.version.patch); 198 node.add_property(prop).unwrap(); 199 // TODO: add version.pre and patch, and cat/subcat enums 200 201 let mut prop = Property::new("insts", PropertyType::Uint32, PropertySubType::ResourceId); 202 prop.set_ui_text("instance resource IDs", "The currently running instances of this plugin"); 203 prop.set_unbounded(); 204 node.add_property(prop).unwrap(); 205 206 // Add method start() 207 208 let sender = self.method_sender.clone(); 209 let method_fn = Box::new(move |arg_data, response_fn| { 210 sender.send(( 211 SentinelMethodEvent::StartPlugin(plugin_rid), 212 node_id, 213 arg_data, 214 response_fn, 215 )); 216 }); 217 node.add_method("start", vec![], vec![("inst_rid", "", PropertyType::Uint32)], method_fn); 218 219 // Link node 220 221 let parent_id = scene_graph.lookup_node_id("/plugin").expect("no plugin node attached"); 222 scene_graph.link(node_id, parent_id).unwrap(); 223 224 Ok(()) 225 } 226 227 fn start_plugin( 228 &mut self, 229 plugin_rid: ResourceId, 230 node_id: SceneNodeId, 231 arg_data: Vec<u8>, 232 ) -> Result<Vec<u8>> { 233 let plugin = self.plugins.get(plugin_rid).expect("plugin not found"); 234 235 // Call init() 236 // Spawn a new thread, allocate it an ID 237 // Thread waits for events from the scene_graph and calls update() when they occur. 238 // See src/net.rs:81 for an example 239 let inst = plugin.start()?; 240 let inst2 = inst.clone(); 241 let inst_rid = self.insts.alloc(inst); 242 243 let _ = thread::spawn(move || { 244 inst2.lock().unwrap().update(PluginEvent::RecvSignal((vec![], vec![]))).unwrap(); 245 }); 246 247 let scene_graph = self.scene_graph.lock().unwrap(); 248 let node = scene_graph.get_node(node_id).expect("node not found"); 249 let prop = node.get_property("insts").unwrap(); 250 prop.push_u32(inst_rid)?; 251 252 // TODO: when the plugin finishes, the instance ID should be cleared up somehow 253 // both from the resource manager and from the property 254 // https://www.chromium.org/developers/design-documents/inter-process-communication/ 255 256 let mut reply = vec![]; 257 inst_rid.encode(&mut reply).unwrap(); 258 Ok(reply) 259 } 260 }