Skip to main content

siglus_scene_vm/runtime/
wait.rs

1//! VM wait/blocking state.
2//!
3//! The original engine has many commands/forms that block execution until:
4//! - a certain time passes, or
5//! - the user presses a key / clicks.
6//!
7//! Cross-platform blocking and wait model.
8
9use crate::platform_time::{Duration, Instant};
10
11use crate::audio::{BgmEngine, KoeEngine, PcmEngine, SeEngine};
12
13use super::constants::RuntimeConstants;
14use super::globals::{GlobalState, ObjectState, StageFormState};
15use super::int_event::IntEvent;
16use super::Value;
17
18fn anim_skip_trace_enabled() -> bool {
19    std::env::var_os("SG_DEBUG").is_some()
20}
21
22fn anim_skip_trace(msg: impl AsRef<str>) {
23    if anim_skip_trace_enabled() {
24        eprintln!("[SG_DEBUG][ANIM_SKIP_TRACE][WAIT] {}", msg.as_ref());
25    }
26}
27
28fn int_event_state(ev: &IntEvent) -> String {
29    format!(
30        "def={} value={} cur={} start={} end={} cur_time={} end_time={} delay={} loop_type={} speed={} real={} active={}",
31        ev.def_value,
32        ev.value,
33        ev.cur_value,
34        ev.start_value,
35        ev.end_value,
36        ev.cur_time,
37        ev.end_time,
38        ev.delay_time,
39        ev.loop_type,
40        ev.speed_type,
41        ev.real_flag,
42        ev.check_event(),
43    )
44}
45
46fn object_event_op_name(ids: &RuntimeConstants, op: i32) -> &'static str {
47    if ids.obj_patno_eve != 0 && op == ids.obj_patno_eve { return "PATNO_EVE"; }
48    if ids.obj_x_eve != 0 && op == ids.obj_x_eve { return "X_EVE"; }
49    if ids.obj_y_eve != 0 && op == ids.obj_y_eve { return "Y_EVE"; }
50    if ids.obj_z_eve != 0 && op == ids.obj_z_eve { return "Z_EVE"; }
51    if ids.obj_center_x_eve != 0 && op == ids.obj_center_x_eve { return "CENTER_X_EVE"; }
52    if ids.obj_center_y_eve != 0 && op == ids.obj_center_y_eve { return "CENTER_Y_EVE"; }
53    if ids.obj_center_z_eve != 0 && op == ids.obj_center_z_eve { return "CENTER_Z_EVE"; }
54    if ids.obj_center_rep_x_eve != 0 && op == ids.obj_center_rep_x_eve { return "CENTER_REP_X_EVE"; }
55    if ids.obj_center_rep_y_eve != 0 && op == ids.obj_center_rep_y_eve { return "CENTER_REP_Y_EVE"; }
56    if ids.obj_center_rep_z_eve != 0 && op == ids.obj_center_rep_z_eve { return "CENTER_REP_Z_EVE"; }
57    if ids.obj_scale_x_eve != 0 && op == ids.obj_scale_x_eve { return "SCALE_X_EVE"; }
58    if ids.obj_scale_y_eve != 0 && op == ids.obj_scale_y_eve { return "SCALE_Y_EVE"; }
59    if ids.obj_scale_z_eve != 0 && op == ids.obj_scale_z_eve { return "SCALE_Z_EVE"; }
60    if ids.obj_rotate_x_eve != 0 && op == ids.obj_rotate_x_eve { return "ROTATE_X_EVE"; }
61    if ids.obj_rotate_y_eve != 0 && op == ids.obj_rotate_y_eve { return "ROTATE_Y_EVE"; }
62    if ids.obj_rotate_z_eve != 0 && op == ids.obj_rotate_z_eve { return "ROTATE_Z_EVE"; }
63    if ids.obj_clip_left_eve != 0 && op == ids.obj_clip_left_eve { return "CLIP_LEFT_EVE"; }
64    if ids.obj_clip_top_eve != 0 && op == ids.obj_clip_top_eve { return "CLIP_TOP_EVE"; }
65    if ids.obj_clip_right_eve != 0 && op == ids.obj_clip_right_eve { return "CLIP_RIGHT_EVE"; }
66    if ids.obj_clip_bottom_eve != 0 && op == ids.obj_clip_bottom_eve { return "CLIP_BOTTOM_EVE"; }
67    if ids.obj_src_clip_left_eve != 0 && op == ids.obj_src_clip_left_eve { return "SRC_CLIP_LEFT_EVE"; }
68    if ids.obj_src_clip_top_eve != 0 && op == ids.obj_src_clip_top_eve { return "SRC_CLIP_TOP_EVE"; }
69    if ids.obj_src_clip_right_eve != 0 && op == ids.obj_src_clip_right_eve { return "SRC_CLIP_RIGHT_EVE"; }
70    if ids.obj_src_clip_bottom_eve != 0 && op == ids.obj_src_clip_bottom_eve { return "SRC_CLIP_BOTTOM_EVE"; }
71    if ids.obj_tr_eve != 0 && op == ids.obj_tr_eve { return "TR_EVE"; }
72    if ids.obj_mono_eve != 0 && op == ids.obj_mono_eve { return "MONO_EVE"; }
73    if ids.obj_reverse_eve != 0 && op == ids.obj_reverse_eve { return "REVERSE_EVE"; }
74    if ids.obj_bright_eve != 0 && op == ids.obj_bright_eve { return "BRIGHT_EVE"; }
75    if ids.obj_dark_eve != 0 && op == ids.obj_dark_eve { return "DARK_EVE"; }
76    if ids.obj_color_r_eve != 0 && op == ids.obj_color_r_eve { return "COLOR_R_EVE"; }
77    if ids.obj_color_g_eve != 0 && op == ids.obj_color_g_eve { return "COLOR_G_EVE"; }
78    if ids.obj_color_b_eve != 0 && op == ids.obj_color_b_eve { return "COLOR_B_EVE"; }
79    if ids.obj_color_rate_eve != 0 && op == ids.obj_color_rate_eve { return "COLOR_RATE_EVE"; }
80    if ids.obj_color_add_r_eve != 0 && op == ids.obj_color_add_r_eve { return "COLOR_ADD_R_EVE"; }
81    if ids.obj_color_add_g_eve != 0 && op == ids.obj_color_add_g_eve { return "COLOR_ADD_G_EVE"; }
82    if ids.obj_color_add_b_eve != 0 && op == ids.obj_color_add_b_eve { return "COLOR_ADD_B_EVE"; }
83    if ids.obj_x_rep_eve != 0 && op == ids.obj_x_rep_eve { return "X_REP_EVE"; }
84    if ids.obj_y_rep_eve != 0 && op == ids.obj_y_rep_eve { return "Y_REP_EVE"; }
85    if ids.obj_z_rep_eve != 0 && op == ids.obj_z_rep_eve { return "Z_REP_EVE"; }
86    if ids.obj_tr_rep_eve != 0 && op == ids.obj_tr_rep_eve { return "TR_REP_EVE"; }
87    "UNKNOWN_EVE"
88}
89
90#[derive(Debug, Clone, Copy)]
91pub enum AudioWait {
92    Bgm,
93    BgmFade,
94    KoeAny,
95    SeAny,
96    PcmAny,
97    PcmSlot(u8),
98}
99
100#[derive(Debug, Clone)]
101pub enum EventWait {
102    ObjectAll {
103        stage_form_id: u32,
104        stage_idx: i64,
105        runtime_slot: usize,
106    },
107    ObjectOne {
108        stage_form_id: u32,
109        stage_idx: i64,
110        runtime_slot: usize,
111        op: i32,
112    },
113    ObjectList {
114        stage_form_id: u32,
115        stage_idx: i64,
116        runtime_slot: usize,
117        list_op: i32,
118        list_idx: usize,
119    },
120    GenericIntEvent {
121        form_id: u32,
122        index: Option<usize>,
123    },
124    FogX,
125    CounterThreshold {
126        form_id: u32,
127        index: usize,
128        target: i64,
129    },
130}
131
132#[derive(Debug, Clone, Copy)]
133pub struct MovieWait {
134    pub stage_form_id: u32,
135    pub stage_idx: i64,
136    pub runtime_slot: usize,
137    pub return_value_flag: bool,
138}
139
140fn object_runtime_slot(idx: usize, obj: &ObjectState) -> usize {
141    obj.runtime_slot_or(idx)
142}
143
144fn find_object_by_runtime_slot<'a>(
145    objects: &'a [ObjectState],
146    runtime_slot: usize,
147) -> Option<&'a ObjectState> {
148    for (idx, obj) in objects.iter().enumerate() {
149        if object_runtime_slot(idx, obj) == runtime_slot {
150            return Some(obj);
151        }
152        if let Some(found) = find_object_by_runtime_slot(&obj.runtime.child_objects, runtime_slot) {
153            return Some(found);
154        }
155    }
156    None
157}
158
159fn find_object_by_runtime_slot_mut<'a>(
160    mut objects: &'a mut [ObjectState],
161    runtime_slot: usize,
162) -> Option<&'a mut ObjectState> {
163    let mut idx = 0usize;
164    while let Some((obj, tail)) = objects.split_first_mut() {
165        if object_runtime_slot(idx, obj) == runtime_slot {
166            return Some(obj);
167        }
168        if let Some(found) =
169            find_object_by_runtime_slot_mut(&mut obj.runtime.child_objects, runtime_slot)
170        {
171            return Some(found);
172        }
173        objects = tail;
174        idx += 1;
175    }
176    None
177}
178
179fn object_event_list_for_wait<'a>(
180    obj: &'a ObjectState,
181    ids: &RuntimeConstants,
182    op: i32,
183) -> Option<&'a Vec<IntEvent>> {
184    obj.int_event_list_by_op(ids, op)
185        .or_else(|| obj.rep_int_event_list_by_rep_op(ids, op))
186}
187
188fn object_event_list_for_wait_mut<'a>(
189    obj: &'a mut ObjectState,
190    ids: &RuntimeConstants,
191    op: i32,
192) -> Option<&'a mut Vec<IntEvent>> {
193    if ids.obj_x_rep_eve != 0 && op == ids.obj_x_rep_eve {
194        Some(&mut obj.runtime.prop_event_lists.x_rep)
195    } else if ids.obj_y_rep_eve != 0 && op == ids.obj_y_rep_eve {
196        Some(&mut obj.runtime.prop_event_lists.y_rep)
197    } else if ids.obj_z_rep_eve != 0 && op == ids.obj_z_rep_eve {
198        Some(&mut obj.runtime.prop_event_lists.z_rep)
199    } else if ids.obj_tr_rep_eve != 0 && op == ids.obj_tr_rep_eve {
200        Some(&mut obj.runtime.prop_event_lists.tr_rep)
201    } else if ids.obj_x_rep != 0 && op == ids.obj_x_rep {
202        Some(&mut obj.runtime.prop_event_lists.x_rep)
203    } else if ids.obj_y_rep != 0 && op == ids.obj_y_rep {
204        Some(&mut obj.runtime.prop_event_lists.y_rep)
205    } else if ids.obj_z_rep != 0 && op == ids.obj_z_rep {
206        Some(&mut obj.runtime.prop_event_lists.z_rep)
207    } else if ids.obj_tr_rep != 0 && op == ids.obj_tr_rep {
208        Some(&mut obj.runtime.prop_event_lists.tr_rep)
209    } else {
210        None
211    }
212}
213
214fn object_active_in_stage_state_by_runtime_slot(
215    st: &StageFormState,
216    stage_idx: i64,
217    runtime_slot: usize,
218) -> Option<&ObjectState> {
219    if let Some(obj) = st
220        .object_lists
221        .get(&stage_idx)
222        .and_then(|list| find_object_by_runtime_slot(list, runtime_slot))
223    {
224        return Some(obj);
225    }
226
227    if let Some(items) = st.btnselitem_lists.get(&stage_idx) {
228        for item in items {
229            if let Some(obj) = find_object_by_runtime_slot(&item.object_list, runtime_slot) {
230                return Some(obj);
231            }
232        }
233    }
234
235    if let Some(mwnds) = st.mwnd_lists.get(&stage_idx) {
236        for mwnd in mwnds {
237            if let Some(obj) = find_object_by_runtime_slot(&mwnd.button_list, runtime_slot) {
238                return Some(obj);
239            }
240            if let Some(obj) = find_object_by_runtime_slot(&mwnd.face_list, runtime_slot) {
241                return Some(obj);
242            }
243            if let Some(obj) = find_object_by_runtime_slot(&mwnd.object_list, runtime_slot) {
244                return Some(obj);
245            }
246        }
247    }
248
249    None
250}
251
252fn object_active_by_runtime_slot(
253    globals: &GlobalState,
254    stage_form_id: u32,
255    stage_idx: i64,
256    runtime_slot: usize,
257) -> Option<&ObjectState> {
258    globals
259        .stage_forms
260        .get(&stage_form_id)
261        .and_then(|st| object_active_in_stage_state_by_runtime_slot(st, stage_idx, runtime_slot))
262}
263
264fn find_object_by_runtime_slot_mut_ptr(
265    objects: &mut [ObjectState],
266    runtime_slot: usize,
267) -> Option<*mut ObjectState> {
268    find_object_by_runtime_slot_mut(objects, runtime_slot).map(|obj| obj as *mut ObjectState)
269}
270
271fn object_active_by_runtime_slot_mut(
272    globals: &mut GlobalState,
273    stage_form_id: u32,
274    stage_idx: i64,
275    runtime_slot: usize,
276) -> Option<&mut ObjectState> {
277    let st = globals.stage_forms.get_mut(&stage_form_id)?;
278
279    if let Some(ptr) = st
280        .object_lists
281        .get_mut(&stage_idx)
282        .and_then(|list| find_object_by_runtime_slot_mut_ptr(list, runtime_slot))
283    {
284        return unsafe { Some(&mut *ptr) };
285    }
286
287    if let Some(items) = st.btnselitem_lists.get_mut(&stage_idx) {
288        for item in items {
289            if let Some(ptr) =
290                find_object_by_runtime_slot_mut_ptr(&mut item.object_list, runtime_slot)
291            {
292                return unsafe { Some(&mut *ptr) };
293            }
294        }
295    }
296
297    if let Some(mwnds) = st.mwnd_lists.get_mut(&stage_idx) {
298        for mwnd in mwnds {
299            if let Some(ptr) =
300                find_object_by_runtime_slot_mut_ptr(&mut mwnd.button_list, runtime_slot)
301            {
302                return unsafe { Some(&mut *ptr) };
303            }
304            if let Some(ptr) =
305                find_object_by_runtime_slot_mut_ptr(&mut mwnd.face_list, runtime_slot)
306            {
307                return unsafe { Some(&mut *ptr) };
308            }
309            if let Some(ptr) =
310                find_object_by_runtime_slot_mut_ptr(&mut mwnd.object_list, runtime_slot)
311            {
312                return unsafe { Some(&mut *ptr) };
313            }
314        }
315    }
316
317    None
318}
319
320fn finish_wait_skipped_event(ev: &mut IntEvent) {
321    let before = if anim_skip_trace_enabled() {
322        Some(int_event_state(ev))
323    } else {
324        None
325    };
326    ev.end_event();
327    ev.frame();
328    if let Some(before) = before {
329        anim_skip_trace(format!(
330            "finish_event before=[{}] after=[{}]",
331            before,
332            int_event_state(ev)
333        ));
334    }
335}
336
337fn event_prop_pairs(ids: &RuntimeConstants) -> [(i32, i32); 36] {
338    [
339        (ids.obj_patno_eve, ids.obj_patno),
340        (ids.obj_x_eve, ids.obj_x),
341        (ids.obj_y_eve, ids.obj_y),
342        (ids.obj_z_eve, ids.obj_z),
343        (ids.obj_center_x_eve, ids.obj_center_x),
344        (ids.obj_center_y_eve, ids.obj_center_y),
345        (ids.obj_center_z_eve, ids.obj_center_z),
346        (ids.obj_center_rep_x_eve, ids.obj_center_rep_x),
347        (ids.obj_center_rep_y_eve, ids.obj_center_rep_y),
348        (ids.obj_center_rep_z_eve, ids.obj_center_rep_z),
349        (ids.obj_scale_x_eve, ids.obj_scale_x),
350        (ids.obj_scale_y_eve, ids.obj_scale_y),
351        (ids.obj_scale_z_eve, ids.obj_scale_z),
352        (ids.obj_rotate_x_eve, ids.obj_rotate_x),
353        (ids.obj_rotate_y_eve, ids.obj_rotate_y),
354        (ids.obj_rotate_z_eve, ids.obj_rotate_z),
355        (ids.obj_clip_left_eve, ids.obj_clip_left),
356        (ids.obj_clip_top_eve, ids.obj_clip_top),
357        (ids.obj_clip_right_eve, ids.obj_clip_right),
358        (ids.obj_clip_bottom_eve, ids.obj_clip_bottom),
359        (ids.obj_src_clip_left_eve, ids.obj_src_clip_left),
360        (ids.obj_src_clip_top_eve, ids.obj_src_clip_top),
361        (ids.obj_src_clip_right_eve, ids.obj_src_clip_right),
362        (ids.obj_src_clip_bottom_eve, ids.obj_src_clip_bottom),
363        (ids.obj_tr_eve, ids.obj_tr),
364        (ids.obj_mono_eve, ids.obj_mono),
365        (ids.obj_reverse_eve, ids.obj_reverse),
366        (ids.obj_bright_eve, ids.obj_bright),
367        (ids.obj_dark_eve, ids.obj_dark),
368        (ids.obj_color_r_eve, ids.obj_color_r),
369        (ids.obj_color_g_eve, ids.obj_color_g),
370        (ids.obj_color_b_eve, ids.obj_color_b),
371        (ids.obj_color_rate_eve, ids.obj_color_rate),
372        (ids.obj_color_add_r_eve, ids.obj_color_add_r),
373        (ids.obj_color_add_g_eve, ids.obj_color_add_g),
374        (ids.obj_color_add_b_eve, ids.obj_color_add_b),
375    ]
376}
377
378fn object_prop_op_for_event_op(ids: &RuntimeConstants, event_op: i32) -> Option<i32> {
379    event_prop_pairs(ids)
380        .into_iter()
381        .find_map(|(ev_op, prop_op)| (ev_op != 0 && event_op == ev_op).then_some(prop_op))
382}
383
384fn finish_wait_skipped_object_event_by_op(
385    obj: &mut ObjectState,
386    ids: &RuntimeConstants,
387    event_op: i32,
388) {
389    let file = obj.file_name.as_deref().unwrap_or("-").to_string();
390    let runtime_slot = obj.runtime_slot_or(usize::MAX);
391    let event_name = object_event_op_name(ids, event_op);
392    let Some(value) = obj.int_event_by_op_mut(ids, event_op).map(|ev| {
393        anim_skip_trace(format!(
394            "finish_object_event begin slot={} file={} op={}({}) state=[{}]",
395            runtime_slot,
396            file,
397            event_op,
398            event_name,
399            int_event_state(ev)
400        ));
401        finish_wait_skipped_event(ev);
402        anim_skip_trace(format!(
403            "finish_object_event event_done slot={} file={} op={}({}) state=[{}]",
404            runtime_slot,
405            file,
406            event_op,
407            event_name,
408            int_event_state(ev)
409        ));
410        ev.get_total_value() as i64
411    }) else {
412        anim_skip_trace(format!(
413            "finish_object_event missing slot={} file={} op={}({})",
414            runtime_slot, file, event_op, event_name
415        ));
416        return;
417    };
418    if let Some(prop_op) = object_prop_op_for_event_op(ids, event_op) {
419        obj.set_int_prop(ids, prop_op, value);
420        anim_skip_trace(format!(
421            "finish_object_event prop_write slot={} file={} event_op={}({}) prop_op={} value={} obj_tr={} obj_alpha={} obj_pos=({}, {})",
422            runtime_slot,
423            file,
424            event_op,
425            event_name,
426            prop_op,
427            value,
428            obj.get_int_prop(ids, ids.obj_tr),
429            obj.base.alpha,
430            obj.get_int_prop(ids, ids.obj_x),
431            obj.get_int_prop(ids, ids.obj_y),
432        ));
433    } else {
434        anim_skip_trace(format!(
435            "finish_object_event no_prop_map slot={} file={} event_op={}({}) value={}",
436            runtime_slot, file, event_op, event_name, value
437        ));
438    }
439}
440
441fn finish_wait_skipped_object_events(obj: &mut ObjectState, ids: &RuntimeConstants) {
442    let file = obj.file_name.as_deref().unwrap_or("-").to_string();
443    let runtime_slot = obj.runtime_slot_or(usize::MAX);
444    anim_skip_trace(format!(
445        "finish_object_all begin slot={} file={} any_active={} tr={} alpha={} pos=({}, {})",
446        runtime_slot,
447        file,
448        obj.any_event_active(),
449        obj.get_int_prop(ids, ids.obj_tr),
450        obj.base.alpha,
451        obj.get_int_prop(ids, ids.obj_x),
452        obj.get_int_prop(ids, ids.obj_y),
453    ));
454    let mut final_values = Vec::new();
455    for (event_op, prop_op) in event_prop_pairs(ids) {
456        if event_op == 0 || prop_op == 0 {
457            continue;
458        }
459        if let Some(ev) = obj.int_event_by_op_mut(ids, event_op) {
460            if ev.check_event() {
461                anim_skip_trace(format!(
462                    "finish_object_all active slot={} file={} op={}({}) state=[{}]",
463                    runtime_slot,
464                    file,
465                    event_op,
466                    object_event_op_name(ids, event_op),
467                    int_event_state(ev)
468                ));
469            }
470            finish_wait_skipped_event(ev);
471            final_values.push((event_op, prop_op, ev.get_total_value() as i64));
472        }
473    }
474    obj.runtime.prop_event_lists.end_all();
475    obj.runtime.prop_event_lists.frame();
476    for (event_op, prop_op, value) in final_values {
477        obj.set_int_prop(ids, prop_op, value);
478        anim_skip_trace(format!(
479            "finish_object_all prop_write slot={} file={} event_op={}({}) prop_op={} value={}",
480            runtime_slot,
481            file,
482            event_op,
483            object_event_op_name(ids, event_op),
484            prop_op,
485            value
486        ));
487    }
488    anim_skip_trace(format!(
489        "finish_object_all end slot={} file={} any_active={} tr={} alpha={} pos=({}, {})",
490        runtime_slot,
491        file,
492        obj.any_event_active(),
493        obj.get_int_prop(ids, ids.obj_tr),
494        obj.base.alpha,
495        obj.get_int_prop(ids, ids.obj_x),
496        obj.get_int_prop(ids, ids.obj_y),
497    ));
498}
499
500fn finish_event_wait_by_key(w: &EventWait, globals: &mut GlobalState, ids: &RuntimeConstants) {
501    match w {
502        EventWait::ObjectAll {
503            stage_form_id,
504            stage_idx,
505            runtime_slot,
506        } => {
507            if let Some(obj) = object_active_by_runtime_slot_mut(
508                globals,
509                *stage_form_id,
510                *stage_idx,
511                *runtime_slot,
512            ) {
513                finish_wait_skipped_object_events(obj, ids);
514            }
515        }
516        EventWait::ObjectOne {
517            stage_form_id,
518            stage_idx,
519            runtime_slot,
520            op,
521        } => {
522            if let Some(obj) = object_active_by_runtime_slot_mut(
523                globals,
524                *stage_form_id,
525                *stage_idx,
526                *runtime_slot,
527            ) {
528                finish_wait_skipped_object_event_by_op(obj, ids, *op);
529            }
530        }
531        EventWait::ObjectList {
532            stage_form_id,
533            stage_idx,
534            runtime_slot,
535            list_op,
536            list_idx,
537        } => {
538            if let Some(obj) = object_active_by_runtime_slot_mut(
539                globals,
540                *stage_form_id,
541                *stage_idx,
542                *runtime_slot,
543            ) {
544                let file = obj.file_name.as_deref().unwrap_or("-").to_string();
545                if let Some(ev) = object_event_list_for_wait_mut(obj, ids, *list_op)
546                    .and_then(|v| v.get_mut(*list_idx))
547                {
548                    anim_skip_trace(format!(
549                        "finish_object_list_event begin stage_form={} stage={} slot={} file={} list_op={}({}) list_idx={} state=[{}]",
550                        stage_form_id,
551                        stage_idx,
552                        runtime_slot,
553                        file,
554                        list_op,
555                        object_event_op_name(ids, *list_op),
556                        list_idx,
557                        int_event_state(ev)
558                    ));
559                    finish_wait_skipped_event(ev);
560                    anim_skip_trace(format!(
561                        "finish_object_list_event end stage_form={} stage={} slot={} file={} list_op={}({}) list_idx={} state=[{}]",
562                        stage_form_id,
563                        stage_idx,
564                        runtime_slot,
565                        file,
566                        list_op,
567                        object_event_op_name(ids, *list_op),
568                        list_idx,
569                        int_event_state(ev)
570                    ));
571                } else {
572                    anim_skip_trace(format!(
573                        "finish_object_list_event missing stage_form={} stage={} slot={} file={} list_op={}({}) list_idx={}",
574                        stage_form_id,
575                        stage_idx,
576                        runtime_slot,
577                        file,
578                        list_op,
579                        object_event_op_name(ids, *list_op),
580                        list_idx
581                    ));
582                }
583            }
584        }
585        EventWait::GenericIntEvent { form_id, index } => match index {
586            Some(i) => {
587                if let Some(ev) = globals
588                    .int_event_lists
589                    .get_mut(form_id)
590                    .and_then(|v| v.get_mut(*i))
591                {
592                    anim_skip_trace(format!(
593                        "finish_generic_int_event begin form_id={} index={} state=[{}]",
594                        form_id, i, int_event_state(ev)
595                    ));
596                    finish_wait_skipped_event(ev);
597                    anim_skip_trace(format!(
598                        "finish_generic_int_event end form_id={} index={} state=[{}]",
599                        form_id, i, int_event_state(ev)
600                    ));
601                }
602            }
603            None => {
604                if let Some(ev) = globals.int_event_roots.get_mut(form_id) {
605                    anim_skip_trace(format!(
606                        "finish_generic_int_event begin form_id={} index=None state=[{}]",
607                        form_id, int_event_state(ev)
608                    ));
609                    finish_wait_skipped_event(ev);
610                    anim_skip_trace(format!(
611                        "finish_generic_int_event end form_id={} index=None state=[{}]",
612                        form_id, int_event_state(ev)
613                    ));
614                }
615            }
616        },
617        EventWait::FogX => {
618            finish_wait_skipped_event(&mut globals.fog_global.x_event);
619            globals.fog_global.scroll_x = globals.fog_global.x_event.get_total_value() as f32;
620        }
621        EventWait::CounterThreshold { .. } => {}
622    }
623}
624
625#[derive(Debug, Default, Clone)]
626pub struct VmWait {
627    until: Option<Instant>,
628    until_frame: Option<u64>,
629    waiting_for_key: bool,
630    /// If set, a key press cancels the current time wait (TIMEWAIT_KEY behavior).
631    skip_time_on_key: bool,
632
633    audio: Option<AudioWait>,
634    audio_return_value: bool,
635
636    event: Option<EventWait>,
637    event_key_skip: bool,
638    event_return_value: bool,
639
640    movie: Option<MovieWait>,
641    movie_key_skip: bool,
642
643    global_movie: bool,
644    global_movie_key_skip: bool,
645    global_movie_return_value: bool,
646
647    movie_skip_info: Option<MovieWait>,
648    pending_value: Option<Value>,
649
650    /// Blocks VM execution until a runtime modal UI supplies a return value.
651    system_modal: bool,
652
653    wipe: bool,
654    wipe_key_skip: bool,
655
656    block_generation: u64,
657}
658
659impl VmWait {
660    pub fn block_generation(&self) -> u64 {
661        self.block_generation
662    }
663
664    pub fn needs_runtime_poll(&self) -> bool {
665        self.until.is_some()
666            || self.until_frame.is_some()
667            || self.audio.is_some()
668            || self.event.is_some()
669            || self.movie.is_some()
670            || self.global_movie
671            || self.wipe
672    }
673
674    fn mark_block_request(&mut self) {
675        self.block_generation = self.block_generation.wrapping_add(1);
676    }
677
678    pub fn poll(
679        &mut self,
680        stack: &mut Vec<Value>,
681        bgm: &mut BgmEngine,
682        koe: &mut KoeEngine,
683        se: &mut SeEngine,
684        pcm: &mut PcmEngine,
685        globals: &mut GlobalState,
686        ids: &RuntimeConstants,
687    ) -> bool {
688        let blocked = self.is_blocked(bgm, koe, se, pcm, globals, ids);
689        if !blocked {
690            if let Some(v) = self.pending_value.take() {
691                stack.push(v);
692            }
693        }
694        blocked
695    }
696
697    pub fn is_blocked(
698        &mut self,
699        bgm: &mut BgmEngine,
700        koe: &mut KoeEngine,
701        se: &mut SeEngine,
702        pcm: &mut PcmEngine,
703        globals: &mut GlobalState,
704        ids: &RuntimeConstants,
705    ) -> bool {
706        // Auto-clear time waits when the deadline is reached.
707        if let Some(t) = self.until {
708            if Instant::now() >= t {
709                let key_skippable_timewait = self.skip_time_on_key;
710                self.until = None;
711                self.skip_time_on_key = false;
712                if key_skippable_timewait {
713                    anim_skip_trace("timewait_key naturally finished pending=0");
714                    self.pending_value = Some(Value::Int(0));
715                }
716            }
717        }
718
719        if let Some(frame) = self.until_frame {
720            if globals.render_frame >= frame {
721                self.until_frame = None;
722            }
723        }
724
725        // Auto-clear audio waits when the predicate is satisfied.
726        if let Some(w) = self.audio {
727            let done = match w {
728                AudioWait::Bgm => !bgm.is_playing(),
729                AudioWait::BgmFade => !bgm.is_fade_out_doing(),
730                AudioWait::KoeAny => !koe.is_playing_any(),
731                AudioWait::SeAny => !se.is_playing_any(),
732                AudioWait::PcmAny => !pcm.is_playing_any(),
733                AudioWait::PcmSlot(s) => !pcm.is_playing_slot(s as usize),
734            };
735            if done {
736                self.audio = None;
737                if self.audio_return_value {
738                    self.pending_value = Some(Value::Int(0));
739                }
740                self.audio_return_value = false;
741            }
742        }
743
744        // Auto-clear event waits when the predicate is satisfied.
745        let event_done = if let Some(w) = self.event.as_ref() {
746            match w {
747                EventWait::ObjectAll {
748                    stage_form_id,
749                    stage_idx,
750                    runtime_slot,
751                } => object_active_by_runtime_slot(
752                    globals,
753                    *stage_form_id,
754                    *stage_idx,
755                    *runtime_slot,
756                )
757                .map(|obj| !obj.used || !obj.any_event_active())
758                .unwrap_or(true),
759                EventWait::ObjectOne {
760                    stage_form_id,
761                    stage_idx,
762                    runtime_slot,
763                    op,
764                } => object_active_by_runtime_slot(
765                    globals,
766                    *stage_form_id,
767                    *stage_idx,
768                    *runtime_slot,
769                )
770                .map(|obj| {
771                    !obj.used
772                        || !obj
773                            .int_event_by_op(ids, *op)
774                            .map(|e| e.check_event())
775                            .unwrap_or(false)
776                })
777                .unwrap_or(true),
778                EventWait::ObjectList {
779                    stage_form_id,
780                    stage_idx,
781                    runtime_slot,
782                    list_op,
783                    list_idx,
784                } => object_active_by_runtime_slot(
785                    globals,
786                    *stage_form_id,
787                    *stage_idx,
788                    *runtime_slot,
789                )
790                .map(|obj| {
791                    let active = object_event_list_for_wait(obj, ids, *list_op)
792                        .and_then(|v| v.get(*list_idx))
793                        .map(|e| e.check_event())
794                        .unwrap_or(false);
795                    !obj.used || !active
796                })
797                .unwrap_or(true),
798                EventWait::GenericIntEvent { form_id, index } => match index {
799                    Some(i) => globals
800                        .int_event_lists
801                        .get(form_id)
802                        .and_then(|v| v.get(*i))
803                        .map(|e| !e.check_event())
804                        .unwrap_or(true),
805                    None => globals
806                        .int_event_roots
807                        .get(form_id)
808                        .map(|e| !e.check_event())
809                        .unwrap_or(true),
810                },
811                EventWait::FogX => !globals.fog_global.x_event.check_event(),
812                EventWait::CounterThreshold {
813                    form_id,
814                    index,
815                    target,
816                } => globals
817                    .counter_lists
818                    .get(form_id)
819                    .and_then(|v| v.get(*index))
820                    .map(|c| c.get_count() - *target >= 0)
821                    .unwrap_or(true),
822            }
823        } else {
824            false
825        };
826        if event_done {
827            let was_event_key_skip = self.event_key_skip;
828            anim_skip_trace(format!(
829                "event_wait naturally finished event={:?} key_skip={} return_value={}",
830                self.event.as_ref(), was_event_key_skip, self.event_return_value
831            ));
832            self.event = None;
833            self.event_key_skip = false;
834            if was_event_key_skip {
835                self.waiting_for_key = false;
836            }
837            if self.event_return_value {
838                self.pending_value = Some(Value::Int(0));
839            }
840            self.event_return_value = false;
841        }
842
843        // Auto-clear GLOBAL.MOV waits when playback ends.
844        if self.global_movie {
845            if !globals.mov.playing {
846                if self.global_movie_return_value {
847                    self.pending_value = Some(Value::Int(0));
848                }
849                self.global_movie = false;
850                self.global_movie_key_skip = false;
851                self.global_movie_return_value = false;
852            }
853        }
854
855        // Auto-clear OBJECT movie waits when playback ends.
856        if let Some(w) = self.movie {
857            let done = object_active_by_runtime_slot(
858                globals,
859                w.stage_form_id,
860                w.stage_idx,
861                w.runtime_slot,
862            )
863            .map(|obj| !obj.used || !obj.movie.check_movie())
864            .unwrap_or(true);
865
866            if done {
867                if w.return_value_flag {
868                    self.pending_value = Some(Value::Int(0));
869                }
870                self.movie = None;
871                self.movie_key_skip = false;
872            }
873        }
874
875        // Auto-clear wipe waits when the wipe is finished.
876        if self.wipe {
877            if globals.wipe_done() {
878                self.wipe = false;
879                self.wipe_key_skip = false;
880            }
881        }
882
883        self.waiting_for_key
884            || self.until.is_some()
885            || self.until_frame.is_some()
886            || self.audio.is_some()
887            || self.event.is_some()
888            || self.movie.is_some()
889            || self.global_movie
890            || self.system_modal
891            || self.wipe
892    }
893
894    pub fn wait_system_modal(&mut self) {
895        self.mark_block_request();
896        self.system_modal = true;
897    }
898
899    pub fn finish_system_modal(&mut self, value: Value) {
900        if self.system_modal {
901            self.system_modal = false;
902            self.pending_value = Some(value);
903        }
904    }
905
906    pub fn finish_system_modal_void(&mut self) {
907        if self.system_modal {
908            self.system_modal = false;
909            self.pending_value = None;
910        }
911    }
912
913    pub fn system_modal_active(&self) -> bool {
914        self.system_modal
915    }
916
917    pub fn wait_ms(&mut self, ms: u64) {
918        if ms == 0 {
919            return;
920        }
921        self.mark_block_request();
922        self.until = Some(Instant::now() + Duration::from_millis(ms));
923        self.skip_time_on_key = false;
924    }
925
926    pub fn wait_next_frame(&mut self, current_frame: u64) {
927        self.mark_block_request();
928        self.until_frame = Some(current_frame.saturating_add(1));
929        self.skip_time_on_key = false;
930    }
931
932    /// Wait for a duration, but allow any key/mouse press to cancel the wait.
933    pub fn wait_ms_key(&mut self, ms: u64) {
934        if ms == 0 {
935            anim_skip_trace("wait_ms_key ignored ms=0");
936            return;
937        }
938        self.mark_block_request();
939        self.until = Some(Instant::now() + Duration::from_millis(ms));
940        self.skip_time_on_key = true;
941        anim_skip_trace(format!("wait_ms_key start ms={} block_generation={}", ms, self.block_generation));
942    }
943
944    pub fn wait_key(&mut self) {
945        self.mark_block_request();
946        self.waiting_for_key = true;
947    }
948
949    pub fn wait_audio(&mut self, w: AudioWait, key: bool) {
950        self.wait_audio_with_return(w, key, false);
951    }
952
953    pub fn wait_audio_with_return(&mut self, w: AudioWait, key: bool, return_value_flag: bool) {
954        self.mark_block_request();
955        self.audio = Some(w);
956        self.audio_return_value = return_value_flag;
957        if key {
958            self.waiting_for_key = true;
959        }
960    }
961
962    pub fn wait_object_all_events(
963        &mut self,
964        stage_form_id: u32,
965        stage_idx: i64,
966        runtime_slot: usize,
967        key_skip: bool,
968    ) {
969        self.mark_block_request();
970        self.event = Some(EventWait::ObjectAll {
971            stage_form_id,
972            stage_idx,
973            runtime_slot,
974        });
975        anim_skip_trace(format!(
976            "wait_object_all_events start stage_form={} stage={} slot={} key_skip={} block_generation={}",
977            stage_form_id, stage_idx, runtime_slot, key_skip, self.block_generation
978        ));
979        self.event_key_skip = key_skip;
980        self.event_return_value = false;
981        if key_skip {
982            self.waiting_for_key = true;
983        }
984    }
985
986    pub fn wait_object_event(
987        &mut self,
988        stage_form_id: u32,
989        stage_idx: i64,
990        runtime_slot: usize,
991        op: i32,
992        key_skip: bool,
993        return_value_flag: bool,
994    ) {
995        self.mark_block_request();
996        self.event = Some(EventWait::ObjectOne {
997            stage_form_id,
998            stage_idx,
999            runtime_slot,
1000            op,
1001        });
1002        anim_skip_trace(format!(
1003            "wait_object_event start stage_form={} stage={} slot={} op={} key_skip={} return_value={} block_generation={}",
1004            stage_form_id, stage_idx, runtime_slot, op, key_skip, return_value_flag, self.block_generation
1005        ));
1006        self.event_key_skip = key_skip;
1007        self.event_return_value = return_value_flag;
1008        if key_skip {
1009            self.waiting_for_key = true;
1010        }
1011    }
1012
1013    pub fn wait_object_event_list(
1014        &mut self,
1015        stage_form_id: u32,
1016        stage_idx: i64,
1017        runtime_slot: usize,
1018        list_op: i32,
1019        list_idx: usize,
1020        key_skip: bool,
1021        return_value_flag: bool,
1022    ) {
1023        self.mark_block_request();
1024        self.event = Some(EventWait::ObjectList {
1025            stage_form_id,
1026            stage_idx,
1027            runtime_slot,
1028            list_op,
1029            list_idx,
1030        });
1031        anim_skip_trace(format!(
1032            "wait_object_event_list start stage_form={} stage={} slot={} list_op={} list_idx={} key_skip={} return_value={} block_generation={}",
1033            stage_form_id, stage_idx, runtime_slot, list_op, list_idx, key_skip, return_value_flag, self.block_generation
1034        ));
1035        self.event_key_skip = key_skip;
1036        self.event_return_value = return_value_flag;
1037        if key_skip {
1038            self.waiting_for_key = true;
1039        }
1040    }
1041
1042    pub fn wait_global_movie(&mut self, key_skip: bool, return_value_flag: bool) {
1043        self.mark_block_request();
1044        self.global_movie = true;
1045        self.global_movie_key_skip = key_skip;
1046        self.global_movie_return_value = return_value_flag;
1047        if key_skip {
1048            self.waiting_for_key = true;
1049        }
1050    }
1051
1052    pub fn wait_object_movie(
1053        &mut self,
1054        stage_form_id: u32,
1055        stage_idx: i64,
1056        runtime_slot: usize,
1057        key_skip: bool,
1058        return_value_flag: bool,
1059    ) {
1060        self.mark_block_request();
1061        self.movie = Some(MovieWait {
1062            stage_form_id,
1063            stage_idx,
1064            runtime_slot,
1065            return_value_flag,
1066        });
1067        self.movie_key_skip = key_skip;
1068        if key_skip {
1069            self.waiting_for_key = true;
1070        }
1071    }
1072
1073    pub fn wait_generic_int_event(
1074        &mut self,
1075        form_id: u32,
1076        index: Option<usize>,
1077        key_skip: bool,
1078        return_value_flag: bool,
1079    ) {
1080        self.mark_block_request();
1081        self.event = Some(EventWait::GenericIntEvent { form_id, index });
1082        anim_skip_trace(format!(
1083            "wait_generic_int_event start form_id={} index={:?} key_skip={} return_value={} block_generation={}",
1084            form_id, index, key_skip, return_value_flag, self.block_generation
1085        ));
1086        self.event_key_skip = key_skip;
1087        self.event_return_value = return_value_flag;
1088        if key_skip {
1089            self.waiting_for_key = true;
1090        }
1091    }
1092
1093    pub fn wait_fog_x_event(&mut self, key_skip: bool, return_value_flag: bool) {
1094        self.mark_block_request();
1095        self.event = Some(EventWait::FogX);
1096        self.event_key_skip = key_skip;
1097        self.event_return_value = return_value_flag;
1098        if key_skip {
1099            self.waiting_for_key = true;
1100        }
1101    }
1102
1103    pub fn wait_counter(
1104        &mut self,
1105        form_id: u32,
1106        index: usize,
1107        target: i64,
1108        key_skip: bool,
1109        return_value_flag: bool,
1110    ) {
1111        self.mark_block_request();
1112        self.event = Some(EventWait::CounterThreshold {
1113            form_id,
1114            index,
1115            target,
1116        });
1117        self.event_key_skip = key_skip;
1118        self.event_return_value = return_value_flag;
1119        if key_skip {
1120            self.waiting_for_key = true;
1121        }
1122    }
1123
1124    pub fn wait_wipe(&mut self, key_skip: bool) {
1125        self.mark_block_request();
1126        self.wipe = true;
1127        self.wipe_key_skip = key_skip;
1128        if key_skip {
1129            self.waiting_for_key = true;
1130        }
1131    }
1132
1133    /// Notify the wait system that a key/mouse input happened.
1134    ///
1135    /// Returns true if the input is interpreted as a wipe-skip (used by WIPE/WAIT_WIPE).
1136    pub fn notify_key(&mut self, _globals: &mut GlobalState, _ids: &RuntimeConstants) -> bool {
1137        let wipe_skipped = self.wipe && self.wipe_key_skip;
1138        self.waiting_for_key = false;
1139        if self.audio.is_some() && self.audio_return_value {
1140            self.pending_value = Some(Value::Int(1));
1141        }
1142        self.audio = None;
1143        self.audio_return_value = false;
1144        // C++ event WAIT_KEY is not skipped by arbitrary input here.
1145        // It is skipped only by DECIDE down-up in notify_movie_down_up().
1146        // C++ MOV/OBJECT movie waits are not skipped by arbitrary input here.
1147        // They are skipped only by DECIDE/CANCEL down-up in notify_movie_down_up().
1148        if self.skip_time_on_key {
1149            anim_skip_trace("notify_key skipped TIMEWAIT_KEY pending=1");
1150            self.until = None;
1151            self.skip_time_on_key = false;
1152            self.pending_value = Some(Value::Int(1));
1153        }
1154
1155        if wipe_skipped {
1156            self.wipe = false;
1157            self.wipe_key_skip = false;
1158        }
1159
1160        wipe_skipped
1161    }
1162
1163    /// Notify MOV/OBJECT movie waits that DECIDE/CANCEL completed a down-up pair.
1164    ///
1165    /// This matches C++ `tnm_mov_wait_proc` / `tnm_obj_mov_wait_proc`:
1166    /// MOV_WAIT_KEY consumes only VK_EX_DECIDE or VK_EX_CANCEL down-up, returning
1167    /// 1 or -1 respectively. Generic key/mouse events must not skip movie waits.
1168    pub fn notify_movie_down_up(
1169        &mut self,
1170        globals: &mut GlobalState,
1171        ids: &RuntimeConstants,
1172        result: i64,
1173    ) -> bool {
1174        let mut skipped = false;
1175        if result == 1 && self.event_key_skip {
1176            if let Some(w) = self.event.take() {
1177                anim_skip_trace(format!(
1178                    "notify_movie_down_up skip event result={} event={:?} return_value={}",
1179                    result, w, self.event_return_value
1180                ));
1181                finish_event_wait_by_key(&w, globals, ids);
1182                if self.event_return_value {
1183                    self.pending_value = Some(Value::Int(1));
1184                }
1185                skipped = true;
1186            } else {
1187                anim_skip_trace(format!(
1188                    "notify_movie_down_up event_key_skip without event result={}",
1189                    result
1190                ));
1191            }
1192            self.event_key_skip = false;
1193            self.event_return_value = false;
1194        }
1195        if self.global_movie && self.global_movie_key_skip {
1196            // C++ tnm_mov_wait_proc returns first, then C_elm_mov::close() tears down
1197            // the native movie.  Do not clear audio_id here; RuntimeContext must still
1198            // see it and stop the Rust movie audio handle.
1199            globals.mov.playing = false;
1200            if self.global_movie_return_value {
1201                self.pending_value = Some(Value::Int(result));
1202            }
1203            self.global_movie = false;
1204            self.global_movie_key_skip = false;
1205            self.global_movie_return_value = false;
1206            skipped = true;
1207        }
1208        // OBJECT movie wait in C++ only consumes VK_EX_DECIDE down-up.
1209        // VK_EX_CANCEL is handled only by GLOBAL MOV_WAIT_KEY.
1210        if self.movie_key_skip && result == 1 {
1211            if let Some(w) = self.movie.take() {
1212                if w.return_value_flag {
1213                    self.pending_value = Some(Value::Int(1));
1214                }
1215                self.movie_skip_info = Some(w);
1216                skipped = true;
1217            }
1218            self.movie_key_skip = false;
1219        }
1220        if skipped {
1221            self.waiting_for_key = false;
1222        }
1223        skipped
1224    }
1225
1226    /// If the current wait was skipped via key input, returns the skipped movie wait info.
1227    pub fn take_movie_skip(&mut self) -> Option<MovieWait> {
1228        self.movie_skip_info.take()
1229    }
1230
1231    pub fn clear(&mut self) {
1232        self.until = None;
1233        self.waiting_for_key = false;
1234        self.skip_time_on_key = false;
1235        self.audio = None;
1236        self.audio_return_value = false;
1237        self.event = None;
1238        self.event_key_skip = false;
1239        self.event_return_value = false;
1240        self.movie = None;
1241        self.movie_key_skip = false;
1242        self.global_movie = false;
1243        self.global_movie_key_skip = false;
1244        self.global_movie_return_value = false;
1245        self.movie_skip_info = None;
1246        self.pending_value = None;
1247        self.system_modal = false;
1248        self.wipe = false;
1249        self.wipe_key_skip = false;
1250    }
1251}