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
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 = 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}