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 pub fn new_with_render_bridge(
156 bridge: Rc<RefCell<super::render_ops::RenderBridgeState>>,
157 ) -> Self {
158 Self::new_with_render_bridge_and_import_map(bridge, ImportMap::new())
159 }
160
161 pub fn new_with_render_bridge_and_import_map(
162 bridge: Rc<RefCell<super::render_ops::RenderBridgeState>>,
163 import_map: ImportMap,
164 ) -> Self {
165 let runtime = JsRuntime::new(RuntimeOptions {
166 module_loader: Some(Rc::new(TsModuleLoader::with_import_map(import_map))),
167 extensions: vec![
168 arcane_ext::init(),
169 super::render_ops::render_ext::init(),
170 ],
171 ..Default::default()
172 });
173
174 let mut rt = Self { runtime };
175
176 {
178 let op_state = rt.runtime.op_state();
179 op_state.borrow_mut().put(bridge);
180 }
181
182 rt.runtime
183 .execute_script("<crypto_polyfill>", CRYPTO_POLYFILL)
184 .expect("Failed to install crypto polyfill");
185 rt
186 }
187
188 pub fn execute_script_string(
190 &mut self,
191 name: &'static str,
192 source: impl Into<String>,
193 ) -> anyhow::Result<()> {
194 let source: String = source.into();
195 self.runtime
196 .execute_script(name, deno_core::FastString::from(source))
197 .context("Script execution failed")?;
198 Ok(())
199 }
200
201 pub fn eval_to_string(&mut self, source: &str) -> anyhow::Result<String> {
204 let script = format!(
206 "Deno.core.ops.op_agent_store_eval_result(String({}))",
207 source
208 );
209 self.runtime
210 .execute_script(
211 "<agent_eval>",
212 deno_core::FastString::from(script),
213 )
214 .context("Agent eval failed")?;
215
216 let op_state = self.runtime.op_state();
218 let result = op_state
219 .borrow_mut()
220 .take::<AgentEvalResult>()
221 .0;
222 Ok(result)
223 }
224
225 pub fn inner(&mut self) -> &mut JsRuntime {
227 &mut self.runtime
228 }
229}