siglus_scene_vm/runtime/forms/
frame_action.rs1use 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}