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