wowlab_engine_combat/state/runtime/
projection.rs1use wowlab_types::sim::SimTime;
4
5use super::CombatInstance;
6use crate::state::program::AuraProjectionDependencyIdx;
7
8bitflags::bitflags! {
9 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
11 pub(crate) struct ProjectionDirty: u8 {
12 const TARGET_HEALTH = 1 << 0;
13 const TARGET_IDENTITY_RANGE_TRAVEL = 1 << 1;
14 const POPULATION = 1 << 2;
15 }
16}
17
18#[derive(Debug)]
20pub(super) struct AuraProjectionDirty {
21 membership: Box<[u64]>,
22 pending: Box<[AuraProjectionDependencyIdx]>,
23 len: usize,
24}
25
26impl AuraProjectionDirty {
27 pub(super) fn new(capacity: usize) -> Self {
28 let word_count = capacity.div_ceil(u64::BITS as usize);
29
30 Self {
31 membership: vec![0; word_count].into_boxed_slice(),
32 pending: vec![AuraProjectionDependencyIdx::default(); capacity].into_boxed_slice(),
33 len: 0,
34 }
35 }
36
37 fn mark(&mut self, index: AuraProjectionDependencyIdx) {
38 let raw = index.as_usize();
39
40 debug_assert!(
41 raw < self.pending.len(),
42 "aura projection dependency {raw} exceeds finalized dirty capacity {}",
43 self.pending.len()
44 );
45
46 if raw >= self.pending.len() {
47 return;
48 }
49
50 let word_index = raw / u64::BITS as usize;
51 let bit = 1_u64 << (raw % u64::BITS as usize);
52 let word = &mut self.membership[word_index];
54
55 if *word & bit != 0 {
56 return;
57 }
58
59 *word |= bit;
60 self.pending[self.len] = index;
62 self.len += 1;
63 }
64
65 fn pop(&mut self) -> Option<AuraProjectionDependencyIdx> {
66 self.len = self.len.checked_sub(1)?;
67 let index = self.pending[self.len];
69 let raw = index.as_usize();
70 let word = &mut self.membership[raw / u64::BITS as usize];
72
73 *word &= !(1_u64 << (raw % u64::BITS as usize));
74
75 Some(index)
76 }
77
78 fn clear(&mut self) {
79 while self.pop().is_some() {}
80 }
81
82 const fn is_empty(&self) -> bool {
83 self.len == 0
84 }
85}
86
87impl CombatInstance {
88 pub(crate) fn mark_projection_dirty(&mut self, dirty: ProjectionDirty, at: SimTime) {
89 self.projection_dirty |= dirty;
90 self.projection_dirty_at = self.projection_dirty_at.max(at);
91 }
92
93 pub(crate) fn take_projection_dirty(&mut self) -> Option<(ProjectionDirty, SimTime)> {
94 if self.projection_dirty.is_empty() {
95 return None;
96 }
97
98 let dirty = std::mem::take(&mut self.projection_dirty);
99 let at = self.projection_dirty_at;
100
101 self.projection_dirty_at = SimTime::ZERO;
102
103 Some((dirty, at))
104 }
105
106 pub(crate) fn mark_aura_projection_dirty(&mut self, index: AuraProjectionDependencyIdx) {
107 self.aura_projection_dirty.mark(index);
108 }
109
110 pub(crate) fn pop_aura_projection_dirty(&mut self) -> Option<AuraProjectionDependencyIdx> {
111 self.aura_projection_dirty.pop()
112 }
113
114 pub(crate) const fn aura_projections_are_dirty(&self) -> bool {
115 !self.aura_projection_dirty.is_empty()
116 }
117
118 pub(crate) fn clear_aura_projection_dirty(&mut self) {
119 self.aura_projection_dirty.clear();
120 }
121
122 pub(crate) fn clear_projection_dirty(&mut self) {
123 self.projection_dirty = ProjectionDirty::empty();
124 self.projection_dirty_at = SimTime::ZERO;
125 self.aura_projection_dirty.clear();
126 }
127
128 #[cfg(test)]
129 pub(crate) fn record_projection_flush(&mut self) {
130 self.projection_flush_count += 1;
131 }
132
133 #[cfg(test)]
134 pub(crate) const fn projection_flush_count(&self) -> u32 {
135 self.projection_flush_count
136 }
137
138 #[cfg(test)]
139 pub(crate) fn reset_projection_flush_count(&mut self) {
140 self.projection_flush_count = 0;
141 }
142}