diff --git a/turbopack/crates/turbo-tasks/README.md b/turbopack/crates/turbo-tasks/README.md index 7a27625d1ecf2..2bf0dea069bf5 100644 --- a/turbopack/crates/turbo-tasks/README.md +++ b/turbopack/crates/turbo-tasks/README.md @@ -14,6 +14,9 @@ It defines some derived elements from that: - **Tasks:** An instance of a function together with its arguments. - **[`Vc`s ("Value Cells")][`Vc`]:** References to locations associated with tasks where values are stored. The contents of a cell can change after the reexecution of a function due to invalidation. A [`Vc`] can be read to get [a read-only reference][crate::ReadRef] to the stored data, representing a snapshot of that cell at that point in time. +There are a few design patterns that are commonly used with Turbo Tasks: +- **[Singleton Pattern][crate::_singleton_pattern]:** Use a private constructor function to ensure a 1:1 mapping between values and value cells. + [blog-post]: https://nextjs.org/blog/turbopack-incremental-computation [cell id equality]: crate::ResolvedVc#equality--hashing [`Vc`]: crate::Vc diff --git a/turbopack/crates/turbo-tasks/singleton_pattern.md b/turbopack/crates/turbo-tasks/singleton_pattern.md new file mode 100644 index 0000000000000..d88addddf87e7 --- /dev/null +++ b/turbopack/crates/turbo-tasks/singleton_pattern.md @@ -0,0 +1,50 @@ +# Singleton Pattern + +In the context of turbo-tasks, the singleton pattern can be used to intern a value into a `Vc`. This ensures that for a single value, there is exactly one resolved `Vc`. This makes it safer to compare [`ResolvedVc`]s for equality and use them as keys in [`IndexMap`]s or [`HashMap`]s. + +[`IndexMap`]: indexmap::map::IndexMap +[`HashMap`]: std::collections::HashMap + +## Usage + +To use the singleton pattern in turbo-tasks: + +1. Make the `.cell()` method private (Use [`#[turbo_tasks::value]`][value] instead of [`#[turbo_tasks::value(shared)]`][value]). +2. Only call the `.cell()` method in a single [`#[turbo_tasks::function]`][function] which acts as a constructor. +3. The [constructor arguments][TaskInput] act as a key for the constructor task which ensures that the same value is always celled in the same task. +4. Keep in mind that you should only compare `ResolvedVc`s by equality. Unresolved `Vc`s might not be equal to each other. + +[value]: crate::value +[function]: crate::function +[TaskInput]: crate::TaskInput + +## Example + +```ignore +#[turbo_tasks::value] +struct SingletonString { + value: String, +} + +#[turbo_tasks::value_impl] +impl SingletonString { + #[turbo_tasks::function] + fn new(value: String) -> Vc { + Self { value }.cell() + } +} + +#[test] +fn test_singleton() { + let a1 = SingletonString::new("a".to_string()).to_resolved().await?; + let a2 = SingletonString::new("a".to_string()).to_resolved().await?; + let b = SingletonString::new("b".to_string()).to_resolved().await?; + assert_eq!(a1, a2); // Resolved Vcs are equal + assert_ne!(a1, b); + + let set = HashSet::from([a1, a2, b]); + assert_eq!(set.len(), 2); // Only two different values +} +``` + +In this example, `SingletonString` is a struct that contains a single `String` value. The `new` function acts as a constructor for `SingletonString`, ensuring that the same string value is always celled in the same task. diff --git a/turbopack/crates/turbo-tasks/src/lib.rs b/turbopack/crates/turbo-tasks/src/lib.rs index a30a0b25a5e5d..ec757e177b2bf 100644 --- a/turbopack/crates/turbo-tasks/src/lib.rs +++ b/turbopack/crates/turbo-tasks/src/lib.rs @@ -159,6 +159,9 @@ macro_rules! fxindexset { }; } +#[doc = include_str!("../singleton_pattern.md")] +pub mod _singleton_pattern {} + #[doc = include_str!("../function.md")] #[rustfmt::skip] pub use turbo_tasks_macros::function;