Skip to main content

turbo_tasks/vc/
operation.rs

1use std::{
2    fmt::Debug,
3    future::Future,
4    hash::Hash,
5    pin::Pin,
6    task::{Context, Poll},
7};
8
9use anyhow::Result;
10use auto_hash_map::AutoSet;
11use bincode::{Decode, Encode};
12use serde::{Deserialize, Serialize};
13pub use turbo_tasks_macros::OperationValue;
14
15use crate::{
16    CollectiblesSource, RawVc, ReadVcFuture, ResolvedVc, TaskInput, UpcastStrict, Vc, VcValueTrait,
17    VcValueTraitCast, VcValueType, marker_trait::impl_auto_marker_trait, trace::TraceRawVcs,
18};
19
20/// A future returned by [`OperationVc::resolve`] that connects an [`OperationVc<T>`] and resolves
21/// it to a [`ResolvedVc<T>`].
22///
23/// Use [`.strongly_consistent()`][Self::strongly_consistent] to opt into strong consistency.
24#[must_use]
25pub struct ResolveOperationVcFuture<T>
26where
27    T: ?Sized,
28{
29    inner: super::ResolveVcFuture<T>,
30}
31
32impl<T: ?Sized> ResolveOperationVcFuture<T> {
33    /// Make the resolution strongly consistent.
34    pub fn strongly_consistent(mut self) -> Self {
35        self.inner.inner = self.inner.inner.strongly_consistent();
36        self
37    }
38}
39
40impl<T: ?Sized> Future for ResolveOperationVcFuture<T> {
41    type Output = anyhow::Result<ResolvedVc<T>>;
42
43    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
44        // SAFETY: we are not moving self
45        let this = unsafe { self.get_unchecked_mut() };
46        // ResolveVcFuture: Unpin, so Pin::new is safe
47        Pin::new(&mut this.inner)
48            .poll(cx)
49            .map(|r| r.map(|node| ResolvedVc { node }))
50    }
51}
52
53impl<T: ?Sized> Unpin for ResolveOperationVcFuture<T> {}
54
55/// A "subtype" (can be converted via [`.connect()`]) of [`Vc`] that
56/// represents a specific call (with arguments) to [a task][macro@crate::function].
57///
58/// Unlike [`Vc`], `OperationVc`:
59///
60/// - Does not potentially refer to task-local information, meaning that it implements
61///   [`NonLocalValue`], and can be used in any [`#[turbo_tasks::value]`][macro@crate::value].
62///
63/// - Has only one potential internal representation, meaning that it has a saner equality
64///   definition.
65///
66/// - Can be [reconnected][OperationVc::connect] to the strongly-consistent compilation graph after
67///   being placed inside of a [`State`].
68///
69/// - Makes sense with [collectibles][`CollectiblesSource`], as it represents a function call, and
70///   only function calls can have issues or side-effects.
71///
72///
73/// ## Equality & Hashing
74///
75/// Equality between two `OperationVc`s means that both have an identical in-memory representation
76/// and point to the same task function call. The implementation of [`Hash`] has similar behavior.
77///
78/// If [connected] and then `.await`ed at the same time, both would likely resolve to the same
79/// [`ReadRef`], though it is possible that they may not if the task or cell is invalidated between
80/// `.await`s.
81///
82/// Because equality is a synchronous operation that cannot read the cell contents, even if the
83/// `OperationVc`s are not equal, it is possible that if `.await`ed, both `OperationVc`s could point
84/// to the same or equal values.
85///
86/// [`.connect()`]: OperationVc::connect
87/// [reconnected]: OperationVc::connect
88/// [connected]: OperationVc::connect
89/// [`NonLocalValue`]: crate::NonLocalValue
90/// [`State`]: crate::State
91/// [`ReadRef`]: crate::ReadRef
92#[must_use]
93#[derive(Serialize, Deserialize, Encode, Decode)]
94#[serde(transparent, bound = "")]
95#[bincode(bounds = "T: ?Sized")]
96#[repr(transparent)]
97pub struct OperationVc<T>
98where
99    T: ?Sized,
100{
101    pub(crate) node: Vc<T>,
102}
103
104impl<T: ?Sized> OperationVc<T> {
105    /// Called by the `#[turbo_tasks::function]` macro.
106    ///
107    /// The macro ensures that the `Vc` is not a local task and it points to a single operation.
108    #[doc(hidden)]
109    #[deprecated = "This is an internal function. Use #[turbo_tasks::function(operation)] instead."]
110    pub fn cell_private(node: Vc<T>) -> Self {
111        debug_assert!(
112            matches!(node.node, RawVc::TaskOutput(..)),
113            "OperationVc::cell_private must be called on the immediate return value of a task \
114             function"
115        );
116        Self { node }
117    }
118
119    /// Marks this operation's underlying function call as a child of the current task, and returns
120    /// a [`Vc`] that can be [resolved][Vc::to_resolved] or read with `.await?`.
121    ///
122    /// By marking this function call as a child of the current task, turbo-tasks will re-run tasks
123    /// as-needed to achieve strong consistency at the root of the function call tree. This explicit
124    /// operation is needed as `OperationVc` types can be stored outside of the call graph as part
125    /// of [`State`][crate::State]s.
126    pub fn connect(self) -> Vc<T> {
127        self.node.node.connect();
128        self.node
129    }
130
131    /// Returns the `RawVc` corresponding to this `Vc`.
132    pub fn into_raw(vc: Self) -> RawVc {
133        vc.node.node
134    }
135
136    /// Upcasts the given `OperationVc<T>` to a `OperationVc<Box<dyn K>>`.
137    ///
138    /// This is also available as an `Into`/`From` conversion.
139    #[inline(always)]
140    pub fn upcast<K>(vc: Self) -> OperationVc<K>
141    where
142        T: UpcastStrict<K>,
143        K: VcValueTrait + ?Sized,
144    {
145        OperationVc {
146            node: Vc::upcast(vc.node),
147        }
148    }
149
150    /// [Connects the `OperationVc`][Self::connect] and resolves the reference until it points to a
151    /// cell directly.
152    ///
153    /// Resolving will wait for task execution to be finished, so that the returned [`ResolvedVc`]
154    /// points to a cell that stores a value.
155    ///
156    /// Resolving is necessary to compare identities of [`Vc`]s.
157    ///
158    /// Use [`.strongly_consistent()`][ResolveOperationVcFuture::strongly_consistent] to opt into
159    /// strong consistency.
160    pub fn resolve(self) -> ResolveOperationVcFuture<T> {
161        ResolveOperationVcFuture {
162            inner: self.connect().resolve(),
163        }
164    }
165
166    /// [Connects the `OperationVc`][Self::connect] and returns a [strongly
167    /// consistent][crate::ReadConsistency::Strong] read of the value.
168    ///
169    /// This ensures that all internal tasks are finished before the read is returned.
170    pub fn read_strongly_consistent(self) -> ReadVcFuture<T>
171    where
172        T: VcValueType,
173    {
174        self.connect()
175            .node
176            .into_read(T::has_serialization())
177            .strongly_consistent()
178            .into()
179    }
180
181    /// [Connects the `OperationVc`][Self::connect] and returns a [strongly
182    /// consistent][crate::ReadConsistency::Strong] read of the value.
183    ///
184    /// This ensures that all internal tasks are finished before the read is returned.
185    pub fn read_trait_strongly_consistent(self) -> ReadVcFuture<T, VcValueTraitCast<T>>
186    where
187        T: VcValueTrait,
188    {
189        self.connect().into_trait_ref().strongly_consistent()
190    }
191}
192
193impl<T> Copy for OperationVc<T> where T: ?Sized {}
194
195impl<T> Clone for OperationVc<T>
196where
197    T: ?Sized,
198{
199    fn clone(&self) -> Self {
200        *self
201    }
202}
203
204impl<T> Hash for OperationVc<T>
205where
206    T: ?Sized,
207{
208    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
209        self.node.hash(state);
210    }
211}
212
213impl<T> PartialEq<OperationVc<T>> for OperationVc<T>
214where
215    T: ?Sized,
216{
217    fn eq(&self, other: &Self) -> bool {
218        self.node == other.node
219    }
220}
221
222impl<T> Eq for OperationVc<T> where T: ?Sized {}
223
224impl<T> Debug for OperationVc<T>
225where
226    T: ?Sized,
227{
228    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229        f.debug_struct("OperationVc")
230            .field("node", &self.node.node)
231            .finish()
232    }
233}
234
235// NOTE: This uses the default implementation of `is_resolved` which returns `true` because we don't
236// want `OperationVc` arguments to get resolved when passed to a `#[turbo_tasks::function]`.
237impl<T> TaskInput for OperationVc<T>
238where
239    T: ?Sized + Send + Sync,
240{
241    fn is_transient(&self) -> bool {
242        self.node.is_transient()
243    }
244}
245
246impl<T> TryFrom<RawVc> for OperationVc<T>
247where
248    T: ?Sized,
249{
250    type Error = anyhow::Error;
251
252    fn try_from(raw: RawVc) -> Result<Self> {
253        if !matches!(raw, RawVc::TaskOutput(..)) {
254            anyhow::bail!("Given RawVc {raw:?} is not a TaskOutput");
255        }
256        Ok(Self {
257            node: Vc::from(raw),
258        })
259    }
260}
261
262impl<T> TraceRawVcs for OperationVc<T>
263where
264    T: ?Sized,
265{
266    fn trace_raw_vcs(&self, trace_context: &mut crate::trace::TraceRawVcsContext) {
267        self.node.trace_raw_vcs(trace_context);
268    }
269}
270
271impl<T> CollectiblesSource for OperationVc<T>
272where
273    T: ?Sized,
274{
275    fn drop_collectibles<Vt: VcValueTrait>(self) {
276        self.node.node.drop_collectibles::<Vt>();
277    }
278
279    fn take_collectibles<Vt: VcValueTrait>(self) -> AutoSet<ResolvedVc<Vt>> {
280        self.node.node.take_collectibles()
281    }
282
283    fn peek_collectibles<Vt: VcValueTrait>(self) -> AutoSet<ResolvedVc<Vt>> {
284        self.node.node.peek_collectibles()
285    }
286}
287
288/// Indicates that a type does not contain any instances of [`Vc`] or [`ResolvedVc`]. It may contain
289/// [`OperationVc`].
290///
291/// # Safety
292///
293/// This trait is marked as unsafe. You should not derive it yourself, but instead you should rely
294/// on [`#[derive(OperationValue)]`][macro@OperationValue] to do it for you.
295pub unsafe trait OperationValue {}
296
297unsafe impl<T: ?Sized + Send> OperationValue for OperationVc<T> {}
298
299impl_auto_marker_trait!(OperationValue);