Skip to main content

wowlab_engine_domain/rotation/buffer/dense/
accessors.rs

1use std::sync::Arc;
2
3use bytemuck::{Pod, Zeroable};
4use wowlab_buffer_contract::SLOT_ALIGNMENT;
5#[cfg(any(test, feature = "test-support"))]
6use wowlab_types::{
7    combat::ResourceType,
8    sim::{AuraKey, AuraProjectionKey, SpellIdx},
9};
10
11use crate::rotation::buffer::{
12    AuraProjectionSlotHandle, AuraSlot, ByteOffset, CombatSlot, CooldownSlot, CooldownSlotHandle,
13    EquipmentSlot, EquipmentSlotHandle, FrozenBufferLayout, HeroTreeSlot, HeroTreeSlotHandle,
14    HistorySlot, HistorySlotHandle, ItemSlot, ItemSlotHandle, LayoutFingerprint, PetSlot,
15    PlayerSlot, ResourceSlot, ResourceSlotHandle, SetBonusSlot, SetBonusSlotHandle, SpellSlot,
16    SpellSlotHandle, SwingSlot, SwingSlotHandle, TalentSlot, TalentSlotHandle, UnitSlot,
17    UnitSlotHandle, WeaponImbueSlot, WeaponImbueSlotHandle,
18};
19
20#[path = "aura_ops.rs"]
21mod aura_ops;
22
23/// `repr(align(8))` backing chunk so the `Vec<SlotChunk>` allocation is `SLOT_ALIGNMENT`-aligned.
24#[derive(Clone, Copy, Debug)]
25#[repr(C, align(8))]
26struct SlotChunk([u8; SLOT_ALIGNMENT]);
27
28// SAFETY: plain `[u8; N]` with no padding is transparently zeroable.
29unsafe impl Zeroable for SlotChunk {}
30// SAFETY: every bit pattern of `[u8; N]` is a valid value.
31unsafe impl Pod for SlotChunk {}
32
33const _: () = assert!(
34    size_of::<SlotChunk>() == SLOT_ALIGNMENT,
35    "SlotChunk must be exactly SLOT_ALIGNMENT bytes wide"
36);
37
38const _: () = assert!(
39    align_of::<SlotChunk>() == SLOT_ALIGNMENT,
40    "SlotChunk must be SLOT_ALIGNMENT-aligned"
41);
42
43#[inline]
44fn chunks_for_bytes(byte_len: usize) -> usize {
45    byte_len.div_ceil(SLOT_ALIGNMENT)
46}
47
48macro_rules! singleton_slot {
49    ($read:ident, $write:ident, $Slot:ty, $field:ident) => {
50        #[inline(always)]
51        pub(crate) fn $read(&self) -> &$Slot {
52            self.slot_ref::<$Slot>(self.layout.offsets.$field)
53        }
54        #[inline(always)]
55        pub(crate) fn $write(&mut self) -> &mut $Slot {
56            self.slot_mut::<$Slot>(self.layout.offsets.$field)
57        }
58    };
59}
60
61macro_rules! keyed_slot {
62    ($read:ident, $write:ident, $Slot:ty, $Key:ty, $map:ident) => {
63        #[cfg(any(test, feature = "test-support"))]
64        #[inline]
65        pub fn $read(&self, key: $Key) -> Option<&$Slot> {
66            wowlab_types::bench_metrics::record_keyed_lookup();
67            let &base = self.layout.offsets.slots.$map.get(&key)?;
68            Some(self.slot_ref::<$Slot>(base))
69        }
70        #[cfg(any(test, feature = "test-support"))]
71        #[inline]
72        pub fn $write(&mut self, key: $Key) -> Option<&mut $Slot> {
73            wowlab_types::bench_metrics::record_keyed_lookup();
74            let &base = self.layout.offsets.slots.$map.get(&key)?;
75            Some(self.slot_mut::<$Slot>(base))
76        }
77    };
78}
79
80macro_rules! keyed_read_slot {
81    ($read:ident, $Slot:ty, $Key:ty, $map:ident) => {
82        #[cfg(any(test, feature = "test-support"))]
83        #[inline]
84        pub fn $read(&self, key: $Key) -> Option<&$Slot> {
85            wowlab_types::bench_metrics::record_keyed_lookup();
86            let &base = self.layout.offsets.slots.$map.get(&key)?;
87            Some(self.slot_ref::<$Slot>(base))
88        }
89    };
90}
91
92macro_rules! keyed_slot_str {
93    ($read:ident, $write:ident, $Slot:ty, $map:ident) => {
94        #[cfg(any(test, feature = "test-support"))]
95        #[inline]
96        pub fn $read(&self, key: &str) -> Option<&$Slot> {
97            wowlab_types::bench_metrics::record_keyed_lookup();
98            let &base = self.layout.offsets.slots.$map.get(key)?;
99            Some(self.slot_ref::<$Slot>(base))
100        }
101        #[cfg(any(test, feature = "test-support"))]
102        #[inline]
103        pub fn $write(&mut self, key: &str) -> Option<&mut $Slot> {
104            wowlab_types::bench_metrics::record_keyed_lookup();
105            let &base = self.layout.offsets.slots.$map.get(key)?;
106            Some(self.slot_mut::<$Slot>(base))
107        }
108    };
109}
110
111macro_rules! handled_slot {
112    ($read:ident, $write:ident, $Slot:ty, $Handle:ty) => {
113        #[inline(always)]
114        pub(crate) fn $read(&self, handle: $Handle) -> &$Slot {
115            self.slot_ref::<$Slot>(handle.offset())
116        }
117
118        #[inline(always)]
119        pub(crate) fn $write(&mut self, handle: $Handle) -> &mut $Slot {
120            self.slot_mut::<$Slot>(handle.offset())
121        }
122    };
123}
124
125/// Flat byte buffer holding all runtime rotation state for zero-allocation evaluation.
126#[derive(Debug)]
127pub struct DenseBuffer {
128    chunks: Vec<SlotChunk>,
129    pub(super) layout: Arc<FrozenBufferLayout>,
130}
131
132impl DenseBuffer {
133    #[must_use]
134    // #t(rust_pub_api_smart_pointers) buffer and program must retain the exact same immutable layout identity.
135    pub(crate) fn new(layout: Arc<FrozenBufferLayout>) -> Self {
136        Self {
137            chunks: vec![SlotChunk([0u8; SLOT_ALIGNMENT]); chunks_for_bytes(layout.byte_len())],
138            layout,
139        }
140    }
141
142    pub fn layout_fingerprint(&self) -> LayoutFingerprint {
143        self.layout.fingerprint()
144    }
145
146    /// Clear every history slot's previous-cast flags (`prev_gcd` / `prev_off_gcd`).
147    pub fn clear_history(&mut self) {
148        for index in 0..self.layout.history_handles().len() {
149            // BOUNDS: index is drawn from the current frozen history-handle slice length.
150            let handle = self.layout.history_handles()[index];
151            let h = self.history_mut_at(handle);
152
153            h.prev_gcd = 0;
154            h.prev_off_gcd = 0;
155        }
156    }
157
158    /// Zero the buffer and write standalone field defaults.
159    pub fn reset_and_defaults(&mut self) {
160        self.bytes_mut().fill(0);
161        self.populate_standalone_defaults();
162    }
163
164    #[cfg(test)]
165    pub(crate) fn layout(&self) -> &Arc<FrozenBufferLayout> {
166        &self.layout
167    }
168
169    #[inline]
170    pub(crate) fn as_mut_ptr(&mut self) -> *mut u8 {
171        self.chunks.as_mut_ptr().cast::<u8>()
172    }
173
174    /// Active byte view truncated to the schema-declared `byte_len`.
175    #[inline]
176    #[must_use]
177    pub(crate) fn bytes(&self) -> &[u8] {
178        // BOUNDS: frozen byte_len determines chunks.len() at construction.
179        &bytemuck::cast_slice::<SlotChunk, u8>(&self.chunks)[..self.layout.byte_len()]
180    }
181    /// Active mutable byte view. See [`Self::bytes`].
182    #[inline]
183    pub(crate) fn bytes_mut(&mut self) -> &mut [u8] {
184        let len = self.layout.byte_len();
185
186        // #t(rust_unchecked_indexing) frozen byte_len determines chunks.len() at construction
187        &mut bytemuck::cast_slice_mut::<SlotChunk, u8>(&mut self.chunks)[..len]
188    }
189    #[inline]
190    fn slot_ref<T>(&self, base: ByteOffset) -> &T
191    where
192        T: Pod,
193    {
194        let base = base.as_usize();
195
196        // #t(rust_unchecked_indexing) base + slot size is schema-validated to lie within byte_len
197        bytemuck::from_bytes(&self.bytes()[base..base + size_of::<T>()])
198    }
199    #[inline]
200    fn slot_mut<T>(&mut self, base: ByteOffset) -> &mut T
201    where
202        T: Pod,
203    {
204        let base = base.as_usize();
205
206        // #t(rust_unchecked_indexing) base + slot size is schema-validated to lie within byte_len
207        bytemuck::from_bytes_mut(&mut self.bytes_mut()[base..base + size_of::<T>()])
208    }
209    fn populate_standalone_defaults(&mut self) {
210        let byte_len = self.layout.byte_len();
211        let defaults = self.layout.standalone_defaults();
212        // #t(rust_unchecked_indexing) frozen byte_len determines chunks.len() at construction
213        let bytes = &mut bytemuck::cast_slice_mut::<SlotChunk, u8>(&mut self.chunks)[..byte_len];
214
215        for &(offset, value) in defaults {
216            value.write_to(bytes, offset);
217        }
218    }
219    singleton_slot!(player, player_mut, PlayerSlot, player);
220    singleton_slot!(combat, combat_mut, CombatSlot, combat);
221    singleton_slot!(pet, pet_mut, PetSlot, pet);
222    handled_slot!(spell_at, spell_mut_at, SpellSlot, SpellSlotHandle);
223    handled_slot!(
224        aura_projection_at,
225        aura_projection_mut_at,
226        AuraSlot,
227        AuraProjectionSlotHandle
228    );
229    handled_slot!(
230        cooldown_at,
231        cooldown_mut_at,
232        CooldownSlot,
233        CooldownSlotHandle
234    );
235    handled_slot!(history_at, history_mut_at, HistorySlot, HistorySlotHandle);
236    handled_slot!(
237        resource_at,
238        resource_mut_at,
239        ResourceSlot,
240        ResourceSlotHandle
241    );
242    handled_slot!(unit_at, unit_mut_at, UnitSlot, UnitSlotHandle);
243    handled_slot!(talent_at, talent_mut_at, TalentSlot, TalentSlotHandle);
244    handled_slot!(
245        hero_tree_at,
246        hero_tree_mut_at,
247        HeroTreeSlot,
248        HeroTreeSlotHandle
249    );
250    handled_slot!(item_at, item_mut_at, ItemSlot, ItemSlotHandle);
251    handled_slot!(swing_at, swing_mut_at, SwingSlot, SwingSlotHandle);
252    handled_slot!(
253        equipment_at,
254        equipment_mut_at,
255        EquipmentSlot,
256        EquipmentSlotHandle
257    );
258    handled_slot!(
259        set_bonus_at,
260        set_bonus_mut_at,
261        SetBonusSlot,
262        SetBonusSlotHandle
263    );
264    handled_slot!(
265        weapon_imbue_at,
266        weapon_imbue_mut_at,
267        WeaponImbueSlot,
268        WeaponImbueSlotHandle
269    );
270    keyed_slot!(cooldown, cooldown_mut, CooldownSlot, SpellIdx, cooldowns);
271    keyed_read_slot!(aura, AuraSlot, AuraKey, auras);
272    keyed_slot!(
273        aura_projection,
274        aura_projection_mut,
275        AuraSlot,
276        AuraProjectionKey,
277        aura_projections
278    );
279    keyed_slot!(
280        resource,
281        resource_mut,
282        ResourceSlot,
283        ResourceType,
284        resources
285    );
286
287    keyed_slot!(spell, spell_mut, SpellSlot, SpellIdx, spells);
288
289    keyed_slot!(history, history_mut, HistorySlot, SpellIdx, history);
290
291    keyed_slot_str!(talent, talent_mut, TalentSlot, talents);
292    keyed_slot_str!(hero_tree, hero_tree_mut, HeroTreeSlot, hero_trees);
293    keyed_slot_str!(item, item_mut, ItemSlot, items);
294    keyed_slot_str!(swing, swing_mut, SwingSlot, swings);
295    keyed_slot_str!(equipment, equipment_mut, EquipmentSlot, equipment);
296    keyed_slot_str!(set_bonus, set_bonus_mut, SetBonusSlot, set_bonuses);
297    keyed_slot_str!(unit, unit_mut, UnitSlot, units);
298    keyed_slot_str!(
299        weapon_imbue,
300        weapon_imbue_mut,
301        WeaponImbueSlot,
302        weapon_imbues
303    );
304}
305
306/// A dense buffer bound to the schema that allocated it.
307///
308/// Typed slot access is available only through this finalized capability.
309/// Raw [`DenseBuffer`] values cannot fabricate or apply layout-local handles.
310#[derive(Debug)]
311pub struct BoundDenseBuffer {
312    inner: DenseBuffer,
313}
314
315impl BoundDenseBuffer {
316    pub(crate) const fn from_schema(inner: DenseBuffer) -> Self {
317        Self { inner }
318    }
319
320    #[inline]
321    #[must_use]
322    pub fn player(&self) -> &PlayerSlot {
323        self.inner.player()
324    }
325
326    #[inline]
327    pub fn player_mut(&mut self) -> &mut PlayerSlot {
328        self.inner.player_mut()
329    }
330
331    #[inline]
332    #[must_use]
333    pub fn combat(&self) -> &CombatSlot {
334        self.inner.combat()
335    }
336
337    #[inline]
338    pub fn combat_mut(&mut self) -> &mut CombatSlot {
339        self.inner.combat_mut()
340    }
341
342    #[inline]
343    #[must_use]
344    pub fn pet(&self) -> &PetSlot {
345        self.inner.pet()
346    }
347
348    #[inline]
349    pub fn pet_mut(&mut self) -> &mut PetSlot {
350        self.inner.pet_mut()
351    }
352}
353
354macro_rules! bound_handled_slot {
355    ($read:ident, $write:ident, $Slot:ty, $Handle:ty) => {
356        impl BoundDenseBuffer {
357            #[inline(always)]
358            #[must_use]
359            pub fn $read(&self, handle: $Handle) -> &$Slot {
360                self.inner.$read(handle)
361            }
362
363            #[inline(always)]
364            pub fn $write(&mut self, handle: $Handle) -> &mut $Slot {
365                self.inner.$write(handle)
366            }
367        }
368    };
369}
370
371bound_handled_slot!(spell_at, spell_mut_at, SpellSlot, SpellSlotHandle);
372bound_handled_slot!(
373    aura_projection_at,
374    aura_projection_mut_at,
375    AuraSlot,
376    AuraProjectionSlotHandle
377);
378bound_handled_slot!(
379    cooldown_at,
380    cooldown_mut_at,
381    CooldownSlot,
382    CooldownSlotHandle
383);
384bound_handled_slot!(history_at, history_mut_at, HistorySlot, HistorySlotHandle);
385bound_handled_slot!(
386    resource_at,
387    resource_mut_at,
388    ResourceSlot,
389    ResourceSlotHandle
390);
391bound_handled_slot!(unit_at, unit_mut_at, UnitSlot, UnitSlotHandle);
392bound_handled_slot!(talent_at, talent_mut_at, TalentSlot, TalentSlotHandle);
393bound_handled_slot!(
394    hero_tree_at,
395    hero_tree_mut_at,
396    HeroTreeSlot,
397    HeroTreeSlotHandle
398);
399bound_handled_slot!(item_at, item_mut_at, ItemSlot, ItemSlotHandle);
400bound_handled_slot!(swing_at, swing_mut_at, SwingSlot, SwingSlotHandle);
401bound_handled_slot!(
402    equipment_at,
403    equipment_mut_at,
404    EquipmentSlot,
405    EquipmentSlotHandle
406);
407bound_handled_slot!(
408    set_bonus_at,
409    set_bonus_mut_at,
410    SetBonusSlot,
411    SetBonusSlotHandle
412);
413bound_handled_slot!(
414    weapon_imbue_at,
415    weapon_imbue_mut_at,
416    WeaponImbueSlot,
417    WeaponImbueSlotHandle
418);
419
420impl std::ops::Deref for BoundDenseBuffer {
421    type Target = DenseBuffer;
422
423    fn deref(&self) -> &Self::Target {
424        &self.inner
425    }
426}
427
428impl std::ops::DerefMut for BoundDenseBuffer {
429    fn deref_mut(&mut self) -> &mut Self::Target {
430        &mut self.inner
431    }
432}