arcane_engine/scripting/
runtime.rs1#[cfg(feature = "renderer")]
2use std::cell::RefCell;
3use std::path::Path;
4use std::rc::Rc;
5
6use anyhow::Context;
7use deno_core::JsRuntime;
8use deno_core::ModuleSpecifier;
9use deno_core::OpState;
10use deno_core::RuntimeOptions;
11
12use super::{ImportMap, TsModuleLoader};
13
14pub struct ArcaneRuntime {
16 runtime: JsRuntime,
17}
18
19struct AgentEvalResult(String);
21
22deno_core::extension!(
23 arcane_ext,
24 ops = [op_crypto_random_uuid, op_agent_store_eval_result],
25);
26
27#[deno_core::op2]
29#[string]
30fn op_crypto_random_uuid() -> String {
31 generate_uuid()
32}
33
34#[deno_core::op2(fast)]
36fn op_agent_store_eval_result(state: &mut OpState, #[string] value: &str) {
37 if state.has::<AgentEvalResult>() {
39 state.take::<AgentEvalResult>();
40 }
41 state.put(AgentEvalResult(value.to_string()));
42}
43
44pub(super) fn generate_uuid() -> String {
46 let mut bytes = [0u8; 16];
47 getrandom(&mut bytes);
48 bytes[6] = (bytes[6] & 0x0f) | 0x40;
49 bytes[8] = (bytes[8] & 0x3f) | 0x80;
50
51 format!(
52 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
53 bytes[0], bytes[1], bytes[2], bytes[3],
54 bytes[4], bytes[5],
55 bytes[6], bytes[7],
56 bytes[8], bytes[9],
57 bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
58 )
59}
60
61fn getrandom(buf: &mut [u8]) {
63 use std::collections::hash_map::RandomState;
64 use std::hash::{BuildHasher, Hasher};
65 let mut i = 0;
66 while i < buf.len() {
67 let s = RandomState::new();
68 let mut h = s.build_hasher();
69 h.write_u64(i as u64);
70 let bytes = h.finish().to_le_bytes();
71 let remaining = buf.len() - i;
72 let copy_len = remaining.min(8);
73 buf[i..i + copy_len].copy_from_slice(&bytes[..copy_len]);
74 i += copy_len;
75 }
76}
77
78const CRYPTO_POLYFILL: &str = r#"
79if (typeof globalThis.crypto === "undefined") {
80 globalThis.crypto = {};
81}
82if (typeof globalThis.crypto.randomUUID !== "function") {
83 globalThis.crypto.randomUUID = () => Deno.core.ops.op_crypto_random_uuid();
84}
85"#;
86
87impl ArcaneRuntime {
88 pub fn new() -> Self {
90 Self::new_with_import_map(ImportMap::new())
91 }
92
93 pub fn new_with_import_map(import_map: ImportMap) -> Self {
95 let runtime = JsRuntime::new(RuntimeOptions {
96 module_loader: Some(Rc::new(TsModuleLoader::with_import_map(import_map))),
97 extensions: vec![arcane_ext::init()],
98 ..Default::default()
99 });
100
101 let mut rt = Self { runtime };
102 rt.runtime
103 .execute_script("<crypto_polyfill>", CRYPTO_POLYFILL)
104 .expect("Failed to install crypto polyfill");
105 rt
106 }
107
108 pub fn execute_script(
110 &mut self,
111 name: &'static str,
112 source: &'static str,
113 ) -> anyhow::Result<()> {
114 self.runtime
115 .execute_script(name, source)
116 .context("Script execution failed")?;
117 Ok(())
118 }
119
120 pub fn execute_script_global(
122 &mut self,
123 name: &'static str,
124 source: &'static str,
125 ) -> anyhow::Result<deno_core::v8::Global<deno_core::v8::Value>> {
126 self.runtime
127 .execute_script(name, source)
128 .map_err(|e| anyhow::anyhow!("{e}"))
129 }
130
131 pub async fn execute_file(&mut self, path: &Path) -> anyhow::Result<()> {
133 let specifier = ModuleSpecifier::from_file_path(path).map_err(|_| {
134 anyhow::anyhow!("Cannot convert path to module specifier: {}", path.display())
135 })?;
136
137 let mod_id = self
138 .runtime
139 .load_main_es_module(&specifier)
140 .await
141 .context("Failed to load module")?;
142
143 let result = self.runtime.mod_evaluate(mod_id);
144 self.runtime
145 .run_event_loop(Default::default())
146 .await
147 .context("Event loop error")?;
148 result.await.context("Module evaluation failed")?;
149 Ok(())
150 }
151
152 #[cfg(feature = "renderer")]
155 #[cfg(feature = "renderer")]
156 pub fn new_with_render_bridge(
157 bridge: Rc<RefCell<super::render_ops::RenderBridgeState>>,
158 ) -> Self {
159 Self::new_with_render_bridge_and_import_map(bridge, ImportMap::new())
160 }
161
162 #[cfg(feature = "renderer")]
163 pub fn new_with_render_bridge_and_import_map(
164 bridge: Rc<RefCell<super::render_ops::RenderBridgeState>>,
165 import_map: ImportMap,
166 ) -> Self {
167 let runtime = JsRuntime::new(RuntimeOptions {
168 module_loader: Some(Rc::new(TsModuleLoader::with_import_map(import_map))),
169 extensions: vec![
170 arcane_ext::init(),
171 super::render_ops::render_ext::init(),
172 ],
173 ..Default::default()
174 });
175
176 let mut rt = Self { runtime };
177
178 {
180 let op_state = rt.runtime.op_state();
181 op_state.borrow_mut().put(bridge);
182 }
183
184 rt.runtime
185 .execute_script("<crypto_polyfill>", CRYPTO_POLYFILL)
186 .expect("Failed to install crypto polyfill");
187 rt
188 }
189
190 pub fn execute_script_string(
192 &mut self,
193 name: &'static str,
194 source: impl Into<String>,
195 ) -> anyhow::Result<()> {
196 let source: String = source.into();
197 self.runtime
198 .execute_script(name, deno_core::FastString::from(source))
199 .context("Script execution failed")?;
200 Ok(())
201 }
202
203 pub fn eval_to_string(&mut self, source: &str) -> anyhow::Result<String> {
206 let script = format!(
208 "Deno.core.ops.op_agent_store_eval_result(String({}))",
209 source
210 );
211 self.runtime
212 .execute_script(
213 "<agent_eval>",
214 deno_core::FastString::from(script),
215 )
216 .context("Agent eval failed")?;
217
218 let op_state = self.runtime.op_state();
220 let result = op_state
221 .borrow_mut()
222 .take::<AgentEvalResult>()
223 .0;
224 Ok(result)
225 }
226
227 pub fn inner(&mut self) -> &mut JsRuntime {
229 &mut self.runtime
230 }
231}