/ bin / app / src / plugin.old.rs
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  }