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}