Skip to main content

siglus_scene_vm/runtime/forms/
prop_access.rs

1use crate::runtime::globals::HashMap;
2use crate::runtime::{CommandContext, Value};
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(form_id: u32, args: &[Value]) -> Option<(usize, &[i32])> {
20    for (i, v) in args.iter().enumerate() {
21        if let Value::Element(ch) = v
22            && ch.first().copied() == Some(form_id as i32)
23        {
24            return Some((i, ch.as_slice()));
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(args: &[Value], chain_pos: usize) -> &[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(ctx: &mut CommandContext, form_id: u32) -> &mut HashMap<i32, i64> {
95    ctx.globals.int_props.entry(form_id).or_default()
96}
97
98fn str_map(ctx: &mut CommandContext, form_id: u32) -> &mut HashMap<i32, String> {
99    ctx.globals.str_props.entry(form_id).or_default()
100}
101
102fn int_list(ctx: &mut CommandContext, form_id: u32) -> &mut Vec<i64> {
103    let fixed_len = fixed_int_list_len(ctx, form_id);
104    let initial_len = fixed_len.unwrap_or(0);
105    let list = ctx
106        .globals
107        .int_lists
108        .entry(form_id)
109        .or_insert_with(|| vec![0; initial_len]);
110    if let Some(fixed_len) = fixed_len
111        && list.len() < fixed_len
112    {
113        list.resize(fixed_len, 0);
114    }
115    list
116}
117
118fn str_list(ctx: &mut CommandContext, form_id: u32) -> &mut Vec<String> {
119    let fixed_len = fixed_str_list_len(ctx, form_id);
120    let initial_len = fixed_len.unwrap_or(0);
121    let list = ctx
122        .globals
123        .str_lists
124        .entry(form_id)
125        .or_insert_with(|| vec![String::new(); initial_len]);
126    if let Some(fixed_len) = fixed_len
127        && list.len() < fixed_len
128    {
129        list.resize_with(fixed_len, String::new);
130    }
131    list
132}
133
134fn fixed_int_list_len(ctx: &CommandContext, form_id: u32) -> Option<usize> {
135    super::int_list::fixed_default_len(ctx, form_id)
136}
137
138fn fixed_str_list_len(ctx: &CommandContext, form_id: u32) -> Option<usize> {
139    super::str_list::fixed_default_len(ctx, form_id)
140}
141
142fn ensure_int_index(ctx: &mut CommandContext, form_id: u32, index: usize) -> bool {
143    super::int_list::ensure_fixed_direct_index(ctx, form_id, index);
144    if let Some(fixed_len) = fixed_int_list_len(ctx, form_id) {
145        let list = int_list(ctx, form_id);
146        if index < list.len() {
147            return true;
148        }
149        log::error!(
150            "INTLIST reference index out of range: form_id={} index={} size={} configured_size={}",
151            form_id,
152            index,
153            list.len(),
154            fixed_len
155        );
156        return false;
157    }
158
159    let list = int_list(ctx, form_id);
160    if index < list.len() {
161        return true;
162    }
163    log::error!(
164        "INTLIST reference index out of range: form_id={} index={} size={}",
165        form_id,
166        index,
167        list.len()
168    );
169    false
170}
171
172fn ensure_str_index(ctx: &mut CommandContext, form_id: u32, index: usize) -> bool {
173    super::str_list::ensure_fixed_direct_index(ctx, form_id, index);
174    if let Some(fixed_len) = fixed_str_list_len(ctx, form_id) {
175        let list = str_list(ctx, form_id);
176        if index < list.len() {
177            return true;
178        }
179        log::error!(
180            "STRLIST reference index out of range: form_id={} index={} size={} configured_size={}",
181            form_id,
182            index,
183            list.len(),
184            fixed_len
185        );
186        return false;
187    }
188
189    let list = str_list(ctx, form_id);
190    if index < list.len() {
191        return true;
192    }
193    log::error!(
194        "STRLIST reference index out of range: form_id={} index={} size={}",
195        form_id,
196        index,
197        list.len()
198    );
199    false
200}
201
202fn prefers_string(ret_form: Option<i64>, rhs: Option<&Value>) -> bool {
203    ret_form_is_string_opt(ret_form) || matches!(rhs, Some(Value::Str(_)))
204}
205
206pub fn chain_key(parts: &[i32]) -> i32 {
207    let mut h: u32 = 0x811C_9DC5;
208    for &p in parts {
209        h ^= p as u32;
210        h = h.wrapping_mul(0x0100_0193);
211    }
212    h as i32
213}
214
215pub fn push_stored_or_default(
216    ctx: &mut CommandContext,
217    form_id: u32,
218    op: i32,
219    ret_form: Option<i64>,
220) {
221    if ret_form_is_string_opt(ret_form) {
222        let s = ctx
223            .globals
224            .str_props
225            .get(&form_id)
226            .and_then(|m| m.get(&op))
227            .cloned()
228            .unwrap_or_default();
229        ctx.push(Value::Str(s));
230        return;
231    }
232
233    if let Some(s) = ctx
234        .globals
235        .str_props
236        .get(&form_id)
237        .and_then(|m| m.get(&op))
238        .cloned()
239    {
240        ctx.push(Value::Str(s));
241        return;
242    }
243
244    let v = ctx
245        .globals
246        .int_props
247        .get(&form_id)
248        .and_then(|m| m.get(&op).copied())
249        .unwrap_or(0);
250    ctx.push(Value::Int(v));
251}
252
253pub fn store_or_push_prop(
254    ctx: &mut CommandContext,
255    form_id: u32,
256    prop_key: i32,
257    chain_pos: usize,
258    args: &[Value],
259) {
260    let (al_id, ret_form, rhs) = infer_assign_and_ret_ctx(ctx, chain_pos, args);
261    if al_id == Some(1) {
262        if let Some(v) = rhs {
263            match v {
264                Value::Str(s) => {
265                    str_map(ctx, form_id).insert(prop_key, s);
266                }
267                Value::Int(n) => {
268                    int_map(ctx, form_id).insert(prop_key, n);
269                }
270                _ => {}
271            }
272        }
273        ctx.push(Value::Int(0));
274        return;
275    }
276
277    push_stored_or_default(ctx, form_id, prop_key, ret_form);
278}
279
280pub fn store_or_push_direct_prop(
281    ctx: &mut CommandContext,
282    form_id: u32,
283    prop_key: i32,
284    args: &[Value],
285    value_idx: usize,
286) {
287    if let Some(v) = args.get(value_idx).cloned() {
288        match v {
289            Value::Str(s) => {
290                str_map(ctx, form_id).insert(prop_key, s);
291            }
292            Value::Int(n) => {
293                int_map(ctx, form_id).insert(prop_key, n);
294            }
295            _ => {}
296        }
297        ctx.push(Value::Int(0));
298        return;
299    }
300
301    push_stored_or_default(ctx, form_id, prop_key, None);
302}
303
304pub fn store_or_push_indexed(
305    ctx: &mut CommandContext,
306    form_id: u32,
307    index: usize,
308    chain_pos: usize,
309    args: &[Value],
310) {
311    let (al_id, ret_form, rhs) = infer_assign_and_ret_ctx(ctx, chain_pos, args);
312    if al_id == Some(1) {
313        match rhs {
314            Some(Value::Str(s)) => {
315                if ensure_str_index(ctx, form_id, index) {
316                    let list = str_list(ctx, form_id);
317                    if let Some(dst) = list.get_mut(index) {
318                        *dst = s;
319                    }
320                }
321            }
322            Some(Value::Int(n)) if ensure_int_index(ctx, form_id, index) => {
323                let list = int_list(ctx, form_id);
324                if let Some(dst) = list.get_mut(index) {
325                    *dst = n;
326                }
327            }
328            _ => {}
329        }
330        ctx.push(Value::Int(0));
331        return;
332    }
333
334    if prefers_string(ret_form, rhs.as_ref()) {
335        if !ensure_str_index(ctx, form_id, index) {
336            ctx.push(Value::Str(String::new()));
337            return;
338        }
339        let value = {
340            let list = str_list(ctx, form_id);
341            list.get(index).cloned().unwrap_or_default()
342        };
343        ctx.push(Value::Str(value));
344    } else {
345        if !ensure_int_index(ctx, form_id, index) {
346            ctx.push(Value::Int(0));
347            return;
348        }
349        let value = {
350            let list = int_list(ctx, form_id);
351            list.get(index).copied().unwrap_or(0)
352        };
353        ctx.push(Value::Int(value));
354    }
355}
356
357pub fn store_or_push_indexed_direct(
358    ctx: &mut CommandContext,
359    form_id: u32,
360    index: usize,
361    args: &[Value],
362    value_idx: usize,
363) {
364    if let Some(v) = args.get(value_idx).cloned() {
365        match v {
366            Value::Str(s) => {
367                if ensure_str_index(ctx, form_id, index) {
368                    let list = str_list(ctx, form_id);
369                    if let Some(dst) = list.get_mut(index) {
370                        *dst = s;
371                    }
372                }
373            }
374            Value::Int(n) if ensure_int_index(ctx, form_id, index) => {
375                let list = int_list(ctx, form_id);
376                if let Some(dst) = list.get_mut(index) {
377                    *dst = n;
378                }
379            }
380            _ => {}
381        }
382        ctx.push(Value::Int(0));
383        return;
384    }
385
386    if fixed_str_list_len(ctx, form_id).is_some() {
387        if !ensure_str_index(ctx, form_id, index) {
388            ctx.push(Value::Str(String::new()));
389            return;
390        }
391        let value = ctx
392            .globals
393            .str_lists
394            .get(&form_id)
395            .and_then(|v| v.get(index))
396            .cloned()
397            .unwrap_or_default();
398        ctx.push(Value::Str(value));
399        return;
400    }
401
402    if !ensure_int_index(ctx, form_id, index) {
403        ctx.push(Value::Int(0));
404        return;
405    }
406    let value = ctx
407        .globals
408        .int_lists
409        .get(&form_id)
410        .and_then(|v| v.get(index).copied())
411        .unwrap_or(0);
412    ctx.push(Value::Int(value));
413}
414
415pub fn dispatch_stateful_form(ctx: &mut CommandContext, form_id: u32, args: &[Value]) {
416    if let Some((chain_pos, chain)) = parse_element_chain_ctx(ctx, form_id, args) {
417        if chain.len() >= 3 && chain[1] == ctx.ids.elm_array {
418            let Ok(index) = usize::try_from(chain[2]) else {
419                log::error!(
420                    "list index is negative: form_id={} index={}",
421                    form_id,
422                    chain[2]
423                );
424                let (_, ret_form) = current_vm_meta(ctx);
425                if ret_form_is_string_opt(ret_form) {
426                    ctx.push(Value::Str(String::new()));
427                } else {
428                    ctx.push(Value::Int(0));
429                }
430                return;
431            };
432            if chain.len() == 3 {
433                store_or_push_indexed(ctx, form_id, index, chain_pos, args);
434            } else {
435                let key = chain_key(&chain[1..]);
436                store_or_push_prop(ctx, form_id, key, chain_pos, args);
437            }
438            return;
439        }
440
441        if chain.len() >= 2 {
442            let key = if chain.len() == 2 {
443                chain[1]
444            } else {
445                chain_key(&chain[1..])
446            };
447            store_or_push_prop(ctx, form_id, key, chain_pos, args);
448            return;
449        }
450    }
451
452    if let Some(op) = args.first().and_then(|v| v.as_i64()) {
453        if op == ctx.ids.elm_array as i64 {
454            let raw_index = args.get(1).and_then(|v| v.as_i64()).unwrap_or(0);
455            let Ok(index) = usize::try_from(raw_index) else {
456                log::error!(
457                    "list index is negative: form_id={} index={}",
458                    form_id,
459                    raw_index
460                );
461                ctx.push(Value::Int(0));
462                return;
463            };
464            store_or_push_indexed_direct(ctx, form_id, index, args, 2);
465        } else {
466            store_or_push_direct_prop(ctx, form_id, op as i32, args, 1);
467        }
468        return;
469    }
470
471    ctx.push(Value::Int(0));
472}
473
474pub fn dispatch_generic_form(ctx: &mut CommandContext, form_id: u32, args: &[Value]) {
475    dispatch_stateful_form(ctx, form_id, args);
476}
477
478pub fn assign_to_chain(ctx: &mut CommandContext, chain: &[i32], value: Value) {
479    if chain.is_empty() {
480        return;
481    }
482    let form_id = chain[0].max(0) as u32;
483    if chain.len() >= 3 && chain[1] == ctx.ids.elm_array {
484        let raw_index = chain[2];
485        let Ok(index) = usize::try_from(raw_index) else {
486            log::error!(
487                "list reference index is negative: form_id={} index={}",
488                form_id,
489                raw_index
490            );
491            return;
492        };
493        match value {
494            Value::Str(s) => {
495                if !ensure_str_index(ctx, form_id, index) {
496                    return;
497                }
498                let list = str_list(ctx, form_id);
499                if let Some(dst) = list.get_mut(index) {
500                    *dst = s;
501                }
502            }
503            Value::Int(n) => {
504                if !ensure_int_index(ctx, form_id, index) {
505                    return;
506                }
507                let list = int_list(ctx, form_id);
508                if let Some(dst) = list.get_mut(index) {
509                    *dst = n;
510                }
511            }
512            _ => {}
513        }
514        return;
515    }
516
517    if chain.len() >= 2 {
518        let key = if chain.len() == 2 {
519            chain[1]
520        } else {
521            chain_key(&chain[1..])
522        };
523        match value {
524            Value::Str(s) => {
525                str_map(ctx, form_id).insert(key, s);
526            }
527            Value::Int(n) => {
528                int_map(ctx, form_id).insert(key, n);
529            }
530            _ => {}
531        }
532    }
533}