1use std::cell::RefCell;
2use std::path::PathBuf;
3use std::rc::Rc;
4
5use deno_core::OpState;
6
7use crate::renderer::SpriteCommand;
8use crate::renderer::TilemapStore;
9use crate::renderer::PointLight;
10use crate::renderer::camera::CameraBounds;
11use crate::renderer::msdf::MsdfFontStore;
12
13#[derive(Clone, Debug)]
15pub enum BridgeAudioCommand {
16 LoadSound { id: u32, path: String },
17 PlaySound { id: u32, volume: f32, looping: bool },
18 StopSound { id: u32 },
19 StopAll,
20 SetMasterVolume { volume: f32 },
21
22 PlaySoundEx {
24 sound_id: u32,
25 instance_id: u64,
26 volume: f32,
27 looping: bool,
28 bus: u32,
29 pan: f32,
30 pitch: f32,
31 low_pass_freq: u32,
32 reverb_mix: f32,
33 reverb_delay_ms: u32,
34 },
35 PlaySoundSpatial {
36 sound_id: u32,
37 instance_id: u64,
38 volume: f32,
39 looping: bool,
40 bus: u32,
41 pitch: f32,
42 source_x: f32,
43 source_y: f32,
44 listener_x: f32,
45 listener_y: f32,
46 },
47 StopInstance { instance_id: u64 },
48 SetInstanceVolume { instance_id: u64, volume: f32 },
49 SetInstancePitch { instance_id: u64, pitch: f32 },
50 UpdateSpatialPositions {
51 updates: Vec<(u64, f32, f32)>, listener_x: f32,
53 listener_y: f32,
54 },
55 SetBusVolume { bus: u32, volume: f32 },
56}
57
58#[derive(Clone)]
61pub struct RenderBridgeState {
62 pub sprite_commands: Vec<SpriteCommand>,
63 pub camera_x: f32,
64 pub camera_y: f32,
65 pub camera_zoom: f32,
66 pub camera_dirty: bool,
68 pub delta_time: f64,
69 pub elapsed_time: f64,
71 pub keys_down: std::collections::HashSet<String>,
73 pub keys_pressed: std::collections::HashSet<String>,
74 pub mouse_x: f32,
75 pub mouse_y: f32,
76 pub mouse_buttons_down: std::collections::HashSet<u8>,
77 pub mouse_buttons_pressed: std::collections::HashSet<u8>,
78 pub gamepad_buttons_down: std::collections::HashSet<String>,
80 pub gamepad_buttons_pressed: std::collections::HashSet<String>,
82 pub gamepad_axes: std::collections::HashMap<String, f32>,
84 pub gamepad_count: u32,
86 pub gamepad_name: String,
88 pub touch_points: Vec<(u64, f32, f32)>,
90 pub touch_count: u32,
92 pub texture_load_queue: Vec<(String, u32)>,
94 pub base_dir: PathBuf,
96 pub next_texture_id: u32,
98 pub texture_path_to_id: std::collections::HashMap<String, u32>,
100 pub tilemaps: TilemapStore,
102 pub ambient_light: [f32; 3],
104 pub point_lights: Vec<PointLight>,
106 pub audio_commands: Vec<BridgeAudioCommand>,
108 pub next_sound_id: u32,
110 pub sound_path_to_id: std::collections::HashMap<String, u32>,
112 pub font_texture_queue: Vec<u32>,
114 pub viewport_width: f32,
116 pub viewport_height: f32,
117 pub scale_factor: f32,
119 pub clear_color: [f32; 4],
121 pub save_dir: PathBuf,
123 pub shader_create_queue: Vec<(u32, String, String)>,
125 pub shader_param_queue: Vec<(u32, u32, [f32; 4])>,
127 pub next_shader_id: u32,
129 pub effect_create_queue: Vec<(u32, String)>,
131 pub effect_param_queue: Vec<(u32, u32, [f32; 4])>,
133 pub effect_remove_queue: Vec<u32>,
135 pub effect_clear: bool,
137 pub next_effect_id: u32,
139 pub camera_bounds: Option<CameraBounds>,
141 pub gi_enabled: bool,
143 pub gi_intensity: f32,
145 pub gi_probe_spacing: Option<f32>,
147 pub gi_interval: Option<f32>,
149 pub gi_cascade_count: Option<u32>,
151 pub emissives: Vec<[f32; 8]>,
153 pub occluders: Vec<[f32; 4]>,
155 pub directional_lights: Vec<[f32; 5]>,
157 pub spot_lights: Vec<[f32; 9]>,
159 pub msdf_fonts: MsdfFontStore,
161 pub msdf_builtin_queue: Vec<(u32, u32)>,
163 pub msdf_shader_queue: Vec<(u32, String)>,
165 pub msdf_shader_pool: Vec<u32>,
167 pub msdf_texture_load_queue: Vec<(String, u32)>,
169 pub raw_texture_upload_queue: Vec<(u32, u32, u32, Vec<u8>)>,
171 pub frame_time_ms: f64,
173 pub draw_call_count: usize,
175}
176
177impl RenderBridgeState {
178 pub fn new(base_dir: PathBuf) -> Self {
179 let save_dir = base_dir.join(".arcane").join("saves");
180 Self {
181 sprite_commands: Vec::new(),
182 camera_x: 0.0,
183 camera_y: 0.0,
184 camera_zoom: 1.0,
185 camera_dirty: false,
186 delta_time: 0.0,
187 elapsed_time: 0.0,
188 keys_down: std::collections::HashSet::new(),
189 keys_pressed: std::collections::HashSet::new(),
190 mouse_x: 0.0,
191 mouse_y: 0.0,
192 mouse_buttons_down: std::collections::HashSet::new(),
193 mouse_buttons_pressed: std::collections::HashSet::new(),
194 gamepad_buttons_down: std::collections::HashSet::new(),
195 gamepad_buttons_pressed: std::collections::HashSet::new(),
196 gamepad_axes: std::collections::HashMap::new(),
197 gamepad_count: 0,
198 gamepad_name: String::new(),
199 touch_points: Vec::new(),
200 touch_count: 0,
201 texture_load_queue: Vec::new(),
202 base_dir,
203 next_texture_id: 1,
204 texture_path_to_id: std::collections::HashMap::new(),
205 tilemaps: TilemapStore::new(),
206 ambient_light: [1.0, 1.0, 1.0],
207 point_lights: Vec::new(),
208 audio_commands: Vec::new(),
209 next_sound_id: 1,
210 sound_path_to_id: std::collections::HashMap::new(),
211 font_texture_queue: Vec::new(),
212 viewport_width: 800.0,
213 viewport_height: 600.0,
214 scale_factor: 1.0,
215 clear_color: [0.1, 0.1, 0.15, 1.0],
216 save_dir,
217 shader_create_queue: Vec::new(),
218 shader_param_queue: Vec::new(),
219 next_shader_id: 1,
220 effect_create_queue: Vec::new(),
221 effect_param_queue: Vec::new(),
222 effect_remove_queue: Vec::new(),
223 effect_clear: false,
224 next_effect_id: 1,
225 camera_bounds: None,
226 gi_enabled: false,
227 gi_intensity: 1.0,
228 gi_probe_spacing: None,
229 gi_interval: None,
230 gi_cascade_count: None,
231 emissives: Vec::new(),
232 occluders: Vec::new(),
233 directional_lights: Vec::new(),
234 spot_lights: Vec::new(),
235 msdf_fonts: MsdfFontStore::new(),
236 msdf_builtin_queue: Vec::new(),
237 msdf_shader_queue: Vec::new(),
238 msdf_shader_pool: Vec::new(),
239 msdf_texture_load_queue: Vec::new(),
240 raw_texture_upload_queue: Vec::new(),
241 frame_time_ms: 0.0,
242 draw_call_count: 0,
243 }
244 }
245}
246
247#[deno_core::op2(fast)]
250pub fn op_draw_sprite(
251 state: &mut OpState,
252 texture_id: u32,
253 x: f64,
254 y: f64,
255 w: f64,
256 h: f64,
257 layer: i32,
258 uv_x: f64,
259 uv_y: f64,
260 uv_w: f64,
261 uv_h: f64,
262 tint_r: f64,
263 tint_g: f64,
264 tint_b: f64,
265 tint_a: f64,
266 rotation: f64,
267 origin_x: f64,
268 origin_y: f64,
269 flip_x: f64,
270 flip_y: f64,
271 opacity: f64,
272 blend_mode: f64,
273 shader_id: f64,
274) {
275 let cmd = SpriteCommand {
276 texture_id,
277 x: x as f32,
278 y: y as f32,
279 w: w as f32,
280 h: h as f32,
281 layer,
282 uv_x: uv_x as f32,
283 uv_y: uv_y as f32,
284 uv_w: uv_w as f32,
285 uv_h: uv_h as f32,
286 tint_r: tint_r as f32,
287 tint_g: tint_g as f32,
288 tint_b: tint_b as f32,
289 tint_a: tint_a as f32,
290 rotation: rotation as f32,
291 origin_x: origin_x as f32,
292 origin_y: origin_y as f32,
293 flip_x: flip_x != 0.0,
294 flip_y: flip_y != 0.0,
295 opacity: opacity as f32,
296 blend_mode: (blend_mode as u8).min(3),
297 shader_id: shader_id as u32,
298 };
299 let active_target = {
301 use super::target_ops::TargetState;
302 let ts = state.borrow::<Rc<RefCell<TargetState>>>();
303 ts.borrow().active_target
304 };
305 if let Some(target_id) = active_target {
306 use super::target_ops::TargetState;
307 let ts = state.borrow::<Rc<RefCell<TargetState>>>();
308 ts.borrow_mut()
309 .target_sprite_queues
310 .entry(target_id)
311 .or_default()
312 .push(cmd);
313 } else {
314 let bridge = state.borrow::<Rc<RefCell<RenderBridgeState>>>();
315 bridge.borrow_mut().sprite_commands.push(cmd);
316 }
317}
318
319#[deno_core::op2(fast)]
321pub fn op_clear_sprites(state: &mut OpState) {
322 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
323 bridge.borrow_mut().sprite_commands.clear();
324}
325
326pub const SPRITE_STRIDE: usize = 22;
331
332#[deno_core::op2(fast)]
336pub fn op_submit_sprite_batch(state: &mut OpState, #[buffer] data: &[u8]) {
337 let floats: &[f32] = bytemuck::cast_slice(data);
338 let sprite_count = floats.len() / SPRITE_STRIDE;
339
340 let active_target = {
342 use super::target_ops::TargetState;
343 let ts = state.borrow::<Rc<RefCell<TargetState>>>();
344 ts.borrow().active_target
345 };
346
347 let parse_cmd = |s: &[f32]| SpriteCommand {
348 texture_id: s[0].to_bits(),
349 x: s[1],
350 y: s[2],
351 w: s[3],
352 h: s[4],
353 layer: s[5].to_bits() as i32,
354 uv_x: s[6],
355 uv_y: s[7],
356 uv_w: s[8],
357 uv_h: s[9],
358 tint_r: s[10],
359 tint_g: s[11],
360 tint_b: s[12],
361 tint_a: s[13],
362 rotation: s[14],
363 origin_x: s[15],
364 origin_y: s[16],
365 flip_x: s[17] != 0.0,
366 flip_y: s[18] != 0.0,
367 opacity: s[19],
368 blend_mode: (s[20] as u8).min(3),
369 shader_id: s[21].to_bits(),
370 };
371
372 if let Some(target_id) = active_target {
373 use super::target_ops::TargetState;
374 let ts = state.borrow::<Rc<RefCell<TargetState>>>();
375 let mut ts = ts.borrow_mut();
376 let queue = ts.target_sprite_queues.entry(target_id).or_default();
377 queue.reserve(sprite_count);
378 for i in 0..sprite_count {
379 let base = i * SPRITE_STRIDE;
380 queue.push(parse_cmd(&floats[base..base + SPRITE_STRIDE]));
381 }
382 } else {
383 let bridge = state.borrow::<Rc<RefCell<RenderBridgeState>>>();
384 let mut b = bridge.borrow_mut();
385 b.sprite_commands.reserve(sprite_count);
386 for i in 0..sprite_count {
387 let base = i * SPRITE_STRIDE;
388 b.sprite_commands.push(parse_cmd(&floats[base..base + SPRITE_STRIDE]));
389 }
390 }
391}
392
393#[deno_core::op2(fast)]
396pub fn op_set_camera(state: &mut OpState, x: f64, y: f64, zoom: f64) {
397 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
398 let mut b = bridge.borrow_mut();
399 b.camera_x = x as f32;
400 b.camera_y = y as f32;
401 b.camera_zoom = zoom as f32;
402 b.camera_dirty = true;
403}
404
405#[deno_core::op2]
407#[serde]
408pub fn op_get_camera(state: &mut OpState) -> Vec<f64> {
409 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
410 let b = bridge.borrow();
411 vec![b.camera_x as f64, b.camera_y as f64, b.camera_zoom as f64]
412}
413
414#[deno_core::op2(fast)]
417pub fn op_load_texture(state: &mut OpState, #[string] path: &str) -> u32 {
418 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
419 let mut b = bridge.borrow_mut();
420
421 let resolved = if std::path::Path::new(path).is_absolute() {
423 path.to_string()
424 } else {
425 b.base_dir.join(path).to_string_lossy().to_string()
426 };
427
428 if let Some(&id) = b.texture_path_to_id.get(&resolved) {
430 return id;
431 }
432
433 let id = b.next_texture_id;
434 b.next_texture_id += 1;
435 b.texture_path_to_id.insert(resolved.clone(), id);
436 b.texture_load_queue.push((resolved, id));
437 id
438}
439
440#[deno_core::op2(fast)]
442pub fn op_is_key_down(state: &mut OpState, #[string] key: &str) -> bool {
443 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
444 bridge.borrow().keys_down.contains(key)
445}
446
447#[deno_core::op2(fast)]
449pub fn op_is_key_pressed(state: &mut OpState, #[string] key: &str) -> bool {
450 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
451 bridge.borrow().keys_pressed.contains(key)
452}
453
454#[deno_core::op2]
456#[serde]
457pub fn op_get_mouse_position(state: &mut OpState) -> Vec<f64> {
458 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
459 let b = bridge.borrow();
460 vec![b.mouse_x as f64, b.mouse_y as f64]
461}
462
463#[deno_core::op2(fast)]
466pub fn op_is_mouse_button_down(state: &mut OpState, button: u8) -> bool {
467 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
468 bridge.borrow().mouse_buttons_down.contains(&button)
469}
470
471#[deno_core::op2(fast)]
474pub fn op_is_mouse_button_pressed(state: &mut OpState, button: u8) -> bool {
475 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
476 bridge.borrow().mouse_buttons_pressed.contains(&button)
477}
478
479#[deno_core::op2(fast)]
481pub fn op_get_delta_time(state: &mut OpState) -> f64 {
482 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
483 bridge.borrow().delta_time
484}
485
486#[deno_core::op2(fast)]
489pub fn op_create_solid_texture(
490 state: &mut OpState,
491 #[string] name: &str,
492 r: u32,
493 g: u32,
494 b: u32,
495 a: u32,
496) -> u32 {
497 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
498 let mut br = bridge.borrow_mut();
499
500 let key = format!("__solid__{name}");
501 if let Some(&id) = br.texture_path_to_id.get(&key) {
502 return id;
503 }
504
505 let id = br.next_texture_id;
506 br.next_texture_id += 1;
507 br.texture_path_to_id.insert(key.clone(), id);
508 br.texture_load_queue
510 .push((format!("__solid__:{name}:{r}:{g}:{b}:{a}"), id));
511 id
512}
513
514#[deno_core::op2(fast)]
517pub fn op_upload_rgba_texture(
518 state: &mut OpState,
519 #[string] name: &str,
520 width: f64,
521 height: f64,
522 #[buffer] pixels: &[u8],
523) -> u32 {
524 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
525 let mut b = bridge.borrow_mut();
526
527 let key = format!("__raw__:{name}");
528 if let Some(&id) = b.texture_path_to_id.get(&key) {
529 return id;
530 }
531
532 let id = b.next_texture_id;
533 b.next_texture_id += 1;
534 b.texture_path_to_id.insert(key, id);
535 b.raw_texture_upload_queue.push((
536 id,
537 width as u32,
538 height as u32,
539 pixels.to_vec(),
540 ));
541 id
542}
543
544#[deno_core::op2(fast)]
546pub fn op_create_tilemap(
547 state: &mut OpState,
548 texture_id: u32,
549 width: u32,
550 height: u32,
551 tile_size: f64,
552 atlas_columns: u32,
553 atlas_rows: u32,
554) -> u32 {
555 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
556 bridge
557 .borrow_mut()
558 .tilemaps
559 .create(texture_id, width, height, tile_size as f32, atlas_columns, atlas_rows)
560}
561
562#[deno_core::op2(fast)]
564pub fn op_set_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32, tile_id: u32) {
565 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
566 if let Some(tm) = bridge.borrow_mut().tilemaps.get_mut(tilemap_id) {
567 tm.set_tile(gx, gy, tile_id as u16);
568 }
569}
570
571#[deno_core::op2(fast)]
573pub fn op_get_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32) -> u32 {
574 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
575 bridge
576 .borrow()
577 .tilemaps
578 .get(tilemap_id)
579 .map(|tm| tm.get_tile(gx, gy) as u32)
580 .unwrap_or(0)
581}
582
583#[deno_core::op2(fast)]
586pub fn op_draw_tilemap(state: &mut OpState, tilemap_id: u32, world_x: f64, world_y: f64, layer: i32) {
587 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
588 let mut b = bridge.borrow_mut();
589 let cam_x = b.camera_x;
590 let cam_y = b.camera_y;
591 let cam_zoom = b.camera_zoom;
592 let vp_w = 800.0;
594 let vp_h = 600.0;
595
596 if let Some(tm) = b.tilemaps.get(tilemap_id) {
597 let cmds = tm.bake_visible(world_x as f32, world_y as f32, layer, cam_x, cam_y, cam_zoom, vp_w, vp_h);
598 b.sprite_commands.extend(cmds);
599 }
600}
601
602#[deno_core::op2(fast)]
607pub fn op_set_ambient_light(state: &mut OpState, r: f64, g: f64, b: f64) {
608 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
609 bridge.borrow_mut().ambient_light = [r as f32, g as f32, b as f32];
610}
611
612#[deno_core::op2(fast)]
615pub fn op_add_point_light(
616 state: &mut OpState,
617 x: f64,
618 y: f64,
619 radius: f64,
620 r: f64,
621 g: f64,
622 b: f64,
623 intensity: f64,
624) {
625 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
626 bridge.borrow_mut().point_lights.push(PointLight {
627 x: x as f32,
628 y: y as f32,
629 radius: radius as f32,
630 r: r as f32,
631 g: g as f32,
632 b: b as f32,
633 intensity: intensity as f32,
634 });
635}
636
637#[deno_core::op2(fast)]
639pub fn op_clear_lights(state: &mut OpState) {
640 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
641 bridge.borrow_mut().point_lights.clear();
642}
643
644#[deno_core::op2(fast)]
648pub fn op_load_sound(state: &mut OpState, #[string] path: &str) -> u32 {
649 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
650 let mut b = bridge.borrow_mut();
651
652 let resolved = if std::path::Path::new(path).is_absolute() {
653 path.to_string()
654 } else {
655 b.base_dir.join(path).to_string_lossy().to_string()
656 };
657
658 if let Some(&id) = b.sound_path_to_id.get(&resolved) {
659 return id;
660 }
661
662 let id = b.next_sound_id;
663 b.next_sound_id += 1;
664 b.sound_path_to_id.insert(resolved.clone(), id);
665 b.audio_commands.push(BridgeAudioCommand::LoadSound { id, path: resolved });
666 id
667}
668
669#[deno_core::op2(fast)]
672pub fn op_play_sound(state: &mut OpState, id: u32, volume: f64, looping: bool) {
673 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
674 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySound { id, volume: volume as f32, looping });
675}
676
677#[deno_core::op2(fast)]
679pub fn op_stop_sound(state: &mut OpState, id: u32) {
680 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
681 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopSound { id });
682}
683
684#[deno_core::op2(fast)]
686pub fn op_stop_all_sounds(state: &mut OpState) {
687 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
688 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopAll);
689}
690
691#[deno_core::op2(fast)]
694pub fn op_set_master_volume(state: &mut OpState, volume: f64) {
695 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
696 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetMasterVolume { volume: volume as f32 });
697}
698
699#[deno_core::op2(fast)]
703pub fn op_create_font_texture(state: &mut OpState) -> u32 {
704 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
705 let mut b = bridge.borrow_mut();
706
707 let key = "__builtin_font__".to_string();
708 if let Some(&id) = b.texture_path_to_id.get(&key) {
709 return id;
710 }
711
712 let id = b.next_texture_id;
713 b.next_texture_id += 1;
714 b.texture_path_to_id.insert(key, id);
715 b.font_texture_queue.push(id);
716 id
717}
718
719#[deno_core::op2]
723#[serde]
724pub fn op_get_viewport_size(state: &mut OpState) -> Vec<f64> {
725 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
726 let b = bridge.borrow();
727 vec![b.viewport_width as f64, b.viewport_height as f64]
728}
729
730#[deno_core::op2(fast)]
732pub fn op_get_scale_factor(state: &mut OpState) -> f64 {
733 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
734 bridge.borrow().scale_factor as f64
735}
736
737#[deno_core::op2(fast)]
739pub fn op_set_background_color(state: &mut OpState, r: f64, g: f64, b: f64) {
740 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
741 let mut br = bridge.borrow_mut();
742 br.clear_color = [r as f32, g as f32, b as f32, 1.0];
743}
744
745#[deno_core::op2(fast)]
749pub fn op_save_file(state: &mut OpState, #[string] key: &str, #[string] value: &str) -> bool {
750 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
751 let save_dir = bridge.borrow().save_dir.clone();
752
753 if !key.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
755 return false;
756 }
757
758 if std::fs::create_dir_all(&save_dir).is_err() {
760 return false;
761 }
762
763 let path = save_dir.join(format!("{key}.json"));
764 std::fs::write(path, value).is_ok()
765}
766
767#[deno_core::op2]
769#[string]
770pub fn op_load_file(state: &mut OpState, #[string] key: &str) -> String {
771 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
772 let save_dir = bridge.borrow().save_dir.clone();
773
774 let path = save_dir.join(format!("{key}.json"));
775 std::fs::read_to_string(path).unwrap_or_default()
776}
777
778#[deno_core::op2(fast)]
780pub fn op_delete_file(state: &mut OpState, #[string] key: &str) -> bool {
781 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
782 let save_dir = bridge.borrow().save_dir.clone();
783
784 let path = save_dir.join(format!("{key}.json"));
785 std::fs::remove_file(path).is_ok()
786}
787
788#[deno_core::op2]
790#[serde]
791pub fn op_list_save_files(state: &mut OpState) -> Vec<String> {
792 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
793 let save_dir = bridge.borrow().save_dir.clone();
794
795 let mut keys = Vec::new();
796 if let Ok(entries) = std::fs::read_dir(&save_dir) {
797 for entry in entries.flatten() {
798 let path = entry.path();
799 if path.extension().map_or(false, |ext| ext == "json") {
800 if let Some(stem) = path.file_stem() {
801 keys.push(stem.to_string_lossy().to_string());
802 }
803 }
804 }
805 }
806 keys.sort();
807 keys
808}
809
810#[deno_core::op2(fast)]
814pub fn op_create_shader(state: &mut OpState, #[string] name: &str, #[string] source: &str) -> u32 {
815 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
816 let mut b = bridge.borrow_mut();
817 let id = b.next_shader_id;
818 b.next_shader_id += 1;
819 b.shader_create_queue
820 .push((id, name.to_string(), source.to_string()));
821 id
822}
823
824#[deno_core::op2(fast)]
826pub fn op_set_shader_param(
827 state: &mut OpState,
828 shader_id: u32,
829 index: u32,
830 x: f64,
831 y: f64,
832 z: f64,
833 w: f64,
834) {
835 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
836 bridge.borrow_mut().shader_param_queue.push((
837 shader_id,
838 index,
839 [x as f32, y as f32, z as f32, w as f32],
840 ));
841}
842
843#[deno_core::op2(fast)]
847pub fn op_add_effect(state: &mut OpState, #[string] effect_type: &str) -> u32 {
848 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
849 let mut b = bridge.borrow_mut();
850 let id = b.next_effect_id;
851 b.next_effect_id += 1;
852 b.effect_create_queue
853 .push((id, effect_type.to_string()));
854 id
855}
856
857#[deno_core::op2(fast)]
859pub fn op_set_effect_param(
860 state: &mut OpState,
861 effect_id: u32,
862 index: u32,
863 x: f64,
864 y: f64,
865 z: f64,
866 w: f64,
867) {
868 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
869 bridge.borrow_mut().effect_param_queue.push((
870 effect_id,
871 index,
872 [x as f32, y as f32, z as f32, w as f32],
873 ));
874}
875
876#[deno_core::op2(fast)]
878pub fn op_remove_effect(state: &mut OpState, effect_id: u32) {
879 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
880 bridge.borrow_mut().effect_remove_queue.push(effect_id);
881}
882
883#[deno_core::op2(fast)]
885pub fn op_clear_effects(state: &mut OpState) {
886 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
887 bridge.borrow_mut().effect_clear = true;
888}
889
890#[deno_core::op2(fast)]
894pub fn op_set_camera_bounds(state: &mut OpState, min_x: f64, min_y: f64, max_x: f64, max_y: f64) {
895 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
896 bridge.borrow_mut().camera_bounds = Some(CameraBounds {
897 min_x: min_x as f32,
898 min_y: min_y as f32,
899 max_x: max_x as f32,
900 max_y: max_y as f32,
901 });
902}
903
904#[deno_core::op2(fast)]
906pub fn op_clear_camera_bounds(state: &mut OpState) {
907 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
908 bridge.borrow_mut().camera_bounds = None;
909}
910
911#[deno_core::op2]
913#[serde]
914pub fn op_get_camera_bounds(state: &mut OpState) -> Vec<f64> {
915 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
916 let b = bridge.borrow();
917 match b.camera_bounds {
918 Some(bounds) => vec![
919 bounds.min_x as f64,
920 bounds.min_y as f64,
921 bounds.max_x as f64,
922 bounds.max_y as f64,
923 ],
924 None => vec![],
925 }
926}
927
928#[deno_core::op2(fast)]
932pub fn op_enable_gi(state: &mut OpState) {
933 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
934 bridge.borrow_mut().gi_enabled = true;
935}
936
937#[deno_core::op2(fast)]
939pub fn op_disable_gi(state: &mut OpState) {
940 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
941 bridge.borrow_mut().gi_enabled = false;
942}
943
944#[deno_core::op2(fast)]
946pub fn op_set_gi_intensity(state: &mut OpState, intensity: f64) {
947 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
948 bridge.borrow_mut().gi_intensity = intensity as f32;
949}
950
951#[deno_core::op2(fast)]
954pub fn op_set_gi_quality(state: &mut OpState, probe_spacing: f64, interval: f64, cascade_count: f64) {
955 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
956 let mut b = bridge.borrow_mut();
957 if probe_spacing > 0.0 {
958 b.gi_probe_spacing = Some(probe_spacing as f32);
959 }
960 if interval > 0.0 {
961 b.gi_interval = Some(interval as f32);
962 }
963 if cascade_count > 0.0 {
964 b.gi_cascade_count = Some(cascade_count as u32);
965 }
966}
967
968#[deno_core::op2(fast)]
970pub fn op_add_emissive(
971 state: &mut OpState,
972 x: f64,
973 y: f64,
974 w: f64,
975 h: f64,
976 r: f64,
977 g: f64,
978 b: f64,
979 intensity: f64,
980) {
981 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
982 bridge.borrow_mut().emissives.push([
983 x as f32,
984 y as f32,
985 w as f32,
986 h as f32,
987 r as f32,
988 g as f32,
989 b as f32,
990 intensity as f32,
991 ]);
992}
993
994#[deno_core::op2(fast)]
996pub fn op_clear_emissives(state: &mut OpState) {
997 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
998 bridge.borrow_mut().emissives.clear();
999}
1000
1001#[deno_core::op2(fast)]
1003pub fn op_add_occluder(state: &mut OpState, x: f64, y: f64, w: f64, h: f64) {
1004 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1005 bridge.borrow_mut().occluders.push([x as f32, y as f32, w as f32, h as f32]);
1006}
1007
1008#[deno_core::op2(fast)]
1010pub fn op_clear_occluders(state: &mut OpState) {
1011 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1012 bridge.borrow_mut().occluders.clear();
1013}
1014
1015#[deno_core::op2(fast)]
1017pub fn op_add_directional_light(
1018 state: &mut OpState,
1019 angle: f64,
1020 r: f64,
1021 g: f64,
1022 b: f64,
1023 intensity: f64,
1024) {
1025 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1026 bridge.borrow_mut().directional_lights.push([
1027 angle as f32,
1028 r as f32,
1029 g as f32,
1030 b as f32,
1031 intensity as f32,
1032 ]);
1033}
1034
1035#[deno_core::op2(fast)]
1037pub fn op_add_spot_light(
1038 state: &mut OpState,
1039 x: f64,
1040 y: f64,
1041 angle: f64,
1042 spread: f64,
1043 range: f64,
1044 r: f64,
1045 g: f64,
1046 b: f64,
1047 intensity: f64,
1048) {
1049 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1050 bridge.borrow_mut().spot_lights.push([
1051 x as f32,
1052 y as f32,
1053 angle as f32,
1054 spread as f32,
1055 range as f32,
1056 r as f32,
1057 g as f32,
1058 b as f32,
1059 intensity as f32,
1060 ]);
1061}
1062
1063#[deno_core::op2(fast)]
1068pub fn op_play_sound_ex(
1069 state: &mut OpState,
1070 sound_id: u32,
1071 instance_id: f64,
1072 volume: f64,
1073 looping: bool,
1074 bus: u32,
1075 pan: f64,
1076 pitch: f64,
1077 low_pass_freq: u32,
1078 reverb_mix: f64,
1079 reverb_delay_ms: u32,
1080) {
1081 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1082 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySoundEx {
1083 sound_id,
1084 instance_id: instance_id as u64,
1085 volume: volume as f32,
1086 looping,
1087 bus,
1088 pan: pan as f32,
1089 pitch: pitch as f32,
1090 low_pass_freq,
1091 reverb_mix: reverb_mix as f32,
1092 reverb_delay_ms,
1093 });
1094}
1095
1096#[deno_core::op2(fast)]
1099pub fn op_play_sound_spatial(
1100 state: &mut OpState,
1101 sound_id: u32,
1102 instance_id: f64,
1103 volume: f64,
1104 looping: bool,
1105 bus: u32,
1106 pitch: f64,
1107 source_x: f64,
1108 source_y: f64,
1109 listener_x: f64,
1110 listener_y: f64,
1111) {
1112 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1113 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySoundSpatial {
1114 sound_id,
1115 instance_id: instance_id as u64,
1116 volume: volume as f32,
1117 looping,
1118 bus,
1119 pitch: pitch as f32,
1120 source_x: source_x as f32,
1121 source_y: source_y as f32,
1122 listener_x: listener_x as f32,
1123 listener_y: listener_y as f32,
1124 });
1125}
1126
1127#[deno_core::op2(fast)]
1130pub fn op_stop_instance(state: &mut OpState, instance_id: f64) {
1131 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1132 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopInstance {
1133 instance_id: instance_id as u64,
1134 });
1135}
1136
1137#[deno_core::op2(fast)]
1140pub fn op_set_instance_volume(state: &mut OpState, instance_id: f64, volume: f64) {
1141 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1142 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetInstanceVolume {
1143 instance_id: instance_id as u64,
1144 volume: volume as f32,
1145 });
1146}
1147
1148#[deno_core::op2(fast)]
1151pub fn op_set_instance_pitch(state: &mut OpState, instance_id: f64, pitch: f64) {
1152 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1153 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetInstancePitch {
1154 instance_id: instance_id as u64,
1155 pitch: pitch as f32,
1156 });
1157}
1158
1159#[deno_core::op2(fast)]
1163pub fn op_update_spatial_positions(state: &mut OpState, #[string] data_json: &str, listener_x: f64, listener_y: f64) {
1164 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1165
1166 let mut updates = Vec::new();
1169
1170 if let Some(ids_start) = data_json.find("\"instanceIds\":[") {
1172 if let Some(xs_start) = data_json.find("\"sourceXs\":[") {
1173 if let Some(ys_start) = data_json.find("\"sourceYs\":[") {
1174 let ids_str = &data_json[ids_start + 15..];
1175 let xs_str = &data_json[xs_start + 12..];
1176 let ys_str = &data_json[ys_start + 12..];
1177
1178 let ids_end = ids_str.find(']').unwrap_or(0);
1179 let xs_end = xs_str.find(']').unwrap_or(0);
1180 let ys_end = ys_str.find(']').unwrap_or(0);
1181
1182 let ids: Vec<u64> = ids_str[..ids_end]
1183 .split(',')
1184 .filter_map(|s| s.trim().parse().ok())
1185 .collect();
1186 let xs: Vec<f32> = xs_str[..xs_end]
1187 .split(',')
1188 .filter_map(|s| s.trim().parse().ok())
1189 .collect();
1190 let ys: Vec<f32> = ys_str[..ys_end]
1191 .split(',')
1192 .filter_map(|s| s.trim().parse().ok())
1193 .collect();
1194
1195 for i in 0..ids.len().min(xs.len()).min(ys.len()) {
1196 updates.push((ids[i], xs[i], ys[i]));
1197 }
1198 }
1199 }
1200 }
1201
1202 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::UpdateSpatialPositions {
1203 updates,
1204 listener_x: listener_x as f32,
1205 listener_y: listener_y as f32,
1206 });
1207}
1208
1209#[deno_core::op2(fast)]
1212pub fn op_set_bus_volume(state: &mut OpState, bus: u32, volume: f64) {
1213 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1214 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetBusVolume {
1215 bus,
1216 volume: volume as f32,
1217 });
1218}
1219
1220#[deno_core::op2]
1225#[string]
1226pub fn op_create_msdf_builtin_font(state: &mut OpState) -> String {
1227 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1228 let mut b = bridge.borrow_mut();
1229
1230 let key = "__msdf_builtin__".to_string();
1232 if let Some(&tex_id) = b.texture_path_to_id.get(&key) {
1233 let font_id_key = format!("__msdf_font_{tex_id}__");
1236 if let Some(&font_id) = b.texture_path_to_id.get(&font_id_key) {
1237 let pool = &b.msdf_shader_pool;
1238 let shader_id = pool.first().copied().unwrap_or(0);
1239 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1240 return format!(
1241 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1242 font_id, tex_id, shader_id, pool_json.join(",")
1243 );
1244 }
1245 }
1246
1247 let tex_id = b.next_texture_id;
1249 b.next_texture_id += 1;
1250 b.texture_path_to_id.insert(key, tex_id);
1251
1252 let (_pixels, _width, _height, mut font) =
1254 crate::renderer::msdf::generate_builtin_msdf_font();
1255 font.texture_id = tex_id;
1256
1257 let font_id = b.msdf_fonts.register(font);
1259 b.texture_path_to_id
1260 .insert(format!("__msdf_font_{tex_id}__"), font_id);
1261
1262 b.msdf_builtin_queue.push((font_id, tex_id));
1265
1266 let pool = ensure_msdf_shader_pool(&mut b);
1268 let shader_id = pool.first().copied().unwrap_or(0);
1269 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1270
1271 format!(
1272 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1273 font_id, tex_id, shader_id, pool_json.join(",")
1274 )
1275}
1276
1277#[deno_core::op2]
1280#[string]
1281pub fn op_get_msdf_glyphs(
1282 state: &mut OpState,
1283 font_id: u32,
1284 #[string] text: &str,
1285) -> String {
1286 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1287 let b = bridge.borrow();
1288
1289 let font = match b.msdf_fonts.get(font_id) {
1290 Some(f) => f,
1291 None => return "[]".to_string(),
1292 };
1293
1294 let mut entries = Vec::new();
1295 for ch in text.chars() {
1296 if let Some(glyph) = font.get_glyph(ch) {
1297 entries.push(format!(
1298 "{{\"char\":{},\"uv\":[{},{},{},{}],\"advance\":{},\"width\":{},\"height\":{},\"offsetX\":{},\"offsetY\":{}}}",
1299 ch as u32,
1300 glyph.uv_x, glyph.uv_y, glyph.uv_w, glyph.uv_h,
1301 glyph.advance, glyph.width, glyph.height,
1302 glyph.offset_x, glyph.offset_y,
1303 ));
1304 }
1305 }
1306
1307 format!("[{}]", entries.join(","))
1308}
1309
1310#[deno_core::op2]
1312#[string]
1313pub fn op_get_msdf_font_info(state: &mut OpState, font_id: u32) -> String {
1314 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1315 let b = bridge.borrow();
1316
1317 match b.msdf_fonts.get(font_id) {
1318 Some(font) => format!(
1319 "{{\"fontSize\":{},\"lineHeight\":{},\"distanceRange\":{},\"textureId\":{}}}",
1320 font.font_size, font.line_height, font.distance_range, font.texture_id,
1321 ),
1322 None => "null".to_string(),
1323 }
1324}
1325
1326#[deno_core::op2]
1329#[string]
1330pub fn op_load_msdf_font(
1331 state: &mut OpState,
1332 #[string] atlas_path: &str,
1333 #[string] metrics_json_or_path: &str,
1334) -> String {
1335 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1336 let mut b = bridge.borrow_mut();
1337
1338 let resolved = if std::path::Path::new(atlas_path).is_absolute() {
1340 atlas_path.to_string()
1341 } else {
1342 b.base_dir.join(atlas_path).to_string_lossy().to_string()
1343 };
1344
1345 let tex_id = if let Some(&id) = b.texture_path_to_id.get(&resolved) {
1347 id
1348 } else {
1349 let id = b.next_texture_id;
1350 b.next_texture_id += 1;
1351 b.texture_path_to_id.insert(resolved.clone(), id);
1352 b.msdf_texture_load_queue.push((resolved, id));
1353 id
1354 };
1355
1356 let metrics_json: String = if metrics_json_or_path.trim_start().starts_with('{') {
1359 metrics_json_or_path.to_string()
1360 } else {
1361 let json_path = if std::path::Path::new(metrics_json_or_path).is_absolute() {
1363 metrics_json_or_path.to_string()
1364 } else {
1365 b.base_dir
1366 .join(metrics_json_or_path)
1367 .to_string_lossy()
1368 .to_string()
1369 };
1370 match std::fs::read_to_string(&json_path) {
1371 Ok(content) => content,
1372 Err(e) => {
1373 return format!("{{\"error\":\"Failed to read metrics file {}: {}\"}}", json_path, e);
1374 }
1375 }
1376 };
1377
1378 let font = match crate::renderer::msdf::parse_msdf_metrics(&metrics_json, tex_id) {
1380 Ok(f) => f,
1381 Err(e) => {
1382 return format!("{{\"error\":\"{}\"}}", e);
1383 }
1384 };
1385
1386 let font_id = b.msdf_fonts.register(font);
1387 let pool = ensure_msdf_shader_pool(&mut b);
1388 let shader_id = pool.first().copied().unwrap_or(0);
1389 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1390
1391 format!(
1392 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1393 font_id, tex_id, shader_id, pool_json.join(",")
1394 )
1395}
1396
1397const MSDF_SHADER_POOL_SIZE: usize = 8;
1399
1400fn ensure_msdf_shader_pool(b: &mut RenderBridgeState) -> Vec<u32> {
1402 if !b.msdf_shader_pool.is_empty() {
1403 return b.msdf_shader_pool.clone();
1404 }
1405
1406 let source = crate::renderer::msdf::MSDF_FRAGMENT_SOURCE.to_string();
1407 let mut pool = Vec::with_capacity(MSDF_SHADER_POOL_SIZE);
1408
1409 for _ in 0..MSDF_SHADER_POOL_SIZE {
1410 let id = b.next_shader_id;
1411 b.next_shader_id += 1;
1412 b.msdf_shader_queue.push((id, source.clone()));
1413 pool.push(id);
1414 }
1415
1416 b.msdf_shader_pool = pool.clone();
1417 pool
1418}
1419
1420#[deno_core::op2(fast)]
1424pub fn op_get_gamepad_count(state: &mut OpState) -> u32 {
1425 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1426 bridge.borrow().gamepad_count
1427}
1428
1429#[deno_core::op2]
1431#[string]
1432pub fn op_get_gamepad_name(state: &mut OpState) -> String {
1433 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1434 bridge.borrow().gamepad_name.clone()
1435}
1436
1437#[deno_core::op2(fast)]
1440pub fn op_is_gamepad_button_down(state: &mut OpState, #[string] button: &str) -> bool {
1441 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1442 bridge.borrow().gamepad_buttons_down.contains(button)
1443}
1444
1445#[deno_core::op2(fast)]
1447pub fn op_is_gamepad_button_pressed(state: &mut OpState, #[string] button: &str) -> bool {
1448 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1449 bridge.borrow().gamepad_buttons_pressed.contains(button)
1450}
1451
1452#[deno_core::op2(fast)]
1455pub fn op_get_gamepad_axis(state: &mut OpState, #[string] axis: &str) -> f64 {
1456 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1457 bridge.borrow().gamepad_axes.get(axis).copied().unwrap_or(0.0) as f64
1458}
1459
1460#[deno_core::op2(fast)]
1464pub fn op_get_touch_count(state: &mut OpState) -> u32 {
1465 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1466 bridge.borrow().touch_count
1467}
1468
1469#[deno_core::op2]
1471#[serde]
1472pub fn op_get_touch_position(state: &mut OpState, index: u32) -> Vec<f64> {
1473 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1474 let b = bridge.borrow();
1475 if let Some(&(_, x, y)) = b.touch_points.get(index as usize) {
1476 vec![x as f64, y as f64]
1477 } else {
1478 vec![]
1479 }
1480}
1481
1482#[deno_core::op2(fast)]
1484pub fn op_is_touch_active(state: &mut OpState) -> bool {
1485 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1486 bridge.borrow().touch_count > 0
1487}
1488
1489deno_core::extension!(
1490 render_ext,
1491 ops = [
1492 op_draw_sprite,
1493 op_clear_sprites,
1494 op_submit_sprite_batch,
1495 op_set_camera,
1496 op_get_camera,
1497 op_load_texture,
1498 op_upload_rgba_texture,
1499 op_is_key_down,
1500 op_is_key_pressed,
1501 op_get_mouse_position,
1502 op_is_mouse_button_down,
1503 op_is_mouse_button_pressed,
1504 op_get_delta_time,
1505 op_create_solid_texture,
1506 op_create_tilemap,
1507 op_set_tile,
1508 op_get_tile,
1509 op_draw_tilemap,
1510 op_set_ambient_light,
1511 op_add_point_light,
1512 op_clear_lights,
1513 op_load_sound,
1514 op_play_sound,
1515 op_stop_sound,
1516 op_stop_all_sounds,
1517 op_set_master_volume,
1518 op_play_sound_ex,
1519 op_play_sound_spatial,
1520 op_stop_instance,
1521 op_set_instance_volume,
1522 op_set_instance_pitch,
1523 op_update_spatial_positions,
1524 op_set_bus_volume,
1525 op_create_font_texture,
1526 op_get_viewport_size,
1527 op_get_scale_factor,
1528 op_set_background_color,
1529 op_save_file,
1530 op_load_file,
1531 op_delete_file,
1532 op_list_save_files,
1533 op_create_shader,
1534 op_set_shader_param,
1535 op_add_effect,
1536 op_set_effect_param,
1537 op_remove_effect,
1538 op_clear_effects,
1539 op_set_camera_bounds,
1540 op_clear_camera_bounds,
1541 op_get_camera_bounds,
1542 op_enable_gi,
1543 op_disable_gi,
1544 op_set_gi_intensity,
1545 op_set_gi_quality,
1546 op_add_emissive,
1547 op_clear_emissives,
1548 op_add_occluder,
1549 op_clear_occluders,
1550 op_add_directional_light,
1551 op_add_spot_light,
1552 op_create_msdf_builtin_font,
1553 op_get_msdf_glyphs,
1554 op_get_msdf_font_info,
1555 op_load_msdf_font,
1556 op_get_gamepad_count,
1557 op_get_gamepad_name,
1558 op_is_gamepad_button_down,
1559 op_is_gamepad_button_pressed,
1560 op_get_gamepad_axis,
1561 op_get_touch_count,
1562 op_get_touch_position,
1563 op_is_touch_active,
1564 ],
1565);