siglus_scene_vm/runtime/forms/
cgtable.rs1use anyhow::Result;
2
3use crate::runtime::forms::prop_access;
4use crate::runtime::{CommandContext, Value};
5
6fn ensure_cg_flags_size(ctx: &mut CommandContext, size: usize) {
7 if let Some(cnt) = ctx.tables.cgtable_flag_cnt {
8 let want = size.max(cnt);
9 if ctx.tables.cg_flags.len() < want {
10 ctx.tables.cg_flags.resize(want, 0);
11 }
12 } else if ctx.tables.cg_flags.len() < size {
13 ctx.tables.cg_flags.resize(size, 0);
14 }
15}
16
17fn get_cg_flag(ctx: &CommandContext, flag_no: i32) -> i64 {
18 if flag_no < 0 {
19 return 0;
20 }
21 ctx.tables
22 .cg_flags
23 .get(flag_no as usize)
24 .copied()
25 .unwrap_or(0) as i64
26}
27
28fn set_cg_flag(ctx: &mut CommandContext, flag_no: i32, value: i64) {
29 if flag_no < 0 {
30 return;
31 }
32 let idx = flag_no as usize;
33 ensure_cg_flags_size(ctx, idx + 1);
34 ctx.tables.cg_flags[idx] = value as i32;
36}
37
38fn cg_flag_is_looked(value: i64) -> bool {
39 value == 1
41}
42
43fn cgtable_look_cnt(ctx: &CommandContext) -> i64 {
44 let Some(table) = ctx.tables.cgtable.as_ref() else {
45 return 0;
46 };
47 table
48 .entries
49 .iter()
50 .filter(|e| e.flag_no >= 0 && cg_flag_is_looked(get_cg_flag(ctx, e.flag_no)))
51 .count() as i64
52}
53
54pub(crate) fn mark_look_by_name(ctx: &mut CommandContext, name: &str) {
55 if ctx.globals.cg_table_off {
56 return;
57 }
58 let flag_no = ctx
59 .tables
60 .cgtable
61 .as_ref()
62 .and_then(|t| t.get_sub_from_name(name))
63 .map(|e| e.flag_no);
64 if let Some(flag_no) = flag_no {
65 set_cg_flag(ctx, flag_no, 1);
66 }
67}
68
69pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
70 let parsed = prop_access::parse_element_chain_ctx(ctx, form_id, args);
71 let (chain_pos, chain) = match parsed {
72 Some((pos, ch @ [_, _, ..])) => (Some(pos), Some(ch)),
73 _ => (None, None),
74 };
75
76 if let Some(chain) = chain {
77 let op = chain[1];
78
79 if op == ctx.ids.cgtable_flag {
80 let rest = &chain[2..];
81 if rest.len() >= 2 && rest[0] == ctx.ids.elm_array {
82 let idx_i32 = rest[1];
83 if idx_i32 < 0 {
84 ctx.push(Value::Int(0));
85 return Ok(true);
86 }
87
88 let idx = idx_i32 as usize;
89 ensure_cg_flags_size(ctx, idx + 1);
90
91 let (al_id, _ret_form, rhs_value) =
92 crate::runtime::forms::prop_access::infer_assign_and_ret_ctx(
93 ctx,
94 chain_pos.unwrap_or(args.len()),
95 args,
96 );
97 let rhs = rhs_value.as_ref().and_then(Value::as_i64);
98
99 if al_id == Some(1) {
100 let v = rhs.unwrap_or(0);
101 ctx.tables.cg_flags[idx] = v as i32;
103 ctx.push(Value::Int(0));
104 } else {
105 ctx.push(Value::Int(ctx.tables.cg_flags[idx] as i64));
106 }
107 return Ok(true);
108 }
109
110 ctx.push(Value::Int(0));
111 return Ok(true);
112 }
113
114 let params = if let Some(pos) = chain_pos {
115 crate::runtime::forms::prop_access::script_args(args, pos)
116 } else {
117 &[]
118 };
119 let p_int = |i: usize| -> i64 { params.get(i).and_then(|v| v.as_i64()).unwrap_or(0) };
120 let p_str = |i: usize| -> &str { params.get(i).and_then(|v| v.as_str()).unwrap_or("") };
121
122 if ctx.ids.cgtable_set_disable != 0 && op == ctx.ids.cgtable_set_disable {
123 ctx.globals.cg_table_off = true;
124 ctx.push(Value::Int(0));
125 return Ok(true);
126 }
127 if ctx.ids.cgtable_set_enable != 0 && op == ctx.ids.cgtable_set_enable {
128 ctx.globals.cg_table_off = false;
129 ctx.push(Value::Int(0));
130 return Ok(true);
131 }
132 if ctx.ids.cgtable_set_all_flag != 0 && op == ctx.ids.cgtable_set_all_flag {
133 let v = p_int(0);
134 let flag_nos: Vec<i32> = ctx
135 .tables
136 .cgtable
137 .as_ref()
138 .map(|t| t.entries.iter().map(|e| e.flag_no).collect())
139 .unwrap_or_default();
140 for flag_no in flag_nos {
141 set_cg_flag(ctx, flag_no, v);
142 }
143 ctx.push(Value::Int(0));
144 return Ok(true);
145 }
146
147 if ctx.ids.cgtable_get_cg_cnt != 0 && op == ctx.ids.cgtable_get_cg_cnt {
148 let cnt = ctx
149 .tables
150 .cgtable
151 .as_ref()
152 .map(|t| t.get_cg_cnt())
153 .unwrap_or(0);
154 ctx.push(Value::Int(cnt as i64));
155 return Ok(true);
156 }
157
158 if ctx.ids.cgtable_get_look_cnt != 0 && op == ctx.ids.cgtable_get_look_cnt {
159 ctx.push(Value::Int(cgtable_look_cnt(ctx)));
160 return Ok(true);
161 }
162
163 if ctx.ids.cgtable_get_look_percent != 0 && op == ctx.ids.cgtable_get_look_percent {
164 let total = ctx
165 .tables
166 .cgtable
167 .as_ref()
168 .map(|t| t.get_cg_cnt())
169 .unwrap_or(0) as i64;
170 let looked = cgtable_look_cnt(ctx);
171 let percent = if total <= 0 || looked <= 0 {
172 0
173 } else {
174 let raw = looked * 100 / total;
175 if raw <= 0 { 1 } else { raw }
176 };
177 ctx.push(Value::Int(percent));
178 return Ok(true);
179 }
180
181 if ctx.ids.cgtable_get_flag_no_by_name != 0 && op == ctx.ids.cgtable_get_flag_no_by_name {
182 let name = p_str(0);
183 let res = ctx
184 .tables
185 .cgtable
186 .as_ref()
187 .and_then(|t| t.get_sub_from_name(name))
188 .map(|e| e.flag_no as i64)
189 .unwrap_or(-1);
190 ctx.push(Value::Int(res));
191 return Ok(true);
192 }
193
194 if ctx.ids.cgtable_get_look_by_name != 0 && op == ctx.ids.cgtable_get_look_by_name {
195 let name = p_str(0);
196 let res = if let Some(e) = ctx
197 .tables
198 .cgtable
199 .as_ref()
200 .and_then(|t| t.get_sub_from_name(name))
201 {
202 get_cg_flag(ctx, e.flag_no)
203 } else {
204 -1
205 };
206 ctx.push(Value::Int(res));
207 return Ok(true);
208 }
209
210 if ctx.ids.cgtable_set_look_by_name != 0 && op == ctx.ids.cgtable_set_look_by_name {
211 let name = p_str(0);
212 let v = p_int(1);
213 let flag_no = ctx
214 .tables
215 .cgtable
216 .as_ref()
217 .and_then(|t| t.get_sub_from_name(name))
218 .map(|e| e.flag_no);
219 if let Some(flag_no) = flag_no {
220 set_cg_flag(ctx, flag_no, v);
221 }
222 ctx.push(Value::Int(0));
223 return Ok(true);
224 }
225
226 if ctx.ids.cgtable_get_name_by_flag_no != 0 && op == ctx.ids.cgtable_get_name_by_flag_no {
227 let flag_no = p_int(0) as i32;
228 let name = ctx
229 .tables
230 .cgtable
231 .as_ref()
232 .and_then(|t| t.get_sub_from_flag_no(flag_no))
233 .map(|e| e.name.as_str())
234 .unwrap_or("");
235 ctx.push(Value::Str(name.to_string()));
236 return Ok(true);
237 }
238
239 prop_access::store_or_push_prop(ctx, form_id, op, chain_pos.unwrap(), args);
240 return Ok(true);
241 }
242
243 Ok(false)
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use crate::runtime::VmCallMeta;
250 use std::path::PathBuf;
251
252 fn flag_array_call(ctx: &mut CommandContext, index: i32, al_id: i64, ret_form: i64) {
253 ctx.vm_call = Some(VmCallMeta {
254 element: vec![
255 ctx.ids.form_global_cgtable as i32,
256 ctx.ids.cgtable_flag,
257 ctx.ids.elm_array,
258 index,
259 ],
260 al_id,
261 ret_form,
262 });
263 }
264
265 #[test]
266 fn flag_array_assignment_preserves_signed_i32_values() {
267 let mut ctx = CommandContext::new(PathBuf::from("."));
268 let form_id = ctx.ids.form_global_cgtable;
269
270 for value in [-7i64, 0, 1, 2, i32::MIN as i64, i32::MAX as i64] {
271 flag_array_call(&mut ctx, 2, 1, 0);
272 assert!(dispatch(&mut ctx, form_id, &[Value::Int(value)]).unwrap());
273 assert_eq!(ctx.tables.cg_flags[2], value as i32);
274
275 ctx.stack.clear();
276 flag_array_call(&mut ctx, 2, 0, 10);
277 assert!(dispatch(&mut ctx, form_id, &[]).unwrap());
278 assert_eq!(ctx.stack.pop().and_then(|v| v.as_i64()), Some(value));
279 }
280 }
281
282 #[test]
283 fn look_count_semantics_require_exactly_one() {
284 assert!(!cg_flag_is_looked(-7));
285 assert!(!cg_flag_is_looked(0));
286 assert!(cg_flag_is_looked(1));
287 assert!(!cg_flag_is_looked(2));
288 assert!(!cg_flag_is_looked(i32::MAX as i64));
289 }
290}