Skip to main content

siglus_scene_vm/runtime/forms/
mov.rs

1use anyhow::{bail, Result};
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.get(0).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<'a>(args: &'a [Value], idx: usize) -> Option<&'a 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 std::env::var_os("SG_DEBUG").is_some()
109                || std::env::var_os("SG_MOVIE_TRACE").is_some()
110            {
111                eprintln!(
112                    "[SG_DEBUG][MOV] PLAY file={} pos=({}, {}) size={}x{} wait={} key_skip={} total_ms={:?} path={}",
113                    name, x, y, w, h, wait, key_skip, info.duration_ms(), info.path.display()
114                );
115            }
116            if wait {
117                let ret_form = crate::runtime::forms::prop_access::current_vm_meta(ctx)
118                    .1
119                    .unwrap_or(0);
120                let return_value_flag = ret_form != super::codes::FM_VOID as i64;
121                ctx.wait.wait_global_movie(key_skip, return_value_flag);
122                ctx.request_wait_proc_boundary(ProcKind::MovieWait);
123            }
124            Ok(true)
125        }
126        mov_op::STOP => {
127            if let Some(id) = ctx.globals.mov.audio_id.take() {
128                ctx.movie.stop_audio(id);
129            }
130            ctx.globals.mov.stop();
131            ctx.movie.stop();
132            Ok(true)
133        }
134        _ => {
135            store_or_push_mov_prop(ctx, op, args);
136            Ok(true)
137        }
138    }
139}