wowlab_engine_combat/builder/
compile.rs1use 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#[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#[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
57pub(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}