Skip to main content

trellis_core/
resource.rs

1use crate::{Graph, GraphResult, NodeId, ScopeId};
2use core::fmt;
3use std::sync::Arc;
4
5/// Stable identity for a desired external resource.
6#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
7pub struct ResourceKey(Box<str>);
8
9impl ResourceKey {
10    /// Creates a resource key from deterministic host-chosen identity.
11    pub fn new(key: impl Into<Box<str>>) -> Self {
12        Self(key.into())
13    }
14
15    /// Returns this key as a string slice.
16    pub fn as_str(&self) -> &str {
17        &self.0
18    }
19}
20
21impl fmt::Debug for ResourceKey {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        f.debug_tuple("ResourceKey").field(&self.0).finish()
24    }
25}
26
27/// Data-only command describing an external resource lifecycle change.
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub enum ResourceCommand<C> {
30    /// Open a resource with an application-defined command payload.
31    Open {
32        /// Resource identity understood by the graph.
33        key: ResourceKey,
34        /// Scope requesting ownership.
35        scope: ScopeId,
36        /// Host-defined command payload.
37        command: C,
38    },
39    /// Close a resource after its final graph-visible owner is removed.
40    Close {
41        /// Resource identity understood by the graph.
42        key: ResourceKey,
43        /// Scope whose ownership was removed.
44        scope: ScopeId,
45    },
46    /// Replace a live resource with an application-defined command payload.
47    Replace {
48        /// Resource identity understood by the graph.
49        key: ResourceKey,
50        /// Scope requesting replacement.
51        scope: ScopeId,
52        /// Host-defined command payload.
53        command: C,
54    },
55    /// Refresh a live resource with an application-defined command payload.
56    Refresh {
57        /// Resource identity understood by the graph.
58        key: ResourceKey,
59        /// Scope requesting refresh.
60        scope: ScopeId,
61        /// Host-defined command payload.
62        command: C,
63    },
64}
65
66impl<C> ResourceCommand<C> {
67    /// Returns the resource key for this command.
68    pub fn key(&self) -> &ResourceKey {
69        match self {
70            Self::Open { key, .. }
71            | Self::Close { key, .. }
72            | Self::Replace { key, .. }
73            | Self::Refresh { key, .. } => key,
74        }
75    }
76
77    /// Returns the scope associated with this command.
78    pub fn scope(&self) -> ScopeId {
79        match self {
80            Self::Open { scope, .. }
81            | Self::Close { scope, .. }
82            | Self::Replace { scope, .. }
83            | Self::Refresh { scope, .. } => *scope,
84        }
85    }
86}
87
88/// Ordered data-only resource plan returned from graph propagation.
89#[derive(Clone, Debug, Eq, PartialEq)]
90pub struct ResourcePlan<C> {
91    commands: Vec<ResourceCommand<C>>,
92}
93
94impl<C> ResourcePlan<C> {
95    /// Creates an empty resource plan.
96    pub fn new() -> Self {
97        Self {
98            commands: Vec::new(),
99        }
100    }
101
102    /// Adds an open command.
103    pub fn open(&mut self, key: ResourceKey, scope: ScopeId, command: C) {
104        self.commands.push(ResourceCommand::Open {
105            key,
106            scope,
107            command,
108        });
109    }
110
111    /// Adds a close command.
112    pub fn close(&mut self, key: ResourceKey, scope: ScopeId) {
113        self.commands.push(ResourceCommand::Close { key, scope });
114    }
115
116    /// Adds a replace command.
117    pub fn replace(&mut self, key: ResourceKey, scope: ScopeId, command: C) {
118        self.commands.push(ResourceCommand::Replace {
119            key,
120            scope,
121            command,
122        });
123    }
124
125    /// Adds a refresh command.
126    pub fn refresh(&mut self, key: ResourceKey, scope: ScopeId, command: C) {
127        self.commands.push(ResourceCommand::Refresh {
128            key,
129            scope,
130            command,
131        });
132    }
133
134    /// Returns ordered commands in this plan.
135    pub fn commands(&self) -> &[ResourceCommand<C>] {
136        &self.commands
137    }
138
139    /// Consumes the plan into ordered commands.
140    pub fn into_commands(self) -> Vec<ResourceCommand<C>> {
141        self.commands
142    }
143
144    pub(crate) fn append(&mut self, other: ResourcePlan<C>) {
145        self.commands.extend(other.commands);
146    }
147}
148
149impl<C> Default for ResourcePlan<C> {
150    fn default() -> Self {
151        Self::new()
152    }
153}
154
155/// Read-only context passed to resource planners.
156pub struct PlanContext<'graph, D> {
157    scope: ScopeId,
158    diff: &'graph D,
159}
160
161impl<'graph, D> PlanContext<'graph, D> {
162    pub(crate) fn new(scope: ScopeId, diff: &'graph D) -> Self {
163        Self { scope, diff }
164    }
165
166    /// Scope that owns resource demand produced by this planner.
167    pub fn scope(&self) -> ScopeId {
168        self.scope
169    }
170
171    /// Structural diff consumed by this planner.
172    pub fn diff(&self) -> &'graph D {
173        self.diff
174    }
175}
176
177type PlannerFn<C, O> = dyn Fn(&Graph<C, O>) -> GraphResult<ResourcePlan<C>>;
178
179pub(crate) struct ResourcePlanner<C, O> {
180    pub(crate) collection: NodeId,
181    pub(crate) scope: ScopeId,
182    run: Arc<PlannerFn<C, O>>,
183}
184
185impl<C, O> Clone for ResourcePlanner<C, O> {
186    fn clone(&self) -> Self {
187        Self {
188            collection: self.collection,
189            scope: self.scope,
190            run: Arc::clone(&self.run),
191        }
192    }
193}
194
195impl<C, O> ResourcePlanner<C, O> {
196    pub(crate) fn new(
197        collection: NodeId,
198        scope: ScopeId,
199        run: impl Fn(&Graph<C, O>) -> GraphResult<ResourcePlan<C>> + 'static,
200    ) -> Self {
201        Self {
202            collection,
203            scope,
204            run: Arc::new(run),
205        }
206    }
207
208    pub(crate) fn run(&self, graph: &Graph<C, O>) -> GraphResult<ResourcePlan<C>> {
209        (self.run)(graph)
210    }
211}