Skip to main content

wowlab_engine_event_authority/
lib.rs

1// #t(file: rust_similar_structs) Opaque event token families deliberately remain distinct so equal-shaped coordinates cannot be interchanged.
2
3//! Engine-owned authority for minting and validating opaque scheduled-event bindings.
4
5use std::sync::atomic::{AtomicU64, Ordering};
6
7use wowlab_types::sim::{ActorId, AuraKey, EnemyIdx, PullId, SpellIdx};
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10#[must_use]
11pub struct ScheduledSpellBinding {
12    program: u64,
13    local: u16,
14    spell_id: SpellIdx,
15}
16
17/// Raw scheduled-spell inspection for engine implementation crates.
18///
19/// This trait is deliberately not re-exported by the public Ports boundary.
20pub trait EngineScheduledSpellAccess {
21    fn engine_coordinates(self) -> SpellIdx;
22}
23
24impl EngineScheduledSpellAccess for ScheduledSpellBinding {
25    fn engine_coordinates(self) -> SpellIdx {
26        self.spell_id
27    }
28}
29
30#[derive(Debug)]
31pub struct ScheduledSpellCatalog {
32    program: u64,
33    spell_ids: Box<[SpellIdx]>,
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37#[must_use]
38pub struct PendingImpactBinding {
39    program: u64,
40    slot: u32,
41    generation: u32,
42}
43
44#[derive(Clone, Copy, Debug, Eq, PartialEq)]
45#[must_use]
46pub struct HookTimerBinding {
47    program: u64,
48    slot: u32,
49    generation: u32,
50}
51
52#[derive(Clone, Copy, Debug, Eq, PartialEq)]
53#[must_use]
54pub struct AuraEventBinding {
55    program: u64,
56    local: u8,
57    concrete: u32,
58    key: AuraKey,
59    target: Option<EnemyIdx>,
60}
61
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63#[must_use]
64pub struct GuardianEventBinding {
65    program: u64,
66    slot: u32,
67    generation: u32,
68    ability: Option<u16>,
69}
70
71#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72#[must_use]
73pub struct PetActionBinding {
74    program: u64,
75    plan: u16,
76}
77
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79#[must_use]
80pub struct RaidEventBinding {
81    program: u64,
82    plan: u16,
83}
84
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86#[must_use]
87pub struct ExternalBuffBinding {
88    program: u64,
89    plan: u16,
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93#[must_use]
94pub struct ActorMovementBinding {
95    program: u64,
96    actor: ActorId,
97    generation: u64,
98}
99
100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
101pub struct PullLifecycleGeneration {
102    pull: PullId,
103    pull_epoch: u32,
104    lifecycle_epoch: u64,
105}
106
107impl PullLifecycleGeneration {
108    #[must_use]
109    pub const fn new(pull: PullId, pull_epoch: u32, lifecycle_epoch: u64) -> Self {
110        Self {
111            pull,
112            pull_epoch,
113            lifecycle_epoch,
114        }
115    }
116
117    #[must_use]
118    pub const fn pull(self) -> PullId {
119        self.pull
120    }
121
122    #[must_use]
123    pub const fn pull_epoch(self) -> u32 {
124        self.pull_epoch
125    }
126
127    #[must_use]
128    pub const fn lifecycle_epoch(self) -> u64 {
129        self.lifecycle_epoch
130    }
131}
132
133#[derive(Clone, Copy, Debug, Eq, PartialEq)]
134#[must_use]
135pub struct RaidAddBinding {
136    program: u64,
137    target: EnemyIdx,
138    activation_generation: u64,
139    encounter_generation: PullLifecycleGeneration,
140}
141
142/// Diagnostic aura inspection available only to engine crates that depend on this authority.
143pub trait DiagnosticEventBinding {
144    type Coordinates;
145
146    fn diagnostic_coordinates(self) -> Self::Coordinates;
147}
148
149impl DiagnosticEventBinding for AuraEventBinding {
150    type Coordinates = (AuraKey, Option<EnemyIdx>);
151
152    fn diagnostic_coordinates(self) -> Self::Coordinates {
153        (self.key, self.target)
154    }
155}
156
157/// Diagnostic guardian inspection available only to engine crates that depend on this authority.
158impl DiagnosticEventBinding for GuardianEventBinding {
159    type Coordinates = (u32, u32);
160
161    fn diagnostic_coordinates(self) -> Self::Coordinates {
162        (self.slot, self.generation)
163    }
164}
165
166/// Diagnostic raid-add inspection available only to engine crates that depend on this authority.
167impl DiagnosticEventBinding for RaidAddBinding {
168    type Coordinates = u64;
169
170    fn diagnostic_coordinates(self) -> Self::Coordinates {
171        self.activation_generation
172    }
173}
174
175#[derive(Debug)]
176pub struct RuntimeEventCatalog {
177    program: u64,
178}
179
180impl RuntimeEventCatalog {
181    #[must_use]
182    pub fn new() -> Self {
183        // #t(rust_global_state) Copy event tokens require a process-unique monotonic program brand to reject cross-program delivery.
184        static NEXT_PROGRAM: AtomicU64 = AtomicU64::new(1);
185
186        Self {
187            program: NEXT_PROGRAM.fetch_add(1, Ordering::Relaxed),
188        }
189    }
190
191    pub const fn pending_impact(&self, slot: u32, generation: u32) -> PendingImpactBinding {
192        PendingImpactBinding {
193            program: self.program,
194            slot,
195            generation,
196        }
197    }
198
199    #[must_use]
200    pub const fn validate_pending_impact(
201        &self,
202        binding: PendingImpactBinding,
203    ) -> Option<(u32, u32)> {
204        if binding.program == self.program {
205            Some((binding.slot, binding.generation))
206        } else {
207            None
208        }
209    }
210
211    pub const fn hook_timer(&self, slot: u32, generation: u32) -> HookTimerBinding {
212        HookTimerBinding {
213            program: self.program,
214            slot,
215            generation,
216        }
217    }
218
219    #[must_use]
220    pub const fn validate_hook_timer(&self, binding: HookTimerBinding) -> Option<(u32, u32)> {
221        if binding.program == self.program {
222            Some((binding.slot, binding.generation))
223        } else {
224            None
225        }
226    }
227
228    pub const fn aura(
229        &self,
230        local: u8,
231        concrete: u32,
232        key: AuraKey,
233        target: Option<EnemyIdx>,
234    ) -> AuraEventBinding {
235        AuraEventBinding {
236            program: self.program,
237            local,
238            concrete,
239            key,
240            target,
241        }
242    }
243
244    #[must_use]
245    pub const fn validate_aura(
246        &self,
247        binding: AuraEventBinding,
248    ) -> Option<(u8, u32, AuraKey, Option<EnemyIdx>)> {
249        if binding.program == self.program {
250            Some((binding.local, binding.concrete, binding.key, binding.target))
251        } else {
252            None
253        }
254    }
255
256    pub const fn guardian(
257        &self,
258        slot: u32,
259        generation: u32,
260        ability: Option<u16>,
261    ) -> GuardianEventBinding {
262        GuardianEventBinding {
263            program: self.program,
264            slot,
265            generation,
266            ability,
267        }
268    }
269
270    #[must_use]
271    pub const fn validate_guardian(
272        &self,
273        binding: GuardianEventBinding,
274    ) -> Option<(u32, u32, Option<u16>)> {
275        if binding.program == self.program {
276            Some((binding.slot, binding.generation, binding.ability))
277        } else {
278            None
279        }
280    }
281
282    pub const fn pet_action(&self, plan: u16) -> PetActionBinding {
283        PetActionBinding {
284            program: self.program,
285            plan,
286        }
287    }
288
289    #[must_use]
290    pub const fn validate_pet_action(&self, binding: PetActionBinding) -> Option<u16> {
291        if binding.program == self.program {
292            Some(binding.plan)
293        } else {
294            None
295        }
296    }
297
298    pub const fn raid_event(&self, plan: u16) -> RaidEventBinding {
299        RaidEventBinding {
300            program: self.program,
301            plan,
302        }
303    }
304
305    #[must_use]
306    pub const fn validate_raid_event(&self, binding: RaidEventBinding) -> Option<u16> {
307        if binding.program == self.program {
308            Some(binding.plan)
309        } else {
310            None
311        }
312    }
313
314    pub const fn external_buff(&self, plan: u16) -> ExternalBuffBinding {
315        ExternalBuffBinding {
316            program: self.program,
317            plan,
318        }
319    }
320
321    #[must_use]
322    pub const fn validate_external_buff(&self, binding: ExternalBuffBinding) -> Option<u16> {
323        if binding.program == self.program {
324            Some(binding.plan)
325        } else {
326            None
327        }
328    }
329
330    pub const fn actor_movement(&self, actor: ActorId, generation: u64) -> ActorMovementBinding {
331        ActorMovementBinding {
332            program: self.program,
333            actor,
334            generation,
335        }
336    }
337
338    #[must_use]
339    pub const fn validate_actor_movement(
340        &self,
341        binding: ActorMovementBinding,
342    ) -> Option<(ActorId, u64)> {
343        if binding.program == self.program {
344            Some((binding.actor, binding.generation))
345        } else {
346            None
347        }
348    }
349
350    pub const fn raid_add(
351        &self,
352        target: EnemyIdx,
353        activation_generation: u64,
354        encounter_generation: PullLifecycleGeneration,
355    ) -> RaidAddBinding {
356        RaidAddBinding {
357            program: self.program,
358            target,
359            activation_generation,
360            encounter_generation,
361        }
362    }
363
364    #[must_use]
365    pub const fn validate_raid_add(
366        &self,
367        binding: RaidAddBinding,
368    ) -> Option<(EnemyIdx, u64, PullLifecycleGeneration)> {
369        if binding.program == self.program {
370            Some((
371                binding.target,
372                binding.activation_generation,
373                binding.encounter_generation,
374            ))
375        } else {
376            None
377        }
378    }
379}
380
381impl Default for RuntimeEventCatalog {
382    fn default() -> Self {
383        Self::new()
384    }
385}
386
387impl ScheduledSpellCatalog {
388    #[must_use]
389    pub fn new(spell_ids: impl IntoIterator<Item = SpellIdx>) -> Self {
390        // #t(rust_global_state) Copy event tokens require a process-unique monotonic program brand to reject cross-program delivery.
391        static NEXT_PROGRAM: AtomicU64 = AtomicU64::new(1);
392
393        Self {
394            program: NEXT_PROGRAM.fetch_add(1, Ordering::Relaxed),
395            spell_ids: spell_ids.into_iter().collect(),
396        }
397    }
398
399    #[must_use]
400    pub fn bind(&self, local: u16) -> Option<ScheduledSpellBinding> {
401        let spell_id = self.spell_ids.get(usize::from(local)).copied()?;
402
403        Some(ScheduledSpellBinding {
404            program: self.program,
405            local,
406            spell_id,
407        })
408    }
409
410    #[must_use]
411    pub fn bind_spell_id(&self, spell_id: SpellIdx) -> Option<ScheduledSpellBinding> {
412        let local = self
413            .spell_ids
414            .iter()
415            .position(|candidate| *candidate == spell_id)
416            .and_then(|index| u16::try_from(index).ok())?;
417
418        self.bind(local)
419    }
420
421    #[must_use]
422    pub fn validate(&self, binding: ScheduledSpellBinding) -> Option<u16> {
423        (binding.program == self.program
424            && self
425                .spell_ids
426                .get(usize::from(binding.local))
427                .is_some_and(|spell| *spell == binding.spell_id))
428        .then_some(binding.local)
429    }
430}