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
11use super::{
12 backend::RotationBackend,
13 buffer::DescriptorTable,
14 context::{ContextSchema, SchemaDraft, schema_draft},
15 error::{Error, Result},
16 expr::FieldType,
17 resolver::SpecResolver,
18};
19
20#[rustfmt::skip]
21pub(super) use value::{EvalValueAbstract, typed_load, typed_select, typed_store};
22
23pub(super) struct Lowerer<'a> {
24 pub(super) schema: &'a ContextSchema,
25 pub(super) resolver: &'a SpecResolver,
26 pub(super) table: &'a DescriptorTable,
27 pub(super) lists: &'a FastMap<String, Vec<AstAction>>,
28 pub(super) variables: &'a FastMap<String, Condition>,
29 pub(super) run_depth: u32,
30 pub(super) unconditionally_returned: bool,
31}
32
33pub(super) fn lower_rotation<B>(
34 b: &mut B,
35 rotation: &Rotation,
36 schema: &ContextSchema,
37 resolver: &SpecResolver,
38 table: &DescriptorTable,
39) where
40 B: RotationBackend,
41{
42 let mut lo = Lowerer {
43 schema,
44 resolver,
45 table,
46 lists: &rotation.lists,
47 variables: &rotation.variables,
48 run_depth: 0,
49 unconditionally_returned: false,
50 };
51
52 action::init_user_variables(&mut lo, b);
53
54 action::lower_actions(&mut lo, b, "actions", &rotation.actions);
55
56 b.return_none();
58}
59
60pub(in crate::rotation) fn eval_condition_as<T>(
61 cond: &Condition,
62 ctx: &mut super::trace::TraceContext<'_>,
63 coerce: impl for<'b> FnOnce(
64 EvalValueAbstract<super::interp_backend::InterpBackend<'b>>,
65 &mut super::interp_backend::InterpBackend<'b>,
66 ) -> T,
67) -> T {
68 let mut backend =
69 super::interp_backend::InterpBackend::new(super::interp_backend::InterpFrame {
70 buffer: ctx.buffer,
71 now: ctx.now_secs,
72 });
73 let mut lo = Lowerer {
74 schema: ctx.schema,
75 resolver: ctx.resolver,
76 table: ctx.table,
77 lists: &ctx.rotation.lists,
78 variables: &ctx.rotation.variables,
79 run_depth: 0,
80 unconditionally_returned: false,
81 };
82 let value = condition::lower_condition(&mut lo, &mut backend, cond);
83
84 coerce(value, &mut backend)
85}
86
87pub(super) fn prepare(
88 rotation: &Rotation,
89 resolver: &SpecResolver,
90) -> Result<(ContextSchema, DescriptorTable)> {
91 fn collect_setvars(actions: &[AstAction], sb: &mut SchemaDraft<'_>, table: &DescriptorTable) {
92 for action in actions {
93 if let AstAction::SetVar { name, value, .. } = action {
94 let ty = infer_field_type(value, table);
95
96 sb.add_user_var(name, ty);
97 }
98 }
99 }
100
101 let table = DescriptorTable::build();
102 let mut sb = schema_draft(&table);
103
104 for (name, init) in &rotation.variables {
105 let ty = infer_field_type(init, &table);
106
107 sb.add_user_var(name, ty);
108 }
109
110 collect_setvars(&rotation.actions, &mut sb, &table);
111
112 for actions in rotation.lists.values() {
113 collect_setvars(actions, &mut sb, &table);
114 }
115
116 for action in &rotation.actions {
117 sb.register_action_fields(action, resolver)?;
118 }
119
120 for actions in rotation.lists.values() {
121 for action in actions {
122 sb.register_action_fields(action, resolver)?;
123 }
124 }
125
126 for cond in rotation.variables.values() {
127 sb.register_condition_fields(cond, resolver)?;
128 }
129
130 let schema = sb.build_with_resolver(Some(resolver));
131
132 let registered = resolver.registered_resource_types();
133
134 for resource_type in schema.buffer_offsets.slots.resources.keys() {
135 if !registered.contains(resource_type) {
136 return Err(Error::validation(format!(
138 "rotation references resource {resource_type:?} but the spec does not register it — \
139 conditions using this resource will always read 0",
140 )));
141 }
142 }
143
144 Ok((schema, table))
145}
146
147pub(super) fn infer_field_type(condition: &Condition, table: &DescriptorTable) -> FieldType {
149 match condition {
150 Condition::Bool { .. }
151 | Condition::Compare { .. }
152 | Condition::And { .. }
153 | Condition::Or { .. }
154 | Condition::Not { .. } => FieldType::Bool,
155 Condition::Int { .. } => FieldType::Int,
156 Condition::Float { .. } | Condition::Var { .. } => FieldType::Float,
157 Condition::Read { field } => {
158 if let Some((_, desc)) = table.lookup(&field.domain, &field.name) {
159 desc.eval_kind
161 .result_field_type()
162 .unwrap_or(desc.field_type)
163 } else {
164 FieldType::Float
165 }
166 }
167 Condition::Arith { .. } | Condition::UnaryMath { .. } | Condition::MinMax { .. } => {
168 FieldType::Float
169 }
170 Condition::IfThenElse { then, .. } => infer_field_type(then, table),
171 _ => unreachable!("rotation validation rejects unsupported condition variants"),
172 }
173}