Skip to main content

siglus_scene_vm/runtime/forms/
frame_action.rs

1use anyhow::Result;
2
3use crate::runtime::forms::prop_access;
4use crate::runtime::globals::{ObjectFrameActionState, PendingFrameActionFinish};
5use crate::runtime::{CommandContext, Value};
6
7fn as_i64(v: &Value) -> Option<i64> {
8    match v {
9        Value::Int(n) => Some(*n),
10        Value::NamedArg { value, .. } => as_i64(value),
11        _ => None,
12    }
13}
14
15fn push_ok(ctx: &mut CommandContext, ret_form: Option<i64>) {
16    let v = if matches!(ret_form, Some(rf) if rf == prop_access::FM_STR) {
17        Value::Str(String::new())
18    } else {
19        Value::Int(0)
20    };
21    ctx.push(v);
22}
23
24fn queue_finish(
25    ctx: &mut CommandContext,
26    fa: &ObjectFrameActionState,
27    frame_action_chain: Vec<i32>,
28    reinit_after_finish: bool,
29) {
30    if fa.cmd_name.is_empty() {
31        return;
32    }
33    ctx.globals
34        .pending_frame_action_finishes
35        .push(PendingFrameActionFinish {
36            frame_action_chain,
37            object_chain: None,
38            snapshot: fa.clone(),
39            reinit_after_finish,
40            scn_name: fa.scn_name.clone(),
41            cmd_name: fa.cmd_name.clone(),
42            end_time: fa.end_time,
43            args: fa.args.clone(),
44        });
45}
46
47fn apply_set_from_parts(
48    fa: &mut ObjectFrameActionState,
49    scene_name: &str,
50    end_time: i64,
51    cmd_name: String,
52    args: Vec<Value>,
53    real_time_flag: bool,
54) {
55    fa.end_time = end_time;
56    fa.cmd_name = cmd_name;
57    fa.scn_name = scene_name.to_string();
58    fa.real_time_flag = real_time_flag;
59    fa.end_flag = false;
60    fa.counter.reset();
61    if real_time_flag {
62        fa.counter.start_real();
63    } else {
64        fa.counter.start();
65    }
66    fa.args = args;
67}
68
69pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
70    let Some((chain_pos, chain_ref)) = prop_access::parse_current_element_chain(ctx, args) else {
71        return Ok(false);
72    };
73    let chain: Vec<i32> = chain_ref.to_vec();
74    let tail: Vec<i32> = chain[1..].to_vec();
75    let script_args: Vec<Value> = prop_access::script_args(args, chain_pos).to_vec();
76    let scene_name = ctx.current_scene_name.clone().unwrap_or_default();
77    let (al_id, ret_form) = prop_access::current_vm_meta(ctx);
78    let rhs = if al_id == Some(1) {
79        script_args.first().cloned()
80    } else {
81        None
82    };
83
84    if tail.is_empty() {
85        push_ok(ctx, ret_form);
86        return Ok(true);
87    }
88
89    match tail[0] {
90        crate::runtime::constants::elm_value::FRAMEACTION_COUNTER => {
91            let counter_tail = &tail[1..];
92            if counter_tail.is_empty() {
93                let set_v = rhs.as_ref().and_then(as_i64).or_else(|| {
94                    if al_id == Some(1) && script_args.len() == 1 {
95                        script_args.first().and_then(as_i64)
96                    } else {
97                        None
98                    }
99                });
100                if let Some(v) = set_v {
101                    {
102                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
103                        fa.counter.set_count(v);
104                    }
105                    push_ok(ctx, ret_form);
106                } else {
107                    let count = {
108                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
109                        fa.counter.get_count()
110                    };
111                    ctx.push(Value::Int(count));
112                }
113                return Ok(true);
114            }
115
116            match counter_tail[0] {
117                crate::runtime::constants::COUNTER_SET => {
118                    let value = script_args.first().and_then(as_i64).unwrap_or(0);
119                    {
120                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
121                        fa.counter.set_count(value);
122                    }
123                    push_ok(ctx, ret_form);
124                }
125                crate::runtime::constants::COUNTER_GET => {
126                    let count = {
127                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
128                        fa.counter.get_count()
129                    };
130                    ctx.push(Value::Int(count));
131                }
132                crate::runtime::constants::COUNTER_RESET => {
133                    {
134                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
135                        fa.counter.reset();
136                    }
137                    push_ok(ctx, ret_form);
138                }
139                crate::runtime::constants::COUNTER_START => {
140                    {
141                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
142                        fa.counter.start();
143                    }
144                    push_ok(ctx, ret_form);
145                }
146                crate::runtime::constants::COUNTER_START_REAL => {
147                    {
148                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
149                        fa.counter.start_real();
150                    }
151                    push_ok(ctx, ret_form);
152                }
153                crate::runtime::constants::COUNTER_START_FRAME => {
154                    let from = script_args.first().and_then(as_i64).unwrap_or(0);
155                    let to = script_args.get(1).and_then(as_i64).unwrap_or(0);
156                    let frame_time = script_args.get(2).and_then(as_i64).unwrap_or(0);
157                    {
158                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
159                        fa.counter.start_frame(from, to, frame_time);
160                    }
161                    push_ok(ctx, ret_form);
162                }
163                crate::runtime::constants::COUNTER_START_FRAME_REAL => {
164                    let from = script_args.first().and_then(as_i64).unwrap_or(0);
165                    let to = script_args.get(1).and_then(as_i64).unwrap_or(0);
166                    let frame_time = script_args.get(2).and_then(as_i64).unwrap_or(0);
167                    {
168                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
169                        fa.counter.start_frame_real(from, to, frame_time);
170                    }
171                    push_ok(ctx, ret_form);
172                }
173                crate::runtime::constants::COUNTER_START_FRAME_LOOP => {
174                    let from = script_args.first().and_then(as_i64).unwrap_or(0);
175                    let to = script_args.get(1).and_then(as_i64).unwrap_or(0);
176                    let frame_time = script_args.get(2).and_then(as_i64).unwrap_or(0);
177                    {
178                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
179                        fa.counter.start_frame_loop(from, to, frame_time);
180                    }
181                    push_ok(ctx, ret_form);
182                }
183                crate::runtime::constants::COUNTER_START_FRAME_LOOP_REAL => {
184                    let from = script_args.first().and_then(as_i64).unwrap_or(0);
185                    let to = script_args.get(1).and_then(as_i64).unwrap_or(0);
186                    let frame_time = script_args.get(2).and_then(as_i64).unwrap_or(0);
187                    {
188                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
189                        fa.counter.start_frame_loop_real(from, to, frame_time);
190                    }
191                    push_ok(ctx, ret_form);
192                }
193                crate::runtime::constants::COUNTER_STOP => {
194                    {
195                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
196                        fa.counter.stop();
197                    }
198                    push_ok(ctx, ret_form);
199                }
200                crate::runtime::constants::COUNTER_RESUME => {
201                    {
202                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
203                        fa.counter.resume();
204                    }
205                    push_ok(ctx, ret_form);
206                }
207                crate::runtime::constants::COUNTER_CHECK_VALUE => {
208                    let target = script_args.first().and_then(as_i64).unwrap_or(0);
209                    let count = {
210                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
211                        fa.counter.get_count()
212                    };
213                    ctx.push(Value::Int(if count - target >= 0 { 1 } else { 0 }));
214                }
215                crate::runtime::constants::COUNTER_CHECK_ACTIVE => {
216                    let active = {
217                        let fa = ctx.globals.frame_actions.entry(form_id).or_default();
218                        fa.counter.is_running()
219                    };
220                    ctx.push(Value::Int(if active { 1 } else { 0 }));
221                }
222                crate::runtime::constants::COUNTER_WAIT
223                | crate::runtime::constants::COUNTER_WAIT_KEY => return Ok(false),
224                _ => return Ok(false),
225            }
226            Ok(true)
227        }
228        crate::runtime::constants::elm_value::FRAMEACTION_START => {
229            let end_time = script_args.first().and_then(as_i64).unwrap_or(0);
230            let cmd_name = script_args
231                .get(1)
232                .and_then(|v| v.as_str())
233                .unwrap_or("")
234                .to_string();
235            let fa_args = script_args.iter().skip(2).cloned().collect();
236            {
237                let fa_snapshot = ctx
238                    .globals
239                    .frame_actions
240                    .entry(form_id)
241                    .or_default()
242                    .clone();
243                queue_finish(ctx, &fa_snapshot, chain.clone(), false);
244                let fa = ctx.globals.frame_actions.entry(form_id).or_default();
245                apply_set_from_parts(fa, &scene_name, end_time, cmd_name, fa_args, false);
246            }
247            push_ok(ctx, ret_form);
248            Ok(true)
249        }
250        crate::runtime::constants::elm_value::FRAMEACTION_END => {
251            let fa_snapshot = ctx
252                .globals
253                .frame_actions
254                .entry(form_id)
255                .or_default()
256                .clone();
257            queue_finish(ctx, &fa_snapshot, chain.clone(), true);
258            let fa = ctx.globals.frame_actions.entry(form_id).or_default();
259            fa.reinit_without_finish();
260            push_ok(ctx, ret_form);
261            Ok(true)
262        }
263        crate::runtime::constants::elm_value::FRAMEACTION_START_REAL => {
264            let end_time = script_args.first().and_then(as_i64).unwrap_or(0);
265            let cmd_name = script_args
266                .get(1)
267                .and_then(|v| v.as_str())
268                .unwrap_or("")
269                .to_string();
270            let fa_args = script_args.iter().skip(2).cloned().collect();
271            {
272                let fa_snapshot = ctx
273                    .globals
274                    .frame_actions
275                    .entry(form_id)
276                    .or_default()
277                    .clone();
278                queue_finish(ctx, &fa_snapshot, chain.clone(), false);
279                let fa = ctx.globals.frame_actions.entry(form_id).or_default();
280                apply_set_from_parts(fa, &scene_name, end_time, cmd_name, fa_args, true);
281            }
282            push_ok(ctx, ret_form);
283            Ok(true)
284        }
285        crate::runtime::constants::elm_value::FRAMEACTION_IS_END_ACTION => {
286            let end_flag = ctx
287                .globals
288                .frame_actions
289                .entry(form_id)
290                .or_default()
291                .end_flag;
292            ctx.push(Value::Int(if end_flag { 1 } else { 0 }));
293            Ok(true)
294        }
295        _ => Ok(false),
296    }
297}
298
299#[cfg(test)]
300mod lifecycle_tests {
301    use super::*;
302    use crate::runtime::VmCallMeta;
303    use std::path::PathBuf;
304
305    fn setup_call(ctx: &mut CommandContext, form_id: u32, op: i32) {
306        ctx.vm_call = Some(VmCallMeta {
307            element: vec![form_id as i32, op],
308            al_id: 0,
309            ret_form: 0,
310        });
311    }
312
313    #[test]
314    fn start_queues_full_old_snapshot_and_installs_replacement() {
315        let mut ctx = CommandContext::new(PathBuf::from("."));
316        let form_id = crate::runtime::constants::global_form::FRAME_ACTION;
317        let mut old = ObjectFrameActionState {
318            scn_name: "old_scene".into(),
319            cmd_name: "old_cmd".into(),
320            end_time: 77,
321            ..Default::default()
322        };
323
324        old.counter.set_count(31);
325        old.args = vec![Value::Int(9)];
326        ctx.globals.frame_actions.insert(form_id, old.clone());
327        ctx.current_scene_name = Some("new_scene".into());
328        setup_call(
329            &mut ctx,
330            form_id,
331            crate::runtime::constants::elm_value::FRAMEACTION_START,
332        );
333
334        dispatch(
335            &mut ctx,
336            form_id,
337            &[Value::Int(100), Value::Str("new_cmd".into()), Value::Int(5)],
338        )
339        .unwrap();
340
341        let pending = ctx.globals.pending_frame_action_finishes.first().unwrap();
342        assert!(!pending.reinit_after_finish);
343        assert_eq!(pending.snapshot.scn_name, old.scn_name);
344        assert_eq!(pending.snapshot.cmd_name, old.cmd_name);
345        assert_eq!(pending.snapshot.end_time, 77);
346        assert_eq!(pending.snapshot.counter.get_count(), 31);
347        assert_eq!(pending.snapshot.args.len(), 1);
348        assert_eq!(pending.snapshot.args[0].as_i64(), Some(9));
349
350        let live = ctx.globals.frame_actions.get(&form_id).unwrap();
351        assert_eq!(live.scn_name, "new_scene");
352        assert_eq!(live.cmd_name, "new_cmd");
353        assert_eq!(live.end_time, 100);
354        assert_eq!(live.args.len(), 1);
355        assert_eq!(live.args[0].as_i64(), Some(5));
356    }
357
358    #[test]
359    fn end_reinit_preserves_end_time_but_clears_active_action() {
360        let mut ctx = CommandContext::new(PathBuf::from("."));
361        let form_id = crate::runtime::constants::global_form::FRAME_ACTION;
362        let mut old = ObjectFrameActionState {
363            scn_name: "scene".into(),
364            cmd_name: "finish".into(),
365            end_time: 55,
366            ..Default::default()
367        };
368
369        old.counter.start();
370        old.args = vec![Value::Int(1)];
371        ctx.globals.frame_actions.insert(form_id, old);
372        setup_call(
373            &mut ctx,
374            form_id,
375            crate::runtime::constants::elm_value::FRAMEACTION_END,
376        );
377
378        dispatch(&mut ctx, form_id, &[]).unwrap();
379
380        let live = ctx.globals.frame_actions.get(&form_id).unwrap();
381        assert!(live.scn_name.is_empty());
382        assert!(live.cmd_name.is_empty());
383        assert!(live.args.is_empty());
384        assert_eq!(live.end_time, 55);
385        assert!(!live.counter.is_running());
386        assert_eq!(ctx.globals.pending_frame_action_finishes.len(), 1);
387        assert!(ctx.globals.pending_frame_action_finishes[0].reinit_after_finish);
388    }
389}