Skip to main content

wowlab_engine_domain/rotation/lower/
action.rs

1use wowlab_types::sim::{Condition, RotationAction as AstAction};
2
3use super::{
4    super::{
5        backend::RotationBackend,
6        decision_trace::{ActionOutcome, ActionRecord},
7    },
8    Lowerer,
9    condition::lower_condition,
10};
11
12mod lists;
13mod terminators;
14mod vars;
15
16use lists::{lower_call, lower_run};
17use terminators::{
18    lower_cast, lower_pool, lower_use_item, lower_use_trinket, lower_wait, lower_wait_until,
19};
20pub(super) use vars::store_user_var;
21use vars::{ModifyVarArgs, lower_modify_var, lower_set_var};
22
23pub(super) fn lower_actions<B>(
24    lo: &mut Lowerer<'_>,
25    b: &mut B,
26    list_id: &str,
27    actions: &[AstAction],
28) where
29    B: RotationBackend,
30{
31    b.begin_list_recording(list_id);
32
33    for (idx, action) in actions.iter().enumerate() {
34        if !action.enabled() {
35            b.record_action(ActionRecord {
36                list_id,
37                action_index: idx,
38                action,
39                outcome: ActionOutcome::Disabled,
40            });
41            continue;
42        }
43
44        if lo.unconditionally_returned {
45            b.record_action(ActionRecord {
46                list_id,
47                action_index: idx,
48                action,
49                outcome: ActionOutcome::NotReached,
50            });
51            continue;
52        }
53
54        let mut ctx = LowerCtx {
55            lo,
56            b,
57            action: ActionRef::new(list_id, idx, action),
58        };
59
60        lower_action(&mut ctx);
61    }
62
63    b.end_list_recording();
64}
65
66pub(super) fn init_user_variables<B>(lo: &mut Lowerer<'_>, b: &mut B)
67where
68    B: RotationBackend,
69{
70    let names: Vec<String> = lo.variables.keys().cloned().collect();
71
72    for name in &names {
73        let init = lo
74            .variables
75            .get(name)
76            .expect("variable name pulled from the same map");
77        // #t(rust_clone_in_loop) drops the &Condition borrow on `variables` so `store_user_var` can take `&mut Lowerer` for the rest of the loop.
78        let init = init.clone();
79
80        store_user_var(lo, b, name, &init);
81    }
82}
83
84struct LoweredGuard<B>
85where
86    B: RotationBackend,
87{
88    bool_val: B::Bool,
89    is_unconditional: bool,
90}
91
92fn lower_optional_guard<B>(
93    lo: &mut Lowerer<'_>,
94    b: &mut B,
95    condition: Option<&Condition>,
96) -> LoweredGuard<B>
97where
98    B: RotationBackend,
99{
100    match condition {
101        Some(c) => LoweredGuard {
102            bool_val: lower_condition(lo, b, c).into_bool(b),
103            is_unconditional: false,
104        },
105        None => LoweredGuard {
106            bool_val: b.const_bool(true),
107            is_unconditional: true,
108        },
109    }
110}
111
112const fn is_terminator(action: &AstAction) -> bool {
113    matches!(
114        action,
115        AstAction::Cast { .. }
116            | AstAction::Wait { .. }
117            | AstAction::WaitUntil { .. }
118            | AstAction::Pool { .. }
119            | AstAction::UseTrinket { .. }
120            | AstAction::UseItem { .. }
121    )
122}
123
124// Recorded so an action with no condition still shows up in the decision trace.
125static ALWAYS_TRUE_SENTINEL: Condition = Condition::Bool { value: true };
126
127#[inline]
128fn failing_condition(condition: Option<&Condition>) -> &Condition {
129    condition.unwrap_or(&ALWAYS_TRUE_SENTINEL)
130}
131
132#[derive(Clone, Copy)]
133struct ActionRef<'a> {
134    list_id: &'a str,
135    action_index: usize,
136    action: &'a AstAction,
137    is_term: bool,
138}
139
140impl<'a> ActionRef<'a> {
141    fn new(list_id: &'a str, action_index: usize, action: &'a AstAction) -> Self {
142        Self {
143            list_id,
144            action_index,
145            action,
146            is_term: is_terminator(action),
147        }
148    }
149}
150
151struct LowerCtx<'ctx, 'action, 'rotation, B> {
152    lo: &'ctx mut Lowerer<'rotation>,
153    b: &'ctx mut B,
154    action: ActionRef<'action>,
155}
156
157#[inline]
158fn record_skipped<B>(ctx: &mut LowerCtx<'_, '_, '_, B>, failing: &Condition)
159where
160    B: RotationBackend,
161{
162    let false_bool = ctx.b.const_bool(false);
163
164    ctx.b.record_action_guarded(
165        ctx.action.list_id,
166        ctx.action.action_index,
167        ctx.action.action,
168        ctx.action.is_term,
169        false_bool,
170        failing,
171    );
172}
173
174#[inline]
175fn record_guarded<B>(
176    ctx: &mut LowerCtx<'_, '_, '_, B>,
177    guard: &LoweredGuard<B>,
178    condition: Option<&Condition>,
179) where
180    B: RotationBackend,
181{
182    ctx.b.record_action_guarded(
183        ctx.action.list_id,
184        ctx.action.action_index,
185        ctx.action.action,
186        ctx.action.is_term,
187        guard.bool_val,
188        failing_condition(condition),
189    );
190
191    if guard.is_unconditional {
192        ctx.lo.unconditionally_returned = true;
193    }
194}
195
196fn lower_action<B>(ctx: &mut LowerCtx<'_, '_, '_, B>)
197where
198    B: RotationBackend,
199{
200    match ctx.action.action {
201        AstAction::Cast {
202            spell,
203            empower_rank,
204            condition,
205            ..
206        } => lower_cast(ctx, spell, *empower_rank, condition.as_ref()),
207        AstAction::Wait {
208            seconds, condition, ..
209        } => lower_wait(ctx, *seconds, condition.as_ref()),
210        AstAction::WaitUntil { condition, .. } => lower_wait_until(ctx, condition),
211        AstAction::Pool {
212            extra, condition, ..
213        } => lower_pool(ctx, *extra, condition.as_ref()),
214        AstAction::UseTrinket {
215            slot,
216            empower_rank,
217            condition,
218            ..
219        } => lower_use_trinket(ctx, *slot, *empower_rank, condition.as_ref()),
220        AstAction::UseItem {
221            name,
222            empower_rank,
223            condition,
224            ..
225        } => lower_use_item(ctx, name, *empower_rank, condition.as_ref()),
226        AstAction::SetVar {
227            name,
228            value,
229            condition,
230            ..
231        } => lower_set_var(ctx, name, value, condition.as_ref()),
232        AstAction::ModifyVar {
233            name,
234            op,
235            value,
236            condition,
237            ..
238        } => lower_modify_var(
239            ctx,
240            &ModifyVarArgs {
241                name,
242                op: *op,
243                value,
244                condition: condition.as_ref(),
245            },
246        ),
247        AstAction::Call {
248            list, condition, ..
249        } => lower_call(ctx, list, condition.as_ref()),
250        AstAction::Run {
251            list, condition, ..
252        } => lower_run(ctx, list, condition.as_ref()),
253        _ => unreachable!("rotation validation rejects unsupported action variants"),
254    }
255}
256
257#[inline]
258fn skip_in_run<B>(ctx: &mut LowerCtx<'_, '_, '_, B>, condition: Option<&Condition>) -> bool
259where
260    B: RotationBackend,
261{
262    if ctx.lo.run_depth > 0 {
263        record_skipped(ctx, failing_condition(condition));
264
265        true
266    } else {
267        false
268    }
269}