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