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().node.into_read().strongly_consistent().into()
175    }
176
177    /// [Connects the `OperationVc`][Self::connect] and returns a [strongly
178    /// consistent][crate::ReadConsistency::Strong] read of the value.
179    ///
180    /// This ensures that all internal tasks are finished before the read is returned.
181    pub fn read_trait_strongly_consistent(self) -> ReadVcFuture<T, VcValueTraitCast<T>>
182    where
183        T: VcValueTrait,
184    {
185        self.connect().into_trait_ref().strongly_consistent()
186    }
187}
188
189impl<T> Copy for OperationVc<T> where T: ?Sized {}
190
191impl<T> Clone for OperationVc<T>
192where
193    T: ?Sized,
194{
195    fn clone(&self) -> Self {
196        *self
197    }
198}
199
200impl<T> Hash for OperationVc<T>
201where
202    T: ?Sized,
203{
204    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
205        self.node.hash(state);
206    }
207}
208
209impl<T> PartialEq<OperationVc<T>> for OperationVc<T>
210where
211    T: ?Sized,
212{
213    fn eq(&self, other: &Self) -> bool {
214        self.node == other.node
215    }
216}
217
218impl<T> Eq for OperationVc<T> where T: ?Sized {}
219
220impl<T> Debug for OperationVc<T>
221where
222    T: ?Sized,
223{
224    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225        f.debug_struct("OperationVc")
226            .field("node", &self.node.node)
227            .finish()
228    }
229}
230
231// NOTE: This uses the default implementation of `is_resolved` which returns `true` because we don't
232// want `OperationVc` arguments to get resolved when passed to a `#[turbo_tasks::function]`.
233impl<T> TaskInput for OperationVc<T>
234where
235    T: ?Sized + Send + Sync,
236{
237    fn is_transient(&self) -> bool {
238        self.node.is_transient()
239    }
240}
241
242impl<T> TryFrom<RawVc> for OperationVc<T>
243where
244    T: ?Sized,
245{
246    type Error = anyhow::Error;
247
248    fn try_from(raw: RawVc) -> Result<Self> {
249        if !matches!(raw, RawVc::TaskOutput(..)) {
250            anyhow::bail!("Given RawVc {raw:?} is not a TaskOutput");
251        }
252        Ok(Self {
253            node: Vc::from(raw),
254        })
255    }
256}
257
258impl<T> TraceRawVcs for OperationVc<T>
259where
260    T: ?Sized,
261{
262    fn trace_raw_vcs(&self, trace_context: &mut crate::trace::TraceRawVcsContext) {
263        self.node.trace_raw_vcs(trace_context);
264    }
265}
266
267impl<T> CollectiblesSource for OperationVc<T>
268where
269    T: ?Sized,
270{
271    fn drop_collectibles<Vt: VcValueTrait>(self) {
272        self.node.node.drop_collectibles::<Vt>();
273    }
274
275    fn take_collectibles<Vt: VcValueTrait>(self) -> AutoSet<ResolvedVc<Vt>> {
276        self.node.node.take_collectibles()
277    }
278
279    fn peek_collectibles<Vt: VcValueTrait>(self) -> AutoSet<ResolvedVc<Vt>> {
280        self.node.node.peek_collectibles()
281    }
282}
283
284/// Indicates that a type does not contain any instances of [`Vc`] or [`ResolvedVc`]. It may contain
285/// [`OperationVc`].
286///
287/// # Safety
288///
289/// This trait is marked as unsafe. You should not derive it yourself, but instead you should rely
290/// on [`#[derive(OperationValue)]`][macro@OperationValue] to do it for you.
291pub unsafe trait OperationValue {}
292
293unsafe impl<T: ?Sized + Send> OperationValue for OperationVc<T> {}
294
295impl_auto_marker_trait!(OperationValue);