1use std::collections::{HashMap, HashSet};
2use std::sync::mpsc;
3use std::thread;
4
5use crate::error::{Result, XriptError};
6use crate::fragment::ModInstance;
7use crate::sandbox::{ExecutionResult, RuntimeOptions};
8
9enum Command {
10 Execute {
11 code: String,
12 tx: mpsc::Sender<Result<ExecutionResult>>,
13 },
14 LoadMod {
15 mod_manifest_json: String,
16 fragment_sources: HashMap<String, String>,
17 granted_capabilities: HashSet<String>,
18 entry_source: Option<String>,
19 tx: mpsc::Sender<Result<ModInstance>>,
20 },
21 ManifestName {
22 tx: mpsc::Sender<String>,
23 },
24 Shutdown,
25}
26
27pub struct XriptHandle {
28 cmd_tx: mpsc::Sender<Command>,
29 thread: Option<thread::JoinHandle<()>>,
30}
31
32impl XriptHandle {
33 pub fn new(manifest_json: String, options: RuntimeOptions) -> Result<Self> {
34 let (cmd_tx, cmd_rx) = mpsc::channel::<Command>();
35 let (init_tx, init_rx) = mpsc::channel::<Result<()>>();
36
37 let thread = thread::spawn(move || {
38 let rt = match crate::create_runtime(&manifest_json, options) {
39 Ok(rt) => {
40 let _ = init_tx.send(Ok(()));
41 rt
42 }
43 Err(e) => {
44 let _ = init_tx.send(Err(e));
45 return;
46 }
47 };
48
49 while let Ok(cmd) = cmd_rx.recv() {
50 match cmd {
51 Command::Execute { code, tx } => {
52 let _ = tx.send(rt.execute(&code));
53 }
54 Command::LoadMod {
55 mod_manifest_json,
56 fragment_sources,
57 granted_capabilities,
58 entry_source,
59 tx,
60 } => {
61 let _ = tx.send(rt.load_mod(
62 &mod_manifest_json,
63 fragment_sources,
64 &granted_capabilities,
65 entry_source.as_deref(),
66 ));
67 }
68 Command::ManifestName { tx } => {
69 let _ = tx.send(rt.manifest().name.clone());
70 }
71 Command::Shutdown => break,
72 }
73 }
74 });
75
76 init_rx
77 .recv()
78 .map_err(|_| XriptError::Engine("runtime thread panicked during init".into()))??;
79
80 Ok(Self {
81 cmd_tx,
82 thread: Some(thread),
83 })
84 }
85
86 pub fn execute(&self, code: &str) -> Result<ExecutionResult> {
87 let (tx, rx) = mpsc::channel();
88 self.cmd_tx
89 .send(Command::Execute {
90 code: code.to_string(),
91 tx,
92 })
93 .map_err(|_| XriptError::Engine("runtime thread is gone".into()))?;
94 rx.recv()
95 .map_err(|_| XriptError::Engine("runtime thread dropped response".into()))?
96 }
97
98 pub fn load_mod(
99 &self,
100 mod_manifest_json: &str,
101 fragment_sources: HashMap<String, String>,
102 granted_capabilities: &HashSet<String>,
103 entry_source: Option<&str>,
104 ) -> Result<ModInstance> {
105 let (tx, rx) = mpsc::channel();
106 self.cmd_tx
107 .send(Command::LoadMod {
108 mod_manifest_json: mod_manifest_json.to_string(),
109 fragment_sources,
110 granted_capabilities: granted_capabilities.clone(),
111 entry_source: entry_source.map(|s| s.to_string()),
112 tx,
113 })
114 .map_err(|_| XriptError::Engine("runtime thread is gone".into()))?;
115 rx.recv()
116 .map_err(|_| XriptError::Engine("runtime thread dropped response".into()))?
117 }
118
119 pub fn manifest_name(&self) -> Result<String> {
120 let (tx, rx) = mpsc::channel();
121 self.cmd_tx
122 .send(Command::ManifestName { tx })
123 .map_err(|_| XriptError::Engine("runtime thread is gone".into()))?;
124 rx.recv()
125 .map_err(|_| XriptError::Engine("runtime thread dropped response".into()))
126 }
127}
128
129impl Drop for XriptHandle {
130 fn drop(&mut self) {
131 let _ = self.cmd_tx.send(Command::Shutdown);
132 if let Some(thread) = self.thread.take() {
133 let _ = thread.join();
134 }
135 }
136}