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}
61
62impl RenderBridgeState {
63 pub fn new(base_dir: PathBuf) -> Self {
64 Self {
65 sprite_commands: Vec::new(),
66 camera_x: 0.0,
67 camera_y: 0.0,
68 camera_zoom: 1.0,
69 delta_time: 0.0,
70 keys_down: std::collections::HashSet::new(),
71 keys_pressed: std::collections::HashSet::new(),
72 mouse_x: 0.0,
73 mouse_y: 0.0,
74 texture_load_queue: Vec::new(),
75 base_dir,
76 next_texture_id: 1,
77 texture_path_to_id: std::collections::HashMap::new(),
78 tilemaps: TilemapStore::new(),
79 ambient_light: [1.0, 1.0, 1.0],
80 point_lights: Vec::new(),
81 audio_commands: Vec::new(),
82 next_sound_id: 1,
83 sound_path_to_id: std::collections::HashMap::new(),
84 font_texture_queue: Vec::new(),
85 viewport_width: 800.0,
86 viewport_height: 600.0,
87 }
88 }
89}
90
91#[deno_core::op2(fast)]
94pub fn op_draw_sprite(
95 state: &mut OpState,
96 texture_id: u32,
97 x: f64,
98 y: f64,
99 w: f64,
100 h: f64,
101 layer: i32,
102 uv_x: f64,
103 uv_y: f64,
104 uv_w: f64,
105 uv_h: f64,
106 tint_r: f64,
107 tint_g: f64,
108 tint_b: f64,
109 tint_a: f64,
110) {
111 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
112 bridge.borrow_mut().sprite_commands.push(SpriteCommand {
113 texture_id,
114 x: x as f32,
115 y: y as f32,
116 w: w as f32,
117 h: h as f32,
118 layer,
119 uv_x: uv_x as f32,
120 uv_y: uv_y as f32,
121 uv_w: uv_w as f32,
122 uv_h: uv_h as f32,
123 tint_r: tint_r as f32,
124 tint_g: tint_g as f32,
125 tint_b: tint_b as f32,
126 tint_a: tint_a as f32,
127 });
128}
129
130#[deno_core::op2(fast)]
132pub fn op_clear_sprites(state: &mut OpState) {
133 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
134 bridge.borrow_mut().sprite_commands.clear();
135}
136
137#[deno_core::op2(fast)]
140pub fn op_set_camera(state: &mut OpState, x: f64, y: f64, zoom: f64) {
141 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
142 let mut b = bridge.borrow_mut();
143 b.camera_x = x as f32;
144 b.camera_y = y as f32;
145 b.camera_zoom = zoom as f32;
146}
147
148#[deno_core::op2]
150#[serde]
151pub fn op_get_camera(state: &mut OpState) -> Vec<f64> {
152 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
153 let b = bridge.borrow();
154 vec![b.camera_x as f64, b.camera_y as f64, b.camera_zoom as f64]
155}
156
157#[deno_core::op2(fast)]
160pub fn op_load_texture(state: &mut OpState, #[string] path: &str) -> u32 {
161 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
162 let mut b = bridge.borrow_mut();
163
164 let resolved = if std::path::Path::new(path).is_absolute() {
166 path.to_string()
167 } else {
168 b.base_dir.join(path).to_string_lossy().to_string()
169 };
170
171 if let Some(&id) = b.texture_path_to_id.get(&resolved) {
173 return id;
174 }
175
176 let id = b.next_texture_id;
177 b.next_texture_id += 1;
178 b.texture_path_to_id.insert(resolved.clone(), id);
179 b.texture_load_queue.push((resolved, id));
180 id
181}
182
183#[deno_core::op2(fast)]
185pub fn op_is_key_down(state: &mut OpState, #[string] key: &str) -> bool {
186 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
187 bridge.borrow().keys_down.contains(key)
188}
189
190#[deno_core::op2(fast)]
192pub fn op_is_key_pressed(state: &mut OpState, #[string] key: &str) -> bool {
193 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
194 bridge.borrow().keys_pressed.contains(key)
195}
196
197#[deno_core::op2]
199#[serde]
200pub fn op_get_mouse_position(state: &mut OpState) -> Vec<f64> {
201 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
202 let b = bridge.borrow();
203 vec![b.mouse_x as f64, b.mouse_y as f64]
204}
205
206#[deno_core::op2(fast)]
208pub fn op_get_delta_time(state: &mut OpState) -> f64 {
209 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
210 bridge.borrow().delta_time
211}
212
213#[deno_core::op2(fast)]
216pub fn op_create_solid_texture(
217 state: &mut OpState,
218 #[string] name: &str,
219 r: u32,
220 g: u32,
221 b: u32,
222 a: u32,
223) -> u32 {
224 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
225 let mut br = bridge.borrow_mut();
226
227 let key = format!("__solid__{name}");
228 if let Some(&id) = br.texture_path_to_id.get(&key) {
229 return id;
230 }
231
232 let id = br.next_texture_id;
233 br.next_texture_id += 1;
234 br.texture_path_to_id.insert(key.clone(), id);
235 br.texture_load_queue
237 .push((format!("__solid__:{name}:{r}:{g}:{b}:{a}"), id));
238 id
239}
240
241#[deno_core::op2(fast)]
243pub fn op_create_tilemap(
244 state: &mut OpState,
245 texture_id: u32,
246 width: u32,
247 height: u32,
248 tile_size: f64,
249 atlas_columns: u32,
250 atlas_rows: u32,
251) -> u32 {
252 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
253 bridge
254 .borrow_mut()
255 .tilemaps
256 .create(texture_id, width, height, tile_size as f32, atlas_columns, atlas_rows)
257}
258
259#[deno_core::op2(fast)]
261pub fn op_set_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32, tile_id: u32) {
262 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
263 if let Some(tm) = bridge.borrow_mut().tilemaps.get_mut(tilemap_id) {
264 tm.set_tile(gx, gy, tile_id as u16);
265 }
266}
267
268#[deno_core::op2(fast)]
270pub fn op_get_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32) -> u32 {
271 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
272 bridge
273 .borrow()
274 .tilemaps
275 .get(tilemap_id)
276 .map(|tm| tm.get_tile(gx, gy) as u32)
277 .unwrap_or(0)
278}
279
280#[deno_core::op2(fast)]
283pub fn op_draw_tilemap(state: &mut OpState, tilemap_id: u32, world_x: f64, world_y: f64, layer: i32) {
284 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
285 let mut b = bridge.borrow_mut();
286 let cam_x = b.camera_x;
287 let cam_y = b.camera_y;
288 let cam_zoom = b.camera_zoom;
289 let vp_w = 800.0;
291 let vp_h = 600.0;
292
293 if let Some(tm) = b.tilemaps.get(tilemap_id) {
294 let cmds = tm.bake_visible(world_x as f32, world_y as f32, layer, cam_x, cam_y, cam_zoom, vp_w, vp_h);
295 b.sprite_commands.extend(cmds);
296 }
297}
298
299#[deno_core::op2(fast)]
304pub fn op_set_ambient_light(state: &mut OpState, r: f64, g: f64, b: f64) {
305 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
306 bridge.borrow_mut().ambient_light = [r as f32, g as f32, b as f32];
307}
308
309#[deno_core::op2(fast)]
312pub fn op_add_point_light(
313 state: &mut OpState,
314 x: f64,
315 y: f64,
316 radius: f64,
317 r: f64,
318 g: f64,
319 b: f64,
320 intensity: f64,
321) {
322 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
323 bridge.borrow_mut().point_lights.push(PointLight {
324 x: x as f32,
325 y: y as f32,
326 radius: radius as f32,
327 r: r as f32,
328 g: g as f32,
329 b: b as f32,
330 intensity: intensity as f32,
331 });
332}
333
334#[deno_core::op2(fast)]
336pub fn op_clear_lights(state: &mut OpState) {
337 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
338 bridge.borrow_mut().point_lights.clear();
339}
340
341#[deno_core::op2(fast)]
345pub fn op_load_sound(state: &mut OpState, #[string] path: &str) -> u32 {
346 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
347 let mut b = bridge.borrow_mut();
348
349 let resolved = if std::path::Path::new(path).is_absolute() {
350 path.to_string()
351 } else {
352 b.base_dir.join(path).to_string_lossy().to_string()
353 };
354
355 if let Some(&id) = b.sound_path_to_id.get(&resolved) {
356 return id;
357 }
358
359 let id = b.next_sound_id;
360 b.next_sound_id += 1;
361 b.sound_path_to_id.insert(resolved.clone(), id);
362 b.audio_commands.push(BridgeAudioCommand::LoadSound { id, path: resolved });
363 id
364}
365
366#[deno_core::op2(fast)]
369pub fn op_play_sound(state: &mut OpState, id: u32, volume: f64, looping: bool) {
370 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
371 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySound { id, volume: volume as f32, looping });
372}
373
374#[deno_core::op2(fast)]
376pub fn op_stop_sound(state: &mut OpState, id: u32) {
377 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
378 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopSound { id });
379}
380
381#[deno_core::op2(fast)]
383pub fn op_stop_all_sounds(state: &mut OpState) {
384 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
385 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopAll);
386}
387
388#[deno_core::op2(fast)]
391pub fn op_set_master_volume(state: &mut OpState, volume: f64) {
392 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
393 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetMasterVolume { volume: volume as f32 });
394}
395
396#[deno_core::op2(fast)]
400pub fn op_create_font_texture(state: &mut OpState) -> u32 {
401 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
402 let mut b = bridge.borrow_mut();
403
404 let key = "__builtin_font__".to_string();
405 if let Some(&id) = b.texture_path_to_id.get(&key) {
406 return id;
407 }
408
409 let id = b.next_texture_id;
410 b.next_texture_id += 1;
411 b.texture_path_to_id.insert(key, id);
412 b.font_texture_queue.push(id);
413 id
414}
415
416#[deno_core::op2]
420#[serde]
421pub fn op_get_viewport_size(state: &mut OpState) -> Vec<f64> {
422 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
423 let b = bridge.borrow();
424 vec![b.viewport_width as f64, b.viewport_height as f64]
425}
426
427deno_core::extension!(
428 render_ext,
429 ops = [
430 op_draw_sprite,
431 op_clear_sprites,
432 op_set_camera,
433 op_get_camera,
434 op_load_texture,
435 op_is_key_down,
436 op_is_key_pressed,
437 op_get_mouse_position,
438 op_get_delta_time,
439 op_create_solid_texture,
440 op_create_tilemap,
441 op_set_tile,
442 op_get_tile,
443 op_draw_tilemap,
444 op_set_ambient_light,
445 op_add_point_light,
446 op_clear_lights,
447 op_load_sound,
448 op_play_sound,
449 op_stop_sound,
450 op_stop_all_sounds,
451 op_set_master_volume,
452 op_create_font_texture,
453 op_get_viewport_size,
454 ],
455);