1use wowlab_buffer_contract::SLOT_ALIGNMENT;
4use wowlab_types::sim::{
5 AuraOn, AuraProjectionKey, Condition, FastMap, RotationAction as AstAction, SpellIdx,
6};
7
8use super::{
9 schema::{ContextField, ContextSchema},
10 slots::{align_up, ensure_slot},
11 spell_usable::{build_cooldown_bypass_offsets, build_spell_usable_offsets},
12};
13use crate::rotation::{
14 buffer::{
15 AuraSlot, BufferOffsets, BufferRequirements, ByteOffset, CooldownSlot, DescriptorId,
16 DescriptorTable, EquipmentSlot, EvalKind, FrozenBufferLayout, HeroTreeSlot, HistorySlot,
17 ItemSlot, ResourceSlot, SetBonusSlot, SlotDescriptor, SlotKind, SlotMaps, SpellSlot,
18 SwingSlot, TalentSlot, UnitSlot, WeaponImbueSlot,
19 },
20 condition::{
21 ResolvedFieldRead, ResolvedKey, domain, resolve_field_read, spell_usable_condition,
22 },
23 error::Result,
24 expr::{FieldType, FieldTypeLayout as _},
25 resolver::{SpecResolver, SpellAuraPolicyKind},
26};
27
28pub(crate) fn schema_draft(table: &DescriptorTable) -> SchemaDraft<'_> {
29 SchemaDraft::new(table)
30}
31
32pub(crate) struct SchemaDraft<'a> {
33 table: &'a DescriptorTable,
34 fields: Vec<ContextField>,
35 current_offset: usize,
36 user_var_offsets: FastMap<String, usize>,
37 user_var_types: FastMap<String, FieldType>,
38
39 slots: SlotMaps<usize>,
40 player_slot: usize,
41 combat_slot: usize,
42 pet_slot: usize,
43
44 standalone_keys: Vec<(DescriptorId, Option<ResolvedKey>, Option<AuraOn>, usize)>,
45
46 spell_usable_pending: Vec<(SpellIdx, usize)>,
47}
48
49impl std::fmt::Debug for SchemaDraft<'_> {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.debug_struct("SchemaDraft")
52 .field("current_offset", &self.current_offset)
53 .field("fields_count", &self.fields.len())
54 .finish()
55 }
56}
57
58impl<'a> SchemaDraft<'a> {
59 fn new(table: &'a DescriptorTable) -> Self {
60 let mut singletons: Vec<&SlotDescriptor> = inventory::iter::<SlotDescriptor>()
61 .filter(|s| s.kind == SlotKind::Singleton)
62 .collect();
63
64 singletons.sort_by_key(|s| s.name);
65
66 let mut current_offset: usize = 0;
67 let mut player_slot: usize = 0;
68 let mut combat_slot: usize = 0;
69 let mut pet_slot: usize = 0;
70
71 for slot in &singletons {
72 current_offset = align_up(current_offset, SLOT_ALIGNMENT);
73
74 match slot.name {
75 domain::PLAYER => player_slot = current_offset,
76 domain::COMBAT => combat_slot = current_offset,
77 domain::PET => pet_slot = current_offset,
78 _ => {}
79 }
80
81 current_offset += slot.size;
82 }
83
84 Self {
85 table,
86 fields: Vec::new(),
87 current_offset,
88 user_var_offsets: FastMap::default(),
89 user_var_types: FastMap::default(),
90 slots: SlotMaps::default(),
91 player_slot,
92 combat_slot,
93 pet_slot,
94 standalone_keys: Vec::new(),
95 spell_usable_pending: Vec::new(),
96 }
97 }
98
99 pub(super) fn add_resolved(&mut self, resolved: &ResolvedFieldRead) -> Option<usize> {
101 if let Some(existing) = self.fields.iter().find(|f| {
102 f.descriptor_id == resolved.descriptor_id
103 && f.key == resolved.key
104 && f.on == resolved.on
105 }) {
106 return Some(existing.offset);
107 }
108
109 let descriptor = self.table.get(resolved.descriptor_id)?;
110
111 let offset = match descriptor.slot_kind {
112 SlotKind::Singleton => {
113 let base = match descriptor.domain {
114 domain::PLAYER => self.player_slot,
115 domain::COMBAT => self.combat_slot,
116 domain::PET => self.pet_slot,
117 _ => return None,
118 };
119
120 base + descriptor.field_offset
121 }
122 SlotKind::Keyed => {
123 let key = resolved.key.as_ref()?;
124 let size = descriptor.slot_size;
125 let off = &mut self.current_offset;
127 let base = match (descriptor.domain, key) {
128 (domain::COOLDOWN, ResolvedKey::Spell(idx)) => {
129 ensure_slot(&mut self.slots.cooldowns, off, size, *idx)
130 }
131 (domain::SPELL, ResolvedKey::Spell(idx)) => {
132 ensure_slot(&mut self.slots.spells, off, size, *idx)
133 }
134 (domain::HISTORY, ResolvedKey::Spell(idx)) => {
135 ensure_slot(&mut self.slots.history, off, size, *idx)
136 }
137 (domain::AURA, ResolvedKey::Aura(idx)) => {
138 let on = resolved.on.unwrap_or(AuraOn::Player);
139 let key = AuraProjectionKey::new(*idx, on);
140
141 ensure_slot(&mut self.slots.aura_projections, off, size, key)
142 }
143 (domain::RESOURCE, ResolvedKey::Resource(rt)) => {
144 ensure_slot(&mut self.slots.resources, off, size, *rt)
145 }
146 (domain::TALENT, ResolvedKey::Named(name)) => {
147 ensure_slot(&mut self.slots.talents, off, size, name.as_str())
148 }
149 (domain::HERO_TREE, ResolvedKey::Named(name)) => {
150 ensure_slot(&mut self.slots.hero_trees, off, size, name.as_str())
151 }
152 (domain::ITEM, ResolvedKey::Named(name)) => {
153 ensure_slot(&mut self.slots.items, off, size, name.as_str())
154 }
155 (domain::SWING, ResolvedKey::Named(name)) => {
156 ensure_slot(&mut self.slots.swings, off, size, name.as_str())
157 }
158 (domain::EQUIPMENT, ResolvedKey::Named(name)) => {
159 ensure_slot(&mut self.slots.equipment, off, size, name.as_str())
160 }
161 (domain::SET_BONUS, ResolvedKey::Named(name)) => {
162 ensure_slot(&mut self.slots.set_bonuses, off, size, name.as_str())
163 }
164 (domain::UNIT, ResolvedKey::Named(name)) => {
165 ensure_slot(&mut self.slots.units, off, size, name.as_str())
166 }
167 (domain::WEAPON_IMBUE, ResolvedKey::Named(name)) => {
168 ensure_slot(&mut self.slots.weapon_imbues, off, size, name.as_str())
169 }
170 _ => return None,
171 };
172
173 base + descriptor.field_offset
174 }
175 _ => return None,
176 };
177
178 let field_type = descriptor.field_type;
179
180 self.fields.push(ContextField {
181 descriptor_id: resolved.descriptor_id,
182 offset,
183 field_type,
184 key: resolved.key.clone(),
185 on: resolved.on,
186 });
187
188 if descriptor.eval_kind == EvalKind::SpellUsable
189 && let Some(ResolvedKey::Spell(idx)) = &resolved.key
190 {
191 self.spell_usable_pending.push((*idx, offset));
192 }
193
194 Some(offset)
195 }
196
197 pub(crate) fn add_user_var(&mut self, name: &str, var_type: FieldType) -> usize {
198 if let Some(&offset) = self.user_var_offsets.get(name) {
199 return offset;
200 }
201
202 self.current_offset = align_up(self.current_offset, var_type.alignment());
203 let offset = self.current_offset;
204
205 self.current_offset += var_type.size();
206 self.user_var_offsets.insert(name.to_string(), offset);
207 self.user_var_types.insert(name.to_string(), var_type);
208
209 offset
210 }
211
212 pub(crate) fn register_condition_fields(
213 &mut self,
214 condition: &Condition,
215 resolver: &SpecResolver,
216 ) -> Result<()> {
217 for field in condition.field_reads() {
218 let resolved_read = resolve_field_read(field, self.table, resolver)?;
219
220 self.add_resolved(&resolved_read).ok_or_else(|| {
221 crate::rotation::Error::validation(
222 "resolved field has no prepared buffer coordinate",
223 )
224 })?;
225 }
226
227 Ok(())
228 }
229
230 pub(crate) fn register_action_fields(
231 &mut self,
232 action: &AstAction,
233 resolver: &SpecResolver,
234 ) -> Result<()> {
235 if !action.enabled() {
236 return Ok(());
237 }
238
239 if let AstAction::Cast { spell, .. } = action {
240 resolver.resolve_spell(spell)?;
241 self.register_condition_fields(&spell_usable_condition(spell), resolver)?;
242 }
243
244 for cond in action.conditions() {
245 self.register_condition_fields(cond, resolver)?;
246 }
247
248 Ok(())
249 }
250
251 pub(crate) fn build_with_resolver(
252 mut self,
253 resolver: Option<&SpecResolver>,
254 requirements: &BufferRequirements,
255 ) -> ContextSchema {
256 if let Some(r) = resolver {
257 self.force_allocate_spec_slots(r);
258 }
259
260 self.allocate_runtime_requirements(requirements);
261
262 let size = self.finalize_size();
263
264 let user_var_offsets = std::mem::take(&mut self.user_var_offsets);
265 let user_var_types = std::mem::take(&mut self.user_var_types);
266 let spell_usable_pending = std::mem::take(&mut self.spell_usable_pending);
267 let fields = std::mem::take(&mut self.fields);
268
269 let buffer_offsets = self.into_buffer_offsets();
270 let spell_usable_offsets =
271 build_spell_usable_offsets(&buffer_offsets, resolver, &spell_usable_pending);
272 let cooldown_bypass_offsets = build_cooldown_bypass_offsets(&buffer_offsets, resolver);
273
274 let layout = FrozenBufferLayout::freeze(buffer_offsets, size);
275
276 ContextSchema {
277 fields,
278 layout,
279 user_var_offsets,
280 user_var_types,
281 spell_usable_offsets,
282 cooldown_bypass_offsets,
283 }
284 }
285
286 fn allocate_runtime_requirements(&mut self, requirements: &BufferRequirements) {
287 macro_rules! allocate {
288 ($field:ident, $size:expr) => {
289 for key in &requirements.$field {
290 ensure_slot(
291 &mut self.slots.$field,
292 &mut self.current_offset,
293 $size,
294 *key,
295 );
296 }
297 };
298 }
299
300 allocate!(cooldowns, CooldownSlot::SIZE);
301 allocate!(auras, AuraSlot::SIZE);
302 allocate!(resources, ResourceSlot::SIZE);
303 allocate!(spells, SpellSlot::SIZE);
304 allocate!(history, HistorySlot::SIZE);
305
306 macro_rules! allocate_named {
307 ($field:ident, $size:expr) => {
308 for key in &requirements.$field {
309 ensure_slot(
310 &mut self.slots.$field,
311 &mut self.current_offset,
312 $size,
313 key.as_ref(),
314 );
315 }
316 };
317 }
318
319 allocate_named!(talents, TalentSlot::SIZE);
320 allocate_named!(hero_trees, HeroTreeSlot::SIZE);
321 allocate_named!(items, ItemSlot::SIZE);
322 allocate_named!(swings, SwingSlot::SIZE);
323 allocate_named!(equipment, EquipmentSlot::SIZE);
324 allocate_named!(set_bonuses, SetBonusSlot::SIZE);
325
326 for key in &requirements.units {
327 self.unit_mut_or_insert(key);
328 }
329
330 allocate_named!(weapon_imbues, WeaponImbueSlot::SIZE);
331 }
332
333 fn unit_mut_or_insert(&mut self, key: &str) -> usize {
335 ensure_slot(
336 &mut self.slots.units,
337 &mut self.current_offset,
338 UnitSlot::SIZE,
339 key,
340 )
341 }
342
343 fn finalize_size(&self) -> usize {
345 align_up(self.current_offset.max(SLOT_ALIGNMENT), SLOT_ALIGNMENT)
346 }
347
348 fn force_allocate_spec_slots(&mut self, r: &SpecResolver) {
350 if let Some(rt) = r.primary_resource_type() {
351 ensure_slot(
352 &mut self.slots.resources,
353 &mut self.current_offset,
354 ResourceSlot::SIZE,
355 rt,
356 );
357 }
358
359 if let Some(rt) = r.secondary_resource_type() {
360 ensure_slot(
361 &mut self.slots.resources,
362 &mut self.current_offset,
363 ResourceSlot::SIZE,
364 rt,
365 );
366 }
367
368 for policy in r.spell_aura_policies() {
369 ensure_slot(
370 &mut self.slots.aura_projections,
371 &mut self.current_offset,
372 AuraSlot::SIZE,
373 AuraProjectionKey::new(policy.aura, policy.on),
374 );
375
376 if let SpellAuraPolicyKind::Override { replacement, .. } = policy.kind {
377 ensure_slot(
378 &mut self.slots.spells,
379 &mut self.current_offset,
380 SpellSlot::SIZE,
381 replacement,
382 );
383 }
384 }
385 }
386
387 fn into_buffer_offsets(self) -> BufferOffsets {
388 let standalone: FastMap<DescriptorId, ByteOffset> = self
389 .standalone_keys
390 .iter()
391 .map(|(desc_id, _, _, offset)| (*desc_id, ByteOffset::new(*offset)))
392 .collect();
393
394 BufferOffsets {
395 slots: self.slots.into_byte_offsets(),
396 player: ByteOffset::new(self.player_slot),
397 combat: ByteOffset::new(self.combat_slot),
398 pet: ByteOffset::new(self.pet_slot),
399 standalone,
400 }
401 }
402}