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
14pub(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}