Skip to main content

trellis_core/
output.rs

1use crate::collection::{downcast_map, downcast_set};
2use crate::input::downcast_input;
3use crate::{
4    CollectionNode, DependencyList, DeriveError, DerivedNode, Graph, InputNode, NodeId,
5    OutputError, OutputKey, Revision, ScopeId, TransactionId,
6};
7use core::marker::PhantomData;
8use std::collections::{BTreeMap, BTreeSet};
9use std::sync::Arc;
10
11type OutputFn<C, O> =
12    dyn for<'ctx> Fn(&OutputContext<'ctx, C, O>) -> Result<O, OutputError> + Send + Sync;
13
14/// Typed handle for a materialized output surface.
15#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
16pub struct MaterializedOutput<O> {
17    key: OutputKey,
18    _marker: PhantomData<fn() -> O>,
19}
20
21impl<O> MaterializedOutput<O> {
22    pub(crate) fn new(key: OutputKey) -> Self {
23        Self {
24            key,
25            _marker: PhantomData,
26        }
27    }
28
29    /// Returns this output's graph-local key.
30    pub fn key(&self) -> OutputKey {
31        self.key
32    }
33}
34
35/// Per-output emission options.
36#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
37pub struct OutputOptions {
38    /// Emit a delta when dependencies changed but materialized value is equal.
39    pub emit_equal: bool,
40}
41
42/// Inspectable metadata for a materialized output.
43#[derive(Clone, Debug, Eq, PartialEq)]
44pub struct OutputMeta {
45    key: OutputKey,
46    debug_name: String,
47    scope: ScopeId,
48    dependencies: DependencyList,
49    options: OutputOptions,
50    created_revision: Revision,
51}
52
53impl OutputMeta {
54    pub(crate) fn new(
55        key: OutputKey,
56        debug_name: impl Into<String>,
57        scope: ScopeId,
58        dependencies: DependencyList,
59        options: OutputOptions,
60        created_revision: Revision,
61    ) -> Self {
62        Self {
63            key,
64            debug_name: debug_name.into(),
65            scope,
66            dependencies,
67            options,
68            created_revision,
69        }
70    }
71
72    /// Returns this output's key.
73    pub fn key(&self) -> OutputKey {
74        self.key
75    }
76
77    /// Returns this output's debug name.
78    pub fn debug_name(&self) -> &str {
79        &self.debug_name
80    }
81
82    /// Returns this output's owning scope.
83    pub fn scope(&self) -> ScopeId {
84        self.scope
85    }
86
87    /// Returns this output's explicit dependencies.
88    pub fn dependencies(&self) -> &DependencyList {
89        &self.dependencies
90    }
91
92    /// Returns this output's emission options.
93    pub fn options(&self) -> OutputOptions {
94        self.options
95    }
96
97    /// Returns the graph revision at which this output was created.
98    pub fn created_revision(&self) -> Revision {
99        self.created_revision
100    }
101}
102
103pub(crate) struct OutputSpec<C, O> {
104    materialize: Arc<OutputFn<C, O>>,
105}
106
107impl<C, O> Clone for OutputSpec<C, O> {
108    fn clone(&self) -> Self {
109        Self {
110            materialize: Arc::clone(&self.materialize),
111        }
112    }
113}
114
115impl<C, O> OutputSpec<C, O> {
116    pub(crate) fn new(
117        materialize: impl for<'ctx> Fn(&OutputContext<'ctx, C, O>) -> Result<O, OutputError>
118        + Send
119        + Sync
120        + 'static,
121    ) -> Self {
122        Self {
123            materialize: Arc::new(materialize),
124        }
125    }
126
127    pub(crate) fn materialize(&self, ctx: &OutputContext<'_, C, O>) -> Result<O, OutputError> {
128        (self.materialize)(ctx)
129    }
130}
131
132/// Read-only context passed to materialized output computations.
133pub struct OutputContext<'graph, C = (), O = ()> {
134    graph: &'graph Graph<C, O>,
135    declared_dependencies: &'graph [NodeId],
136}
137
138impl<'graph, C, O> OutputContext<'graph, C, O> {
139    pub(crate) fn new(graph: &'graph Graph<C, O>, declared_dependencies: &'graph [NodeId]) -> Self {
140        Self {
141            graph,
142            declared_dependencies,
143        }
144    }
145
146    /// Reads a declared input dependency.
147    pub fn input<T>(&self, input: InputNode<T>) -> Result<&'graph T, DeriveError>
148    where
149        T: Clone + PartialEq + Send + Sync + 'static,
150    {
151        let node = input.id();
152        self.require_declared(node)?;
153        self.graph
154            .input_values
155            .get(&node)
156            .and_then(|value| downcast_input::<T>(value.as_ref()))
157            .ok_or(DeriveError::MissingValue(node))
158    }
159
160    /// Reads a declared scalar derived dependency.
161    pub fn derived<T>(&self, derived: DerivedNode<T>) -> Result<&'graph T, DeriveError>
162    where
163        T: Clone + PartialEq + Send + Sync + 'static,
164    {
165        let node = derived.id();
166        self.require_declared(node)?;
167        self.graph
168            .derived_values
169            .get(&node)
170            .and_then(|value| downcast_input::<T>(value.as_ref()))
171            .ok_or(DeriveError::MissingValue(node))
172    }
173
174    /// Reads a declared map collection dependency.
175    pub fn map_collection<K, V>(
176        &self,
177        collection: CollectionNode<K, V>,
178    ) -> Result<&'graph BTreeMap<K, V>, DeriveError>
179    where
180        K: Clone + Ord + Send + Sync + 'static,
181        V: Clone + PartialEq + Send + Sync + 'static,
182    {
183        let node = collection.id();
184        self.require_declared(node)?;
185        self.graph
186            .validate_map_collection_read::<K, V>(node)
187            .map_err(|_| DeriveError::WrongCollectionType(node))?;
188        self.graph
189            .collection_values
190            .get(&node)
191            .and_then(|value| downcast_map::<K, V>(value.as_ref()))
192            .ok_or(DeriveError::MissingValue(node))
193    }
194
195    /// Reads a declared set collection dependency.
196    pub fn set_collection<K>(
197        &self,
198        collection: CollectionNode<K, ()>,
199    ) -> Result<&'graph BTreeSet<K>, DeriveError>
200    where
201        K: Clone + Ord + Send + Sync + 'static,
202    {
203        let node = collection.id();
204        self.require_declared(node)?;
205        self.graph
206            .validate_set_collection_read::<K>(node)
207            .map_err(|_| DeriveError::WrongCollectionType(node))?;
208        self.graph
209            .collection_values
210            .get(&node)
211            .and_then(|value| downcast_set::<K>(value.as_ref()))
212            .ok_or(DeriveError::MissingValue(node))
213    }
214
215    fn require_declared(&self, node: NodeId) -> Result<(), DeriveError> {
216        if self.declared_dependencies.contains(&node) {
217            Ok(())
218        } else {
219            Err(DeriveError::UndeclaredDependency(node))
220        }
221    }
222}
223
224/// Reason a materialized output was cleared.
225#[derive(Copy, Clone, Debug, Eq, PartialEq)]
226#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
227pub enum ClearReason {
228    /// The owning scope was closed.
229    ScopeClosed,
230}
231
232/// Reason a materialized output was rebaselined.
233#[derive(Copy, Clone, Debug, Eq, PartialEq)]
234#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
235pub enum RebaselineReason {
236    /// The host explicitly requested a rebaseline.
237    Requested,
238}
239
240/// Data-only output frame kind.
241#[derive(Clone, Debug, Eq, PartialEq)]
242#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
243pub enum OutputFrameKind<O> {
244    /// Complete current state for a newly attached output.
245    Baseline(O),
246    /// State-replacement delta for an existing output.
247    Delta(O),
248    /// Clear the consumer state for this output.
249    Clear(ClearReason),
250    /// Complete current state after an explicit discontinuity.
251    Rebaseline(O, RebaselineReason),
252}
253
254/// Data-only materialized output frame returned from a transaction.
255#[derive(Clone, Debug, Eq, PartialEq)]
256#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
257pub struct OutputFrame<O> {
258    /// Output key this frame targets.
259    pub output_key: OutputKey,
260    /// Scope that owns this output.
261    pub scope: ScopeId,
262    /// Transaction that emitted this frame.
263    pub transaction_id: TransactionId,
264    /// Graph revision this frame belongs to.
265    pub revision: Revision,
266    /// Frame payload.
267    pub kind: OutputFrameKind<O>,
268}