Skip to main content

torvyn_engine/
types.rs

1//! Engine-specific types for the Torvyn runtime.
2//!
3//! These types bridge Torvyn's domain model with the underlying Wasm engine.
4//! They are designed for the hot path: [`StreamElement`] and [`OutputElement`]
5//! are passed through [`ComponentInvoker`](crate::ComponentInvoker) on every element.
6
7#[cfg(feature = "mock")]
8use torvyn_types::BackpressureSignal;
9use torvyn_types::{BufferHandle, ComponentId, ElementMeta};
10
11// ---------------------------------------------------------------------------
12// CompiledComponent
13// ---------------------------------------------------------------------------
14
15/// A compiled WebAssembly component, ready for instantiation.
16///
17/// This is an opaque wrapper around the engine-specific compiled
18/// representation. For the Wasmtime backend, this wraps
19/// `wasmtime::component::Component`.
20///
21/// `CompiledComponent` is cheaply cloneable (reference-counted internally
22/// by Wasmtime).
23///
24/// # Invariants
25/// - Always produced by a [`WasmEngine::compile_component`](crate::WasmEngine::compile_component) call.
26/// - The inner representation is valid native code for the engine
27///   configuration that produced it.
28///
29/// # COLD PATH — created during pipeline setup.
30#[derive(Clone)]
31pub struct CompiledComponent {
32    /// The engine-specific compiled component.
33    pub(crate) inner: CompiledComponentInner,
34}
35
36/// Backend-specific compiled component storage.
37#[derive(Clone)]
38#[allow(dead_code)]
39pub(crate) enum CompiledComponentInner {
40    /// Wasmtime backend.
41    #[cfg(feature = "wasmtime-backend")]
42    Wasmtime(wasmtime::component::Component),
43    /// Mock variant for testing.
44    #[cfg(feature = "mock")]
45    Mock(MockCompiledComponent),
46    /// Placeholder to prevent empty enum when no features are enabled.
47    #[allow(dead_code)]
48    _Placeholder,
49}
50
51/// Mock compiled component data.
52#[cfg(feature = "mock")]
53#[derive(Clone, Debug)]
54pub struct MockCompiledComponent {
55    /// Unique identifier for this mock compiled component.
56    #[allow(dead_code)]
57    pub(crate) id: u64,
58}
59
60impl std::fmt::Debug for CompiledComponent {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        write!(f, "CompiledComponent")
63    }
64}
65
66// ---------------------------------------------------------------------------
67// ComponentInstance
68// ---------------------------------------------------------------------------
69
70/// A live WebAssembly component instance, ready for invocation.
71///
72/// Wraps the engine-specific instance along with Torvyn metadata
73/// (component ID, fuel state).
74///
75/// # Invariants
76/// - Produced by [`WasmEngine::instantiate`](crate::WasmEngine::instantiate).
77/// - The instance is ready for function calls.
78/// - Fuel and memory limits are applied.
79///
80/// # WARM PATH — created once per component, used on every invocation.
81pub struct ComponentInstance {
82    /// The unique identity of this component instance.
83    pub(crate) component_id: ComponentId,
84
85    /// The engine-specific instance state.
86    pub(crate) inner: ComponentInstanceInner,
87
88    /// Whether the component exports the `lifecycle` interface.
89    pub(crate) has_lifecycle: bool,
90
91    /// Whether the component exports the `processor` interface.
92    pub(crate) has_processor: bool,
93
94    /// Whether the component exports the `source` interface.
95    pub(crate) has_source: bool,
96
97    /// Whether the component exports the `sink` interface.
98    pub(crate) has_sink: bool,
99}
100
101/// Backend-specific instance state.
102pub(crate) enum ComponentInstanceInner {
103    /// Wasmtime backend.
104    #[cfg(feature = "wasmtime-backend")]
105    Wasmtime(WasmtimeInstanceState),
106    /// Mock backend for testing.
107    #[cfg(feature = "mock")]
108    Mock(MockInstanceState),
109    /// Placeholder.
110    #[allow(dead_code)]
111    _Placeholder,
112}
113
114/// Wasmtime-specific instance state.
115///
116/// Holds the `Store` (which contains the instance) and pre-resolved
117/// function handles for hot-path invocation.
118#[cfg(feature = "wasmtime-backend")]
119pub(crate) struct WasmtimeInstanceState {
120    /// The Wasmtime store containing the instance.
121    pub(crate) store: wasmtime::Store<HostState>,
122
123    /// The component instance. Retained for future use (e.g., dynamic export
124    /// discovery when Wasmtime adds Component Model export enumeration).
125    #[allow(dead_code)]
126    pub(crate) instance: wasmtime::component::Instance,
127
128    /// Pre-resolved function handle for `process` (if exported).
129    pub(crate) func_process: Option<wasmtime::component::Func>,
130
131    /// Pre-resolved function handle for `pull` (if exported).
132    pub(crate) func_pull: Option<wasmtime::component::Func>,
133
134    /// Pre-resolved function handle for `push` (if exported).
135    pub(crate) func_push: Option<wasmtime::component::Func>,
136
137    /// Pre-resolved function handle for `lifecycle.init` (if exported).
138    pub(crate) func_init: Option<wasmtime::component::Func>,
139
140    /// Pre-resolved function handle for `lifecycle.teardown` (if exported).
141    pub(crate) func_teardown: Option<wasmtime::component::Func>,
142}
143
144/// Host state stored in each Wasmtime `Store`.
145///
146/// This is the `T` in `Store<T>`. It provides Torvyn-specific context
147/// that host-defined functions can access during component execution.
148#[cfg(feature = "wasmtime-backend")]
149pub(crate) struct HostState {
150    /// The component ID for this instance. Used by host trait impls
151    /// when Torvyn registers host-defined resources.
152    #[allow(dead_code)]
153    pub(crate) component_id: ComponentId,
154
155    /// Resource limits for this store.
156    pub(crate) limits: wasmtime::StoreLimits,
157
158    /// The fuel budget configured for this component. Tracked for
159    /// observability/diagnostics.
160    #[allow(dead_code)]
161    pub(crate) fuel_budget: u64,
162}
163
164/// Mock instance state for testing.
165#[cfg(feature = "mock")]
166pub(crate) struct MockInstanceState {
167    /// Component ID. Retained for diagnostic/logging use.
168    #[allow(dead_code)]
169    pub(crate) component_id: ComponentId,
170    /// Remaining fuel.
171    pub(crate) fuel: u64,
172    /// Simulated memory usage.
173    pub(crate) memory_bytes: usize,
174    /// How many calls have been made.
175    pub(crate) call_count: u64,
176    /// Configurable process response.
177    pub(crate) process_response: Option<ProcessResult>,
178    /// Configurable pull response.
179    pub(crate) pull_response: Option<Option<OutputElement>>,
180    /// Configurable push response.
181    pub(crate) push_response: Option<BackpressureSignal>,
182    /// If true, invocations will trap.
183    pub(crate) should_trap: bool,
184}
185
186impl ComponentInstance {
187    /// Returns the component ID for this instance.
188    ///
189    /// # HOT PATH — zero-cost accessor.
190    #[inline]
191    pub fn component_id(&self) -> ComponentId {
192        self.component_id
193    }
194
195    /// Returns whether this component exports the lifecycle interface.
196    #[inline]
197    pub fn has_lifecycle(&self) -> bool {
198        self.has_lifecycle
199    }
200
201    /// Returns whether this component exports the processor interface.
202    #[inline]
203    pub fn has_processor(&self) -> bool {
204        self.has_processor
205    }
206
207    /// Returns whether this component exports the source interface.
208    #[inline]
209    pub fn has_source(&self) -> bool {
210        self.has_source
211    }
212
213    /// Returns whether this component exports the sink interface.
214    #[inline]
215    pub fn has_sink(&self) -> bool {
216        self.has_sink
217    }
218}
219
220impl std::fmt::Debug for ComponentInstance {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        f.debug_struct("ComponentInstance")
223            .field("component_id", &self.component_id)
224            .field("has_lifecycle", &self.has_lifecycle)
225            .field("has_processor", &self.has_processor)
226            .field("has_source", &self.has_source)
227            .field("has_sink", &self.has_sink)
228            .finish()
229    }
230}
231
232// ---------------------------------------------------------------------------
233// ImportBindings
234// ---------------------------------------------------------------------------
235
236/// Import bindings for component instantiation.
237///
238/// This type carries the resolved imports that satisfy a component's
239/// import requirements. In the Wasmtime backend, this wraps a
240/// configured `wasmtime::component::Linker`.
241///
242/// # COLD PATH — constructed once during pipeline linking.
243pub struct ImportBindings {
244    /// Backend-specific import bindings.
245    pub(crate) inner: ImportBindingsInner,
246}
247
248/// Backend-specific import bindings.
249#[allow(dead_code, clippy::large_enum_variant)]
250pub(crate) enum ImportBindingsInner {
251    /// Wasmtime linker.
252    #[cfg(feature = "wasmtime-backend")]
253    Wasmtime(wasmtime::component::Linker<HostState>),
254    /// Mock bindings for testing.
255    #[cfg(feature = "mock")]
256    Mock,
257    /// Placeholder.
258    #[allow(dead_code)]
259    _Placeholder,
260}
261
262impl std::fmt::Debug for ImportBindings {
263    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264        write!(f, "ImportBindings")
265    }
266}
267
268// ---------------------------------------------------------------------------
269// StreamElement
270// ---------------------------------------------------------------------------
271
272/// A stream element as seen by the engine layer.
273///
274/// This is the Rust representation of the WIT `stream-element` record
275/// that is marshaled into Wasm Component Model types before invocation.
276///
277/// # HOT PATH — passed per element through ComponentInvoker.
278///
279/// # Examples
280/// ```
281/// use torvyn_types::{BufferHandle, ElementMeta, ResourceId};
282/// use torvyn_engine::StreamElement;
283///
284/// let meta = ElementMeta::new(0, 1_000_000, "application/json".into());
285/// let handle = BufferHandle::new(ResourceId::new(5, 0));
286/// let element = StreamElement { meta, payload: handle };
287/// assert_eq!(element.meta.sequence, 0);
288/// ```
289#[derive(Clone, Debug)]
290pub struct StreamElement {
291    /// Metadata for this stream element.
292    pub meta: ElementMeta,
293
294    /// Handle to the buffer containing the element's payload.
295    pub payload: BufferHandle,
296}
297
298// ---------------------------------------------------------------------------
299// OutputElement
300// ---------------------------------------------------------------------------
301
302/// An output element produced by a component.
303///
304/// Returned by source `pull` and processor `process` invocations.
305///
306/// # HOT PATH — returned per element from ComponentInvoker.
307#[derive(Clone, Debug)]
308pub struct OutputElement {
309    /// Metadata for the output element.
310    pub meta: ElementMeta,
311
312    /// Handle to the output buffer.
313    pub payload: BufferHandle,
314}
315
316// ---------------------------------------------------------------------------
317// ProcessResult
318// ---------------------------------------------------------------------------
319
320/// Result of a processor component's `process` invocation.
321///
322/// # HOT PATH — returned per element from `invoke_process`.
323#[derive(Clone, Debug)]
324pub enum ProcessResult {
325    /// The processor produced a transformed output element.
326    Output(OutputElement),
327
328    /// The processor filtered out the element (no output produced).
329    Filtered,
330
331    /// The processor produced multiple output elements (fan-out).
332    Multiple(Vec<OutputElement>),
333}
334
335impl ProcessResult {
336    /// Returns `true` if the processor produced output.
337    ///
338    /// # HOT PATH
339    #[inline]
340    pub fn has_output(&self) -> bool {
341        !matches!(self, ProcessResult::Filtered)
342    }
343
344    /// Returns the number of output elements.
345    ///
346    /// # HOT PATH
347    #[inline]
348    pub fn output_count(&self) -> usize {
349        match self {
350            ProcessResult::Output(_) => 1,
351            ProcessResult::Filtered => 0,
352            ProcessResult::Multiple(v) => v.len(),
353        }
354    }
355}
356
357// ---------------------------------------------------------------------------
358// InvocationResult
359// ---------------------------------------------------------------------------
360
361/// Metadata about a completed invocation, for observability.
362///
363/// # HOT PATH — created after every invocation.
364#[derive(Clone, Debug)]
365pub struct InvocationResult {
366    /// Component that was invoked.
367    pub component_id: ComponentId,
368    /// Function that was called.
369    pub function_name: &'static str,
370    /// Fuel consumed by this invocation.
371    pub fuel_consumed: u64,
372    /// Wall-clock duration of the invocation in nanoseconds.
373    pub duration_ns: u64,
374    /// Whether the invocation succeeded.
375    pub success: bool,
376}
377
378// ---------------------------------------------------------------------------
379// Tests
380// ---------------------------------------------------------------------------
381
382#[cfg(test)]
383mod tests {
384    use super::*;
385    use torvyn_types::{BufferHandle, ElementMeta, ResourceId};
386
387    #[test]
388    fn test_stream_element_creation() {
389        let meta = ElementMeta::new(0, 1000, "text/plain".into());
390        let handle = BufferHandle::new(ResourceId::new(1, 0));
391        let elem = StreamElement {
392            meta,
393            payload: handle,
394        };
395        assert_eq!(elem.meta.sequence, 0);
396        assert_eq!(elem.payload.resource_id().index(), 1);
397    }
398
399    #[test]
400    fn test_process_result_output() {
401        let meta = ElementMeta::new(1, 2000, "text/plain".into());
402        let handle = BufferHandle::new(ResourceId::new(2, 0));
403        let result = ProcessResult::Output(OutputElement {
404            meta,
405            payload: handle,
406        });
407        assert!(result.has_output());
408        assert_eq!(result.output_count(), 1);
409    }
410
411    #[test]
412    fn test_process_result_filtered() {
413        let result = ProcessResult::Filtered;
414        assert!(!result.has_output());
415        assert_eq!(result.output_count(), 0);
416    }
417
418    #[test]
419    fn test_process_result_multiple() {
420        let outputs = vec![
421            OutputElement {
422                meta: ElementMeta::new(0, 100, "a".into()),
423                payload: BufferHandle::new(ResourceId::new(0, 0)),
424            },
425            OutputElement {
426                meta: ElementMeta::new(1, 200, "b".into()),
427                payload: BufferHandle::new(ResourceId::new(1, 0)),
428            },
429        ];
430        let result = ProcessResult::Multiple(outputs);
431        assert!(result.has_output());
432        assert_eq!(result.output_count(), 2);
433    }
434
435    #[test]
436    fn test_component_instance_debug() {
437        let id = ComponentId::new(42);
438        let debug_str = format!("component_id: {:?}", id);
439        assert!(debug_str.contains("42"));
440    }
441
442    #[test]
443    fn test_compiled_component_debug() {
444        // Ensure CompiledComponent's Debug doesn't panic with _Placeholder.
445        let cc = CompiledComponent {
446            inner: CompiledComponentInner::_Placeholder,
447        };
448        let s = format!("{:?}", cc);
449        assert!(s.contains("CompiledComponent"));
450    }
451
452    #[test]
453    fn test_import_bindings_debug() {
454        let ib = ImportBindings {
455            inner: ImportBindingsInner::_Placeholder,
456        };
457        let s = format!("{:?}", ib);
458        assert!(s.contains("ImportBindings"));
459    }
460
461    #[test]
462    fn test_invocation_result() {
463        let result = InvocationResult {
464            component_id: ComponentId::new(1),
465            function_name: "process",
466            fuel_consumed: 100,
467            duration_ns: 5000,
468            success: true,
469        };
470        assert!(result.success);
471        assert_eq!(result.fuel_consumed, 100);
472    }
473}