1use std::marker::PhantomData;
7
8use wowlab_types::{
9 constants::MS_PER_SECOND,
10 numeric::{float_eq, float_ne, safe_div, true_mod},
11 sim::{Condition, Rotation, RotationAction, SpellIdx},
12};
13
14use super::{
15 backend::RotationBackend,
16 buffer::{DenseBuffer, DescriptorTable},
17 context::ContextSchema,
18 decision_trace::{
19 ActionEvaluation, ActionOutcome, ActionRecord, Decision, DecisionTraceSink,
20 DecisionTraceTarget, EvaluationStatus,
21 },
22 resolver::SpecResolver,
23 result::EvalResult,
24 trace::{TraceContext, condition_label_with_values},
25};
26
27#[derive(Clone, Copy)]
28pub(crate) struct ReferenceRecorderCtx<'a> {
29 pub(crate) rotation: &'a Rotation,
30 pub(crate) schema: &'a ContextSchema,
31 pub(crate) resolver: &'a SpecResolver,
32 pub(crate) table: &'a DescriptorTable,
33}
34
35pub(crate) struct ReferenceFrame<'a> {
36 pub(crate) buffer: &'a mut DenseBuffer,
37 pub(crate) now: f64,
38}
39
40struct TraceState<'a> {
41 sink: DecisionTraceTarget,
42 ctx: ReferenceRecorderCtx<'a>,
43 buffer: *mut DenseBuffer,
44 stack: Vec<Decision>,
45 time_ms: u32,
46}
47
48pub(crate) struct ReferenceBackend<'a> {
49 buf: *mut u8,
50 len: usize,
51 now: f64,
52 result: Option<EvalResult>,
53 trace: Option<TraceState<'a>>,
54 _phantom: PhantomData<&'a mut DenseBuffer>,
55}
56
57impl<'a> ReferenceBackend<'a> {
58 pub(crate) fn new(frame: ReferenceFrame<'a>) -> Self {
59 let ReferenceFrame { buffer: buf, now } = frame;
60 let len = buf.bytes().len();
61
62 Self {
63 buf: buf.as_mut_ptr(),
64 len,
65 now,
66 result: None,
67 trace: None,
68 _phantom: PhantomData,
69 }
70 }
71
72 pub(crate) fn with_sink(
73 frame: ReferenceFrame<'a>,
74 sink: DecisionTraceTarget,
75 ctx: ReferenceRecorderCtx<'a>,
76 ) -> Self {
77 let ReferenceFrame { buffer: buf, now } = frame;
78 let time_ms = wowlab_types::numeric::f64_to_u32_saturating_round(now * MS_PER_SECOND);
79 let len = buf.bytes().len();
80 let buffer_ptr: *mut DenseBuffer = buf;
81 let data_ptr = buf.as_mut_ptr();
82
83 Self {
84 buf: data_ptr,
85 len,
86 now,
87 result: None,
88 trace: Some(TraceState {
89 sink,
90 ctx,
91 buffer: buffer_ptr,
92 stack: Vec::new(),
93 time_ms,
94 }),
95 _phantom: PhantomData,
96 }
97 }
98
99 pub(crate) fn finish(self) -> EvalResult {
100 if let Some(state) = self.trace {
101 for d in state.stack.into_iter().rev() {
102 state.sink.record(d);
103 }
104 }
105
106 self.result.unwrap_or(EvalResult::NONE)
107 }
108
109 #[inline]
110 fn bytes(&self) -> &[u8] {
111 unsafe { std::slice::from_raw_parts(self.buf, self.len) }
113 }
114
115 #[inline]
116 fn capture(&mut self, r: EvalResult) {
117 if self.result.is_none() {
118 self.result = Some(r);
119 }
120 }
121}
122
123impl TraceState<'_> {
124 fn rejection_reason(&self, now_secs: f64, failing: &Condition) -> Option<String> {
125 let buffer = unsafe { &mut *self.buffer };
127 let mut tc = TraceContext {
128 rotation: self.ctx.rotation,
129 schema: self.ctx.schema,
130 resolver: self.ctx.resolver,
131 table: self.ctx.table,
132 buffer,
133 now_secs,
134 };
135 let label = condition_label_with_values(failing, &mut tc);
136
137 if label.is_empty() { None } else { Some(label) }
138 }
139}
140
141impl RotationBackend for ReferenceBackend<'_> {
142 type Bool = bool;
143 type Int = i64;
144 type Float = f64;
145 type AndChain = bool;
146 type OrChain = bool;
147 #[cfg(feature = "rotation-optimizations")]
148 type CastGuard = bool;
149
150 #[inline]
151 fn const_bool(&mut self, v: bool) -> bool {
152 v
153 }
154 #[inline]
155 fn const_i32(&mut self, v: i32) -> i64 {
156 i64::from(v)
157 }
158 #[inline]
159 fn const_i64(&mut self, v: i64) -> i64 {
160 v
161 }
162 #[inline]
163 fn const_f64(&mut self, v: f64) -> f64 {
164 v
165 }
166
167 #[inline]
168 fn load_bool(&mut self, offset: usize) -> bool {
169 bytemuck::pod_read_unaligned::<i32>(&self.bytes()[offset..offset + size_of::<i32>()]) != 0
171 }
172 #[inline]
173 fn load_i32(&mut self, offset: usize) -> i64 {
174 i64::from(bytemuck::pod_read_unaligned::<i32>(
176 &self.bytes()[offset..offset + size_of::<i32>()],
177 ))
178 }
179 #[inline]
180 fn load_f64(&mut self, offset: usize) -> f64 {
181 bytemuck::pod_read_unaligned::<f64>(&self.bytes()[offset..offset + size_of::<f64>()])
183 }
184
185 #[inline]
186 fn store_bool(&mut self, offset: usize, v: bool) {
187 if self.result.is_some() {
188 return;
189 }
190
191 let bytes = i32::from(v).to_ne_bytes();
192 unsafe { std::slice::from_raw_parts_mut(self.buf.add(offset), bytes.len()) }
195 .copy_from_slice(&bytes);
196 }
197 #[inline]
198 fn store_i32(&mut self, offset: usize, v: i64) {
199 if self.result.is_some() {
200 return;
201 }
202
203 let bytes = crate::numeric::wrapping_i64_to_i32(v).to_ne_bytes();
204 unsafe { std::slice::from_raw_parts_mut(self.buf.add(offset), bytes.len()) }
207 .copy_from_slice(&bytes);
208 }
209 #[inline]
210 fn store_f64(&mut self, offset: usize, v: f64) {
211 if self.result.is_some() {
212 return;
213 }
214
215 let bytes = v.to_ne_bytes();
216 unsafe { std::slice::from_raw_parts_mut(self.buf.add(offset), bytes.len()) }
219 .copy_from_slice(&bytes);
220 }
221
222 #[inline]
223 fn now(&mut self) -> f64 {
224 self.now
225 }
226
227 #[inline]
228 fn add_f(&mut self, a: f64, b: f64) -> f64 {
229 a + b
230 }
231 #[inline]
232 fn sub_f(&mut self, a: f64, b: f64) -> f64 {
233 a - b
234 }
235 #[inline]
236 fn mul_f(&mut self, a: f64, b: f64) -> f64 {
237 a * b
238 }
239 #[inline]
240 fn safe_div_f(&mut self, a: f64, b: f64) -> f64 {
241 safe_div(a, b)
242 }
243 #[inline]
244 fn true_mod_f(&mut self, a: f64, b: f64) -> f64 {
245 true_mod(a, b)
246 }
247 #[inline]
248 fn min_f(&mut self, a: f64, b: f64) -> f64 {
249 a.min(b)
250 }
251 #[inline]
252 fn max_f(&mut self, a: f64, b: f64) -> f64 {
253 a.max(b)
254 }
255 #[inline]
256 fn floor_f(&mut self, v: f64) -> f64 {
257 v.floor()
258 }
259 #[inline]
260 fn ceil_f(&mut self, v: f64) -> f64 {
261 v.ceil()
262 }
263 #[inline]
264 fn abs_f(&mut self, v: f64) -> f64 {
265 v.abs()
266 }
267
268 #[inline]
269 fn cmp_gt_f(&mut self, a: f64, b: f64) -> bool {
270 a > b
271 }
272 #[inline]
273 fn cmp_gte_f(&mut self, a: f64, b: f64) -> bool {
274 a >= b
275 }
276 #[inline]
277 fn cmp_lt_f(&mut self, a: f64, b: f64) -> bool {
278 a < b
279 }
280 #[inline]
281 fn cmp_lte_f(&mut self, a: f64, b: f64) -> bool {
282 a <= b
283 }
284 #[inline]
285 fn cmp_eq_f(&mut self, a: f64, b: f64) -> bool {
286 float_eq(a, b)
287 }
288 #[inline]
289 fn cmp_ne_f(&mut self, a: f64, b: f64) -> bool {
290 float_ne(a, b)
291 }
292 #[inline]
293 fn cmp_eq_i(&mut self, a: i64, b: i64) -> bool {
294 a == b
295 }
296 #[inline]
297 fn cmp_ne_i(&mut self, a: i64, b: i64) -> bool {
298 a != b
299 }
300 #[inline]
301 fn cmp_gt_i(&mut self, a: i64, b: i64) -> bool {
302 a > b
303 }
304 #[inline]
305 fn cmp_gte_i(&mut self, a: i64, b: i64) -> bool {
306 a >= b
307 }
308 #[inline]
309 fn cmp_lt_i(&mut self, a: i64, b: i64) -> bool {
310 a < b
311 }
312 #[inline]
313 fn cmp_lte_i(&mut self, a: i64, b: i64) -> bool {
314 a <= b
315 }
316
317 #[inline]
318 fn and_b(&mut self, a: bool, b: bool) -> bool {
319 a && b
320 }
321 #[inline]
322 fn or_b(&mut self, a: bool, b: bool) -> bool {
323 a || b
324 }
325 #[inline]
326 fn not_b(&mut self, a: bool) -> bool {
327 !a
328 }
329
330 #[inline]
331 fn begin_and(&mut self, first: bool) -> Self::AndChain {
332 first
333 }
334
335 #[inline]
336 fn push_and(&mut self, chain: &mut Self::AndChain, value: bool) {
337 *chain = *chain && value;
338 }
339
340 #[inline]
341 fn finish_and(&mut self, chain: Self::AndChain) -> bool {
342 chain
343 }
344
345 #[inline]
346 fn begin_or(&mut self, first: bool) -> Self::OrChain {
347 first
348 }
349
350 #[inline]
351 fn push_or(&mut self, chain: &mut Self::OrChain, value: bool) {
352 *chain = *chain || value;
353 }
354
355 #[inline]
356 fn finish_or(&mut self, chain: Self::OrChain) -> bool {
357 chain
358 }
359
360 #[cfg(feature = "rotation-optimizations")]
361 #[inline]
362 fn begin_cast_guard(&mut self, first: bool) -> Self::CastGuard {
363 first
364 }
365
366 #[cfg(feature = "rotation-optimizations")]
367 #[inline]
368 fn push_cast_guard(&mut self, guard: &mut Self::CastGuard, value: bool) {
369 *guard = *guard && value;
370 }
371
372 #[cfg(feature = "rotation-optimizations")]
373 #[inline]
374 fn finish_cast_guard(
375 &mut self,
376 guard: Self::CastGuard,
377 spell_id: u32,
378 empower_rank: u8,
379 ) -> bool {
380 self.return_cast_if(guard, spell_id, empower_rank);
381
382 guard
383 }
384
385 #[inline]
386 fn bool_to_float(&mut self, v: bool) -> f64 {
387 if v { 1.0 } else { 0.0 }
388 }
389 #[inline]
390 fn bool_to_int(&mut self, v: bool) -> i64 {
391 i64::from(v)
392 }
393 #[inline]
394 fn int_to_float(&mut self, v: i64) -> f64 {
395 wowlab_types::numeric::i64_to_f64(v)
396 }
397 #[inline]
398 fn int_to_bool(&mut self, v: i64) -> bool {
399 v != 0
400 }
401 #[inline]
402 fn float_to_bool(&mut self, v: f64) -> bool {
403 v != 0.0
404 }
405 #[inline]
406 fn float_to_int(&mut self, v: f64) -> i64 {
407 wowlab_types::numeric::f64_to_i64_saturating_trunc(v)
408 }
409
410 #[inline]
411 fn select_f(&mut self, cond: bool, t: f64, f: f64) -> f64 {
412 if cond { t } else { f }
413 }
414 #[inline]
415 fn select_i(&mut self, cond: bool, t: i64, f: i64) -> i64 {
416 if cond { t } else { f }
417 }
418 #[inline]
419 fn select_b(&mut self, cond: bool, t: bool, f: bool) -> bool {
420 if cond { t } else { f }
421 }
422
423 #[inline]
424 fn return_cast_if(&mut self, cond: bool, spell_id: u32, empower_rank: u8) {
425 if cond {
426 self.capture(EvalResult::cast(SpellIdx(spell_id), empower_rank));
427 }
428 }
429 #[inline]
430 fn return_wait_if(&mut self, cond: bool, seconds: f32) {
431 if cond {
432 self.capture(EvalResult::wait(seconds));
433 }
434 }
435 #[inline]
436 fn return_pool_if(&mut self, cond: bool, target: f32) {
437 if cond {
438 self.capture(EvalResult::pool(target));
439 }
440 }
441 #[inline]
442 fn return_use_item_if(&mut self, cond: bool, gear_slot: u8, empower_rank: u8) {
443 if cond {
444 self.capture(EvalResult::use_item_raw(gear_slot, empower_rank));
445 }
446 }
447 #[inline]
448 fn return_none(&mut self) {
449 self.capture(EvalResult::NONE);
450 }
451
452 #[inline]
453 fn record_action(&mut self, record: ActionRecord<'_>) {
454 let now_secs = self.now;
455 let Some(state) = self.trace.as_mut() else {
456 return;
457 };
458 let status = record.outcome.status();
459 let rejection_reason = match &record.outcome {
460 ActionOutcome::Rejected { failing } => state.rejection_reason(now_secs, failing),
461 _ => None,
462 };
463 let Some(decision) = state.stack.last_mut() else {
464 return;
465 };
466
467 decision.evaluations.push(ActionEvaluation {
468 list_id: record.list_id.to_string(),
469 action_index: record.action_index,
470 status,
471 rejection_reason,
472 });
473
474 if matches!(record.outcome, ActionOutcome::Fired) {
475 decision.fired_action_index = Some(record.action_index);
476 }
477 }
478
479 #[inline]
480 fn record_action_guarded(
481 &mut self,
482 list_id: &str,
483 action_index: usize,
484 _action: &RotationAction,
485 is_terminator: bool,
486 guard_held: bool,
487 failing: &Condition,
488 ) {
489 let now_secs = self.now;
490 let Some(state) = self.trace.as_mut() else {
491 return;
492 };
493 let already_fired = state
494 .stack
495 .last()
496 .is_some_and(|d| d.fired_action_index.is_some());
497 let (status, rejection_reason) = if already_fired {
498 (EvaluationStatus::NotReached, None)
499 } else if guard_held {
500 if is_terminator {
501 (EvaluationStatus::Fired, None)
502 } else {
503 (EvaluationStatus::Executed, None)
504 }
505 } else {
506 let reason = state.rejection_reason(now_secs, failing);
507
508 (EvaluationStatus::Rejected, reason)
509 };
510 let Some(decision) = state.stack.last_mut() else {
511 return;
512 };
513
514 decision.evaluations.push(ActionEvaluation {
515 list_id: list_id.to_string(),
516 action_index,
517 status,
518 rejection_reason,
519 });
520
521 if status == EvaluationStatus::Fired {
522 decision.fired_action_index = Some(action_index);
523 }
524 }
525
526 #[inline]
527 fn begin_list_recording(&mut self, list_id: &str) {
528 let Some(state) = self.trace.as_mut() else {
529 return;
530 };
531
532 state.stack.push(Decision {
533 time_ms: state.time_ms,
534 list_id: list_id.to_string(),
535 fired_action_index: None,
536 evaluations: Vec::new(),
537 nested: Vec::new(),
538 });
539 }
540
541 #[inline]
542 fn end_list_recording(&mut self) {
543 let Some(state) = self.trace.as_mut() else {
544 return;
545 };
546 let Some(finished) = state.stack.pop() else {
547 return;
548 };
549
550 if let Some(parent) = state.stack.last_mut() {
551 parent.nested.push(finished);
552 } else {
553 state.sink.record(finished);
554 }
555 }
556}