Skip to main content

wowlab_engine_domain/rotation/
engine.rs

1//! Rotation engine enum that dispatches to JIT or interpreter backend.
2
3use wowlab_types::sim::Rotation;
4
5#[cfg(feature = "jit")]
6use super::compiler::CompiledRotation;
7use super::{
8    backend::RuntimeBackend, buffer::DenseBuffer, context::ContextSchema,
9    decision_trace::DecisionTraceTarget, error::Result, interpreter::InterpretedRotation,
10    resolver::SpecResolver, result::EvalResult,
11};
12
13/// Dispatches rotation evaluation to JIT or interpreter backend.
14#[non_exhaustive]
15pub enum RotationEngine {
16    #[cfg(feature = "jit")]
17    Jit(Box<CompiledRotation>),
18    Interpreted(Box<InterpretedRotation>),
19}
20
21impl RotationEngine {
22    /// Compile a canonical rotation AST using the configured backend.
23    ///
24    /// # Errors
25    /// Returns an error when validation, lowering, or native compilation fails.
26    pub fn compile(rotation: &Rotation, resolver: &SpecResolver) -> Result<Self> {
27        if !rotation.is_empty() {
28            super::ast::validate(rotation)?;
29        }
30
31        Self::compile_ast(rotation, resolver)
32    }
33
34    /// Uses JIT when the `jit` feature is enabled, interpreter otherwise.
35    ///
36    /// # Errors
37    /// Returns an error when parsing, validation, lowering, or compilation fails.
38    pub fn compile_json(json: &str, resolver: &SpecResolver) -> Result<Self> {
39        let rotation = super::ast::parse_and_validate(json)?;
40
41        Self::compile(&rotation, resolver)
42    }
43
44    /// Compile an empty, no-op rotation.
45    ///
46    /// # Errors
47    /// Returns an error when the empty rotation cannot be lowered or compiled.
48    pub fn compile_empty(resolver: &SpecResolver) -> Result<Self> {
49        let rotation = Rotation::empty();
50
51        Self::compile_ast(&rotation, resolver)
52    }
53
54    /// Force the JIT backend.
55    ///
56    /// # Errors
57    /// Returns an error when parsing, lowering, or native compilation fails.
58    #[cfg(feature = "jit")]
59    pub fn compile_jit(json: &str, resolver: &SpecResolver) -> Result<Self> {
60        Ok(Self::Jit(Box::new(CompiledRotation::compile_json(
61            json, resolver,
62        )?)))
63    }
64
65    /// Force the interpreter backend.
66    ///
67    /// # Errors
68    /// Returns an error when parsing, validation, or lowering fails.
69    pub fn compile_interpreted(json: &str, resolver: &SpecResolver) -> Result<Self> {
70        Ok(Self::Interpreted(Box::new(
71            InterpretedRotation::compile_json(json, resolver)?,
72        )))
73    }
74
75    /// Attach a [`DecisionTraceTarget`]. JIT engines are recompiled through the interpreter.
76    #[must_use]
77    pub fn with_decision_trace(mut self, sink: DecisionTraceTarget) -> Self {
78        self.set_decision_trace(sink);
79
80        self
81    }
82
83    /// True when a sink is attached.
84    #[must_use]
85    pub fn has_decision_trace(&self) -> bool {
86        match self {
87            #[cfg(feature = "jit")]
88            Self::Jit(_) => false,
89            Self::Interpreted(r) => r.has_sink(),
90        }
91    }
92
93    /// Attach a sink in place.
94    pub fn set_decision_trace(&mut self, sink: DecisionTraceTarget) {
95        match self {
96            #[cfg(feature = "jit")]
97            Self::Jit(jit) => {
98                // Reuse the JIT's already-prepared schema so this conversion is infallible.
99                let interp = InterpretedRotation::from_parts(
100                    jit.rotation.clone(),
101                    jit.resolver.clone(),
102                    jit.schema().clone(),
103                );
104                let mut boxed = Box::new(interp);
105
106                boxed.set_sink(sink);
107                *self = Self::Interpreted(boxed);
108            }
109            Self::Interpreted(r) => {
110                r.set_sink(sink);
111            }
112        }
113    }
114
115    fn compile_ast(rotation: &Rotation, resolver: &SpecResolver) -> Result<Self> {
116        #[cfg(feature = "jit")]
117        {
118            Ok(Self::Jit(Box::new(CompiledRotation::compile(
119                rotation, resolver,
120            )?)))
121        }
122
123        #[cfg(not(feature = "jit"))]
124        {
125            Ok(Self::Interpreted(Box::new(InterpretedRotation::compile(
126                rotation, resolver,
127            )?)))
128        }
129    }
130}
131
132impl std::fmt::Debug for RotationEngine {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        match self {
135            #[cfg(feature = "jit")]
136            Self::Jit(_) => f.debug_tuple("Jit").finish(),
137            Self::Interpreted(r) => f
138                .debug_struct("Interpreted")
139                .field("has_sink", &r.has_sink())
140                .finish(),
141        }
142    }
143}
144
145impl RuntimeBackend for RotationEngine {
146    fn evaluate(&mut self, buffer: &mut DenseBuffer, now_secs: f64) -> EvalResult {
147        match self {
148            #[cfg(feature = "jit")]
149            Self::Jit(r) => r.evaluate(buffer, now_secs),
150            Self::Interpreted(r) => r.evaluate(buffer, now_secs),
151        }
152    }
153
154    fn schema(&self) -> &ContextSchema {
155        match self {
156            #[cfg(feature = "jit")]
157            Self::Jit(r) => r.schema(),
158            Self::Interpreted(r) => r.schema(),
159        }
160    }
161}