Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions turbopack/crates/turbo-tasks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions turbopack/crates/turbo-tasks/singleton_pattern.md
Original file line number Diff line number Diff line change
@@ -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<SingletonString> {
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.
3 changes: 3 additions & 0 deletions turbopack/crates/turbo-tasks/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading