Skip to content

Commit

Permalink
[Turbopack] Introduce OperationVc that wraps operations (#70242)
Browse files Browse the repository at this point in the history
### What?

Introduce `OperationVc` that wraps operations

*(Note: This was called `VcOperation`, but I changed it to `OperationVc` to better match the `ResolvedVc` naming convention -- @bgw)*

We should only expose `OperationVc` into JS to connect to the whole computation (and be strongly consistent with the whole computation).

~also fix query string~ Fixed in #70461

### Why?

We want operations to be strongly consistent to the whole operation. Also HMR subscriptions should include the whole entrypoints and endpoint operation. The `OperationVc` type makes it easy to track that and enforces connecting to the operation correctly.

In regards of the `ResolvedVc` work, we also want operations to be explicit.
  • Loading branch information
sokra authored Dec 9, 2024
1 parent ed26786 commit 3c414b9
Show file tree
Hide file tree
Showing 5 changed files with 353 additions and 4 deletions.
138 changes: 138 additions & 0 deletions crates/next-api/src/operation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use anyhow::Result;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use turbo_tasks::{debug::ValueDebugFormat, trace::TraceRawVcs, OperationVc, RcStr, Vc};

use crate::{
entrypoints::Entrypoints,
route::{Endpoint, Route},
};

/// A derived type of Entrypoints, but with OperationVc<Endpoint> for every endpoint.
///
/// This is needed to call `write_to_disk` which expects an `OperationVc<Endpoint>`.
/// This is important as OperationVcs can be stored in the VersionedContentMap and can be exposed to
/// JS via napi.
#[turbo_tasks::value(shared)]
pub struct EntrypointsOperation {
pub routes: IndexMap<RcStr, RouteOperation>,
pub middleware: Option<MiddlewareOperation>,
pub instrumentation: Option<InstrumentationOperation>,
pub pages_document_endpoint: OperationVc<Box<dyn Endpoint>>,
pub pages_app_endpoint: OperationVc<Box<dyn Endpoint>>,
pub pages_error_endpoint: OperationVc<Box<dyn Endpoint>>,
}

#[turbo_tasks::value_impl]
impl EntrypointsOperation {
#[turbo_tasks::function]
pub async fn new(entrypoints: OperationVc<Entrypoints>) -> Result<Vc<Self>> {
let e = entrypoints.connect().await?;
Ok(Self {
routes: e
.routes
.iter()
.map(|(k, v)| (k.clone(), wrap_route(v, entrypoints)))
.collect(),
middleware: e.middleware.as_ref().map(|m| MiddlewareOperation {
endpoint: wrap(m.endpoint, entrypoints),
}),
instrumentation: e
.instrumentation
.as_ref()
.map(|i| InstrumentationOperation {
node_js: wrap(i.node_js, entrypoints),
edge: wrap(i.edge, entrypoints),
}),
pages_document_endpoint: wrap(e.pages_document_endpoint, entrypoints),
pages_app_endpoint: wrap(e.pages_app_endpoint, entrypoints),
pages_error_endpoint: wrap(e.pages_error_endpoint, entrypoints),
}
.cell())
}
}

fn wrap_route(route: &Route, entrypoints: OperationVc<Entrypoints>) -> RouteOperation {
match route {
Route::Page {
html_endpoint,
data_endpoint,
} => RouteOperation::Page {
html_endpoint: wrap(*html_endpoint, entrypoints),
data_endpoint: wrap(*data_endpoint, entrypoints),
},
Route::PageApi { endpoint } => RouteOperation::PageApi {
endpoint: wrap(*endpoint, entrypoints),
},
Route::AppPage(pages) => RouteOperation::AppPage(
pages
.iter()
.map(|p| AppPageRouteOperation {
original_name: p.original_name.clone(),
html_endpoint: wrap(p.html_endpoint, entrypoints),
rsc_endpoint: wrap(p.rsc_endpoint, entrypoints),
})
.collect(),
),
Route::AppRoute {
original_name,
endpoint,
} => RouteOperation::AppRoute {
original_name: original_name.clone(),
endpoint: wrap(*endpoint, entrypoints),
},
Route::Conflict => RouteOperation::Conflict,
}
}

#[turbo_tasks::function]
fn wrap_endpoint(
endpoint: Vc<Box<dyn Endpoint>>,
op: OperationVc<Entrypoints>,
) -> Vc<Box<dyn Endpoint>> {
let _ = op.connect();
endpoint
}

fn wrap(
endpoint: Vc<Box<dyn Endpoint>>,
op: OperationVc<Entrypoints>,
) -> OperationVc<Box<dyn Endpoint>> {
OperationVc::new(wrap_endpoint(endpoint, op))
}

#[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat)]
pub struct InstrumentationOperation {
pub node_js: OperationVc<Box<dyn Endpoint>>,
pub edge: OperationVc<Box<dyn Endpoint>>,
}

#[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat)]
pub struct MiddlewareOperation {
pub endpoint: OperationVc<Box<dyn Endpoint>>,
}

#[turbo_tasks::value(shared)]
#[derive(Clone, Debug)]
pub enum RouteOperation {
Page {
html_endpoint: OperationVc<Box<dyn Endpoint>>,
data_endpoint: OperationVc<Box<dyn Endpoint>>,
},
PageApi {
endpoint: OperationVc<Box<dyn Endpoint>>,
},
AppPage(Vec<AppPageRouteOperation>),
AppRoute {
original_name: String,
endpoint: OperationVc<Box<dyn Endpoint>>,
},
Conflict,
}

#[derive(TraceRawVcs, Serialize, Deserialize, PartialEq, Eq, ValueDebugFormat, Clone, Debug)]
pub struct AppPageRouteOperation {
pub original_name: String,
pub html_endpoint: OperationVc<Box<dyn Endpoint>>,
pub rsc_endpoint: OperationVc<Box<dyn Endpoint>>,
}
6 changes: 3 additions & 3 deletions turbopack/crates/turbo-tasks/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ pub use turbo_tasks_macros::{function, value_impl, value_trait, KeyValuePair, Ta
pub use value::{TransientInstance, TransientValue, Value};
pub use value_type::{TraitMethod, TraitType, ValueType};
pub use vc::{
Dynamic, ResolvedValue, ResolvedVc, TypedForInput, Upcast, ValueDefault, Vc, VcCast,
VcCellNewMode, VcCellSharedMode, VcDefaultRead, VcRead, VcTransparentRead, VcValueTrait,
VcValueTraitCast, VcValueType, VcValueTypeCast,
Dynamic, OperationVc, ResolvedValue, ResolvedVc, TypedForInput, Upcast, ValueDefault, Vc,
VcCast, VcCellNewMode, VcCellSharedMode, VcDefaultRead, VcRead, VcTransparentRead,
VcValueTrait, VcValueTraitCast, VcValueType, VcValueTypeCast,
};

pub type FxIndexSet<T> = indexmap::IndexSet<T, BuildHasherDefault<FxHasher>>;
Expand Down
53 changes: 52 additions & 1 deletion turbopack/crates/turbo-tasks/src/task/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use std::{future::Future, marker::PhantomData, pin::Pin};
use anyhow::Result;

use super::{TaskInput, TaskOutput};
use crate::{magic_any::MagicAny, RawVc, Vc, VcRead, VcValueType};
use crate::{magic_any::MagicAny, OperationVc, RawVc, Vc, VcRead, VcValueType};

pub type NativeTaskFuture = Pin<Box<dyn Future<Output = Result<RawVc>> + Send>>;

Expand Down Expand Up @@ -151,6 +151,12 @@ impl TaskFnMode for FunctionMode {}
pub struct AsyncFunctionMode;
impl TaskFnMode for AsyncFunctionMode {}

pub struct OperationMode;
impl TaskFnMode for OperationMode {}

pub struct AsyncOperationMode;
impl TaskFnMode for AsyncOperationMode {}

pub struct MethodMode;
impl TaskFnMode for MethodMode {}

Expand Down Expand Up @@ -273,6 +279,29 @@ macro_rules! task_fn_impl {
}
}

impl<F, Output, Recv, $($arg,)*> TaskFnInputFunctionWithThis<OperationMode, Recv, ($($arg,)*)> for F
where
Recv: Sync + Send + 'static,
$($arg: TaskInput + 'static,)*
F: Fn(OperationVc<Recv>, $($arg,)*) -> Output + Send + Sync + Clone + 'static,
Output: TaskOutput + 'static,
{
#[allow(non_snake_case)]
fn functor(&self, this: RawVc, arg: &dyn MagicAny) -> Result<NativeTaskFuture> {
let task_fn = self.clone();
let recv = OperationVc::<Recv>::from(this);

let ($($arg,)*) = get_args::<($($arg,)*)>(arg)?;
$(
let $arg = $arg.clone();
)*

Ok(Box::pin(async move {
Output::try_into_raw_vc((task_fn)(recv, $($arg,)*))
}))
}
}

pub trait $async_fn_trait<A0, $($arg,)*>: Fn(A0, $($arg,)*) -> Self::OutputFuture {
type OutputFuture: Future<Output = <Self as $async_fn_trait<A0, $($arg,)*>>::Output> + Send;
type Output: TaskOutput;
Expand Down Expand Up @@ -333,6 +362,28 @@ macro_rules! task_fn_impl {
}))
}
}

impl<F, Recv, $($arg,)*> TaskFnInputFunctionWithThis<AsyncOperationMode, Recv, ($($arg,)*)> for F
where
Recv: Sync + Send + 'static,
$($arg: TaskInput + 'static,)*
F: $async_fn_trait<OperationVc<Recv>, $($arg,)*> + Clone + Send + Sync + 'static,
{
#[allow(non_snake_case)]
fn functor(&self, this: RawVc, arg: &dyn MagicAny) -> Result<NativeTaskFuture> {
let task_fn = self.clone();
let recv = OperationVc::<Recv>::from(this);

let ($($arg,)*) = get_args::<($($arg,)*)>(arg)?;
$(
let $arg = $arg.clone();
)*

Ok(Box::pin(async move {
<F as $async_fn_trait<OperationVc<Recv>, $($arg,)*>>::Output::try_into_raw_vc((task_fn)(recv, $($arg,)*).await)
}))
}
}
};
}

Expand Down
2 changes: 2 additions & 0 deletions turbopack/crates/turbo-tasks/src/vc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub(crate) mod cast;
mod cell_mode;
pub(crate) mod default;
pub(crate) mod operation;
mod read;
pub(crate) mod resolved;
mod traits;
Expand All @@ -22,6 +23,7 @@ pub use self::{
cast::{VcCast, VcValueTraitCast, VcValueTypeCast},
cell_mode::{VcCellMode, VcCellNewMode, VcCellSharedMode},
default::ValueDefault,
operation::OperationVc,
read::{ReadVcFuture, VcDefaultRead, VcRead, VcTransparentRead},
resolved::{ResolvedValue, ResolvedVc},
traits::{Dynamic, TypedForInput, Upcast, VcValueTrait, VcValueType},
Expand Down
Loading

0 comments on commit 3c414b9

Please # to comment.