wowlab_engine_domain/rotation/lower/
mod.rs1mod action;
4mod condition;
5mod eval_kind;
6pub(super) mod ops;
7mod value;
8
9use wowlab_types::sim::{Condition, FastMap, Rotation, RotationAction as AstAction};
10
11#[cfg(test)]
12thread_local! {
13 static LOWERING_COUNT: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
14}
15
16use super::{
17 backend::RotationBackend,
18 buffer::{BufferRequirements, DescriptorTable},
19 context::{ContextSchema, SchemaDraft, schema_draft},
20 error::{Error, Result},
21 expr::FieldType,
22 resolver::SpecResolver,
23};
24
25#[rustfmt::skip]
26pub(super) use value::{EvalValueAbstract, typed_load, typed_select, typed_store};
27
28pub(super) struct Lowerer<'a> {
29 pub(super) schema: &'a ContextSchema,
30 pub(super) resolver: &'a SpecResolver,
31 pub(super) table: &'a DescriptorTable,
32 pub(super) lists: &'a FastMap<String, Vec<AstAction>>,
33 pub(super) variables: &'a FastMap<String, Condition>,
34 pub(super) run_depth: u32,
35 pub(super) unconditionally_returned: bool,
36 pub(super) spell_usability_prevalidated: bool,
37}
38
39pub(super) fn lower_rotation<B>(
40 b: &mut B,
41 rotation: &Rotation,
42 schema: &ContextSchema,
43 resolver: &SpecResolver,
44 table: &DescriptorTable,
45 spell_usability_prevalidated: bool,
46) where
47 B: RotationBackend,
48{
49 wowlab_types::bench_metrics::record_rotation_ast_lowering();
50
51 #[cfg(test)]
52 LOWERING_COUNT.with(|count| count.set(count.get().saturating_add(1)));
53
54 let mut lo = Lowerer {
55 schema,
56 resolver,
57 table,
58 lists: &rotation.lists,
59 variables: &rotation.variables,
60 run_depth: 0,
61 unconditionally_returned: false,
62 spell_usability_prevalidated,
63 };
64
65 action::init_user_variables(&mut lo, b);
66
67 action::lower_actions(&mut lo, b, "actions", &rotation.actions);
68
69 b.return_none();
71}
72
73#[cfg(test)]
74pub(super) fn reset_lowering_count() {
75 LOWERING_COUNT.with(|count| count.set(0));
76}
77
78#[cfg(test)]
79pub(super) fn lowering_count() -> usize {
80 LOWERING_COUNT.with(std::cell::Cell::get)
81}
82
83pub(in crate::rotation) fn eval_condition_as<T>(
84 cond: &Condition,
85 ctx: &mut super::trace::TraceContext<'_>,
86 coerce: impl for<'b> FnOnce(
87 EvalValueAbstract<super::reference_backend::ReferenceBackend<'b>>,
88 &mut super::reference_backend::ReferenceBackend<'b>,
89 ) -> T,
90) -> T {
91 let mut backend =
92 super::reference_backend::ReferenceBackend::new(super::reference_backend::ReferenceFrame {
93 buffer: ctx.buffer,
94 now: ctx.now_secs,
95 });
96 let mut lo = Lowerer {
97 schema: ctx.schema,
98 resolver: ctx.resolver,
99 table: ctx.table,
100 lists: &ctx.rotation.lists,
101 variables: &ctx.rotation.variables,
102 run_depth: 0,
103 unconditionally_returned: false,
104 spell_usability_prevalidated: false,
105 };
106 let value = condition::lower_condition(&mut lo, &mut backend, cond);
107
108 coerce(value, &mut backend)
109}
110
111pub(super) fn prepare(
112 rotation: &Rotation,
113 resolver: &SpecResolver,
114 table: &DescriptorTable,
115 requirements: &BufferRequirements,
116) -> Result<ContextSchema> {
117 fn collect_setvars(actions: &[AstAction], sb: &mut SchemaDraft<'_>, table: &DescriptorTable) {
118 for action in actions {
119 if let AstAction::SetVar { name, value, .. } = action {
120 let ty = infer_field_type(value, table);
121
122 sb.add_user_var(name, ty);
123 }
124 }
125 }
126
127 let mut sb = schema_draft(table);
128
129 for (name, init) in &rotation.variables {
130 let ty = infer_field_type(init, table);
131
132 sb.add_user_var(name, ty);
133 }
134
135 collect_setvars(&rotation.actions, &mut sb, table);
136
137 for actions in rotation.lists.values() {
138 collect_setvars(actions, &mut sb, table);
139 }
140
141 for action in &rotation.actions {
142 sb.register_action_fields(action, resolver)?;
143 }
144
145 for actions in rotation.lists.values() {
146 for action in actions {
147 sb.register_action_fields(action, resolver)?;
148 }
149 }
150
151 for cond in rotation.variables.values() {
152 sb.register_condition_fields(cond, resolver)?;
153 }
154
155 let schema = sb.build_with_resolver(Some(resolver), requirements);
156
157 let registered = resolver.registered_resource_types();
158
159 for resource_type in schema.layout.offsets.slots.resources.keys() {
160 if !registered.contains(resource_type) {
161 return Err(Error::validation(format!(
163 "rotation references resource {resource_type:?} but the spec does not register it — \
164 conditions using this resource will always read 0",
165 )));
166 }
167 }
168
169 Ok(schema)
170}
171
172pub(super) fn infer_field_type(condition: &Condition, table: &DescriptorTable) -> FieldType {
174 match condition {
175 Condition::Bool { .. }
176 | Condition::Compare { .. }
177 | Condition::And { .. }
178 | Condition::Or { .. }
179 | Condition::Not { .. } => FieldType::Bool,
180 Condition::Int { .. } => FieldType::Int,
181 Condition::Float { .. } | Condition::Var { .. } => FieldType::Float,
182 Condition::Read { field } => {
183 if let Some((_, desc)) = table.lookup(&field.domain, &field.name) {
184 desc.eval_kind
186 .result_field_type()
187 .unwrap_or(desc.field_type)
188 } else {
189 FieldType::Float
190 }
191 }
192 Condition::Arith { .. } | Condition::UnaryMath { .. } | Condition::MinMax { .. } => {
193 FieldType::Float
194 }
195 Condition::IfThenElse { then, .. } => infer_field_type(then, table),
196 _ => unreachable!("rotation validation rejects unsupported condition variants"),
197 }
198}