Skip to main content

arcane_engine/scripting/
render_ops.rs

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/// Audio command queued from TS ops, drained by the frame callback.
12#[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/// Shared state between render ops and the main loop.
22/// This is placed into `OpState` when running in renderer mode.
23#[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    /// Input state snapshot (updated each frame by the event loop).
31    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    /// Pending texture load requests (path → result channel).
36    pub texture_load_queue: Vec<(String, u32)>,
37    /// Base directory for resolving relative texture paths.
38    pub base_dir: PathBuf,
39    /// Next texture ID to assign (for pre-registration before GPU load).
40    pub next_texture_id: u32,
41    /// Map of path → already-assigned texture ID.
42    pub texture_path_to_id: std::collections::HashMap<String, u32>,
43    /// Tilemap storage (managed by tilemap ops).
44    pub tilemaps: TilemapStore,
45    /// Lighting: ambient color (0-1 per channel). Default white = no darkening.
46    pub ambient_light: [f32; 3],
47    /// Lighting: point lights for this frame.
48    pub point_lights: Vec<PointLight>,
49    /// Audio commands queued by TS, drained each frame.
50    pub audio_commands: Vec<BridgeAudioCommand>,
51    /// Next sound ID to assign.
52    pub next_sound_id: u32,
53    /// Map of sound path → assigned sound ID.
54    pub sound_path_to_id: std::collections::HashMap<String, u32>,
55    /// Font texture creation queue (texture IDs to create as built-in font).
56    pub font_texture_queue: Vec<u32>,
57    /// Current viewport dimensions in logical pixels (synced from renderer each frame).
58    pub viewport_width: f32,
59    pub viewport_height: f32,
60    /// Display scale factor (e.g. 2.0 on Retina).
61    pub scale_factor: f32,
62    /// Clear/background color [r, g, b, a] in 0.0-1.0 range.
63    pub clear_color: [f32; 4],
64    /// Directory for save files (.arcane/saves/ relative to game entry file).
65    pub save_dir: PathBuf,
66    /// Custom shader creation queue: (id, name, wgsl_source).
67    pub shader_create_queue: Vec<(u32, String, String)>,
68    /// Custom shader param updates: (shader_id, index, [x, y, z, w]).
69    pub shader_param_queue: Vec<(u32, u32, [f32; 4])>,
70    /// Next shader ID to assign.
71    pub next_shader_id: u32,
72    /// Post-process effect creation queue: (id, effect_type_name).
73    pub effect_create_queue: Vec<(u32, String)>,
74    /// Post-process effect param updates: (effect_id, index, [x, y, z, w]).
75    pub effect_param_queue: Vec<(u32, u32, [f32; 4])>,
76    /// Post-process effect removal queue.
77    pub effect_remove_queue: Vec<u32>,
78    /// Flag to clear all post-process effects.
79    pub effect_clear: bool,
80    /// Next effect ID to assign.
81    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/// Queue a sprite draw command for this frame.
126/// Accepts f64 (JavaScript's native number type), converts to f32 for GPU.
127#[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/// Clear all queued sprite commands for this frame.
181#[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/// Update the camera position and zoom.
188/// Accepts f64 (JavaScript's native number type), converts to f32 for GPU.
189#[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/// Get camera state as [x, y, zoom].
199#[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/// Register a texture to be loaded. Returns a texture ID immediately.
208/// The actual GPU upload happens on the main thread before the next render.
209#[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    // Resolve relative paths against base_dir
215    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    // Check cache
222    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/// Check if a key is currently held down.
234#[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/// Check if a key was pressed this frame.
241#[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/// Get mouse position as [x, y].
248#[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/// Get the delta time (seconds since last frame).
257#[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/// Create a solid-color texture from TS. Returns texture ID.
264/// The actual GPU upload happens on the main thread.
265#[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    // Encode color in the path as a signal to the loader
286    br.texture_load_queue
287        .push((format!("__solid__:{name}:{r}:{g}:{b}:{a}"), id));
288    id
289}
290
291/// Create a tilemap. Returns tilemap ID.
292#[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/// Set a tile in a tilemap.
310#[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/// Get a tile from a tilemap.
319#[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/// Draw a tilemap's visible tiles as sprite commands (camera-culled).
331/// Accepts f64 (JavaScript's native number type), converts to f32 for GPU.
332#[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    // Default viewport for culling; actual viewport is synced by renderer
340    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// --- Lighting ops ---
350
351/// Set the ambient light color (0-1 per channel).
352/// Accepts f64 (JavaScript's native number type), converts to f32 for GPU.
353#[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/// Add a point light at world position (x,y) with radius, color, and intensity.
360/// Accepts f64 (JavaScript's native number type), converts to f32 for GPU.
361#[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/// Clear all point lights for this frame.
385#[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// --- Audio ops ---
392
393/// Load a sound file. Returns a sound ID.
394#[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/// Play a loaded sound.
417/// Accepts f64 (JavaScript's native number type), converts to f32 for audio.
418#[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/// Stop a specific sound.
425#[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/// Stop all sounds.
432#[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/// Set the master volume.
439/// Accepts f64 (JavaScript's native number type), converts to f32 for audio.
440#[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// --- Font ops ---
447
448/// Create the built-in font texture. Returns a texture ID.
449#[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// --- Viewport ops ---
467
468/// Get the current viewport size as [width, height].
469#[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/// Get the display scale factor (e.g. 2.0 on Retina).
478#[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/// Set the background/clear color (r, g, b in 0.0-1.0 range).
485#[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// --- File I/O ops (save/load) ---
493
494/// Write a save file. Returns true on success.
495#[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    // Sanitize key: only allow alphanumeric, underscore, dash
501    if !key.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
502        return false;
503    }
504
505    // Ensure save directory exists
506    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/// Load a save file. Returns the contents or empty string if not found.
515#[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/// Delete a save file. Returns true on success.
526#[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/// List all save file keys (filenames without .json extension).
536#[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// --- Shader ops ---
558
559/// Create a custom fragment shader from WGSL source. Returns a shader ID.
560#[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/// Set a vec4 parameter slot on a custom shader. Index 0-15.
572#[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// --- Post-process effect ops ---
591
592/// Add a post-process effect. Returns an effect ID.
593#[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/// Set a vec4 parameter slot on a post-process effect. Index 0-3.
605#[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/// Remove a single post-process effect by ID.
624#[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/// Remove all post-process effects.
631#[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);