wowlab_engine_combat/state/runtime/
buff_cache.rs1use std::cell::Cell;
4
5use wowlab_types::sim::{ActorId, PetIdx};
6
7#[cfg(feature = "rotation-optimizations")]
8use crate::state::buff_effect::DAMAGE_SCHOOL_COUNT;
9use crate::state::{BuffTotals, buff_effect::PlayerScalingBuffs};
10
11#[derive(Clone, Copy, Debug, PartialEq)]
13#[cfg(feature = "rotation-optimizations")]
14pub(crate) struct DamageBuffProjection {
15 pub(crate) attack_power: f64,
16 pub(crate) spell_power: f64,
17 pub(crate) attack_power_pct: f64,
18 pub(crate) crit_pct: f64,
19 pub(crate) mastery: f64,
20 pub(crate) vers_pct: f64,
21 pub(crate) damage_mult: f64,
22 pub(crate) pet_damage_mult: f64,
23 pub(crate) auto_attack_damage_mult: f64,
24 pub(crate) auto_attack_crit_pct: f64,
25 pub(crate) school_damage_mult: [f64; DAMAGE_SCHOOL_COUNT],
26}
27
28#[cfg(feature = "rotation-optimizations")]
29impl Default for DamageBuffProjection {
30 fn default() -> Self {
31 Self {
32 attack_power: 0.0,
33 spell_power: 0.0,
34 attack_power_pct: 0.0,
35 crit_pct: 0.0,
36 mastery: 0.0,
37 vers_pct: 0.0,
38 damage_mult: 1.0,
39 pet_damage_mult: 1.0,
40 auto_attack_damage_mult: 1.0,
41 auto_attack_crit_pct: 0.0,
42 school_damage_mult: [1.0; DAMAGE_SCHOOL_COUNT],
43 }
44 }
45}
46
47#[derive(Clone, Copy, Debug, Default)]
48struct BuffCacheEntry {
49 valid: bool,
50 revision: u64,
51 owner_revision: u64,
52 totals: BuffTotals,
53}
54
55#[derive(Debug)]
56struct ActorCaches<T>
57where
58 T: Copy,
59{
60 player: Cell<T>,
61 external: Cell<T>,
62 pets: Box<[Cell<T>]>,
63 enemies: Box<[Cell<T>]>,
64}
65
66impl<T> ActorCaches<T>
67where
68 T: Copy,
69{
70 fn new(enemy_count: usize, initial: T) -> Self {
71 Self {
72 player: Cell::new(initial),
73 external: Cell::new(initial),
74 pets: vec![Cell::new(initial); PetIdx::MAX].into_boxed_slice(),
75 enemies: vec![Cell::new(initial); enemy_count].into_boxed_slice(),
76 }
77 }
78
79 fn get(&self, actor: ActorId) -> Option<&Cell<T>> {
80 match actor {
81 ActorId::Player => Some(&self.player),
82 ActorId::External => Some(&self.external),
83 ActorId::Pet(pet) => self.pets.get(pet.as_usize()),
84 ActorId::Enemy(enemy) => self.enemies.get(enemy.as_usize()),
85 }
86 }
87
88 fn clear(&self, initial: T) {
89 self.player.set(initial);
90 self.external.set(initial);
91
92 for cache in &self.pets {
93 cache.set(initial);
94 }
95
96 for cache in &self.enemies {
97 cache.set(initial);
98 }
99 }
100}
101
102#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
103#[cfg(feature = "rotation-optimizations")]
104pub(crate) struct DamageProjectionKey {
105 revision: u64,
106 owner_revision: u64,
107 #[cfg(any(test, feature = "test-support"))]
108 sheet_attack_power_bits: u64,
109 #[cfg(any(test, feature = "test-support"))]
110 sheet_spell_power_bits: u64,
111 #[cfg(any(test, feature = "test-support"))]
112 sheet_crit_bits: u64,
113 #[cfg(any(test, feature = "test-support"))]
114 sheet_mastery_bits: u64,
115 #[cfg(any(test, feature = "test-support"))]
116 sheet_vers_bits: u64,
117}
118
119#[derive(Clone, Copy, Debug)]
120#[cfg(all(
121 feature = "rotation-optimizations",
122 any(test, feature = "test-support")
123))]
124pub(crate) struct DamageProjectionSheetStats {
125 pub(crate) attack_power: f64,
126 pub(crate) spell_power: f64,
127 pub(crate) crit: f64,
128 pub(crate) mastery: f64,
129 pub(crate) versatility: f64,
130}
131
132#[cfg(feature = "rotation-optimizations")]
133impl DamageProjectionKey {
134 #[cfg(any(test, feature = "test-support"))]
135 pub(crate) fn new(
136 revision: u64,
137 owner_revision: u64,
138 sheet: DamageProjectionSheetStats,
139 ) -> Self {
140 Self {
141 revision,
142 owner_revision,
143 sheet_attack_power_bits: sheet.attack_power.to_bits(),
144 sheet_spell_power_bits: sheet.spell_power.to_bits(),
145 sheet_crit_bits: sheet.crit.to_bits(),
146 sheet_mastery_bits: sheet.mastery.to_bits(),
147 sheet_vers_bits: sheet.versatility.to_bits(),
148 }
149 }
150
151 #[cfg(not(any(test, feature = "test-support")))]
152 pub(crate) const fn new(revision: u64, owner_revision: u64) -> Self {
153 Self {
154 revision,
155 owner_revision,
156 }
157 }
158}
159
160#[derive(Clone, Copy, Debug, Default)]
161#[cfg(feature = "rotation-optimizations")]
162struct DamageProjectionCacheEntry {
163 valid: bool,
164 key: DamageProjectionKey,
165 projection: DamageBuffProjection,
166}
167
168#[derive(Debug)]
170pub(in crate::state) struct BuffTotalsCaches {
171 actor: ActorCaches<BuffCacheEntry>,
172 actor_only: ActorCaches<BuffCacheEntry>,
173 #[cfg(feature = "rotation-optimizations")]
174 damage: ActorCaches<DamageProjectionCacheEntry>,
175 player_scaling: Cell<PlayerScalingCacheEntry>,
176}
177
178#[derive(Clone, Copy, Debug, Default)]
179struct PlayerScalingCacheEntry {
180 valid: bool,
181 revision: u64,
182 buffs: PlayerScalingBuffs,
183}
184
185impl BuffTotalsCaches {
186 pub(super) fn new(enemy_count: usize) -> Self {
187 Self {
188 actor: ActorCaches::new(enemy_count, BuffCacheEntry::default()),
189 actor_only: ActorCaches::new(enemy_count, BuffCacheEntry::default()),
190 #[cfg(feature = "rotation-optimizations")]
191 damage: ActorCaches::new(enemy_count, DamageProjectionCacheEntry::default()),
192 player_scaling: Cell::new(PlayerScalingCacheEntry::default()),
193 }
194 }
195
196 pub(super) fn actor(&self, actor: ActorId, revision: u64) -> Option<BuffTotals> {
197 let cached = self.actor.get(actor)?.get();
198
199 (cached.valid && cached.revision == revision).then_some(cached.totals)
200 }
201
202 pub(super) fn store_actor(&self, actor: ActorId, revision: u64, totals: &BuffTotals) {
203 if let Some(cache) = self.actor.get(actor) {
204 cache.set(BuffCacheEntry {
205 valid: true,
206 revision,
207 owner_revision: 0,
208 totals: *totals,
209 });
210 }
211 }
212
213 pub(super) fn actor_only(
214 &self,
215 actor: ActorId,
216 revision: u64,
217 owner_revision: u64,
218 ) -> Option<BuffTotals> {
219 let cached = self.actor_only.get(actor)?.get();
220
221 (cached.valid && cached.revision == revision && cached.owner_revision == owner_revision)
222 .then_some(cached.totals)
223 }
224
225 pub(super) fn store_actor_only(
226 &self,
227 actor: ActorId,
228 revision: u64,
229 owner_revision: u64,
230 totals: &BuffTotals,
231 ) {
232 if let Some(cache) = self.actor_only.get(actor) {
233 cache.set(BuffCacheEntry {
234 valid: true,
235 revision,
236 owner_revision,
237 totals: *totals,
238 });
239 }
240 }
241
242 pub(super) fn clear(&self) {
243 self.actor.clear(BuffCacheEntry::default());
244 self.actor_only.clear(BuffCacheEntry::default());
245 #[cfg(feature = "rotation-optimizations")]
246 self.damage.clear(DamageProjectionCacheEntry::default());
247 }
248
249 pub(super) fn player_scaling(&self, revision: u64) -> Option<PlayerScalingBuffs> {
250 let cached = self.player_scaling.get();
251
252 (cached.valid && cached.revision == revision).then_some(cached.buffs)
253 }
254
255 pub(super) fn store_player_scaling(&self, revision: u64, buffs: PlayerScalingBuffs) {
256 self.player_scaling.set(PlayerScalingCacheEntry {
257 valid: true,
258 revision,
259 buffs,
260 });
261 }
262
263 pub(super) fn clear_player_scaling(&self) {
264 self.player_scaling.set(PlayerScalingCacheEntry::default());
265 }
266
267 #[cfg(feature = "rotation-optimizations")]
268 fn damage(&self, actor: ActorId, key: DamageProjectionKey) -> Option<DamageBuffProjection> {
269 let cached = self.damage.get(actor)?.get();
270
271 (cached.valid && cached.key == key).then_some(cached.projection)
272 }
273
274 #[cfg(feature = "rotation-optimizations")]
275 fn store_damage(
276 &self,
277 actor: ActorId,
278 key: DamageProjectionKey,
279 projection: DamageBuffProjection,
280 ) {
281 if let Some(cache) = self.damage.get(actor) {
282 cache.set(DamageProjectionCacheEntry {
283 valid: true,
284 key,
285 projection,
286 });
287 }
288 }
289}
290
291#[cfg(feature = "rotation-optimizations")]
292impl super::CombatInstance {
293 pub(crate) fn cached_damage_buff_projection(
294 &self,
295 actor: ActorId,
296 key: DamageProjectionKey,
297 ) -> Option<DamageBuffProjection> {
298 self.buff_totals_caches.damage(actor, key)
299 }
300
301 pub(crate) fn cache_damage_buff_projection(
302 &self,
303 actor: ActorId,
304 key: DamageProjectionKey,
305 projection: DamageBuffProjection,
306 ) {
307 self.buff_totals_caches.store_damage(actor, key, projection);
308 }
309}