arcane_core/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 super::geometry_ops::geometry_ext::init(),
182 super::particle_ops::particle_ext::init(),
183 super::target_ops::target_ext::init(),
184 super::sdf_ops::sdf_ext::init(),
185 ],
186 ..Default::default()
187 });
188
189 let mut rt = Self { runtime };
190
191 {
193 let op_state = rt.runtime.op_state();
194 let mut state = op_state.borrow_mut();
195 state.put(bridge);
196 state.put(Rc::new(RefCell::new(super::physics_ops::PhysicsState(None))));
197 state.put(Rc::new(RefCell::new(super::geometry_ops::GeoState::new())));
198 state.put(Rc::new(RefCell::new(super::particle_ops::ParticleState::new())));
199 state.put(Rc::new(RefCell::new(super::target_ops::TargetState::new())));
200 state.put(Rc::new(RefCell::new(super::sdf_ops::SdfState::new())));
201 }
202
203 rt.runtime
204 .execute_script("<crypto_polyfill>", CRYPTO_POLYFILL)
205 .expect("Failed to install crypto polyfill");
206 rt
207 }
208
209 pub fn execute_script_string(
211 &mut self,
212 name: &'static str,
213 source: impl Into<String>,
214 ) -> anyhow::Result<()> {
215 let source: String = source.into();
216 self.runtime
217 .execute_script(name, deno_core::FastString::from(source))
218 .context("Script execution failed")?;
219 Ok(())
220 }
221
222 pub fn eval_to_string(&mut self, source: &str) -> anyhow::Result<String> {
225 let script = format!(
227 "Deno.core.ops.op_agent_store_eval_result(String({}))",
228 source
229 );
230 self.runtime
231 .execute_script(
232 "<agent_eval>",
233 deno_core::FastString::from(script),
234 )
235 .context("Agent eval failed")?;
236
237 let op_state = self.runtime.op_state();
239 let result = op_state
240 .borrow_mut()
241 .take::<AgentEvalResult>()
242 .0;
243 Ok(result)
244 }
245
246 pub fn inner(&mut self) -> &mut JsRuntime {
248 &mut self.runtime
249 }
250}