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::{EvalInputs, 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 = resolve_field_read(field, lowerer.table, lowerer.resolver)
31                .expect("PreparedRotation resolves every field before lowering");
32            let offset = lowerer
33                .schema
34                .field_offset(&resolved)
35                .expect("PreparedRotation assigns every resolved field a coordinate");
36            let descriptor: &FieldDescriptor = lowerer
37                .table
38                .get(resolved.descriptor_id)
39                .expect("PreparedRotation validates every descriptor identity");
40            let extras = match descriptor.eval_kind {
41                EvalKind::SpellUsable => lowerer.schema.spell_usable_offsets(offset),
42                _ => None,
43            };
44            let cooldown_bypass_aura_offset = match descriptor.eval_kind {
45                EvalKind::CooldownReady => lowerer.schema.cooldown_bypass_aura_offset(offset),
46                _ => None,
47            };
48
49            lower_eval_kind(
50                backend,
51                descriptor.eval_kind,
52                offset,
53                descriptor.field_type,
54                EvalInputs {
55                    spell_usable: extras,
56                    cooldown_bypass_aura_offset,
57                    spell_usability_prevalidated: lowerer.spell_usability_prevalidated,
58                },
59            )
60        }
61
62        Condition::Var { name } => load_user_var(lowerer, backend, name),
63
64        Condition::Compare { op, left, right } => {
65            let left_value = lower_condition(lowerer, backend, left);
66            let right_value = lower_condition(lowerer, backend, right);
67
68            EvalValueAbstract::Bool(lower_compare(backend, *op, left_value, right_value))
69        }
70
71        Condition::And { operands } => {
72            #[cfg(feature = "rotation-optimizations")]
73            return lower_and_without_neutral_literals(lowerer, backend, operands);
74
75            #[cfg(not(feature = "rotation-optimizations"))]
76            {
77                let Some((first, rest)) = operands.split_first() else {
78                    return EvalValueAbstract::Bool(backend.const_bool(true));
79                };
80                let first = lower_condition(lowerer, backend, first).into_bool(backend);
81                let mut chain = backend.begin_and(first);
82
83                for operand in rest {
84                    let value = lower_condition(lowerer, backend, operand).into_bool(backend);
85
86                    backend.push_and(&mut chain, value);
87                }
88
89                EvalValueAbstract::Bool(backend.finish_and(chain))
90            }
91        }
92
93        Condition::Or { operands } => {
94            #[cfg(feature = "rotation-optimizations")]
95            return lower_or_without_neutral_literals(lowerer, backend, operands);
96
97            #[cfg(not(feature = "rotation-optimizations"))]
98            {
99                let Some((first, rest)) = operands.split_first() else {
100                    return EvalValueAbstract::Bool(backend.const_bool(false));
101                };
102                let first = lower_condition(lowerer, backend, first).into_bool(backend);
103                let mut chain = backend.begin_or(first);
104
105                for operand in rest {
106                    let value = lower_condition(lowerer, backend, operand).into_bool(backend);
107
108                    backend.push_or(&mut chain, value);
109                }
110
111                EvalValueAbstract::Bool(backend.finish_or(chain))
112            }
113        }
114
115        Condition::Not { operand } => {
116            let value = lower_condition(lowerer, backend, operand).into_bool(backend);
117
118            EvalValueAbstract::Bool(backend.not_b(value))
119        }
120
121        Condition::Arith { op, left, right } => {
122            let left_value = lower_condition(lowerer, backend, left).into_float(backend);
123            let right_value = lower_condition(lowerer, backend, right).into_float(backend);
124            let value = match op {
125                ArithOp::Add => backend.add_f(left_value, right_value),
126                ArithOp::Sub => backend.sub_f(left_value, right_value),
127                ArithOp::Mul => backend.mul_f(left_value, right_value),
128                ArithOp::Div => backend.safe_div_f(left_value, right_value),
129                ArithOp::Mod => backend.true_mod_f(left_value, right_value),
130                _ => unreachable!("rotation validation rejects unsupported arithmetic operators"),
131            };
132
133            EvalValueAbstract::Float(value)
134        }
135
136        Condition::UnaryMath { op, operand } => {
137            let value = lower_condition(lowerer, backend, operand).into_float(backend);
138            let value = match op {
139                UnaryMathOp::Floor => backend.floor_f(value),
140                UnaryMathOp::Ceil => backend.ceil_f(value),
141                UnaryMathOp::Abs => backend.abs_f(value),
142                _ => unreachable!("rotation validation rejects unsupported unary math operators"),
143            };
144
145            EvalValueAbstract::Float(value)
146        }
147
148        Condition::MinMax { op, left, right } => {
149            let left_value = lower_condition(lowerer, backend, left).into_float(backend);
150            let right_value = lower_condition(lowerer, backend, right).into_float(backend);
151            let value = match op {
152                MinMaxOp::Min => backend.min_f(left_value, right_value),
153                MinMaxOp::Max => backend.max_f(left_value, right_value),
154                _ => unreachable!("rotation validation rejects unsupported min/max operators"),
155            };
156
157            EvalValueAbstract::Float(value)
158        }
159
160        Condition::IfThenElse {
161            condition,
162            then,
163            otherwise,
164        } => lower_if_then_else(lowerer, backend, condition, then, otherwise),
165        _ => unreachable!("rotation validation rejects unsupported condition variants"),
166    }
167}
168
169#[cfg(feature = "rotation-optimizations")]
170fn lower_and_without_neutral_literals<B>(
171    lowerer: &mut Lowerer<'_>,
172    backend: &mut B,
173    operands: &[Condition],
174) -> EvalValueAbstract<B>
175where
176    B: RotationBackend,
177{
178    if operands
179        .iter()
180        .any(|operand| matches!(operand, Condition::Bool { value: false }))
181    {
182        return EvalValueAbstract::Bool(backend.const_bool(false));
183    }
184
185    let mut meaningful = operands
186        .iter()
187        .filter(|operand| !matches!(operand, Condition::Bool { value: true }));
188    let Some(first) = meaningful.next() else {
189        return EvalValueAbstract::Bool(backend.const_bool(true));
190    };
191    let first = lower_condition(lowerer, backend, first).into_bool(backend);
192    let Some(second) = meaningful.next() else {
193        return EvalValueAbstract::Bool(first);
194    };
195    let mut chain = backend.begin_and(first);
196    let second = lower_condition(lowerer, backend, second).into_bool(backend);
197
198    backend.push_and(&mut chain, second);
199
200    for operand in meaningful {
201        let value = lower_condition(lowerer, backend, operand).into_bool(backend);
202
203        backend.push_and(&mut chain, value);
204    }
205
206    EvalValueAbstract::Bool(backend.finish_and(chain))
207}
208
209#[cfg(feature = "rotation-optimizations")]
210fn lower_or_without_neutral_literals<B>(
211    lowerer: &mut Lowerer<'_>,
212    backend: &mut B,
213    operands: &[Condition],
214) -> EvalValueAbstract<B>
215where
216    B: RotationBackend,
217{
218    if operands
219        .iter()
220        .any(|operand| matches!(operand, Condition::Bool { value: true }))
221    {
222        return EvalValueAbstract::Bool(backend.const_bool(true));
223    }
224
225    let mut meaningful = operands
226        .iter()
227        .filter(|operand| !matches!(operand, Condition::Bool { value: false }));
228    let Some(first) = meaningful.next() else {
229        return EvalValueAbstract::Bool(backend.const_bool(false));
230    };
231    let first = lower_condition(lowerer, backend, first).into_bool(backend);
232    let Some(second) = meaningful.next() else {
233        return EvalValueAbstract::Bool(first);
234    };
235    let mut chain = backend.begin_or(first);
236    let second = lower_condition(lowerer, backend, second).into_bool(backend);
237
238    backend.push_or(&mut chain, second);
239
240    for operand in meaningful {
241        let value = lower_condition(lowerer, backend, operand).into_bool(backend);
242
243        backend.push_or(&mut chain, value);
244    }
245
246    EvalValueAbstract::Bool(backend.finish_or(chain))
247}
248
249fn lower_if_then_else<B>(
250    lowerer: &mut Lowerer<'_>,
251    backend: &mut B,
252    condition: &Condition,
253    then: &Condition,
254    otherwise: &Condition,
255) -> EvalValueAbstract<B>
256where
257    B: RotationBackend,
258{
259    let branch_condition = lower_condition(lowerer, backend, condition).into_bool(backend);
260    let target_ty = infer_field_type(then, lowerer.table);
261    let then_value = lower_condition(lowerer, backend, then);
262    let else_value = lower_condition(lowerer, backend, otherwise);
263
264    typed_select(backend, target_ty, branch_condition, then_value, else_value)
265}
266
267fn lower_compare<B>(
268    backend: &mut B,
269    op: CompareOp,
270    left: EvalValueAbstract<B>,
271    right: EvalValueAbstract<B>,
272) -> B::Bool
273where
274    B: RotationBackend,
275{
276    match (&left, &right) {
277        (EvalValueAbstract::Int(_), EvalValueAbstract::Int(_))
278        | (EvalValueAbstract::Bool(_), EvalValueAbstract::Bool(_)) => {
279            let left_int = left.into_int(backend);
280            let right_int = right.into_int(backend);
281
282            match op {
283                CompareOp::Gt => backend.cmp_gt_i(left_int, right_int),
284                CompareOp::Gte => backend.cmp_gte_i(left_int, right_int),
285                CompareOp::Lt => backend.cmp_lt_i(left_int, right_int),
286                CompareOp::Lte => backend.cmp_lte_i(left_int, right_int),
287                CompareOp::Eq => backend.cmp_eq_i(left_int, right_int),
288                CompareOp::Ne => backend.cmp_ne_i(left_int, right_int),
289                _ => unreachable!("rotation validation rejects unsupported comparison operators"),
290            }
291        }
292        _ => {
293            let left_float = left.into_float(backend);
294            let right_float = right.into_float(backend);
295
296            match op {
297                CompareOp::Gt => backend.cmp_gt_f(left_float, right_float),
298                CompareOp::Gte => backend.cmp_gte_f(left_float, right_float),
299                CompareOp::Lt => backend.cmp_lt_f(left_float, right_float),
300                CompareOp::Lte => backend.cmp_lte_f(left_float, right_float),
301                CompareOp::Eq => backend.cmp_eq_f(left_float, right_float),
302                CompareOp::Ne => backend.cmp_ne_f(left_float, right_float),
303                _ => unreachable!("rotation validation rejects unsupported comparison operators"),
304            }
305        }
306    }
307}
308
309pub(super) fn load_user_var<B>(lo: &mut Lowerer<'_>, b: &mut B, name: &str) -> EvalValueAbstract<B>
310where
311    B: RotationBackend,
312{
313    let (Some(offset), Some(ty)) = (
314        lo.schema.user_var_offset(name),
315        lo.schema.user_var_type(name),
316    ) else {
317        tracing::warn!(
318            var = %name,
319            "load_user_var: unknown user variable, evaluating as 0.0"
320        );
321        debug_assert!(false, "load_user_var: unknown user variable `{name}`");
322
323        return EvalValueAbstract::Float(b.const_f64(0.0));
324    };
325
326    typed_load(b, offset, ty)
327}