Skip to main content

wowlab_engine_domain/rotation/
engine.rs

1//! Production rotation facade with portable bytecode and explicitly selected native JIT.
2
3use wowlab_types::sim::Rotation;
4
5#[cfg(feature = "jit")]
6use super::compiler::CompiledRotation;
7use super::{
8    backend::RuntimeBackend,
9    buffer::{BufferRequirements, DenseBuffer},
10    bytecode::PortableRotation,
11    context::ContextSchema,
12    decision_trace::DecisionTraceTarget,
13    error::Result,
14    prepared::{PreparedRotation, RotationPreparation},
15    resolver::SpecResolver,
16    result::EvalResult,
17};
18
19/// Production rotation executable with structural evaluation limited to attached decision traces.
20#[non_exhaustive]
21pub enum RotationEngine {
22    Portable(Box<PortableRotation>),
23    #[cfg(feature = "jit")]
24    Jit(Box<CompiledRotation>),
25}
26
27impl RotationEngine {
28    /// Construct the ordinary portable executable from one prepared artifact.
29    #[must_use]
30    pub fn portable(prepared: PreparedRotation) -> Self {
31        Self::Portable(Box::new(PortableRotation::compile(prepared)))
32    }
33
34    /// Prepare and compile a canonical rotation to portable bytecode.
35    ///
36    /// # Errors
37    /// Returns an error when validation, resolution, or schema preparation fails.
38    pub fn compile(rotation: &Rotation, resolver: &SpecResolver) -> Result<Self> {
39        let prepared =
40            RotationPreparation::prepare(rotation, resolver)?.bind(&BufferRequirements::new())?;
41
42        Ok(Self::portable(prepared))
43    }
44
45    /// Parse JSON and compile to portable bytecode.
46    ///
47    /// # Errors
48    /// Returns an error when parsing, validation, resolution, or schema preparation fails.
49    pub fn compile_json(json: &str, resolver: &SpecResolver) -> Result<Self> {
50        let rotation = super::ast::parse_and_validate(json)?;
51
52        Self::compile(&rotation, resolver)
53    }
54
55    /// Compile an empty portable rotation.
56    ///
57    /// # Errors
58    /// Returns an error when schema preparation fails.
59    pub fn compile_empty(resolver: &SpecResolver) -> Result<Self> {
60        Self::compile(&Rotation::empty(), resolver)
61    }
62
63    /// Parse, prepare, and explicitly compile the native JIT executable.
64    ///
65    /// # Errors
66    /// Returns an error when parsing, preparation, lowering, or native compilation fails.
67    #[cfg(feature = "jit")]
68    pub fn compile_jit(json: &str, resolver: &SpecResolver) -> Result<Self> {
69        let rotation = super::ast::parse_and_validate(json)?;
70        let prepared =
71            RotationPreparation::prepare(&rotation, resolver)?.bind(&BufferRequirements::new())?;
72
73        Self::jit(prepared)
74    }
75
76    /// Explicitly compile the native JIT executable from one prepared artifact.
77    ///
78    /// # Errors
79    /// Returns an error when native lowering or compilation fails.
80    #[cfg(feature = "jit")]
81    pub fn jit(prepared: PreparedRotation) -> Result<Self> {
82        Ok(Self::Jit(Box::new(CompiledRotation::compile(prepared)?)))
83    }
84
85    /// Attach explicit decision tracing. This does not replace the selected production backend.
86    #[must_use]
87    pub fn with_decision_trace(mut self, sink: DecisionTraceTarget) -> Self {
88        self.set_decision_trace(sink);
89
90        self
91    }
92
93    /// True when explicit decision tracing is attached.
94    #[must_use]
95    pub fn has_decision_trace(&self) -> bool {
96        match self {
97            Self::Portable(rotation) => rotation.has_trace(),
98            #[cfg(feature = "jit")]
99            Self::Jit(rotation) => rotation.has_trace(),
100        }
101    }
102
103    /// Attach the explicit diagnostic trace target in place.
104    pub fn set_decision_trace(&mut self, sink: DecisionTraceTarget) {
105        match self {
106            Self::Portable(rotation) => rotation.set_trace(sink),
107            #[cfg(feature = "jit")]
108            Self::Jit(rotation) => rotation.set_trace(sink),
109        }
110    }
111
112    /// Prepared schema and complete reference metadata shared by both production compilers.
113    #[must_use]
114    pub fn prepared(&self) -> &PreparedRotation {
115        match self {
116            Self::Portable(rotation) => rotation.prepared(),
117            #[cfg(feature = "jit")]
118            Self::Jit(rotation) => rotation.prepared(),
119        }
120    }
121
122    /// Evaluate the selected compiled backend against one matching dense buffer.
123    pub fn evaluate(&mut self, buffer: &mut DenseBuffer, now_secs: f64) -> EvalResult {
124        RuntimeBackend::evaluate(self, buffer, now_secs)
125    }
126
127    /// Return the immutable schema prepared for the selected executable.
128    #[must_use]
129    pub fn schema(&self) -> &ContextSchema {
130        RuntimeBackend::schema(self)
131    }
132}
133
134impl std::fmt::Debug for RotationEngine {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        match self {
137            Self::Portable(rotation) => f
138                .debug_struct("Portable")
139                .field("has_trace", &rotation.has_trace())
140                .finish(),
141            #[cfg(feature = "jit")]
142            Self::Jit(rotation) => f
143                .debug_struct("Jit")
144                .field("has_trace", &rotation.has_trace())
145                .finish(),
146        }
147    }
148}
149
150impl RuntimeBackend for RotationEngine {
151    fn evaluate(&mut self, buffer: &mut DenseBuffer, now_secs: f64) -> EvalResult {
152        match self {
153            Self::Portable(rotation) => rotation.evaluate(buffer, now_secs),
154            #[cfg(feature = "jit")]
155            Self::Jit(rotation) => rotation.evaluate(buffer, now_secs),
156        }
157    }
158
159    fn schema(&self) -> &ContextSchema {
160        match self {
161            Self::Portable(rotation) => rotation.schema(),
162            #[cfg(feature = "jit")]
163            Self::Jit(rotation) => rotation.schema(),
164        }
165    }
166}