1use anyhow::{Result, bail};
2
3use crate::runtime::globals::{ObjectFrameActionState, PendingFrameActionFinish};
4use crate::runtime::{CommandContext, Value, VmCallMeta};
5
6use super::codes::{self, excall_op};
7use super::{counter, frame_action, frame_action_ch, int_list, script, stage};
8
9const EXCALL_LOCAL_NS_XOR: u32 = 0x4000;
10
11fn excall_form_key(ctx: &CommandContext) -> u32 {
12 if ctx.ids.form_global_excall != 0 {
13 ctx.ids.form_global_excall
14 } else {
15 super::codes::FORM_GLOBAL_EXCALL
16 }
17}
18
19fn stage_form_key(ctx: &CommandContext) -> u32 {
20 if ctx.ids.form_global_stage != 0 {
21 ctx.ids.form_global_stage
22 } else {
23 super::codes::FORM_GLOBAL_STAGE
24 }
25}
26
27fn excall_stage_form_key(ctx: &CommandContext, selector: i32) -> u32 {
28 let base = stage_form_key(ctx);
29 if selector == 0 {
30 base
31 } else {
32 base ^ EXCALL_LOCAL_NS_XOR
33 }
34}
35
36fn synth_form_key(base: u32, selector: i32, op: i32) -> u32 {
37 (base << 8) ^ (((selector as u32) & 0x0f) << 4) ^ (op as u32 & 0x0f)
38}
39
40type ParsedExcall = (
41 usize,
42 Vec<i32>,
43 i32,
44 i32,
45 Vec<Value>,
46 Option<i64>,
47 Option<i64>,
48);
49
50fn parse_call(ctx: &CommandContext, args: &[Value]) -> Option<ParsedExcall> {
51 let form_id = excall_form_key(ctx);
52 let (chain_pos, chain) = super::prop_access::parse_element_chain_ctx(ctx, form_id, args)?;
53 let (selector, op_pos) = if chain.len() >= 3
54 && chain[1] == crate::runtime::forms::codes::ELM_ARRAY
55 && (chain[2] == 0 || chain[2] == 1)
56 {
57 (chain[2], 3usize)
58 } else {
59 (1i32, 1usize)
60 };
61 let op = chain
62 .get(op_pos)
63 .copied()
64 .or_else(|| args.first().and_then(|v| v.as_i64()).map(|v| v as i32))?;
65 let params = super::prop_access::script_args(args, chain_pos.min(args.len()));
66 let (meta_al_id, meta_ret_form) = crate::runtime::forms::prop_access::current_vm_meta(ctx);
67 let al_id = meta_al_id;
68 let ret_form = meta_ret_form;
69 Some((
74 chain_pos,
75 chain.to_vec(),
76 selector,
77 op,
78 params.to_vec(),
79 al_id,
80 ret_form,
81 ))
82}
83
84fn local_flag_count(ctx: &CommandContext) -> usize {
85 ctx.tables
86 .gameexe
87 .as_ref()
88 .and_then(|cfg| {
89 cfg.get_usize("#FLAG.CNT")
90 .or_else(|| cfg.get_usize("FLAG.CNT"))
91 })
92 .unwrap_or(1000)
93 .min(10000)
94}
95
96fn counter_count(ctx: &CommandContext) -> usize {
97 ctx.tables
98 .gameexe
99 .as_ref()
100 .and_then(|cfg| {
101 cfg.get_usize("#COUNTER.CNT")
102 .or_else(|| cfg.get_usize("COUNTER.CNT"))
103 })
104 .unwrap_or(16)
105 .min(256)
106}
107
108fn frame_action_ch_count(ctx: &CommandContext) -> usize {
109 ctx.tables
110 .gameexe
111 .as_ref()
112 .and_then(|cfg| {
113 cfg.get_usize("#FRAME_ACTION_CH.CNT")
114 .or_else(|| cfg.get_usize("FRAME_ACTION_CH.CNT"))
115 })
116 .unwrap_or(4)
117 .min(256)
118}
119
120fn excall_local_f_key(ctx: &CommandContext) -> u32 {
121 synth_form_key(excall_form_key(ctx), 1, excall_op::OP_7)
122}
123
124fn queue_frame_action_finish(
125 ctx: &mut CommandContext,
126 fa: &ObjectFrameActionState,
127 frame_action_chain: Vec<i32>,
128) {
129 if fa.cmd_name.is_empty() {
130 return;
131 }
132 ctx.globals
133 .pending_frame_action_finishes
134 .push(PendingFrameActionFinish {
135 frame_action_chain,
136 object_chain: None,
137 snapshot: fa.clone(),
138 reinit_after_finish: false,
139 scn_name: fa.scn_name.clone(),
140 cmd_name: fa.cmd_name.clone(),
141 end_time: fa.end_time,
142 args: fa.args.clone(),
143 });
144}
145
146fn begin_free(ctx: &mut CommandContext) {
147 let targets = tick_targets(ctx);
148
149 if let Some(fa) = ctx
151 .globals
152 .frame_actions
153 .get(&targets.frame_action_id)
154 .cloned()
155 {
156 queue_frame_action_finish(ctx, &fa, vec![targets.frame_action_id as i32]);
157 }
158 let ch_snapshot = ctx
159 .globals
160 .frame_action_lists
161 .get(&targets.frame_action_ch_list_id)
162 .cloned()
163 .unwrap_or_default();
164 for (idx, fa) in ch_snapshot.iter().enumerate() {
165 queue_frame_action_finish(
166 ctx,
167 fa,
168 vec![
169 targets.frame_action_ch_list_id as i32,
170 crate::runtime::forms::codes::ELM_ARRAY,
171 idx as i32,
172 ],
173 );
174 }
175 stage::queue_stage_form_finishes(ctx, targets.stage_form_id);
176
177 ctx.excall_state.pending_free = true;
180}
181
182pub(crate) fn finalize_pending_free(ctx: &mut CommandContext) {
183 if !ctx.excall_state.pending_free {
184 return;
185 }
186 let targets = tick_targets(ctx);
187 let f_key = excall_local_f_key(ctx);
188 ctx.globals.int_lists.remove(&f_key);
189 ctx.globals.counter_lists.remove(&targets.counter_list_id);
190 ctx.globals
191 .frame_action_lists
192 .remove(&targets.frame_action_ch_list_id);
193 stage::free_stage_form(ctx, targets.stage_form_id);
194 ctx.excall_state.ready = false;
195 ctx.excall_state.pending_free = false;
196 ctx.excall_state.font_name.clear();
198}
199
200pub(crate) fn tick_targets(ctx: &CommandContext) -> crate::runtime::globals::ExcallTickTargets {
201 let form_key = excall_form_key(ctx);
202 crate::runtime::globals::ExcallTickTargets {
203 counter_list_id: synth_form_key(form_key, 1, excall_op::OP_6),
204 frame_action_id: synth_form_key(form_key, 1, excall_op::OP_9),
205 frame_action_ch_list_id: synth_form_key(form_key, 1, excall_op::OP_10),
206 stage_form_id: excall_stage_form_key(ctx, 1),
207 }
208}
209
210fn push_default(ctx: &mut CommandContext, ret_form: Option<i64>) {
211 if ret_form == Some(codes::FM_STR as i64) || ret_form == Some(codes::FM_STRREF as i64) {
212 ctx.push(Value::Str(String::new()));
213 } else {
214 ctx.push(Value::Int(0));
215 }
216}
217
218fn child_requires_ready(op: i32) -> bool {
219 matches!(
220 op,
221 excall_op::OP_0
222 | excall_op::OP_1
223 | excall_op::OP_2
224 | excall_op::OP_3
225 | excall_op::OP_6
226 | excall_op::OP_7
227 | excall_op::OP_9
228 | excall_op::OP_10
229 | excall_op::OP_13
230 )
231}
232
233fn translated_call_element(form_id: u32, chain_tail: &[i32]) -> Vec<i32> {
234 let mut chain = Vec::with_capacity(1 + chain_tail.len());
235 chain.push(form_id as i32);
236 chain.extend_from_slice(chain_tail);
237 chain
238}
239
240fn excall_stage_index(op: i32) -> Option<i32> {
245 match op {
246 excall_op::FRONT => Some(1),
247 excall_op::BACK => Some(0),
248 excall_op::NEXT => Some(2),
249 _ => None,
250 }
251}
252
253fn translated_stage_element(
254 stage_form_id: u32,
255 stage_idx: Option<i32>,
256 chain_tail: &[i32],
257) -> Vec<i32> {
258 let mut chain = Vec::new();
259 chain.push(stage_form_id as i32);
260 if let Some(idx) = stage_idx {
261 chain.push(crate::runtime::forms::codes::ELM_ARRAY);
262 chain.push(idx);
263 }
264 chain.extend_from_slice(chain_tail);
265 chain
266}
267
268fn with_forwarded_vm_call<F>(ctx: &mut CommandContext, element: Vec<i32>, f: F) -> Result<bool>
269where
270 F: FnOnce(&mut CommandContext) -> Result<bool>,
271{
272 let saved = ctx.vm_call.clone();
276 let (al_id, ret_form) = saved
277 .as_ref()
278 .map(|m| (m.al_id, m.ret_form))
279 .unwrap_or((0, 0));
280 ctx.vm_call = Some(VmCallMeta {
281 element,
282 al_id,
283 ret_form,
284 });
285 let result = f(ctx);
286 ctx.vm_call = saved;
287 result
288}
289
290pub fn dispatch(ctx: &mut CommandContext, args: &[Value]) -> Result<bool> {
291 let Some((_chain_pos, chain, selector, op, params, _al_id, ret_form)) = parse_call(ctx, args)
292 else {
293 if args.is_empty() {
294 bail!("EXCALL form expects at least one argument (op id)");
295 }
296 return Ok(false);
297 };
298
299 let op_pos =
300 if chain.len() >= 3 && chain[1] == codes::ELM_ARRAY && (chain[2] == 0 || chain[2] == 1) {
301 3usize
302 } else {
303 1usize
304 };
305 let tail = chain.get(op_pos + 1..).unwrap_or(&[]);
306 let form_key = excall_form_key(ctx);
307
308 match op {
309 excall_op::OP_4 => {
310 if selector != 1 {
311 log::error!("EXCALL.ALLOC requires EXCALL[1], selector={}", selector);
312 push_default(ctx, ret_form);
313 return Ok(true);
314 }
315 let targets = tick_targets(ctx);
316 let f_key = excall_local_f_key(ctx);
317 let f_count = local_flag_count(ctx);
318 let counter_count = counter_count(ctx);
319 let frame_action_ch_count = frame_action_ch_count(ctx);
320 ctx.globals
321 .int_lists
322 .entry(f_key)
323 .or_default()
324 .resize(f_count, 0);
325 ctx.globals
326 .counter_lists
327 .entry(targets.counter_list_id)
328 .or_default()
329 .resize_with(counter_count, Default::default);
330 ctx.globals
331 .frame_actions
332 .entry(targets.frame_action_id)
333 .or_default();
334 ctx.globals
335 .frame_action_lists
336 .entry(targets.frame_action_ch_list_id)
337 .or_default()
338 .resize_with(frame_action_ch_count, ObjectFrameActionState::default);
339 stage::ensure_stage_form_allocated(ctx, targets.stage_form_id);
340 ctx.excall_state.ready = true;
341 push_default(ctx, ret_form);
342 return Ok(true);
343 }
344 excall_op::OP_5 => {
345 if selector != 1 {
346 log::error!("EXCALL.FREE requires EXCALL[1], selector={}", selector);
347 push_default(ctx, ret_form);
348 return Ok(true);
349 }
350 begin_free(ctx);
351 push_default(ctx, ret_form);
352 return Ok(true);
353 }
354 excall_op::OP_8 => {
355 ctx.push(Value::Int(if selector == 1 && ctx.excall_state.ready {
356 1
357 } else {
358 0
359 }));
360 return Ok(true);
361 }
362 excall_op::OP_12 => {
363 ctx.push(Value::Int(if ctx.excall_state.ex_call_flag {
364 1
365 } else {
366 0
367 }));
368 return Ok(true);
369 }
370 _ => {}
371 }
372
373 if selector == 1 && child_requires_ready(op) && !ctx.excall_state.ready {
374 log::error!(
375 "EXCALL child accessed before ALLOC: op={} chain={:?}",
376 op,
377 chain
378 );
379 push_default(ctx, ret_form);
380 return Ok(true);
381 }
382
383 match op {
384 excall_op::OP_0 => {
385 let element =
386 translated_stage_element(excall_stage_form_key(ctx, selector), None, tail);
387 with_forwarded_vm_call(ctx, element, |ctx| stage::dispatch(ctx, ¶ms))
388 }
389 excall_op::FRONT | excall_op::BACK | excall_op::NEXT => {
390 let stage_idx = excall_stage_index(op)
391 .expect("FRONT/BACK/NEXT must map to a concrete EXCALL stage");
392 let element = translated_stage_element(
393 excall_stage_form_key(ctx, selector),
394 Some(stage_idx),
395 tail,
396 );
397 with_forwarded_vm_call(ctx, element, |ctx| stage::dispatch(ctx, ¶ms))
398 }
399 excall_op::OP_6 => {
400 let key = synth_form_key(form_key, selector, op);
401 let element = translated_call_element(key, tail);
402 with_forwarded_vm_call(ctx, element, |ctx| counter::dispatch(ctx, key, ¶ms))
403 }
404 excall_op::OP_7 => {
405 let key = if selector == 0 {
406 codes::ELM_GLOBAL_F as u32
407 } else {
408 excall_local_f_key(ctx)
409 };
410 let element = translated_call_element(key, tail);
411 with_forwarded_vm_call(ctx, element, |ctx| int_list::dispatch(ctx, key, ¶ms))
412 }
413 excall_op::OP_9 => {
414 let key = synth_form_key(form_key, selector, op);
415 let element = translated_call_element(key, tail);
416 with_forwarded_vm_call(ctx, element, |ctx| {
417 frame_action::dispatch(ctx, key, ¶ms)
418 })
419 }
420 excall_op::OP_10 => {
421 let key = synth_form_key(form_key, selector, op);
422 let element = translated_call_element(key, tail);
423 with_forwarded_vm_call(ctx, element, |ctx| {
424 frame_action_ch::dispatch(ctx, key, ¶ms)
425 })
426 }
427 excall_op::OP_13 => {
428 let Some(script_op) = tail.first().copied() else {
429 push_default(ctx, ret_form);
430 return Ok(true);
431 };
432 if script::dispatch_excall(ctx, script_op, ¶ms)? {
433 Ok(true)
434 } else {
435 log::error!(
436 "unsupported EXCALL.SCRIPT operation: selector={} op={}",
437 selector,
438 script_op
439 );
440 Ok(false)
441 }
442 }
443 _ => {
444 log::error!(
445 "unsupported EXCALL operation: selector={} op={}",
446 selector,
447 op
448 );
449 push_default(ctx, ret_form);
450 Ok(true)
451 }
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 #[test]
460 fn excall_stage_selectors_match_original_stage_indices() {
461 assert_eq!(excall_stage_index(excall_op::FRONT), Some(1));
466 assert_eq!(excall_stage_index(excall_op::BACK), Some(0));
467 assert_eq!(excall_stage_index(excall_op::NEXT), Some(2));
468 assert_eq!(excall_stage_index(excall_op::STAGE), None);
469 }
470}