Skip to main content

wowlab_engine_domain/rotation/lower/ops/
mod.rs

1//! Per-`EvalKind` algorithm functions, generic over `B: RotationBackend`, defined once for both backends.
2
3use wowlab_types::constants::HUNDRED;
4
5use crate::{constants::AURA_PANDEMIC_THRESHOLD, rotation::backend::RotationBackend};
6
7pub(super) mod aura;
8pub(super) mod cooldown;
9pub(super) mod direct;
10pub(super) mod resource;
11pub(super) mod spell;
12pub(super) mod timestamp;
13pub(super) mod unit;
14
15pub(in crate::rotation) trait EvalRuleOps {
16    type Bool: Copy;
17    type Int: Copy;
18    type Float: Copy;
19
20    fn const_bool(&mut self, value: bool) -> Self::Bool;
21    fn const_i32(&mut self, value: i32) -> Self::Int;
22    fn const_f64(&mut self, value: f64) -> Self::Float;
23    fn sub_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float;
24    fn mul_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float;
25    fn safe_div_f(&mut self, numerator: Self::Float, denominator: Self::Float) -> Self::Float;
26    fn max_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float;
27    fn cmp_gt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool;
28    fn cmp_lt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool;
29    fn cmp_lte_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool;
30    fn cmp_gt_i(&mut self, lhs: Self::Int, rhs: Self::Int) -> Self::Bool;
31    fn and_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool;
32    fn or_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool;
33    fn select_f(
34        &mut self,
35        condition: Self::Bool,
36        if_true: Self::Float,
37        if_false: Self::Float,
38    ) -> Self::Float;
39    fn select_b(
40        &mut self,
41        condition: Self::Bool,
42        if_true: Self::Bool,
43        if_false: Self::Bool,
44    ) -> Self::Bool;
45}
46
47impl<B> EvalRuleOps for B
48where
49    B: RotationBackend,
50{
51    type Bool = B::Bool;
52    type Int = B::Int;
53    type Float = B::Float;
54
55    fn const_bool(&mut self, value: bool) -> Self::Bool {
56        RotationBackend::const_bool(self, value)
57    }
58
59    fn const_i32(&mut self, value: i32) -> Self::Int {
60        RotationBackend::const_i32(self, value)
61    }
62
63    fn const_f64(&mut self, value: f64) -> Self::Float {
64        RotationBackend::const_f64(self, value)
65    }
66
67    fn sub_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
68        RotationBackend::sub_f(self, lhs, rhs)
69    }
70
71    fn mul_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
72        RotationBackend::mul_f(self, lhs, rhs)
73    }
74
75    fn safe_div_f(&mut self, numerator: Self::Float, denominator: Self::Float) -> Self::Float {
76        RotationBackend::safe_div_f(self, numerator, denominator)
77    }
78
79    fn max_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
80        RotationBackend::max_f(self, lhs, rhs)
81    }
82
83    fn cmp_gt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
84        RotationBackend::cmp_gt_f(self, lhs, rhs)
85    }
86
87    fn cmp_lt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
88        RotationBackend::cmp_lt_f(self, lhs, rhs)
89    }
90
91    fn cmp_lte_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
92        RotationBackend::cmp_lte_f(self, lhs, rhs)
93    }
94
95    fn cmp_gt_i(&mut self, lhs: Self::Int, rhs: Self::Int) -> Self::Bool {
96        RotationBackend::cmp_gt_i(self, lhs, rhs)
97    }
98
99    fn and_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool {
100        RotationBackend::and_b(self, lhs, rhs)
101    }
102
103    fn or_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool {
104        RotationBackend::or_b(self, lhs, rhs)
105    }
106
107    fn select_f(
108        &mut self,
109        condition: Self::Bool,
110        if_true: Self::Float,
111        if_false: Self::Float,
112    ) -> Self::Float {
113        RotationBackend::select_f(self, condition, if_true, if_false)
114    }
115
116    fn select_b(
117        &mut self,
118        condition: Self::Bool,
119        if_true: Self::Bool,
120        if_false: Self::Bool,
121    ) -> Self::Bool {
122        RotationBackend::select_b(self, condition, if_true, if_false)
123    }
124}
125
126pub(in crate::rotation) struct ScalarEvalOps;
127
128impl EvalRuleOps for ScalarEvalOps {
129    type Bool = bool;
130    type Int = i32;
131    type Float = f64;
132
133    fn const_bool(&mut self, value: bool) -> Self::Bool {
134        value
135    }
136
137    fn const_i32(&mut self, value: i32) -> Self::Int {
138        value
139    }
140
141    fn const_f64(&mut self, value: f64) -> Self::Float {
142        value
143    }
144
145    fn sub_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
146        lhs - rhs
147    }
148
149    fn mul_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
150        lhs * rhs
151    }
152
153    fn safe_div_f(&mut self, numerator: Self::Float, denominator: Self::Float) -> Self::Float {
154        wowlab_types::numeric::safe_div(numerator, denominator)
155    }
156
157    fn max_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
158        lhs.max(rhs)
159    }
160
161    fn cmp_gt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
162        lhs > rhs
163    }
164
165    fn cmp_lt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
166        lhs < rhs
167    }
168
169    fn cmp_lte_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
170        lhs <= rhs
171    }
172
173    fn cmp_gt_i(&mut self, lhs: Self::Int, rhs: Self::Int) -> Self::Bool {
174        lhs > rhs
175    }
176
177    fn and_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool {
178        lhs && rhs
179    }
180
181    fn or_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool {
182        lhs || rhs
183    }
184
185    fn select_f(
186        &mut self,
187        condition: Self::Bool,
188        if_true: Self::Float,
189        if_false: Self::Float,
190    ) -> Self::Float {
191        if condition { if_true } else { if_false }
192    }
193
194    fn select_b(
195        &mut self,
196        condition: Self::Bool,
197        if_true: Self::Bool,
198        if_false: Self::Bool,
199    ) -> Self::Bool {
200        if condition { if_true } else { if_false }
201    }
202}
203
204pub(in crate::rotation) fn eval_cooldown_ready<O>(
205    ops: &mut O,
206    ready_at: O::Float,
207    current_charges: O::Int,
208    max_charges: O::Int,
209    now: O::Float,
210    bypass_active: O::Bool,
211) -> O::Bool
212where
213    O: EvalRuleOps,
214{
215    let one = ops.const_i32(1);
216    let zero = ops.const_i32(0);
217    let charged = ops.cmp_gt_i(max_charges, one);
218    let has_charge = ops.cmp_gt_i(current_charges, zero);
219    let time_ready = ops.cmp_lte_f(ready_at, now);
220    let ready = ops.select_b(charged, has_charge, time_ready);
221
222    ops.or_b(ready, bypass_active)
223}
224
225pub(in crate::rotation) fn eval_aura_refreshable<O>(
226    ops: &mut O,
227    expires_at: O::Float,
228    base_duration: O::Float,
229    now: O::Float,
230) -> O::Bool
231where
232    O: EvalRuleOps,
233{
234    let zero = ops.const_f64(0.0);
235    let positive = ops.cmp_gt_f(expires_at, zero);
236    let now_before = ops.cmp_lt_f(now, expires_at);
237    let active = ops.and_b(positive, now_before);
238    let remaining = ops.sub_f(expires_at, now);
239    let pandemic_factor = ops.const_f64(AURA_PANDEMIC_THRESHOLD);
240    let pandemic_window = ops.mul_f(pandemic_factor, base_duration);
241    let in_pandemic = ops.cmp_lt_f(remaining, pandemic_window);
242    let inactive_is_refreshable = ops.const_bool(true);
243
244    ops.select_b(active, in_pandemic, inactive_is_refreshable)
245}
246
247pub(in crate::rotation) fn eval_resource_pct<O>(
248    ops: &mut O,
249    current: O::Float,
250    max: O::Float,
251) -> O::Float
252where
253    O: EvalRuleOps,
254{
255    pct_from(ops, current, max)
256}
257
258pub(in crate::rotation) fn eval_resource_deficit<O>(
259    ops: &mut O,
260    current: O::Float,
261    max: O::Float,
262) -> O::Float
263where
264    O: EvalRuleOps,
265{
266    clamped_diff(ops, current, max)
267}
268
269pub(in crate::rotation) fn eval_resource_deficit_pct<O>(
270    ops: &mut O,
271    current: O::Float,
272    max: O::Float,
273) -> O::Float
274where
275    O: EvalRuleOps,
276{
277    let deficit = clamped_diff(ops, current, max);
278
279    pct_from(ops, deficit, max)
280}
281
282pub(in crate::rotation) fn eval_resource_time_to_max<O>(
283    ops: &mut O,
284    current: O::Float,
285    max: O::Float,
286    regen_per_sec: O::Float,
287) -> O::Float
288where
289    O: EvalRuleOps,
290{
291    let deficit = clamped_diff(ops, current, max);
292    let zero = ops.const_f64(0.0);
293    let positive_regen = ops.cmp_gt_f(regen_per_sec, zero);
294    let time = ops.safe_div_f(deficit, regen_per_sec);
295
296    ops.select_f(positive_regen, time, zero)
297}
298
299pub(in crate::rotation) fn eval_unit_health_pct<O>(
300    ops: &mut O,
301    health: O::Float,
302    max_health: O::Float,
303) -> O::Float
304where
305    O: EvalRuleOps,
306{
307    pct_from(ops, health, max_health)
308}
309
310pub(in crate::rotation) fn eval_unit_health_deficit<O>(
311    ops: &mut O,
312    health: O::Float,
313    max_health: O::Float,
314) -> O::Float
315where
316    O: EvalRuleOps,
317{
318    clamped_diff(ops, health, max_health)
319}
320
321fn clamped_diff<O>(ops: &mut O, current: O::Float, max: O::Float) -> O::Float
322where
323    O: EvalRuleOps,
324{
325    let difference = ops.sub_f(max, current);
326    let zero = ops.const_f64(0.0);
327
328    ops.max_f(difference, zero)
329}
330
331fn pct_from<O>(ops: &mut O, current: O::Float, max: O::Float) -> O::Float
332where
333    O: EvalRuleOps,
334{
335    let ratio = ops.safe_div_f(current, max);
336    let hundred = ops.const_f64(HUNDRED);
337
338    ops.mul_f(ratio, hundred)
339}