Skip to main content

siglus_scene_vm/runtime/forms/
mov.rs

1use anyhow::{Result, bail};
2
3use crate::runtime::{CommandContext, ProcKind, Value};
4
5use super::codes::mov_op;
6
7fn store_or_push_mov_prop(ctx: &mut CommandContext, op: i32, args: &[Value]) {
8    let form_key = if ctx.ids.form_global_mov != 0 {
9        ctx.ids.form_global_mov
10    } else {
11        super::codes::FORM_GLOBAL_MOV
12    };
13    let prop = op;
14    if let Some(v) = args.first().cloned() {
15        match v {
16            Value::Str(s) => {
17                ctx.globals
18                    .str_props
19                    .entry(form_key)
20                    .or_default()
21                    .insert(prop, s);
22            }
23            Value::Int(n) => {
24                ctx.globals
25                    .int_props
26                    .entry(form_key)
27                    .or_default()
28                    .insert(prop, n);
29            }
30            _ => {}
31        }
32        ctx.push(Value::Int(0));
33        return;
34    }
35    if let Some(s) = ctx
36        .globals
37        .str_props
38        .get(&form_key)
39        .and_then(|m| m.get(&prop))
40        .cloned()
41    {
42        ctx.push(Value::Str(s));
43        return;
44    }
45    let v = ctx
46        .globals
47        .int_props
48        .get(&form_key)
49        .and_then(|m| m.get(&prop).copied())
50        .unwrap_or(0);
51    ctx.push(Value::Int(v));
52}
53
54fn arg_str(args: &[Value], idx: usize) -> Option<&str> {
55    args.get(idx).and_then(|v| v.as_str())
56}
57
58fn arg_int(args: &[Value], idx: usize) -> Option<i64> {
59    args.get(idx).and_then(|v| v.as_i64())
60}
61
62pub fn dispatch(ctx: &mut CommandContext, args: &[Value]) -> Result<bool> {
63    let Some(op) = crate::runtime::forms::prop_access::current_op_from_ctx_or_args(ctx, args)
64    else {
65        bail!("MOV form expects an element opcode");
66    };
67    let args = crate::runtime::forms::prop_access::params_without_op(ctx, args);
68
69    match op {
70        mov_op::PLAY | mov_op::PLAY_WAIT | mov_op::PLAY_WAIT_KEY => {
71            let name = match arg_str(args, 0) {
72                Some(s) => s,
73                None => {
74                    store_or_push_mov_prop(ctx, op, args);
75                    return Ok(true);
76                }
77            };
78            let x = arg_int(args, 1).unwrap_or(0) as i32;
79            let y = arg_int(args, 2).unwrap_or(0) as i32;
80            let raw_w = arg_int(args, 3).unwrap_or(ctx.screen_w as i64);
81            let raw_h = arg_int(args, 4).unwrap_or(ctx.screen_h as i64);
82            let w = if raw_w <= 0 {
83                ctx.screen_w.max(1)
84            } else {
85                raw_w as u32
86            };
87            let h = if raw_h <= 0 {
88                ctx.screen_h.max(1)
89            } else {
90                raw_h as u32
91            };
92
93            let wait = op == mov_op::PLAY_WAIT || op == mov_op::PLAY_WAIT_KEY;
94            let key_skip = op == mov_op::PLAY_WAIT_KEY;
95            if let Some(id) = ctx.globals.mov.audio_id.take() {
96                ctx.movie.stop_audio(id);
97            }
98            ctx.movie.stop();
99            let info = ctx.movie.play(name, wait, key_skip)?;
100            // C++ C_elm_mov::play starts native playback immediately and MOV_WAIT observes
101            // is_playing().  Do not synchronously decode the whole movie here: doing so makes
102            // the next frame delta include decode/setup time and can finish MOV_WAIT before the
103            // first frame is shown.  The renderer-side sync path decodes and then installs the
104            // exact asset duration once the first frame is available.
105            ctx.globals
106                .mov
107                .start(name.to_string(), x, y, w, h, None, key_skip);
108            if env_is_set!("SG_DEBUG") || env_is_set!("SG_MOVIE_TRACE") {
109                eprintln!(
110                    "[SG_DEBUG][MOV] PLAY file={} pos=({}, {}) size={}x{} wait={} key_skip={} total_ms={:?} path={}",
111                    name,
112                    x,
113                    y,
114                    w,
115                    h,
116                    wait,
117                    key_skip,
118                    info.duration_ms(),
119                    info.path.display()
120                );
121            }
122            if wait {
123                let ret_form = crate::runtime::forms::prop_access::current_vm_meta(ctx)
124                    .1
125                    .unwrap_or(0);
126                let return_value_flag = ret_form != super::codes::FM_VOID as i64;
127                ctx.wait.wait_global_movie(key_skip, return_value_flag);
128                ctx.request_wait_proc_boundary(ProcKind::MovieWait);
129            }
130            Ok(true)
131        }
132        mov_op::STOP => {
133            if let Some(id) = ctx.globals.mov.audio_id.take() {
134                ctx.movie.stop_audio(id);
135            }
136            ctx.globals.mov.stop();
137            ctx.movie.stop();
138            Ok(true)
139        }
140        _ => {
141            store_or_push_mov_prop(ctx, op, args);
142            Ok(true)
143        }
144    }
145}