Skip to main content

wowlab_engine_combat/builder/
compile.rs

1// #t(file: rust_similar_structs) cooldown and resource-cost bypasses remain distinct typed rotation policies.
2
3//! Rotation compilation and dense-buffer allocation for the builder.
4
5use wowlab_engine_domain::rotation::{
6    BufferRequirements, CatalogHints, PreparedRotation, RotationEngine, RotationPreparation,
7    SpecCatalog,
8};
9use wowlab_types::{
10    combat::ResourceType,
11    sim::{FastMap, Rotation},
12};
13
14/// Threaded into the resolver so `is_usable` agrees with `can_cast`.
15#[derive(Debug)]
16pub(crate) struct SpellGate {
17    pub(crate) spell_id: u32,
18    pub(crate) aura_id: u32,
19    pub(crate) min_stacks: u8,
20}
21
22/// Threaded into the resolver so cost-bypass agrees with `can_cast`.
23#[derive(Debug)]
24pub(crate) struct CostBypass {
25    pub(crate) spell_id: u32,
26    pub(crate) aura_id: u32,
27}
28
29#[derive(Debug)]
30pub(crate) struct CooldownBypass {
31    pub(crate) spell_id: u32,
32    pub(crate) aura_id: u32,
33}
34
35#[derive(Debug)]
36pub(crate) struct SpellOverride {
37    pub(crate) spell_id: u32,
38    pub(crate) replacement_spell_id: u32,
39    pub(crate) aura_id: u32,
40    pub(crate) shares_base_cooldown: bool,
41}
42
43pub(crate) struct RotationCompileInput<'a> {
44    pub(crate) rotation: &'a Rotation,
45    pub(crate) spell_ids: &'a FastMap<String, u32>,
46    pub(crate) aura_ids: &'a FastMap<String, u32>,
47    pub(crate) secondary_resource_name: Option<&'a str>,
48    pub(crate) primary_resource_type: Option<ResourceType>,
49    pub(crate) secondary_resource_type: Option<ResourceType>,
50    pub(crate) hints: &'a CatalogHints,
51    pub(crate) gating: &'a [SpellGate],
52    pub(crate) cost_bypass: &'a [CostBypass],
53    pub(crate) cooldown_bypass: &'a [CooldownBypass],
54    pub(crate) overrides: &'a [SpellOverride],
55}
56
57// #t(fn: rust_cyclomatic_complexity) linear registration of independent finalized resolver capabilities
58pub(crate) fn prepare_rotation(
59    input: &RotationCompileInput<'_>,
60) -> Result<RotationPreparation, wowlab_engine_domain::rotation::Error> {
61    let mut catalog = SpecCatalog::new(input.spell_ids, input.aura_ids, input.hints);
62
63    if let Some(sec_name) = input.secondary_resource_name {
64        catalog.register_secondary_resource(sec_name);
65    }
66
67    let mut resolver = catalog.to_resolver("auto");
68
69    if let Some(resource_type) = input.primary_resource_type {
70        resolver = resolver.resource_type(resource_type.to_string(), resource_type);
71
72        if let Some(resource_name) = input.hints.primary_resource.as_deref() {
73            resolver = resolver.resource_type(resource_name, resource_type);
74        }
75    }
76
77    if let Some(resource_type) = input.secondary_resource_type {
78        resolver = resolver.resource_type(resource_type.to_string(), resource_type);
79
80        if let Some(resource_name) = input.secondary_resource_name {
81            resolver = resolver.resource_type(resource_name, resource_type);
82        }
83    }
84
85    for gate in input.gating {
86        resolver = resolver.spell_gating(
87            wowlab_types::sim::SpellIdx(gate.spell_id),
88            wowlab_types::sim::AuraIdx(gate.aura_id),
89            wowlab_types::sim::AuraOn::Player,
90            gate.min_stacks,
91        );
92    }
93
94    for bypass in input.cost_bypass {
95        resolver = resolver.spell_cost_bypass(
96            wowlab_types::sim::SpellIdx(bypass.spell_id),
97            wowlab_types::sim::AuraIdx(bypass.aura_id),
98            wowlab_types::sim::AuraOn::Player,
99        );
100    }
101
102    for bypass in input.cooldown_bypass {
103        resolver = resolver.spell_cooldown_bypass(
104            wowlab_types::sim::SpellIdx(bypass.spell_id),
105            wowlab_types::sim::AuraIdx(bypass.aura_id),
106            wowlab_types::sim::AuraOn::Player,
107        );
108    }
109
110    for replacement in input.overrides {
111        resolver = resolver.spell_override(
112            wowlab_types::sim::SpellIdx(replacement.spell_id),
113            wowlab_types::sim::SpellIdx(replacement.replacement_spell_id),
114            wowlab_types::sim::AuraIdx(replacement.aura_id),
115            wowlab_types::sim::AuraOn::Player,
116            replacement.shares_base_cooldown,
117        );
118    }
119
120    RotationPreparation::prepare(input.rotation, &resolver)
121}
122
123pub(crate) fn bind_rotation(
124    preparation: RotationPreparation,
125    requirements: &BufferRequirements,
126) -> Result<PreparedRotation, wowlab_engine_domain::rotation::Error> {
127    preparation.bind(requirements)
128}
129
130#[expect(
131    clippy::unnecessary_wraps,
132    reason = "the portable and JIT emitters share one fallible compilation boundary"
133)]
134pub(crate) fn emit_rotation(
135    prepared: PreparedRotation,
136) -> Result<RotationEngine, wowlab_engine_domain::rotation::Error> {
137    #[cfg(feature = "rotation-optimizations")]
138    let prepared = prepared.with_prevalidated_spell_usability();
139
140    #[cfg(feature = "jit")]
141    let engine = RotationEngine::jit(prepared)?;
142
143    #[cfg(not(feature = "jit"))]
144    let engine = RotationEngine::portable(prepared);
145
146    Ok(engine)
147}