Skip to main content

siglus_scene_vm/runtime/forms/
database.rs

1use anyhow::Result;
2
3use crate::runtime::forms::prop_access;
4use crate::runtime::{CommandContext, Value};
5
6fn composite_db_op_key(db_no: i32, op: i32) -> i32 {
7    ((db_no & 0x7fff) << 16) ^ (op & 0xffff)
8}
9
10fn ref_chain_from_value(v: &Value) -> Option<(Option<i32>, &[i32])> {
11    match v {
12        Value::Element(chain) => Some((None, chain.as_slice())),
13        Value::NamedArg { id, value } => match value.as_ref() {
14            Value::Element(chain) => Some((Some(*id), chain.as_slice())),
15            _ => None,
16        },
17        _ => None,
18    }
19}
20
21fn database_get_data(
22    ctx: &mut CommandContext,
23    db: &siglus_assets::dbs::DbsDatabase,
24    item_call_no: i32,
25    refs: &[Value],
26) {
27    let mut positional_column_no = 0i32;
28    for arg in refs {
29        let Some((explicit_column_no, chain)) = ref_chain_from_value(arg) else {
30            positional_column_no += 1;
31            continue;
32        };
33        let column_call_no = explicit_column_no.unwrap_or(positional_column_no);
34        positional_column_no += 1;
35
36        match db.check_column_no(column_call_no) {
37            1 => {
38                if let Ok(Some(v)) = db.get_data_int(item_call_no, column_call_no) {
39                    prop_access::assign_to_chain(ctx, chain, Value::Int(v as i64));
40                } else {
41                    prop_access::assign_to_chain(ctx, chain, Value::Int(0));
42                }
43            }
44            2 => {
45                if let Ok(Some(v)) = db.get_data_str(item_call_no, column_call_no) {
46                    prop_access::assign_to_chain(ctx, chain, Value::Str(v));
47                } else {
48                    prop_access::assign_to_chain(ctx, chain, Value::Str(String::new()));
49                }
50            }
51            _ => {
52                prop_access::assign_to_chain(ctx, chain, Value::Int(0));
53            }
54        }
55    }
56}
57
58fn push_unloaded_database_result(ctx: &mut CommandContext, db_op: i32) {
59    if ctx.ids.database_get_str != 0 && db_op == ctx.ids.database_get_str {
60        ctx.push(Value::Str(String::new()));
61        return;
62    }
63
64    if ctx.ids.database_get_data != 0 && db_op == ctx.ids.database_get_data {
65        // GET_DATA is VOID. With no loaded database there is nothing to copy
66        // into the supplied refs, and no command return value is produced.
67        return;
68    }
69
70    if (ctx.ids.database_find_num != 0 && db_op == ctx.ids.database_find_num)
71        || (ctx.ids.database_find_str != 0 && db_op == ctx.ids.database_find_str)
72        || (ctx.ids.database_find_str_real != 0 && db_op == ctx.ids.database_find_str_real)
73    {
74        ctx.push(Value::Int(-1));
75        return;
76    }
77
78    // GET_NUM / CHECK_ITEM / CHECK_COLUMN return zero for an unloaded DB.
79    ctx.push(Value::Int(0));
80}
81
82pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
83    let parsed = prop_access::parse_element_chain_ctx(ctx, form_id, args);
84    let (chain_pos, chain) = match parsed {
85        Some((pos, ch @ [_, _, ..])) => (Some(pos), Some(ch)),
86        _ => (None, None),
87    };
88
89    if let Some(chain) = chain {
90        let op = chain[1];
91        let params = if let Some(pos) = chain_pos {
92            prop_access::script_args(args, pos)
93        } else {
94            &[]
95        };
96        let p_i32 =
97            |i: usize| -> i32 { params.get(i).and_then(|v| v.as_i64()).unwrap_or(0) as i32 };
98        let p_str = |i: usize| -> &str { params.get(i).and_then(|v| v.as_str()).unwrap_or("") };
99
100        if ctx.ids.database_list_get_size != 0 && op == ctx.ids.database_list_get_size {
101            if ctx.globals.database_off {
102                ctx.push(Value::Int(0));
103            } else {
104                ctx.push(Value::Int(ctx.tables.databases.len() as i64));
105            }
106            return Ok(true);
107        }
108
109        if op == ctx.ids.elm_array {
110            if chain.len() < 4 {
111                ctx.push(Value::Int(0));
112                return Ok(true);
113            }
114
115            let db_no = chain[2];
116            let db_op = chain[3];
117            if db_no < 0 {
118                push_unloaded_database_result(ctx, db_op);
119                return Ok(true);
120            }
121
122            if ctx.globals.database_off {
123                push_unloaded_database_result(ctx, db_op);
124                return Ok(true);
125            }
126
127            let db = match ctx
128                .tables
129                .databases
130                .get(db_no as usize)
131                .and_then(|db| db.clone())
132            {
133                Some(db) => db,
134                None => {
135                    push_unloaded_database_result(ctx, db_op);
136                    return Ok(true);
137                }
138            };
139
140            if db_op == ctx.ids.database_get_num {
141                let item_call_no = p_i32(0);
142                let col_call_no = p_i32(1);
143                let mut out: i64 = 0;
144                if let Ok(Some(v)) = db.get_data_int(item_call_no, col_call_no) {
145                    out = v as i64;
146                }
147                ctx.push(Value::Int(out));
148                return Ok(true);
149            }
150
151            if ctx.ids.database_get_str != 0 && db_op == ctx.ids.database_get_str {
152                let item_call_no = p_i32(0);
153                let col_call_no = p_i32(1);
154                let mut out = String::new();
155                if let Ok(Some(v)) = db.get_data_str(item_call_no, col_call_no) {
156                    out = v;
157                }
158                ctx.push(Value::Str(out));
159                return Ok(true);
160            }
161
162            if ctx.ids.database_get_data != 0 && db_op == ctx.ids.database_get_data {
163                let item_call_no = p_i32(0);
164                let refs = if params.len() > 1 { &params[1..] } else { &[] };
165                database_get_data(ctx, &db, item_call_no, refs);
166                return Ok(true);
167            }
168
169            if ctx.ids.database_check_item != 0 && db_op == ctx.ids.database_check_item {
170                let item_call_no = p_i32(0);
171                ctx.push(Value::Int(db.check_item_no(item_call_no) as i64));
172                return Ok(true);
173            }
174
175            if ctx.ids.database_check_column != 0 && db_op == ctx.ids.database_check_column {
176                let col_call_no = p_i32(0);
177                ctx.push(Value::Int(db.check_column_no(col_call_no) as i64));
178                return Ok(true);
179            }
180
181            if ctx.ids.database_find_num != 0 && db_op == ctx.ids.database_find_num {
182                let col_call_no = p_i32(0);
183                let value = p_i32(1);
184                let out = db.find_num(col_call_no, value).unwrap_or(-1);
185                ctx.push(Value::Int(out as i64));
186                return Ok(true);
187            }
188
189            if ctx.ids.database_find_str != 0 && db_op == ctx.ids.database_find_str {
190                let col_call_no = p_i32(0);
191                let needle = p_str(1);
192                let out = db.find_str(col_call_no, needle).unwrap_or(-1);
193                ctx.push(Value::Int(out as i64));
194                return Ok(true);
195            }
196
197            if ctx.ids.database_find_str_real != 0 && db_op == ctx.ids.database_find_str_real {
198                let col_call_no = p_i32(0);
199                let needle = p_str(1);
200                let out = db.find_str_real(col_call_no, needle).unwrap_or(-1);
201                ctx.push(Value::Int(out as i64));
202                return Ok(true);
203            }
204
205            let prop_key = composite_db_op_key(db_no, db_op);
206            prop_access::store_or_push_prop(ctx, form_id, prop_key, chain_pos.unwrap(), args);
207            return Ok(true);
208        }
209
210        prop_access::store_or_push_prop(ctx, form_id, op, chain_pos.unwrap(), args);
211        return Ok(true);
212    }
213
214    Ok(false)
215}