Skip to main content

siglus_scene_vm/runtime/forms/
prop_access.rs

1use crate::runtime::{CommandContext, Value};
2use std::collections::HashMap;
3
4pub const FM_VOID: i64 = 0;
5pub const FM_INT: i64 = 10;
6pub const FM_INTLIST: i64 = 11;
7pub const FM_STR: i64 = 20;
8pub const FM_STRLIST: i64 = 21;
9pub const FM_LABEL: i64 = 30;
10
11pub fn ret_form_is_string(ret_form: i64) -> bool {
12    ret_form == FM_STR
13}
14
15pub fn ret_form_is_string_opt(ret_form: Option<i64>) -> bool {
16    matches!(ret_form, Some(rf) if ret_form_is_string(rf))
17}
18
19pub fn parse_element_chain<'a>(form_id: u32, args: &'a [Value]) -> Option<(usize, &'a [i32])> {
20    for (i, v) in args.iter().enumerate() {
21        if let Value::Element(ch) = v {
22            if ch.first().copied() == Some(form_id as i32) {
23                return Some((i, ch.as_slice()));
24            }
25        }
26    }
27    None
28}
29
30pub fn parse_element_chain_ctx<'a>(
31    ctx: &'a CommandContext,
32    form_id: u32,
33    args: &'a [Value],
34) -> Option<(usize, &'a [i32])> {
35    let vm_call = ctx.vm_call.as_ref()?;
36    if vm_call.element.first().copied() == Some(form_id as i32) {
37        return Some((args.len(), vm_call.element.as_slice()));
38    }
39    None
40}
41
42pub fn parse_current_element_chain<'a>(
43    ctx: &'a CommandContext,
44    args: &'a [Value],
45) -> Option<(usize, &'a [i32])> {
46    let vm_call = ctx.vm_call.as_ref()?;
47    Some((args.len(), vm_call.element.as_slice()))
48}
49
50pub fn script_args<'a>(args: &'a [Value], chain_pos: usize) -> &'a [Value] {
51    if chain_pos == args.len() {
52        args
53    } else if chain_pos > 1 {
54        &args[1..chain_pos]
55    } else {
56        &[]
57    }
58}
59
60pub fn current_op_from_chain(chain: &[i32]) -> Option<i32> {
61    chain.get(1).copied()
62}
63
64pub fn current_op_from_ctx_or_args(ctx: &CommandContext, _args: &[Value]) -> Option<i32> {
65    let vm_call = ctx.vm_call.as_ref()?;
66    current_op_from_chain(&vm_call.element)
67}
68
69pub fn params_without_op<'a>(_ctx: &CommandContext, args: &'a [Value]) -> &'a [Value] {
70    args
71}
72
73pub fn current_vm_meta(ctx: &CommandContext) -> (Option<i64>, Option<i64>) {
74    ctx.vm_call
75        .as_ref()
76        .map(|m| (Some(m.al_id), Some(m.ret_form)))
77        .unwrap_or((None, None))
78}
79
80pub fn infer_assign_and_ret_ctx(
81    ctx: &CommandContext,
82    _chain_pos: usize,
83    args: &[Value],
84) -> (Option<i64>, Option<i64>, Option<Value>) {
85    let (meta_al, meta_ret) = current_vm_meta(ctx);
86    let rhs = if meta_al == Some(1) {
87        args.first().cloned()
88    } else {
89        None
90    };
91    (meta_al, meta_ret, rhs)
92}
93
94fn int_map<'a>(ctx: &'a mut CommandContext, form_id: u32) -> &'a mut HashMap<i32, i64> {
95    ctx.globals
96        .int_props
97        .entry(form_id)
98        .or_insert_with(HashMap::new)
99}
100
101fn str_map<'a>(ctx: &'a mut CommandContext, form_id: u32) -> &'a mut HashMap<i32, String> {
102    ctx.globals
103        .str_props
104        .entry(form_id)
105        .or_insert_with(HashMap::new)
106}
107
108fn int_list<'a>(ctx: &'a mut CommandContext, form_id: u32) -> &'a mut Vec<i64> {
109    ctx.globals
110        .int_lists
111        .entry(form_id)
112        .or_insert_with(|| vec![0; 32])
113}
114
115fn str_list<'a>(ctx: &'a mut CommandContext, form_id: u32) -> &'a mut Vec<String> {
116    ctx.globals
117        .str_lists
118        .entry(form_id)
119        .or_insert_with(Vec::new)
120}
121
122fn ensure_int_len(v: &mut Vec<i64>, idx: usize) {
123    if v.len() <= idx {
124        v.resize(idx + 1, 0);
125    }
126}
127
128fn ensure_str_len(v: &mut Vec<String>, idx: usize) {
129    if v.len() <= idx {
130        v.resize_with(idx + 1, String::new);
131    }
132}
133
134fn prefers_string(ret_form: Option<i64>, rhs: Option<&Value>) -> bool {
135    ret_form_is_string_opt(ret_form) || matches!(rhs, Some(Value::Str(_)))
136}
137
138pub fn chain_key(parts: &[i32]) -> i32 {
139    let mut h: u32 = 0x811C_9DC5;
140    for &p in parts {
141        h ^= p as u32;
142        h = h.wrapping_mul(0x0100_0193);
143    }
144    h as i32
145}
146
147pub fn push_stored_or_default(
148    ctx: &mut CommandContext,
149    form_id: u32,
150    op: i32,
151    ret_form: Option<i64>,
152) {
153    if ret_form_is_string_opt(ret_form) {
154        let s = ctx
155            .globals
156            .str_props
157            .get(&form_id)
158            .and_then(|m| m.get(&op))
159            .cloned()
160            .unwrap_or_default();
161        ctx.push(Value::Str(s));
162        return;
163    }
164
165    if let Some(s) = ctx
166        .globals
167        .str_props
168        .get(&form_id)
169        .and_then(|m| m.get(&op))
170        .cloned()
171    {
172        ctx.push(Value::Str(s));
173        return;
174    }
175
176    let v = ctx
177        .globals
178        .int_props
179        .get(&form_id)
180        .and_then(|m| m.get(&op).copied())
181        .unwrap_or(0);
182    ctx.push(Value::Int(v));
183}
184
185pub fn store_or_push_prop(
186    ctx: &mut CommandContext,
187    form_id: u32,
188    prop_key: i32,
189    chain_pos: usize,
190    args: &[Value],
191) {
192    let (al_id, ret_form, rhs) = infer_assign_and_ret_ctx(ctx, chain_pos, args);
193    if al_id == Some(1) {
194        if let Some(v) = rhs {
195            match v {
196                Value::Str(s) => {
197                    str_map(ctx, form_id).insert(prop_key, s);
198                }
199                Value::Int(n) => {
200                    int_map(ctx, form_id).insert(prop_key, n);
201                }
202                _ => {}
203            }
204        }
205        ctx.push(Value::Int(0));
206        return;
207    }
208
209    push_stored_or_default(ctx, form_id, prop_key, ret_form);
210}
211
212pub fn store_or_push_direct_prop(
213    ctx: &mut CommandContext,
214    form_id: u32,
215    prop_key: i32,
216    args: &[Value],
217    value_idx: usize,
218) {
219    if let Some(v) = args.get(value_idx).cloned() {
220        match v {
221            Value::Str(s) => {
222                str_map(ctx, form_id).insert(prop_key, s);
223            }
224            Value::Int(n) => {
225                int_map(ctx, form_id).insert(prop_key, n);
226            }
227            _ => {}
228        }
229        ctx.push(Value::Int(0));
230        return;
231    }
232
233    push_stored_or_default(ctx, form_id, prop_key, None);
234}
235
236pub fn store_or_push_indexed(
237    ctx: &mut CommandContext,
238    form_id: u32,
239    index: usize,
240    chain_pos: usize,
241    args: &[Value],
242) {
243    let (al_id, ret_form, rhs) = infer_assign_and_ret_ctx(ctx, chain_pos, args);
244    if al_id == Some(1) {
245        match rhs {
246            Some(Value::Str(s)) => {
247                let list = str_list(ctx, form_id);
248                ensure_str_len(list, index);
249                list[index] = s;
250            }
251            Some(Value::Int(n)) => {
252                let list = int_list(ctx, form_id);
253                ensure_int_len(list, index);
254                list[index] = n;
255            }
256            _ => {}
257        }
258        ctx.push(Value::Int(0));
259        return;
260    }
261
262    if prefers_string(ret_form, rhs.as_ref()) {
263        let value = {
264            let list = str_list(ctx, form_id);
265            ensure_str_len(list, index);
266            list[index].clone()
267        };
268        ctx.push(Value::Str(value));
269    } else {
270        let value = {
271            let list = int_list(ctx, form_id);
272            ensure_int_len(list, index);
273            list[index]
274        };
275        ctx.push(Value::Int(value));
276    }
277}
278
279pub fn store_or_push_indexed_direct(
280    ctx: &mut CommandContext,
281    form_id: u32,
282    index: usize,
283    args: &[Value],
284    value_idx: usize,
285) {
286    if let Some(v) = args.get(value_idx).cloned() {
287        match v {
288            Value::Str(s) => {
289                let list = str_list(ctx, form_id);
290                ensure_str_len(list, index);
291                list[index] = s;
292            }
293            Value::Int(n) => {
294                let list = int_list(ctx, form_id);
295                ensure_int_len(list, index);
296                list[index] = n;
297            }
298            _ => {}
299        }
300        ctx.push(Value::Int(0));
301        return;
302    }
303
304    if let Some(s) = ctx
305        .globals
306        .str_lists
307        .get(&form_id)
308        .and_then(|v| v.get(index))
309        .cloned()
310    {
311        ctx.push(Value::Str(s));
312        return;
313    }
314
315    let v = ctx
316        .globals
317        .int_lists
318        .get(&form_id)
319        .and_then(|v| v.get(index).copied())
320        .unwrap_or(0);
321    ctx.push(Value::Int(v));
322}
323
324pub fn dispatch_stateful_form(ctx: &mut CommandContext, form_id: u32, args: &[Value]) {
325    if let Some((chain_pos, chain)) = parse_element_chain_ctx(ctx, form_id, args) {
326        if chain.len() >= 3 && chain[1] == ctx.ids.elm_array {
327            let index = chain[2].max(0) as usize;
328            if chain.len() == 3 {
329                store_or_push_indexed(ctx, form_id, index, chain_pos, args);
330            } else {
331                let key = chain_key(&chain[1..]);
332                store_or_push_prop(ctx, form_id, key, chain_pos, args);
333            }
334            return;
335        }
336
337        if chain.len() >= 2 {
338            let key = if chain.len() == 2 {
339                chain[1]
340            } else {
341                chain_key(&chain[1..])
342            };
343            store_or_push_prop(ctx, form_id, key, chain_pos, args);
344            return;
345        }
346    }
347
348    if let Some(op) = args.get(0).and_then(|v| v.as_i64()) {
349        if op == ctx.ids.elm_array as i64 {
350            let index = args.get(1).and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
351            store_or_push_indexed_direct(ctx, form_id, index, args, 2);
352        } else {
353            store_or_push_direct_prop(ctx, form_id, op as i32, args, 1);
354        }
355        return;
356    }
357
358    ctx.push(Value::Int(0));
359}
360
361pub fn dispatch_generic_form(ctx: &mut CommandContext, form_id: u32, args: &[Value]) {
362    dispatch_stateful_form(ctx, form_id, args);
363}
364
365pub fn assign_to_chain(ctx: &mut CommandContext, chain: &[i32], value: Value) {
366    if chain.is_empty() {
367        return;
368    }
369    let form_id = chain[0].max(0) as u32;
370    if chain.len() >= 3 && chain[1] == ctx.ids.elm_array {
371        let index = chain[2].max(0) as usize;
372        match value {
373            Value::Str(s) => {
374                let list = str_list(ctx, form_id);
375                ensure_str_len(list, index);
376                list[index] = s;
377            }
378            Value::Int(n) => {
379                let list = int_list(ctx, form_id);
380                ensure_int_len(list, index);
381                list[index] = n;
382            }
383            _ => {}
384        }
385        return;
386    }
387
388    if chain.len() >= 2 {
389        let key = if chain.len() == 2 {
390            chain[1]
391        } else {
392            chain_key(&chain[1..])
393        };
394        match value {
395            Value::Str(s) => {
396                str_map(ctx, form_id).insert(key, s);
397            }
398            Value::Int(n) => {
399                int_map(ctx, form_id).insert(key, n);
400            }
401            _ => {}
402        }
403    }
404}