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