Skip to main content

wowlab_engine_domain/rotation/lower/
condition.rs

1use wowlab_types::sim::{ArithOp, CompareOp, Condition, MinMaxOp, UnaryMathOp};
2
3use super::{
4    super::{
5        backend::RotationBackend,
6        buffer::{EvalKind, FieldDescriptor},
7        condition::resolve_field_read,
8    },
9    EvalValueAbstract, Lowerer,
10    eval_kind::lower_eval_kind,
11    infer_field_type, typed_load, typed_select,
12};
13
14// #t(fn: rust_recursive_fn) depth bounded by validate_rotation's MAX_CONDITION_DEPTH check (rejected pre-lowering)
15// #t(fn: rust_cyclomatic_complexity) flat exhaustive match over Condition variants
16pub(super) fn lower_condition<B>(
17    lowerer: &mut Lowerer<'_>,
18    backend: &mut B,
19    condition: &Condition,
20) -> EvalValueAbstract<B>
21where
22    B: RotationBackend,
23{
24    match condition {
25        Condition::Bool { value } => EvalValueAbstract::Bool(backend.const_bool(*value)),
26        Condition::Int { value } => EvalValueAbstract::Int(backend.const_i64(*value)),
27        Condition::Float { value } => EvalValueAbstract::Float(backend.const_f64(*value)),
28
29        Condition::Read { field } => {
30            let resolved = match resolve_field_read(field, lowerer.table, lowerer.resolver) {
31                Ok(resolved) => resolved,
32                Err(err) => {
33                    tracing::warn!(
34                        domain = %field.domain,
35                        name = %field.name,
36                        %err,
37                        "lower_condition: unresolved field read, evaluating as 0.0"
38                    );
39
40                    return EvalValueAbstract::Float(backend.const_f64(0.0));
41                }
42            };
43            let Some(offset) = lowerer.schema.field_offset(&resolved) else {
44                tracing::warn!(
45                    domain = %field.domain,
46                    name = %field.name,
47                    descriptor_id = resolved.descriptor_id.get(),
48                    "lower_condition: resolved field has no schema offset, evaluating as 0.0"
49                );
50
51                return EvalValueAbstract::Float(backend.const_f64(0.0));
52            };
53            let Some(descriptor): Option<&FieldDescriptor> =
54                lowerer.table.get(resolved.descriptor_id)
55            else {
56                tracing::warn!(
57                    domain = %field.domain,
58                    name = %field.name,
59                    descriptor_id = resolved.descriptor_id.get(),
60                    "lower_condition: descriptor id missing from table, evaluating as 0.0"
61                );
62
63                return EvalValueAbstract::Float(backend.const_f64(0.0));
64            };
65            let extras = match descriptor.eval_kind {
66                EvalKind::SpellUsable => lowerer.schema.spell_usable_offsets(offset),
67                _ => None,
68            };
69            let cooldown_bypass_aura_offset = match descriptor.eval_kind {
70                EvalKind::CooldownReady => lowerer.schema.cooldown_bypass_aura_offset(offset),
71                _ => None,
72            };
73
74            lower_eval_kind(
75                backend,
76                descriptor.eval_kind,
77                offset,
78                descriptor.field_type,
79                extras,
80                cooldown_bypass_aura_offset,
81            )
82        }
83
84        Condition::Var { name } => load_user_var(lowerer, backend, name),
85
86        Condition::Compare { op, left, right } => {
87            let left_value = lower_condition(lowerer, backend, left);
88            let right_value = lower_condition(lowerer, backend, right);
89
90            EvalValueAbstract::Bool(lower_compare(backend, *op, left_value, right_value))
91        }
92
93        Condition::And { operands } => {
94            let mut acc = backend.const_bool(true);
95
96            for operand in operands {
97                let value = lower_condition(lowerer, backend, operand).into_bool(backend);
98
99                acc = backend.and_b(acc, value);
100            }
101
102            EvalValueAbstract::Bool(acc)
103        }
104
105        Condition::Or { operands } => {
106            let mut acc = backend.const_bool(false);
107
108            for operand in operands {
109                let value = lower_condition(lowerer, backend, operand).into_bool(backend);
110
111                acc = backend.or_b(acc, value);
112            }
113
114            EvalValueAbstract::Bool(acc)
115        }
116
117        Condition::Not { operand } => {
118            let value = lower_condition(lowerer, backend, operand).into_bool(backend);
119
120            EvalValueAbstract::Bool(backend.not_b(value))
121        }
122
123        Condition::Arith { op, left, right } => {
124            let left_value = lower_condition(lowerer, backend, left).into_float(backend);
125            let right_value = lower_condition(lowerer, backend, right).into_float(backend);
126            let value = match op {
127                ArithOp::Add => backend.add_f(left_value, right_value),
128                ArithOp::Sub => backend.sub_f(left_value, right_value),
129                ArithOp::Mul => backend.mul_f(left_value, right_value),
130                ArithOp::Div => backend.safe_div_f(left_value, right_value),
131                ArithOp::Mod => backend.true_mod_f(left_value, right_value),
132                _ => unreachable!("rotation validation rejects unsupported arithmetic operators"),
133            };
134
135            EvalValueAbstract::Float(value)
136        }
137
138        Condition::UnaryMath { op, operand } => {
139            let value = lower_condition(lowerer, backend, operand).into_float(backend);
140            let value = match op {
141                UnaryMathOp::Floor => backend.floor_f(value),
142                UnaryMathOp::Ceil => backend.ceil_f(value),
143                UnaryMathOp::Abs => backend.abs_f(value),
144                _ => unreachable!("rotation validation rejects unsupported unary math operators"),
145            };
146
147            EvalValueAbstract::Float(value)
148        }
149
150        Condition::MinMax { op, left, right } => {
151            let left_value = lower_condition(lowerer, backend, left).into_float(backend);
152            let right_value = lower_condition(lowerer, backend, right).into_float(backend);
153            let value = match op {
154                MinMaxOp::Min => backend.min_f(left_value, right_value),
155                MinMaxOp::Max => backend.max_f(left_value, right_value),
156                _ => unreachable!("rotation validation rejects unsupported min/max operators"),
157            };
158
159            EvalValueAbstract::Float(value)
160        }
161
162        Condition::IfThenElse {
163            condition,
164            then,
165            otherwise,
166        } => lower_if_then_else(lowerer, backend, condition, then, otherwise),
167        _ => unreachable!("rotation validation rejects unsupported condition variants"),
168    }
169}
170
171fn lower_if_then_else<B>(
172    lowerer: &mut Lowerer<'_>,
173    backend: &mut B,
174    condition: &Condition,
175    then: &Condition,
176    otherwise: &Condition,
177) -> EvalValueAbstract<B>
178where
179    B: RotationBackend,
180{
181    let branch_condition = lower_condition(lowerer, backend, condition).into_bool(backend);
182    let target_ty = infer_field_type(then, lowerer.table);
183    let then_value = lower_condition(lowerer, backend, then);
184    let else_value = lower_condition(lowerer, backend, otherwise);
185
186    typed_select(backend, target_ty, branch_condition, then_value, else_value)
187}
188
189fn lower_compare<B>(
190    backend: &mut B,
191    op: CompareOp,
192    left: EvalValueAbstract<B>,
193    right: EvalValueAbstract<B>,
194) -> B::Bool
195where
196    B: RotationBackend,
197{
198    match (&left, &right) {
199        (EvalValueAbstract::Int(_), EvalValueAbstract::Int(_))
200        | (EvalValueAbstract::Bool(_), EvalValueAbstract::Bool(_)) => {
201            let left_int = left.into_int(backend);
202            let right_int = right.into_int(backend);
203
204            match op {
205                CompareOp::Gt => backend.cmp_gt_i(left_int, right_int),
206                CompareOp::Gte => backend.cmp_gte_i(left_int, right_int),
207                CompareOp::Lt => backend.cmp_lt_i(left_int, right_int),
208                CompareOp::Lte => backend.cmp_lte_i(left_int, right_int),
209                CompareOp::Eq => backend.cmp_eq_i(left_int, right_int),
210                CompareOp::Ne => backend.cmp_ne_i(left_int, right_int),
211                _ => unreachable!("rotation validation rejects unsupported comparison operators"),
212            }
213        }
214        _ => {
215            let left_float = left.into_float(backend);
216            let right_float = right.into_float(backend);
217
218            match op {
219                CompareOp::Gt => backend.cmp_gt_f(left_float, right_float),
220                CompareOp::Gte => backend.cmp_gte_f(left_float, right_float),
221                CompareOp::Lt => backend.cmp_lt_f(left_float, right_float),
222                CompareOp::Lte => backend.cmp_lte_f(left_float, right_float),
223                CompareOp::Eq => backend.cmp_eq_f(left_float, right_float),
224                CompareOp::Ne => backend.cmp_ne_f(left_float, right_float),
225                _ => unreachable!("rotation validation rejects unsupported comparison operators"),
226            }
227        }
228    }
229}
230
231pub(super) fn load_user_var<B>(lo: &mut Lowerer<'_>, b: &mut B, name: &str) -> EvalValueAbstract<B>
232where
233    B: RotationBackend,
234{
235    let (Some(offset), Some(ty)) = (
236        lo.schema.user_var_offset(name),
237        lo.schema.user_var_type(name),
238    ) else {
239        tracing::warn!(
240            var = %name,
241            "load_user_var: unknown user variable, evaluating as 0.0"
242        );
243        debug_assert!(false, "load_user_var: unknown user variable `{name}`");
244
245        return EvalValueAbstract::Float(b.const_f64(0.0));
246    };
247
248    typed_load(b, offset, ty)
249}