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 keys_down: std::collections::HashSet<String>,
71 pub keys_pressed: std::collections::HashSet<String>,
72 pub mouse_x: f32,
73 pub mouse_y: f32,
74 pub mouse_buttons_down: std::collections::HashSet<u8>,
75 pub mouse_buttons_pressed: std::collections::HashSet<u8>,
76 pub gamepad_buttons_down: std::collections::HashSet<String>,
78 pub gamepad_buttons_pressed: std::collections::HashSet<String>,
80 pub gamepad_axes: std::collections::HashMap<String, f32>,
82 pub gamepad_count: u32,
84 pub gamepad_name: String,
86 pub touch_points: Vec<(u64, f32, f32)>,
88 pub touch_count: u32,
90 pub texture_load_queue: Vec<(String, u32)>,
92 pub base_dir: PathBuf,
94 pub next_texture_id: u32,
96 pub texture_path_to_id: std::collections::HashMap<String, u32>,
98 pub tilemaps: TilemapStore,
100 pub ambient_light: [f32; 3],
102 pub point_lights: Vec<PointLight>,
104 pub audio_commands: Vec<BridgeAudioCommand>,
106 pub next_sound_id: u32,
108 pub sound_path_to_id: std::collections::HashMap<String, u32>,
110 pub font_texture_queue: Vec<u32>,
112 pub viewport_width: f32,
114 pub viewport_height: f32,
115 pub scale_factor: f32,
117 pub clear_color: [f32; 4],
119 pub save_dir: PathBuf,
121 pub shader_create_queue: Vec<(u32, String, String)>,
123 pub shader_param_queue: Vec<(u32, u32, [f32; 4])>,
125 pub next_shader_id: u32,
127 pub effect_create_queue: Vec<(u32, String)>,
129 pub effect_param_queue: Vec<(u32, u32, [f32; 4])>,
131 pub effect_remove_queue: Vec<u32>,
133 pub effect_clear: bool,
135 pub next_effect_id: u32,
137 pub camera_bounds: Option<CameraBounds>,
139 pub gi_enabled: bool,
141 pub gi_intensity: f32,
143 pub gi_probe_spacing: Option<f32>,
145 pub gi_interval: Option<f32>,
147 pub gi_cascade_count: Option<u32>,
149 pub emissives: Vec<[f32; 8]>,
151 pub occluders: Vec<[f32; 4]>,
153 pub directional_lights: Vec<[f32; 5]>,
155 pub spot_lights: Vec<[f32; 9]>,
157 pub msdf_fonts: MsdfFontStore,
159 pub msdf_builtin_queue: Vec<(u32, u32)>,
161 pub msdf_shader_queue: Vec<(u32, String)>,
163 pub msdf_shader_pool: Vec<u32>,
165 pub msdf_texture_load_queue: Vec<(String, u32)>,
167 pub raw_texture_upload_queue: Vec<(u32, u32, u32, Vec<u8>)>,
169 pub frame_time_ms: f64,
171 pub draw_call_count: usize,
173}
174
175impl RenderBridgeState {
176 pub fn new(base_dir: PathBuf) -> Self {
177 let save_dir = base_dir.join(".arcane").join("saves");
178 Self {
179 sprite_commands: Vec::new(),
180 camera_x: 0.0,
181 camera_y: 0.0,
182 camera_zoom: 1.0,
183 camera_dirty: false,
184 delta_time: 0.0,
185 keys_down: std::collections::HashSet::new(),
186 keys_pressed: std::collections::HashSet::new(),
187 mouse_x: 0.0,
188 mouse_y: 0.0,
189 mouse_buttons_down: std::collections::HashSet::new(),
190 mouse_buttons_pressed: std::collections::HashSet::new(),
191 gamepad_buttons_down: std::collections::HashSet::new(),
192 gamepad_buttons_pressed: std::collections::HashSet::new(),
193 gamepad_axes: std::collections::HashMap::new(),
194 gamepad_count: 0,
195 gamepad_name: String::new(),
196 touch_points: Vec::new(),
197 touch_count: 0,
198 texture_load_queue: Vec::new(),
199 base_dir,
200 next_texture_id: 1,
201 texture_path_to_id: std::collections::HashMap::new(),
202 tilemaps: TilemapStore::new(),
203 ambient_light: [1.0, 1.0, 1.0],
204 point_lights: Vec::new(),
205 audio_commands: Vec::new(),
206 next_sound_id: 1,
207 sound_path_to_id: std::collections::HashMap::new(),
208 font_texture_queue: Vec::new(),
209 viewport_width: 800.0,
210 viewport_height: 600.0,
211 scale_factor: 1.0,
212 clear_color: [0.1, 0.1, 0.15, 1.0],
213 save_dir,
214 shader_create_queue: Vec::new(),
215 shader_param_queue: Vec::new(),
216 next_shader_id: 1,
217 effect_create_queue: Vec::new(),
218 effect_param_queue: Vec::new(),
219 effect_remove_queue: Vec::new(),
220 effect_clear: false,
221 next_effect_id: 1,
222 camera_bounds: None,
223 gi_enabled: false,
224 gi_intensity: 1.0,
225 gi_probe_spacing: None,
226 gi_interval: None,
227 gi_cascade_count: None,
228 emissives: Vec::new(),
229 occluders: Vec::new(),
230 directional_lights: Vec::new(),
231 spot_lights: Vec::new(),
232 msdf_fonts: MsdfFontStore::new(),
233 msdf_builtin_queue: Vec::new(),
234 msdf_shader_queue: Vec::new(),
235 msdf_shader_pool: Vec::new(),
236 msdf_texture_load_queue: Vec::new(),
237 raw_texture_upload_queue: Vec::new(),
238 frame_time_ms: 0.0,
239 draw_call_count: 0,
240 }
241 }
242}
243
244#[deno_core::op2(fast)]
247pub fn op_draw_sprite(
248 state: &mut OpState,
249 texture_id: u32,
250 x: f64,
251 y: f64,
252 w: f64,
253 h: f64,
254 layer: i32,
255 uv_x: f64,
256 uv_y: f64,
257 uv_w: f64,
258 uv_h: f64,
259 tint_r: f64,
260 tint_g: f64,
261 tint_b: f64,
262 tint_a: f64,
263 rotation: f64,
264 origin_x: f64,
265 origin_y: f64,
266 flip_x: f64,
267 flip_y: f64,
268 opacity: f64,
269 blend_mode: f64,
270 shader_id: f64,
271) {
272 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
273 bridge.borrow_mut().sprite_commands.push(SpriteCommand {
274 texture_id,
275 x: x as f32,
276 y: y as f32,
277 w: w as f32,
278 h: h as f32,
279 layer,
280 uv_x: uv_x as f32,
281 uv_y: uv_y as f32,
282 uv_w: uv_w as f32,
283 uv_h: uv_h as f32,
284 tint_r: tint_r as f32,
285 tint_g: tint_g as f32,
286 tint_b: tint_b as f32,
287 tint_a: tint_a as f32,
288 rotation: rotation as f32,
289 origin_x: origin_x as f32,
290 origin_y: origin_y as f32,
291 flip_x: flip_x != 0.0,
292 flip_y: flip_y != 0.0,
293 opacity: opacity as f32,
294 blend_mode: (blend_mode as u8).min(3),
295 shader_id: shader_id as u32,
296 });
297}
298
299#[deno_core::op2(fast)]
301pub fn op_clear_sprites(state: &mut OpState) {
302 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
303 bridge.borrow_mut().sprite_commands.clear();
304}
305
306#[deno_core::op2(fast)]
309pub fn op_set_camera(state: &mut OpState, x: f64, y: f64, zoom: f64) {
310 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
311 let mut b = bridge.borrow_mut();
312 b.camera_x = x as f32;
313 b.camera_y = y as f32;
314 b.camera_zoom = zoom as f32;
315 b.camera_dirty = true;
316}
317
318#[deno_core::op2]
320#[serde]
321pub fn op_get_camera(state: &mut OpState) -> Vec<f64> {
322 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
323 let b = bridge.borrow();
324 vec![b.camera_x as f64, b.camera_y as f64, b.camera_zoom as f64]
325}
326
327#[deno_core::op2(fast)]
330pub fn op_load_texture(state: &mut OpState, #[string] path: &str) -> u32 {
331 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
332 let mut b = bridge.borrow_mut();
333
334 let resolved = if std::path::Path::new(path).is_absolute() {
336 path.to_string()
337 } else {
338 b.base_dir.join(path).to_string_lossy().to_string()
339 };
340
341 if let Some(&id) = b.texture_path_to_id.get(&resolved) {
343 return id;
344 }
345
346 let id = b.next_texture_id;
347 b.next_texture_id += 1;
348 b.texture_path_to_id.insert(resolved.clone(), id);
349 b.texture_load_queue.push((resolved, id));
350 id
351}
352
353#[deno_core::op2(fast)]
355pub fn op_is_key_down(state: &mut OpState, #[string] key: &str) -> bool {
356 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
357 bridge.borrow().keys_down.contains(key)
358}
359
360#[deno_core::op2(fast)]
362pub fn op_is_key_pressed(state: &mut OpState, #[string] key: &str) -> bool {
363 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
364 bridge.borrow().keys_pressed.contains(key)
365}
366
367#[deno_core::op2]
369#[serde]
370pub fn op_get_mouse_position(state: &mut OpState) -> Vec<f64> {
371 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
372 let b = bridge.borrow();
373 vec![b.mouse_x as f64, b.mouse_y as f64]
374}
375
376#[deno_core::op2(fast)]
379pub fn op_is_mouse_button_down(state: &mut OpState, button: u8) -> bool {
380 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
381 bridge.borrow().mouse_buttons_down.contains(&button)
382}
383
384#[deno_core::op2(fast)]
387pub fn op_is_mouse_button_pressed(state: &mut OpState, button: u8) -> bool {
388 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
389 bridge.borrow().mouse_buttons_pressed.contains(&button)
390}
391
392#[deno_core::op2(fast)]
394pub fn op_get_delta_time(state: &mut OpState) -> f64 {
395 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
396 bridge.borrow().delta_time
397}
398
399#[deno_core::op2(fast)]
402pub fn op_create_solid_texture(
403 state: &mut OpState,
404 #[string] name: &str,
405 r: u32,
406 g: u32,
407 b: u32,
408 a: u32,
409) -> u32 {
410 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
411 let mut br = bridge.borrow_mut();
412
413 let key = format!("__solid__{name}");
414 if let Some(&id) = br.texture_path_to_id.get(&key) {
415 return id;
416 }
417
418 let id = br.next_texture_id;
419 br.next_texture_id += 1;
420 br.texture_path_to_id.insert(key.clone(), id);
421 br.texture_load_queue
423 .push((format!("__solid__:{name}:{r}:{g}:{b}:{a}"), id));
424 id
425}
426
427#[deno_core::op2(fast)]
430pub fn op_upload_rgba_texture(
431 state: &mut OpState,
432 #[string] name: &str,
433 width: f64,
434 height: f64,
435 #[buffer] pixels: &[u8],
436) -> u32 {
437 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
438 let mut b = bridge.borrow_mut();
439
440 let key = format!("__raw__:{name}");
441 if let Some(&id) = b.texture_path_to_id.get(&key) {
442 return id;
443 }
444
445 let id = b.next_texture_id;
446 b.next_texture_id += 1;
447 b.texture_path_to_id.insert(key, id);
448 b.raw_texture_upload_queue.push((
449 id,
450 width as u32,
451 height as u32,
452 pixels.to_vec(),
453 ));
454 id
455}
456
457#[deno_core::op2(fast)]
459pub fn op_create_tilemap(
460 state: &mut OpState,
461 texture_id: u32,
462 width: u32,
463 height: u32,
464 tile_size: f64,
465 atlas_columns: u32,
466 atlas_rows: u32,
467) -> u32 {
468 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
469 bridge
470 .borrow_mut()
471 .tilemaps
472 .create(texture_id, width, height, tile_size as f32, atlas_columns, atlas_rows)
473}
474
475#[deno_core::op2(fast)]
477pub fn op_set_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32, tile_id: u32) {
478 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
479 if let Some(tm) = bridge.borrow_mut().tilemaps.get_mut(tilemap_id) {
480 tm.set_tile(gx, gy, tile_id as u16);
481 }
482}
483
484#[deno_core::op2(fast)]
486pub fn op_get_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32) -> u32 {
487 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
488 bridge
489 .borrow()
490 .tilemaps
491 .get(tilemap_id)
492 .map(|tm| tm.get_tile(gx, gy) as u32)
493 .unwrap_or(0)
494}
495
496#[deno_core::op2(fast)]
499pub fn op_draw_tilemap(state: &mut OpState, tilemap_id: u32, world_x: f64, world_y: f64, layer: i32) {
500 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
501 let mut b = bridge.borrow_mut();
502 let cam_x = b.camera_x;
503 let cam_y = b.camera_y;
504 let cam_zoom = b.camera_zoom;
505 let vp_w = 800.0;
507 let vp_h = 600.0;
508
509 if let Some(tm) = b.tilemaps.get(tilemap_id) {
510 let cmds = tm.bake_visible(world_x as f32, world_y as f32, layer, cam_x, cam_y, cam_zoom, vp_w, vp_h);
511 b.sprite_commands.extend(cmds);
512 }
513}
514
515#[deno_core::op2(fast)]
520pub fn op_set_ambient_light(state: &mut OpState, r: f64, g: f64, b: f64) {
521 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
522 bridge.borrow_mut().ambient_light = [r as f32, g as f32, b as f32];
523}
524
525#[deno_core::op2(fast)]
528pub fn op_add_point_light(
529 state: &mut OpState,
530 x: f64,
531 y: f64,
532 radius: f64,
533 r: f64,
534 g: f64,
535 b: f64,
536 intensity: f64,
537) {
538 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
539 bridge.borrow_mut().point_lights.push(PointLight {
540 x: x as f32,
541 y: y as f32,
542 radius: radius as f32,
543 r: r as f32,
544 g: g as f32,
545 b: b as f32,
546 intensity: intensity as f32,
547 });
548}
549
550#[deno_core::op2(fast)]
552pub fn op_clear_lights(state: &mut OpState) {
553 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
554 bridge.borrow_mut().point_lights.clear();
555}
556
557#[deno_core::op2(fast)]
561pub fn op_load_sound(state: &mut OpState, #[string] path: &str) -> u32 {
562 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
563 let mut b = bridge.borrow_mut();
564
565 let resolved = if std::path::Path::new(path).is_absolute() {
566 path.to_string()
567 } else {
568 b.base_dir.join(path).to_string_lossy().to_string()
569 };
570
571 if let Some(&id) = b.sound_path_to_id.get(&resolved) {
572 return id;
573 }
574
575 let id = b.next_sound_id;
576 b.next_sound_id += 1;
577 b.sound_path_to_id.insert(resolved.clone(), id);
578 b.audio_commands.push(BridgeAudioCommand::LoadSound { id, path: resolved });
579 id
580}
581
582#[deno_core::op2(fast)]
585pub fn op_play_sound(state: &mut OpState, id: u32, volume: f64, looping: bool) {
586 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
587 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySound { id, volume: volume as f32, looping });
588}
589
590#[deno_core::op2(fast)]
592pub fn op_stop_sound(state: &mut OpState, id: u32) {
593 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
594 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopSound { id });
595}
596
597#[deno_core::op2(fast)]
599pub fn op_stop_all_sounds(state: &mut OpState) {
600 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
601 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopAll);
602}
603
604#[deno_core::op2(fast)]
607pub fn op_set_master_volume(state: &mut OpState, volume: f64) {
608 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
609 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetMasterVolume { volume: volume as f32 });
610}
611
612#[deno_core::op2(fast)]
616pub fn op_create_font_texture(state: &mut OpState) -> u32 {
617 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
618 let mut b = bridge.borrow_mut();
619
620 let key = "__builtin_font__".to_string();
621 if let Some(&id) = b.texture_path_to_id.get(&key) {
622 return id;
623 }
624
625 let id = b.next_texture_id;
626 b.next_texture_id += 1;
627 b.texture_path_to_id.insert(key, id);
628 b.font_texture_queue.push(id);
629 id
630}
631
632#[deno_core::op2]
636#[serde]
637pub fn op_get_viewport_size(state: &mut OpState) -> Vec<f64> {
638 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
639 let b = bridge.borrow();
640 vec![b.viewport_width as f64, b.viewport_height as f64]
641}
642
643#[deno_core::op2(fast)]
645pub fn op_get_scale_factor(state: &mut OpState) -> f64 {
646 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
647 bridge.borrow().scale_factor as f64
648}
649
650#[deno_core::op2(fast)]
652pub fn op_set_background_color(state: &mut OpState, r: f64, g: f64, b: f64) {
653 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
654 let mut br = bridge.borrow_mut();
655 br.clear_color = [r as f32, g as f32, b as f32, 1.0];
656}
657
658#[deno_core::op2(fast)]
662pub fn op_save_file(state: &mut OpState, #[string] key: &str, #[string] value: &str) -> bool {
663 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
664 let save_dir = bridge.borrow().save_dir.clone();
665
666 if !key.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
668 return false;
669 }
670
671 if std::fs::create_dir_all(&save_dir).is_err() {
673 return false;
674 }
675
676 let path = save_dir.join(format!("{key}.json"));
677 std::fs::write(path, value).is_ok()
678}
679
680#[deno_core::op2]
682#[string]
683pub fn op_load_file(state: &mut OpState, #[string] key: &str) -> String {
684 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
685 let save_dir = bridge.borrow().save_dir.clone();
686
687 let path = save_dir.join(format!("{key}.json"));
688 std::fs::read_to_string(path).unwrap_or_default()
689}
690
691#[deno_core::op2(fast)]
693pub fn op_delete_file(state: &mut OpState, #[string] key: &str) -> bool {
694 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
695 let save_dir = bridge.borrow().save_dir.clone();
696
697 let path = save_dir.join(format!("{key}.json"));
698 std::fs::remove_file(path).is_ok()
699}
700
701#[deno_core::op2]
703#[serde]
704pub fn op_list_save_files(state: &mut OpState) -> Vec<String> {
705 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
706 let save_dir = bridge.borrow().save_dir.clone();
707
708 let mut keys = Vec::new();
709 if let Ok(entries) = std::fs::read_dir(&save_dir) {
710 for entry in entries.flatten() {
711 let path = entry.path();
712 if path.extension().map_or(false, |ext| ext == "json") {
713 if let Some(stem) = path.file_stem() {
714 keys.push(stem.to_string_lossy().to_string());
715 }
716 }
717 }
718 }
719 keys.sort();
720 keys
721}
722
723#[deno_core::op2(fast)]
727pub fn op_create_shader(state: &mut OpState, #[string] name: &str, #[string] source: &str) -> u32 {
728 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
729 let mut b = bridge.borrow_mut();
730 let id = b.next_shader_id;
731 b.next_shader_id += 1;
732 b.shader_create_queue
733 .push((id, name.to_string(), source.to_string()));
734 id
735}
736
737#[deno_core::op2(fast)]
739pub fn op_set_shader_param(
740 state: &mut OpState,
741 shader_id: u32,
742 index: u32,
743 x: f64,
744 y: f64,
745 z: f64,
746 w: f64,
747) {
748 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
749 bridge.borrow_mut().shader_param_queue.push((
750 shader_id,
751 index,
752 [x as f32, y as f32, z as f32, w as f32],
753 ));
754}
755
756#[deno_core::op2(fast)]
760pub fn op_add_effect(state: &mut OpState, #[string] effect_type: &str) -> u32 {
761 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
762 let mut b = bridge.borrow_mut();
763 let id = b.next_effect_id;
764 b.next_effect_id += 1;
765 b.effect_create_queue
766 .push((id, effect_type.to_string()));
767 id
768}
769
770#[deno_core::op2(fast)]
772pub fn op_set_effect_param(
773 state: &mut OpState,
774 effect_id: u32,
775 index: u32,
776 x: f64,
777 y: f64,
778 z: f64,
779 w: f64,
780) {
781 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
782 bridge.borrow_mut().effect_param_queue.push((
783 effect_id,
784 index,
785 [x as f32, y as f32, z as f32, w as f32],
786 ));
787}
788
789#[deno_core::op2(fast)]
791pub fn op_remove_effect(state: &mut OpState, effect_id: u32) {
792 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
793 bridge.borrow_mut().effect_remove_queue.push(effect_id);
794}
795
796#[deno_core::op2(fast)]
798pub fn op_clear_effects(state: &mut OpState) {
799 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
800 bridge.borrow_mut().effect_clear = true;
801}
802
803#[deno_core::op2(fast)]
807pub fn op_set_camera_bounds(state: &mut OpState, min_x: f64, min_y: f64, max_x: f64, max_y: f64) {
808 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
809 bridge.borrow_mut().camera_bounds = Some(CameraBounds {
810 min_x: min_x as f32,
811 min_y: min_y as f32,
812 max_x: max_x as f32,
813 max_y: max_y as f32,
814 });
815}
816
817#[deno_core::op2(fast)]
819pub fn op_clear_camera_bounds(state: &mut OpState) {
820 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
821 bridge.borrow_mut().camera_bounds = None;
822}
823
824#[deno_core::op2]
826#[serde]
827pub fn op_get_camera_bounds(state: &mut OpState) -> Vec<f64> {
828 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
829 let b = bridge.borrow();
830 match b.camera_bounds {
831 Some(bounds) => vec![
832 bounds.min_x as f64,
833 bounds.min_y as f64,
834 bounds.max_x as f64,
835 bounds.max_y as f64,
836 ],
837 None => vec![],
838 }
839}
840
841#[deno_core::op2(fast)]
845pub fn op_enable_gi(state: &mut OpState) {
846 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
847 bridge.borrow_mut().gi_enabled = true;
848}
849
850#[deno_core::op2(fast)]
852pub fn op_disable_gi(state: &mut OpState) {
853 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
854 bridge.borrow_mut().gi_enabled = false;
855}
856
857#[deno_core::op2(fast)]
859pub fn op_set_gi_intensity(state: &mut OpState, intensity: f64) {
860 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
861 bridge.borrow_mut().gi_intensity = intensity as f32;
862}
863
864#[deno_core::op2(fast)]
867pub fn op_set_gi_quality(state: &mut OpState, probe_spacing: f64, interval: f64, cascade_count: f64) {
868 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
869 let mut b = bridge.borrow_mut();
870 if probe_spacing > 0.0 {
871 b.gi_probe_spacing = Some(probe_spacing as f32);
872 }
873 if interval > 0.0 {
874 b.gi_interval = Some(interval as f32);
875 }
876 if cascade_count > 0.0 {
877 b.gi_cascade_count = Some(cascade_count as u32);
878 }
879}
880
881#[deno_core::op2(fast)]
883pub fn op_add_emissive(
884 state: &mut OpState,
885 x: f64,
886 y: f64,
887 w: f64,
888 h: f64,
889 r: f64,
890 g: f64,
891 b: f64,
892 intensity: f64,
893) {
894 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
895 bridge.borrow_mut().emissives.push([
896 x as f32,
897 y as f32,
898 w as f32,
899 h as f32,
900 r as f32,
901 g as f32,
902 b as f32,
903 intensity as f32,
904 ]);
905}
906
907#[deno_core::op2(fast)]
909pub fn op_clear_emissives(state: &mut OpState) {
910 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
911 bridge.borrow_mut().emissives.clear();
912}
913
914#[deno_core::op2(fast)]
916pub fn op_add_occluder(state: &mut OpState, x: f64, y: f64, w: f64, h: f64) {
917 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
918 bridge.borrow_mut().occluders.push([x as f32, y as f32, w as f32, h as f32]);
919}
920
921#[deno_core::op2(fast)]
923pub fn op_clear_occluders(state: &mut OpState) {
924 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
925 bridge.borrow_mut().occluders.clear();
926}
927
928#[deno_core::op2(fast)]
930pub fn op_add_directional_light(
931 state: &mut OpState,
932 angle: f64,
933 r: f64,
934 g: f64,
935 b: f64,
936 intensity: f64,
937) {
938 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
939 bridge.borrow_mut().directional_lights.push([
940 angle as f32,
941 r as f32,
942 g as f32,
943 b as f32,
944 intensity as f32,
945 ]);
946}
947
948#[deno_core::op2(fast)]
950pub fn op_add_spot_light(
951 state: &mut OpState,
952 x: f64,
953 y: f64,
954 angle: f64,
955 spread: f64,
956 range: f64,
957 r: f64,
958 g: f64,
959 b: f64,
960 intensity: f64,
961) {
962 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
963 bridge.borrow_mut().spot_lights.push([
964 x as f32,
965 y as f32,
966 angle as f32,
967 spread as f32,
968 range as f32,
969 r as f32,
970 g as f32,
971 b as f32,
972 intensity as f32,
973 ]);
974}
975
976#[deno_core::op2(fast)]
981pub fn op_play_sound_ex(
982 state: &mut OpState,
983 sound_id: u32,
984 instance_id: f64,
985 volume: f64,
986 looping: bool,
987 bus: u32,
988 pan: f64,
989 pitch: f64,
990 low_pass_freq: u32,
991 reverb_mix: f64,
992 reverb_delay_ms: u32,
993) {
994 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
995 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySoundEx {
996 sound_id,
997 instance_id: instance_id as u64,
998 volume: volume as f32,
999 looping,
1000 bus,
1001 pan: pan as f32,
1002 pitch: pitch as f32,
1003 low_pass_freq,
1004 reverb_mix: reverb_mix as f32,
1005 reverb_delay_ms,
1006 });
1007}
1008
1009#[deno_core::op2(fast)]
1012pub fn op_play_sound_spatial(
1013 state: &mut OpState,
1014 sound_id: u32,
1015 instance_id: f64,
1016 volume: f64,
1017 looping: bool,
1018 bus: u32,
1019 pitch: f64,
1020 source_x: f64,
1021 source_y: f64,
1022 listener_x: f64,
1023 listener_y: f64,
1024) {
1025 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1026 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySoundSpatial {
1027 sound_id,
1028 instance_id: instance_id as u64,
1029 volume: volume as f32,
1030 looping,
1031 bus,
1032 pitch: pitch as f32,
1033 source_x: source_x as f32,
1034 source_y: source_y as f32,
1035 listener_x: listener_x as f32,
1036 listener_y: listener_y as f32,
1037 });
1038}
1039
1040#[deno_core::op2(fast)]
1043pub fn op_stop_instance(state: &mut OpState, instance_id: f64) {
1044 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1045 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopInstance {
1046 instance_id: instance_id as u64,
1047 });
1048}
1049
1050#[deno_core::op2(fast)]
1053pub fn op_set_instance_volume(state: &mut OpState, instance_id: f64, volume: f64) {
1054 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1055 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetInstanceVolume {
1056 instance_id: instance_id as u64,
1057 volume: volume as f32,
1058 });
1059}
1060
1061#[deno_core::op2(fast)]
1064pub fn op_set_instance_pitch(state: &mut OpState, instance_id: f64, pitch: f64) {
1065 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1066 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetInstancePitch {
1067 instance_id: instance_id as u64,
1068 pitch: pitch as f32,
1069 });
1070}
1071
1072#[deno_core::op2(fast)]
1076pub fn op_update_spatial_positions(state: &mut OpState, #[string] data_json: &str, listener_x: f64, listener_y: f64) {
1077 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1078
1079 let mut updates = Vec::new();
1082
1083 if let Some(ids_start) = data_json.find("\"instanceIds\":[") {
1085 if let Some(xs_start) = data_json.find("\"sourceXs\":[") {
1086 if let Some(ys_start) = data_json.find("\"sourceYs\":[") {
1087 let ids_str = &data_json[ids_start + 15..];
1088 let xs_str = &data_json[xs_start + 12..];
1089 let ys_str = &data_json[ys_start + 12..];
1090
1091 let ids_end = ids_str.find(']').unwrap_or(0);
1092 let xs_end = xs_str.find(']').unwrap_or(0);
1093 let ys_end = ys_str.find(']').unwrap_or(0);
1094
1095 let ids: Vec<u64> = ids_str[..ids_end]
1096 .split(',')
1097 .filter_map(|s| s.trim().parse().ok())
1098 .collect();
1099 let xs: Vec<f32> = xs_str[..xs_end]
1100 .split(',')
1101 .filter_map(|s| s.trim().parse().ok())
1102 .collect();
1103 let ys: Vec<f32> = ys_str[..ys_end]
1104 .split(',')
1105 .filter_map(|s| s.trim().parse().ok())
1106 .collect();
1107
1108 for i in 0..ids.len().min(xs.len()).min(ys.len()) {
1109 updates.push((ids[i], xs[i], ys[i]));
1110 }
1111 }
1112 }
1113 }
1114
1115 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::UpdateSpatialPositions {
1116 updates,
1117 listener_x: listener_x as f32,
1118 listener_y: listener_y as f32,
1119 });
1120}
1121
1122#[deno_core::op2(fast)]
1125pub fn op_set_bus_volume(state: &mut OpState, bus: u32, volume: f64) {
1126 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1127 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetBusVolume {
1128 bus,
1129 volume: volume as f32,
1130 });
1131}
1132
1133#[deno_core::op2]
1138#[string]
1139pub fn op_create_msdf_builtin_font(state: &mut OpState) -> String {
1140 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1141 let mut b = bridge.borrow_mut();
1142
1143 let key = "__msdf_builtin__".to_string();
1145 if let Some(&tex_id) = b.texture_path_to_id.get(&key) {
1146 let font_id_key = format!("__msdf_font_{tex_id}__");
1149 if let Some(&font_id) = b.texture_path_to_id.get(&font_id_key) {
1150 let pool = &b.msdf_shader_pool;
1151 let shader_id = pool.first().copied().unwrap_or(0);
1152 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1153 return format!(
1154 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1155 font_id, tex_id, shader_id, pool_json.join(",")
1156 );
1157 }
1158 }
1159
1160 let tex_id = b.next_texture_id;
1162 b.next_texture_id += 1;
1163 b.texture_path_to_id.insert(key, tex_id);
1164
1165 let (_pixels, _width, _height, mut font) =
1167 crate::renderer::msdf::generate_builtin_msdf_font();
1168 font.texture_id = tex_id;
1169
1170 let font_id = b.msdf_fonts.register(font);
1172 b.texture_path_to_id
1173 .insert(format!("__msdf_font_{tex_id}__"), font_id);
1174
1175 b.msdf_builtin_queue.push((font_id, tex_id));
1178
1179 let pool = ensure_msdf_shader_pool(&mut b);
1181 let shader_id = pool.first().copied().unwrap_or(0);
1182 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1183
1184 format!(
1185 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1186 font_id, tex_id, shader_id, pool_json.join(",")
1187 )
1188}
1189
1190#[deno_core::op2]
1193#[string]
1194pub fn op_get_msdf_glyphs(
1195 state: &mut OpState,
1196 font_id: u32,
1197 #[string] text: &str,
1198) -> String {
1199 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1200 let b = bridge.borrow();
1201
1202 let font = match b.msdf_fonts.get(font_id) {
1203 Some(f) => f,
1204 None => return "[]".to_string(),
1205 };
1206
1207 let mut entries = Vec::new();
1208 for ch in text.chars() {
1209 if let Some(glyph) = font.get_glyph(ch) {
1210 entries.push(format!(
1211 "{{\"char\":{},\"uv\":[{},{},{},{}],\"advance\":{},\"width\":{},\"height\":{},\"offsetX\":{},\"offsetY\":{}}}",
1212 ch as u32,
1213 glyph.uv_x, glyph.uv_y, glyph.uv_w, glyph.uv_h,
1214 glyph.advance, glyph.width, glyph.height,
1215 glyph.offset_x, glyph.offset_y,
1216 ));
1217 }
1218 }
1219
1220 format!("[{}]", entries.join(","))
1221}
1222
1223#[deno_core::op2]
1225#[string]
1226pub fn op_get_msdf_font_info(state: &mut OpState, font_id: u32) -> String {
1227 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1228 let b = bridge.borrow();
1229
1230 match b.msdf_fonts.get(font_id) {
1231 Some(font) => format!(
1232 "{{\"fontSize\":{},\"lineHeight\":{},\"distanceRange\":{},\"textureId\":{}}}",
1233 font.font_size, font.line_height, font.distance_range, font.texture_id,
1234 ),
1235 None => "null".to_string(),
1236 }
1237}
1238
1239#[deno_core::op2]
1242#[string]
1243pub fn op_load_msdf_font(
1244 state: &mut OpState,
1245 #[string] atlas_path: &str,
1246 #[string] metrics_json: &str,
1247) -> String {
1248 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1249 let mut b = bridge.borrow_mut();
1250
1251 let resolved = if std::path::Path::new(atlas_path).is_absolute() {
1253 atlas_path.to_string()
1254 } else {
1255 b.base_dir.join(atlas_path).to_string_lossy().to_string()
1256 };
1257
1258 let tex_id = if let Some(&id) = b.texture_path_to_id.get(&resolved) {
1260 id
1261 } else {
1262 let id = b.next_texture_id;
1263 b.next_texture_id += 1;
1264 b.texture_path_to_id.insert(resolved.clone(), id);
1265 b.msdf_texture_load_queue.push((resolved, id));
1266 id
1267 };
1268
1269 let font = match crate::renderer::msdf::parse_msdf_metrics(metrics_json, tex_id) {
1271 Ok(f) => f,
1272 Err(e) => {
1273 return format!("{{\"error\":\"{}\"}}", e);
1274 }
1275 };
1276
1277 let font_id = b.msdf_fonts.register(font);
1278 let pool = ensure_msdf_shader_pool(&mut b);
1279 let shader_id = pool.first().copied().unwrap_or(0);
1280 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1281
1282 format!(
1283 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1284 font_id, tex_id, shader_id, pool_json.join(",")
1285 )
1286}
1287
1288const MSDF_SHADER_POOL_SIZE: usize = 8;
1290
1291fn ensure_msdf_shader_pool(b: &mut RenderBridgeState) -> Vec<u32> {
1293 if !b.msdf_shader_pool.is_empty() {
1294 return b.msdf_shader_pool.clone();
1295 }
1296
1297 let source = crate::renderer::msdf::MSDF_FRAGMENT_SOURCE.to_string();
1298 let mut pool = Vec::with_capacity(MSDF_SHADER_POOL_SIZE);
1299
1300 for _ in 0..MSDF_SHADER_POOL_SIZE {
1301 let id = b.next_shader_id;
1302 b.next_shader_id += 1;
1303 b.msdf_shader_queue.push((id, source.clone()));
1304 pool.push(id);
1305 }
1306
1307 b.msdf_shader_pool = pool.clone();
1308 pool
1309}
1310
1311#[deno_core::op2(fast)]
1315pub fn op_get_gamepad_count(state: &mut OpState) -> u32 {
1316 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1317 bridge.borrow().gamepad_count
1318}
1319
1320#[deno_core::op2]
1322#[string]
1323pub fn op_get_gamepad_name(state: &mut OpState) -> String {
1324 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1325 bridge.borrow().gamepad_name.clone()
1326}
1327
1328#[deno_core::op2(fast)]
1331pub fn op_is_gamepad_button_down(state: &mut OpState, #[string] button: &str) -> bool {
1332 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1333 bridge.borrow().gamepad_buttons_down.contains(button)
1334}
1335
1336#[deno_core::op2(fast)]
1338pub fn op_is_gamepad_button_pressed(state: &mut OpState, #[string] button: &str) -> bool {
1339 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1340 bridge.borrow().gamepad_buttons_pressed.contains(button)
1341}
1342
1343#[deno_core::op2(fast)]
1346pub fn op_get_gamepad_axis(state: &mut OpState, #[string] axis: &str) -> f64 {
1347 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1348 bridge.borrow().gamepad_axes.get(axis).copied().unwrap_or(0.0) as f64
1349}
1350
1351#[deno_core::op2(fast)]
1355pub fn op_get_touch_count(state: &mut OpState) -> u32 {
1356 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1357 bridge.borrow().touch_count
1358}
1359
1360#[deno_core::op2]
1362#[serde]
1363pub fn op_get_touch_position(state: &mut OpState, index: u32) -> Vec<f64> {
1364 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1365 let b = bridge.borrow();
1366 if let Some(&(_, x, y)) = b.touch_points.get(index as usize) {
1367 vec![x as f64, y as f64]
1368 } else {
1369 vec![]
1370 }
1371}
1372
1373#[deno_core::op2(fast)]
1375pub fn op_is_touch_active(state: &mut OpState) -> bool {
1376 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1377 bridge.borrow().touch_count > 0
1378}
1379
1380deno_core::extension!(
1381 render_ext,
1382 ops = [
1383 op_draw_sprite,
1384 op_clear_sprites,
1385 op_set_camera,
1386 op_get_camera,
1387 op_load_texture,
1388 op_upload_rgba_texture,
1389 op_is_key_down,
1390 op_is_key_pressed,
1391 op_get_mouse_position,
1392 op_is_mouse_button_down,
1393 op_is_mouse_button_pressed,
1394 op_get_delta_time,
1395 op_create_solid_texture,
1396 op_create_tilemap,
1397 op_set_tile,
1398 op_get_tile,
1399 op_draw_tilemap,
1400 op_set_ambient_light,
1401 op_add_point_light,
1402 op_clear_lights,
1403 op_load_sound,
1404 op_play_sound,
1405 op_stop_sound,
1406 op_stop_all_sounds,
1407 op_set_master_volume,
1408 op_play_sound_ex,
1409 op_play_sound_spatial,
1410 op_stop_instance,
1411 op_set_instance_volume,
1412 op_set_instance_pitch,
1413 op_update_spatial_positions,
1414 op_set_bus_volume,
1415 op_create_font_texture,
1416 op_get_viewport_size,
1417 op_get_scale_factor,
1418 op_set_background_color,
1419 op_save_file,
1420 op_load_file,
1421 op_delete_file,
1422 op_list_save_files,
1423 op_create_shader,
1424 op_set_shader_param,
1425 op_add_effect,
1426 op_set_effect_param,
1427 op_remove_effect,
1428 op_clear_effects,
1429 op_set_camera_bounds,
1430 op_clear_camera_bounds,
1431 op_get_camera_bounds,
1432 op_enable_gi,
1433 op_disable_gi,
1434 op_set_gi_intensity,
1435 op_set_gi_quality,
1436 op_add_emissive,
1437 op_clear_emissives,
1438 op_add_occluder,
1439 op_clear_occluders,
1440 op_add_directional_light,
1441 op_add_spot_light,
1442 op_create_msdf_builtin_font,
1443 op_get_msdf_glyphs,
1444 op_get_msdf_font_info,
1445 op_load_msdf_font,
1446 op_get_gamepad_count,
1447 op_get_gamepad_name,
1448 op_is_gamepad_button_down,
1449 op_is_gamepad_button_pressed,
1450 op_get_gamepad_axis,
1451 op_get_touch_count,
1452 op_get_touch_position,
1453 op_is_touch_active,
1454 ],
1455);