Skip to main content

turbo_tasks/vc/
operation.rs

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