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;
10
11#[derive(Clone, Debug)]
13pub enum BridgeAudioCommand {
14 LoadSound { id: u32, path: String },
15 PlaySound { id: u32, volume: f32, looping: bool },
16 StopSound { id: u32 },
17 StopAll,
18 SetMasterVolume { volume: f32 },
19}
20
21#[derive(Clone)]
24pub struct RenderBridgeState {
25 pub sprite_commands: Vec<SpriteCommand>,
26 pub camera_x: f32,
27 pub camera_y: f32,
28 pub camera_zoom: f32,
29 pub delta_time: f64,
30 pub keys_down: std::collections::HashSet<String>,
32 pub keys_pressed: std::collections::HashSet<String>,
33 pub mouse_x: f32,
34 pub mouse_y: f32,
35 pub texture_load_queue: Vec<(String, u32)>,
37 pub base_dir: PathBuf,
39 pub next_texture_id: u32,
41 pub texture_path_to_id: std::collections::HashMap<String, u32>,
43 pub tilemaps: TilemapStore,
45 pub ambient_light: [f32; 3],
47 pub point_lights: Vec<PointLight>,
49 pub audio_commands: Vec<BridgeAudioCommand>,
51 pub next_sound_id: u32,
53 pub sound_path_to_id: std::collections::HashMap<String, u32>,
55 pub font_texture_queue: Vec<u32>,
57 pub viewport_width: f32,
59 pub viewport_height: f32,
60 pub scale_factor: f32,
62 pub clear_color: [f32; 4],
64 pub save_dir: PathBuf,
66 pub shader_create_queue: Vec<(u32, String, String)>,
68 pub shader_param_queue: Vec<(u32, u32, [f32; 4])>,
70 pub next_shader_id: u32,
72 pub effect_create_queue: Vec<(u32, String)>,
74 pub effect_param_queue: Vec<(u32, u32, [f32; 4])>,
76 pub effect_remove_queue: Vec<u32>,
78 pub effect_clear: bool,
80 pub next_effect_id: u32,
82}
83
84impl RenderBridgeState {
85 pub fn new(base_dir: PathBuf) -> Self {
86 let save_dir = base_dir.join(".arcane").join("saves");
87 Self {
88 sprite_commands: Vec::new(),
89 camera_x: 0.0,
90 camera_y: 0.0,
91 camera_zoom: 1.0,
92 delta_time: 0.0,
93 keys_down: std::collections::HashSet::new(),
94 keys_pressed: std::collections::HashSet::new(),
95 mouse_x: 0.0,
96 mouse_y: 0.0,
97 texture_load_queue: Vec::new(),
98 base_dir,
99 next_texture_id: 1,
100 texture_path_to_id: std::collections::HashMap::new(),
101 tilemaps: TilemapStore::new(),
102 ambient_light: [1.0, 1.0, 1.0],
103 point_lights: Vec::new(),
104 audio_commands: Vec::new(),
105 next_sound_id: 1,
106 sound_path_to_id: std::collections::HashMap::new(),
107 font_texture_queue: Vec::new(),
108 viewport_width: 800.0,
109 viewport_height: 600.0,
110 scale_factor: 1.0,
111 clear_color: [0.1, 0.1, 0.15, 1.0],
112 save_dir,
113 shader_create_queue: Vec::new(),
114 shader_param_queue: Vec::new(),
115 next_shader_id: 1,
116 effect_create_queue: Vec::new(),
117 effect_param_queue: Vec::new(),
118 effect_remove_queue: Vec::new(),
119 effect_clear: false,
120 next_effect_id: 1,
121 }
122 }
123}
124
125#[deno_core::op2(fast)]
128pub fn op_draw_sprite(
129 state: &mut OpState,
130 texture_id: u32,
131 x: f64,
132 y: f64,
133 w: f64,
134 h: f64,
135 layer: i32,
136 uv_x: f64,
137 uv_y: f64,
138 uv_w: f64,
139 uv_h: f64,
140 tint_r: f64,
141 tint_g: f64,
142 tint_b: f64,
143 tint_a: f64,
144 rotation: f64,
145 origin_x: f64,
146 origin_y: f64,
147 flip_x: f64,
148 flip_y: f64,
149 opacity: f64,
150 blend_mode: f64,
151 shader_id: f64,
152) {
153 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
154 bridge.borrow_mut().sprite_commands.push(SpriteCommand {
155 texture_id,
156 x: x as f32,
157 y: y as f32,
158 w: w as f32,
159 h: h as f32,
160 layer,
161 uv_x: uv_x as f32,
162 uv_y: uv_y as f32,
163 uv_w: uv_w as f32,
164 uv_h: uv_h as f32,
165 tint_r: tint_r as f32,
166 tint_g: tint_g as f32,
167 tint_b: tint_b as f32,
168 tint_a: tint_a as f32,
169 rotation: rotation as f32,
170 origin_x: origin_x as f32,
171 origin_y: origin_y as f32,
172 flip_x: flip_x != 0.0,
173 flip_y: flip_y != 0.0,
174 opacity: opacity as f32,
175 blend_mode: (blend_mode as u8).min(3),
176 shader_id: shader_id as u32,
177 });
178}
179
180#[deno_core::op2(fast)]
182pub fn op_clear_sprites(state: &mut OpState) {
183 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
184 bridge.borrow_mut().sprite_commands.clear();
185}
186
187#[deno_core::op2(fast)]
190pub fn op_set_camera(state: &mut OpState, x: f64, y: f64, zoom: f64) {
191 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
192 let mut b = bridge.borrow_mut();
193 b.camera_x = x as f32;
194 b.camera_y = y as f32;
195 b.camera_zoom = zoom as f32;
196}
197
198#[deno_core::op2]
200#[serde]
201pub fn op_get_camera(state: &mut OpState) -> Vec<f64> {
202 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
203 let b = bridge.borrow();
204 vec![b.camera_x as f64, b.camera_y as f64, b.camera_zoom as f64]
205}
206
207#[deno_core::op2(fast)]
210pub fn op_load_texture(state: &mut OpState, #[string] path: &str) -> u32 {
211 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
212 let mut b = bridge.borrow_mut();
213
214 let resolved = if std::path::Path::new(path).is_absolute() {
216 path.to_string()
217 } else {
218 b.base_dir.join(path).to_string_lossy().to_string()
219 };
220
221 if let Some(&id) = b.texture_path_to_id.get(&resolved) {
223 return id;
224 }
225
226 let id = b.next_texture_id;
227 b.next_texture_id += 1;
228 b.texture_path_to_id.insert(resolved.clone(), id);
229 b.texture_load_queue.push((resolved, id));
230 id
231}
232
233#[deno_core::op2(fast)]
235pub fn op_is_key_down(state: &mut OpState, #[string] key: &str) -> bool {
236 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
237 bridge.borrow().keys_down.contains(key)
238}
239
240#[deno_core::op2(fast)]
242pub fn op_is_key_pressed(state: &mut OpState, #[string] key: &str) -> bool {
243 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
244 bridge.borrow().keys_pressed.contains(key)
245}
246
247#[deno_core::op2]
249#[serde]
250pub fn op_get_mouse_position(state: &mut OpState) -> Vec<f64> {
251 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
252 let b = bridge.borrow();
253 vec![b.mouse_x as f64, b.mouse_y as f64]
254}
255
256#[deno_core::op2(fast)]
258pub fn op_get_delta_time(state: &mut OpState) -> f64 {
259 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
260 bridge.borrow().delta_time
261}
262
263#[deno_core::op2(fast)]
266pub fn op_create_solid_texture(
267 state: &mut OpState,
268 #[string] name: &str,
269 r: u32,
270 g: u32,
271 b: u32,
272 a: u32,
273) -> u32 {
274 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
275 let mut br = bridge.borrow_mut();
276
277 let key = format!("__solid__{name}");
278 if let Some(&id) = br.texture_path_to_id.get(&key) {
279 return id;
280 }
281
282 let id = br.next_texture_id;
283 br.next_texture_id += 1;
284 br.texture_path_to_id.insert(key.clone(), id);
285 br.texture_load_queue
287 .push((format!("__solid__:{name}:{r}:{g}:{b}:{a}"), id));
288 id
289}
290
291#[deno_core::op2(fast)]
293pub fn op_create_tilemap(
294 state: &mut OpState,
295 texture_id: u32,
296 width: u32,
297 height: u32,
298 tile_size: f64,
299 atlas_columns: u32,
300 atlas_rows: u32,
301) -> u32 {
302 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
303 bridge
304 .borrow_mut()
305 .tilemaps
306 .create(texture_id, width, height, tile_size as f32, atlas_columns, atlas_rows)
307}
308
309#[deno_core::op2(fast)]
311pub fn op_set_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32, tile_id: u32) {
312 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
313 if let Some(tm) = bridge.borrow_mut().tilemaps.get_mut(tilemap_id) {
314 tm.set_tile(gx, gy, tile_id as u16);
315 }
316}
317
318#[deno_core::op2(fast)]
320pub fn op_get_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32) -> u32 {
321 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
322 bridge
323 .borrow()
324 .tilemaps
325 .get(tilemap_id)
326 .map(|tm| tm.get_tile(gx, gy) as u32)
327 .unwrap_or(0)
328}
329
330#[deno_core::op2(fast)]
333pub fn op_draw_tilemap(state: &mut OpState, tilemap_id: u32, world_x: f64, world_y: f64, layer: i32) {
334 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
335 let mut b = bridge.borrow_mut();
336 let cam_x = b.camera_x;
337 let cam_y = b.camera_y;
338 let cam_zoom = b.camera_zoom;
339 let vp_w = 800.0;
341 let vp_h = 600.0;
342
343 if let Some(tm) = b.tilemaps.get(tilemap_id) {
344 let cmds = tm.bake_visible(world_x as f32, world_y as f32, layer, cam_x, cam_y, cam_zoom, vp_w, vp_h);
345 b.sprite_commands.extend(cmds);
346 }
347}
348
349#[deno_core::op2(fast)]
354pub fn op_set_ambient_light(state: &mut OpState, r: f64, g: f64, b: f64) {
355 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
356 bridge.borrow_mut().ambient_light = [r as f32, g as f32, b as f32];
357}
358
359#[deno_core::op2(fast)]
362pub fn op_add_point_light(
363 state: &mut OpState,
364 x: f64,
365 y: f64,
366 radius: f64,
367 r: f64,
368 g: f64,
369 b: f64,
370 intensity: f64,
371) {
372 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
373 bridge.borrow_mut().point_lights.push(PointLight {
374 x: x as f32,
375 y: y as f32,
376 radius: radius as f32,
377 r: r as f32,
378 g: g as f32,
379 b: b as f32,
380 intensity: intensity as f32,
381 });
382}
383
384#[deno_core::op2(fast)]
386pub fn op_clear_lights(state: &mut OpState) {
387 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
388 bridge.borrow_mut().point_lights.clear();
389}
390
391#[deno_core::op2(fast)]
395pub fn op_load_sound(state: &mut OpState, #[string] path: &str) -> u32 {
396 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
397 let mut b = bridge.borrow_mut();
398
399 let resolved = if std::path::Path::new(path).is_absolute() {
400 path.to_string()
401 } else {
402 b.base_dir.join(path).to_string_lossy().to_string()
403 };
404
405 if let Some(&id) = b.sound_path_to_id.get(&resolved) {
406 return id;
407 }
408
409 let id = b.next_sound_id;
410 b.next_sound_id += 1;
411 b.sound_path_to_id.insert(resolved.clone(), id);
412 b.audio_commands.push(BridgeAudioCommand::LoadSound { id, path: resolved });
413 id
414}
415
416#[deno_core::op2(fast)]
419pub fn op_play_sound(state: &mut OpState, id: u32, volume: f64, looping: bool) {
420 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
421 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySound { id, volume: volume as f32, looping });
422}
423
424#[deno_core::op2(fast)]
426pub fn op_stop_sound(state: &mut OpState, id: u32) {
427 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
428 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopSound { id });
429}
430
431#[deno_core::op2(fast)]
433pub fn op_stop_all_sounds(state: &mut OpState) {
434 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
435 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopAll);
436}
437
438#[deno_core::op2(fast)]
441pub fn op_set_master_volume(state: &mut OpState, volume: f64) {
442 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
443 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetMasterVolume { volume: volume as f32 });
444}
445
446#[deno_core::op2(fast)]
450pub fn op_create_font_texture(state: &mut OpState) -> u32 {
451 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
452 let mut b = bridge.borrow_mut();
453
454 let key = "__builtin_font__".to_string();
455 if let Some(&id) = b.texture_path_to_id.get(&key) {
456 return id;
457 }
458
459 let id = b.next_texture_id;
460 b.next_texture_id += 1;
461 b.texture_path_to_id.insert(key, id);
462 b.font_texture_queue.push(id);
463 id
464}
465
466#[deno_core::op2]
470#[serde]
471pub fn op_get_viewport_size(state: &mut OpState) -> Vec<f64> {
472 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
473 let b = bridge.borrow();
474 vec![b.viewport_width as f64, b.viewport_height as f64]
475}
476
477#[deno_core::op2(fast)]
479pub fn op_get_scale_factor(state: &mut OpState) -> f64 {
480 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
481 bridge.borrow().scale_factor as f64
482}
483
484#[deno_core::op2(fast)]
486pub fn op_set_background_color(state: &mut OpState, r: f64, g: f64, b: f64) {
487 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
488 let mut br = bridge.borrow_mut();
489 br.clear_color = [r as f32, g as f32, b as f32, 1.0];
490}
491
492#[deno_core::op2(fast)]
496pub fn op_save_file(state: &mut OpState, #[string] key: &str, #[string] value: &str) -> bool {
497 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
498 let save_dir = bridge.borrow().save_dir.clone();
499
500 if !key.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
502 return false;
503 }
504
505 if std::fs::create_dir_all(&save_dir).is_err() {
507 return false;
508 }
509
510 let path = save_dir.join(format!("{key}.json"));
511 std::fs::write(path, value).is_ok()
512}
513
514#[deno_core::op2]
516#[string]
517pub fn op_load_file(state: &mut OpState, #[string] key: &str) -> String {
518 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
519 let save_dir = bridge.borrow().save_dir.clone();
520
521 let path = save_dir.join(format!("{key}.json"));
522 std::fs::read_to_string(path).unwrap_or_default()
523}
524
525#[deno_core::op2(fast)]
527pub fn op_delete_file(state: &mut OpState, #[string] key: &str) -> bool {
528 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
529 let save_dir = bridge.borrow().save_dir.clone();
530
531 let path = save_dir.join(format!("{key}.json"));
532 std::fs::remove_file(path).is_ok()
533}
534
535#[deno_core::op2]
537#[serde]
538pub fn op_list_save_files(state: &mut OpState) -> Vec<String> {
539 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
540 let save_dir = bridge.borrow().save_dir.clone();
541
542 let mut keys = Vec::new();
543 if let Ok(entries) = std::fs::read_dir(&save_dir) {
544 for entry in entries.flatten() {
545 let path = entry.path();
546 if path.extension().map_or(false, |ext| ext == "json") {
547 if let Some(stem) = path.file_stem() {
548 keys.push(stem.to_string_lossy().to_string());
549 }
550 }
551 }
552 }
553 keys.sort();
554 keys
555}
556
557#[deno_core::op2(fast)]
561pub fn op_create_shader(state: &mut OpState, #[string] name: &str, #[string] source: &str) -> u32 {
562 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
563 let mut b = bridge.borrow_mut();
564 let id = b.next_shader_id;
565 b.next_shader_id += 1;
566 b.shader_create_queue
567 .push((id, name.to_string(), source.to_string()));
568 id
569}
570
571#[deno_core::op2(fast)]
573pub fn op_set_shader_param(
574 state: &mut OpState,
575 shader_id: u32,
576 index: u32,
577 x: f64,
578 y: f64,
579 z: f64,
580 w: f64,
581) {
582 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
583 bridge.borrow_mut().shader_param_queue.push((
584 shader_id,
585 index,
586 [x as f32, y as f32, z as f32, w as f32],
587 ));
588}
589
590#[deno_core::op2(fast)]
594pub fn op_add_effect(state: &mut OpState, #[string] effect_type: &str) -> u32 {
595 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
596 let mut b = bridge.borrow_mut();
597 let id = b.next_effect_id;
598 b.next_effect_id += 1;
599 b.effect_create_queue
600 .push((id, effect_type.to_string()));
601 id
602}
603
604#[deno_core::op2(fast)]
606pub fn op_set_effect_param(
607 state: &mut OpState,
608 effect_id: u32,
609 index: u32,
610 x: f64,
611 y: f64,
612 z: f64,
613 w: f64,
614) {
615 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
616 bridge.borrow_mut().effect_param_queue.push((
617 effect_id,
618 index,
619 [x as f32, y as f32, z as f32, w as f32],
620 ));
621}
622
623#[deno_core::op2(fast)]
625pub fn op_remove_effect(state: &mut OpState, effect_id: u32) {
626 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
627 bridge.borrow_mut().effect_remove_queue.push(effect_id);
628}
629
630#[deno_core::op2(fast)]
632pub fn op_clear_effects(state: &mut OpState) {
633 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
634 bridge.borrow_mut().effect_clear = true;
635}
636
637deno_core::extension!(
638 render_ext,
639 ops = [
640 op_draw_sprite,
641 op_clear_sprites,
642 op_set_camera,
643 op_get_camera,
644 op_load_texture,
645 op_is_key_down,
646 op_is_key_pressed,
647 op_get_mouse_position,
648 op_get_delta_time,
649 op_create_solid_texture,
650 op_create_tilemap,
651 op_set_tile,
652 op_get_tile,
653 op_draw_tilemap,
654 op_set_ambient_light,
655 op_add_point_light,
656 op_clear_lights,
657 op_load_sound,
658 op_play_sound,
659 op_stop_sound,
660 op_stop_all_sounds,
661 op_set_master_volume,
662 op_create_font_texture,
663 op_get_viewport_size,
664 op_get_scale_factor,
665 op_set_background_color,
666 op_save_file,
667 op_load_file,
668 op_delete_file,
669 op_list_save_files,
670 op_create_shader,
671 op_set_shader_param,
672 op_add_effect,
673 op_set_effect_param,
674 op_remove_effect,
675 op_clear_effects,
676 ],
677);