Skip to main content

siglus_scene_vm/runtime/forms/
excall.rs

1use anyhow::{bail, Result};
2
3use crate::runtime::{CommandContext, Value};
4
5use super::codes::excall_op;
6use super::{counter, frame_action, frame_action_ch, int_list, script, stage};
7
8const EXCALL_LOCAL_NS_XOR: u32 = 0x4000;
9
10fn excall_form_key(ctx: &CommandContext) -> u32 {
11    if ctx.ids.form_global_excall != 0 {
12        ctx.ids.form_global_excall
13    } else {
14        super::codes::FORM_GLOBAL_EXCALL
15    }
16}
17
18fn stage_form_key(ctx: &CommandContext) -> u32 {
19    if ctx.ids.form_global_stage != 0 {
20        ctx.ids.form_global_stage
21    } else {
22        super::codes::FORM_GLOBAL_STAGE
23    }
24}
25
26fn script_form_key(ctx: &CommandContext) -> u32 {
27    if ctx.ids.form_global_script != 0 {
28        ctx.ids.form_global_script
29    } else {
30        0
31    }
32}
33
34fn excall_stage_form_key(ctx: &CommandContext, selector: i32) -> u32 {
35    let base = stage_form_key(ctx);
36    if selector == 0 {
37        base
38    } else {
39        base ^ EXCALL_LOCAL_NS_XOR
40    }
41}
42
43fn synth_form_key(base: u32, selector: i32, op: i32) -> u32 {
44    (base << 8) ^ (((selector as u32) & 0x0f) << 4) ^ (op as u32 & 0x0f)
45}
46
47fn parse_call<'a>(
48    ctx: &'a CommandContext,
49    args: &'a [Value],
50) -> Option<(
51    usize,
52    &'a [i32],
53    i32,
54    i32,
55    &'a [Value],
56    Option<i64>,
57    Option<i64>,
58)> {
59    let form_id = excall_form_key(ctx);
60    let (chain_pos, chain) = super::prop_access::parse_element_chain_ctx(ctx, form_id, args)?;
61    let (selector, op_pos) = if chain.len() >= 3
62        && chain[1] == crate::runtime::forms::codes::ELM_ARRAY
63        && (chain[2] == 0 || chain[2] == 1)
64    {
65        (chain[2], 3usize)
66    } else {
67        (1i32, 1usize)
68    };
69    let op = chain
70        .get(op_pos)
71        .copied()
72        .or_else(|| args.get(0).and_then(|v| v.as_i64()).map(|v| v as i32))?;
73    let params = if chain_pos > 1 {
74        &args[1..chain_pos]
75    } else {
76        &[]
77    };
78    let (meta_al_id, meta_ret_form) = crate::runtime::forms::prop_access::current_vm_meta(ctx);
79    let al_id = meta_al_id;
80    let ret_form = meta_ret_form;
81    Some((chain_pos, chain, selector, op, params, al_id, ret_form))
82}
83
84fn translated_call_args(
85    form_id: u32,
86    chain_tail: &[i32],
87    params: &[Value],
88    al_id: Option<i64>,
89    ret_form: Option<i64>,
90) -> Vec<Value> {
91    let mut out = Vec::new();
92    let op0 = chain_tail.first().copied().unwrap_or(0) as i64;
93    out.push(Value::Int(op0));
94    out.extend(params.iter().cloned());
95    let mut chain = Vec::with_capacity(1 + chain_tail.len());
96    chain.push(form_id as i32);
97    chain.extend_from_slice(chain_tail);
98    out.push(Value::Element(chain));
99    out.push(Value::Int(al_id.unwrap_or(0)));
100    out.push(Value::Int(ret_form.unwrap_or(0)));
101    out
102}
103
104fn translated_stage_args_to_form(
105    stage_form_id: u32,
106    stage_idx: Option<i32>,
107    chain_tail: &[i32],
108    params: &[Value],
109    al_id: Option<i64>,
110    ret_form: Option<i64>,
111) -> Vec<Value> {
112    let mut out = Vec::new();
113    let op0 = chain_tail.first().copied().unwrap_or(0) as i64;
114    out.push(Value::Int(op0));
115    out.extend(params.iter().cloned());
116
117    let mut chain = Vec::new();
118    chain.push(stage_form_id as i32);
119    if let Some(idx) = stage_idx {
120        chain.push(crate::runtime::forms::codes::ELM_ARRAY);
121        chain.push(idx);
122    }
123    chain.extend_from_slice(chain_tail);
124    out.push(Value::Element(chain));
125    out.push(Value::Int(al_id.unwrap_or(0)));
126    out.push(Value::Int(ret_form.unwrap_or(0)));
127    out
128}
129
130pub fn dispatch(ctx: &mut CommandContext, args: &[Value]) -> Result<bool> {
131    let Some((_chain_pos, chain, selector, op, params, al_id, ret_form)) = parse_call(ctx, args)
132    else {
133        if args.is_empty() {
134            bail!("EXCALL form expects at least one argument (op id)");
135        }
136        return Ok(false);
137    };
138
139    let op_pos = if chain.len() >= 3
140        && chain[1] == crate::runtime::forms::codes::ELM_ARRAY
141        && (chain[2] == 0 || chain[2] == 1)
142    {
143        3usize
144    } else {
145        1usize
146    };
147    let tail = chain.get(op_pos + 1..).unwrap_or(&[]);
148    let form_key = excall_form_key(ctx);
149
150    match op {
151        excall_op::OP_4 => {
152            ctx.excall_state.ready = true;
153            ctx.push(Value::Int(0));
154        }
155        excall_op::OP_5 => {
156            ctx.excall_state.ready = false;
157            ctx.push(Value::Int(0));
158        }
159        excall_op::OP_8 => {
160            ctx.push(Value::Int(if ctx.excall_state.ready { 1 } else { 0 }));
161        }
162        excall_op::OP_12 => {
163            ctx.push(Value::Int(if selector == 1 { 1 } else { 0 }));
164        }
165        excall_op::OP_0 => {
166            let forwarded = translated_stage_args_to_form(
167                excall_stage_form_key(ctx, selector),
168                None,
169                tail,
170                params,
171                al_id,
172                ret_form,
173            );
174            return stage::dispatch(ctx, &forwarded);
175        }
176        excall_op::OP_1 => {
177            let forwarded = translated_stage_args_to_form(
178                excall_stage_form_key(ctx, selector),
179                Some(0),
180                tail,
181                params,
182                al_id,
183                ret_form,
184            );
185            return stage::dispatch(ctx, &forwarded);
186        }
187        excall_op::OP_2 => {
188            let forwarded = translated_stage_args_to_form(
189                excall_stage_form_key(ctx, selector),
190                Some(1),
191                tail,
192                params,
193                al_id,
194                ret_form,
195            );
196            return stage::dispatch(ctx, &forwarded);
197        }
198        excall_op::OP_3 => {
199            let forwarded = translated_stage_args_to_form(
200                excall_stage_form_key(ctx, selector),
201                Some(2),
202                tail,
203                params,
204                al_id,
205                ret_form,
206            );
207            return stage::dispatch(ctx, &forwarded);
208        }
209        excall_op::OP_6 => {
210            let forwarded = translated_call_args(
211                synth_form_key(form_key, selector, op),
212                tail,
213                params,
214                al_id,
215                ret_form,
216            );
217            return counter::dispatch(ctx, synth_form_key(form_key, selector, op), &forwarded);
218        }
219        excall_op::OP_7 => {
220            let forwarded = translated_call_args(
221                synth_form_key(form_key, selector, op),
222                tail,
223                params,
224                al_id,
225                ret_form,
226            );
227            return int_list::dispatch(ctx, synth_form_key(form_key, selector, op), &forwarded);
228        }
229        excall_op::OP_9 => {
230            let forwarded = translated_call_args(
231                synth_form_key(form_key, selector, op),
232                tail,
233                params,
234                al_id,
235                ret_form,
236            );
237            return frame_action::dispatch(ctx, synth_form_key(form_key, selector, op), &forwarded);
238        }
239        excall_op::OP_10 => {
240            let forwarded = translated_call_args(
241                synth_form_key(form_key, selector, op),
242                tail,
243                params,
244                al_id,
245                ret_form,
246            );
247            return frame_action_ch::dispatch(
248                ctx,
249                synth_form_key(form_key, selector, op),
250                &forwarded,
251            );
252        }
253        excall_op::OP_13 => {
254            let script_form = script_form_key(ctx);
255            if script_form == 0 {
256                ctx.push(Value::Int(0));
257                return Ok(true);
258            }
259            let forwarded = translated_call_args(script_form, tail, params, al_id, ret_form);
260            return script::dispatch(ctx, script_form, &forwarded);
261        }
262        _ => {
263            let _ = form_key;
264            ctx.push(Value::Int(0));
265        }
266    }
267
268    Ok(true)
269}