siglus_scene_vm/runtime/forms/
prop_access.rs1use 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}