Skip to main content

trellis_core/
output.rs

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