Skip to main content

wowlab_engine_combat/builder/
compile.rs

1//! Rotation compilation and dense-buffer allocation for the builder.
2
3use wowlab_engine_domain::rotation::{
4    CatalogHints, DenseBuffer, RotationEngine, RuntimeBackend, SpecCatalog,
5};
6use wowlab_types::sim::{FastMap, Rotation};
7
8/// Threaded into the resolver so `is_usable` agrees with `can_cast`.
9pub(crate) struct SpellGate {
10    pub(crate) spell_id: u32,
11    pub(crate) aura_id: u32,
12    pub(crate) min_stacks: u8,
13}
14
15/// Threaded into the resolver so cost-bypass agrees with `can_cast`.
16pub(crate) struct CostBypass {
17    pub(crate) spell_id: u32,
18    pub(crate) aura_id: u32,
19}
20
21// #t(rust_similar_structs) cooldown and resource-cost bypasses remain distinct typed rotation policies
22pub(crate) struct CooldownBypass {
23    pub(crate) spell_id: u32,
24    pub(crate) aura_id: u32,
25}
26
27pub(crate) struct SpellOverride {
28    pub(crate) spell_id: u32,
29    pub(crate) replacement_spell_id: u32,
30    pub(crate) aura_id: u32,
31    pub(crate) shares_base_cooldown: bool,
32}
33
34pub(crate) struct RotationCompileInput<'a> {
35    pub(crate) rotation: &'a Rotation,
36    pub(crate) spell_ids: &'a FastMap<String, u32>,
37    pub(crate) aura_ids: &'a FastMap<String, u32>,
38    pub(crate) secondary_resource_name: Option<&'a str>,
39    pub(crate) hints: &'a CatalogHints,
40    pub(crate) gating: &'a [SpellGate],
41    pub(crate) cost_bypass: &'a [CostBypass],
42    pub(crate) cooldown_bypass: &'a [CooldownBypass],
43    pub(crate) overrides: &'a [SpellOverride],
44}
45
46pub(crate) fn compile_rotation(
47    input: &RotationCompileInput<'_>,
48) -> Result<(RotationEngine, DenseBuffer), wowlab_engine_domain::rotation::Error> {
49    let mut catalog = SpecCatalog::new(input.spell_ids, input.aura_ids, input.hints);
50
51    if let Some(sec_name) = input.secondary_resource_name {
52        catalog.register_secondary_resource(sec_name);
53    }
54
55    let mut resolver = catalog.to_resolver("auto");
56
57    for gate in input.gating {
58        resolver = resolver.spell_gating(
59            wowlab_types::sim::SpellIdx(gate.spell_id),
60            wowlab_types::sim::AuraIdx(gate.aura_id),
61            wowlab_types::sim::AuraOn::Player,
62            gate.min_stacks,
63        );
64    }
65
66    for bypass in input.cost_bypass {
67        resolver = resolver.spell_cost_bypass(
68            wowlab_types::sim::SpellIdx(bypass.spell_id),
69            wowlab_types::sim::AuraIdx(bypass.aura_id),
70            wowlab_types::sim::AuraOn::Player,
71        );
72    }
73
74    for bypass in input.cooldown_bypass {
75        resolver = resolver.spell_cooldown_bypass(
76            wowlab_types::sim::SpellIdx(bypass.spell_id),
77            wowlab_types::sim::AuraIdx(bypass.aura_id),
78            wowlab_types::sim::AuraOn::Player,
79        );
80    }
81
82    for replacement in input.overrides {
83        resolver = resolver.spell_override(
84            wowlab_types::sim::SpellIdx(replacement.spell_id),
85            wowlab_types::sim::SpellIdx(replacement.replacement_spell_id),
86            wowlab_types::sim::AuraIdx(replacement.aura_id),
87            wowlab_types::sim::AuraOn::Player,
88            replacement.shares_base_cooldown,
89        );
90    }
91
92    let engine = RotationEngine::compile(input.rotation, &resolver)?;
93
94    let schema = engine.schema();
95    let offsets = schema.buffer_offsets.clone();
96    let size = schema.size;
97
98    let buffer = DenseBuffer::new(offsets, size);
99
100    Ok((engine, buffer))
101}