Helps write code using vvvv gamma's Spread<T> immutable collection type and SpreadBuilder<T>. Use when working with Spreads, SpreadBuilder, collections, arrays, iteration, mapping, filtering, zipping, accumulating, or converting between Span and Spread. Trigger whenever the user writes collection-processing C# code in vvvv — even if they say 'list', 'array', or 'IEnumerable' instead of Spread, this skill likely applies.
Installs into .claude/skills of the current project.
Are you the author of Vvvv Spreads?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tebjan-vvvv-spreads)
---
name: vvvv-spreads
description: "Helps write code using vvvv gamma's Spread<T> immutable collection type and SpreadBuilder<T>. Use when working with Spreads, SpreadBuilder, collections, arrays, iteration, mapping, filtering, zipping, accumulating, or converting between Span and Spread. Trigger whenever the user writes collection-processing C# code in vvvv — even if they say 'list', 'array', or 'IEnumerable' instead of Spread, this skill likely applies."
license: CC-BY-SA-4.0
compatibility: Designed for coding AI agents assisting with vvvv gamma development
metadata:
author: Tebjan Halm
version: "1.1"
---
# vvvv Spreads
## What Are Spreads
`Spread<T>` is vvvv's immutable collection type, conceptually similar to `ImmutableArray<T>`. Nodes return collections as `Spread<T>`; they never take `Spread<T>` as an input type (see Collection Inputs).
Key properties:
- **Immutable** — never modify in place, always create new spreads
- **Value semantics** — two spreads with same elements are considered equal
- **Cyclic indexing** — in visual patches, indexing wraps around (not in C# API)
- **Never null** — use `Spread<T>.Empty` instead of `null`
## Creating Spreads
### SpreadBuilder (Primary Method)
```csharp
var builder = new SpreadBuilder<float>(expectedCount);
for (int i = 0; i < count; i++)
builder.Add(ComputeValue(i));
Spread<float> result = builder.ToSpread();
```
### From Existing Data
```csharp
// From array (extension method)
Spread<int> fromArray = new int[] { 1, 2, 3 }.ToSpread();
// From array (static factory)
Spread<Waypoint> fromResult = Spread.Create(resultArray);
// Empty spread (NEVER use null)
Spread<float> empty = Spread<float>.Empty;
// Single element
var single = new SpreadBuilder<float>(1);
single.Add(42f);
Spread<float> one = single.ToSpread();
```
## Accessing Elements
```csharp
// Always check Count before indexing
if (spread.Count > 0)
{
float first = spread[0];
float last = spread[spread.Count - 1];
}
// Iterate (preferred — no allocation)
foreach (var item in spread)
Process(item);
// Index access in loop
for (int i = 0; i < spread.Count; i++)
Process(spread[i]);
```
## Common Patterns in C#
### Map (Transform Each Element)
```csharp
public static Spread<float> Scale(IReadOnlyList<float> input, float factor = 1f)
{
var builder = new SpreadBuilder<float>(input.Count);
foreach (var value in input)
builder.Add(value * factor);
return builder.ToSpread();
}
```
### Filter
```csharp
public static Spread<float> FilterAbove(IEnumerable<float> input, float threshold = 0.5f)
{
var builder = new SpreadBuilder<float>();
foreach (var value in input)
{
if (value > threshold)
builder.Add(value);
}
return builder.ToSpread();
}
```
### Zip (Process Two Spreads Together)
```csharp
public static Spread<float> Add(IReadOnlyList<float> a, IReadOnlyList<float> b)
{
int count = Math.Max(a.Count, b.Count);
var builder = new SpreadBuilder<float>(count);
for (int i = 0; i < count; i++)
{
float va = a.Count > 0 ? a[i % a.Count] : 0f;
float vb = b.Count > 0 ? b[i % b.Count] : 0f;
builder.Add(va + vb);
}
return builder.ToSpread();
}
```
### Accumulate (Running Total)
```csharp
public static Spread<float> RunningSum(IReadOnlyList<float> input)
{
var builder = new SpreadBuilder<float>(input.Count);
float sum = 0f;
foreach (var value in input)
{
sum += value;
builder.Add(sum);
}
return builder.ToSpread();
}
```
## ReadOnlySpan as High-Performance Alternative
For hot-path output (e.g., per-frame simulation data), `ReadOnlySpan<T>` avoids allocation entirely:
```csharp
[ProcessNode]
public class ParticleSimulator
{
private ParticleState[] _states;
public ReadOnlySpan<ParticleState> Update(SimulationConfig config, float deltaTime)
{
// Simulate into pre-allocated array — zero allocation
Simulate(_states, config, deltaTime);
return _states.AsSpan();
}
}
```
## Collection Inputs: Never `Spread<T>`
| The node needs | Input type |
|---|---|
| the count, or index access | `IReadOnlyList<T>` |
| only to iterate | `IEnumerable<T>` |
A `Spread<T>` connects to both (it implements `IReadOnlyList<T>`), and so do arrays, lists and every
other collection a user wires. An input typed `Spread<T>` accepts nothing else and forces
conversions. Outputs stay `Spread<T>`.
The one place VL.StandardLibs declares a `Spread<T>` parameter is a collection pin group
(`PinGroupKind.Collection`, the add/remove-button pins). Check before giving a pin group another type.
## Performance Rules
- **Pre-allocate builder**: `new SpreadBuilder<T>(expectedCount)` when count is known
- **No LINQ in hot paths**: `.Where()`, `.Select()`, `.ToList()` create hidden allocations
- **Cache spreads**: If output doesn't change, return the cached spread reference
- **Avoid repeated `.ToSpread()`**: Build once, output the result
- **For large spreads**: Consider `Span<T>` internally, convert to Spread at the API boundary
- **Spread change detection**: Since Spreads are immutable, reference equality (`!=` or `ReferenceEquals`) is sufficient — if the reference changed, the content changed
## Spreads in ProcessNodes
```csharp
[ProcessNode]
public class SpreadProcessor
{
private IReadOnlyList<float> _lastInput = Spread<float>.Empty;
private Spread<float> _cachedOutput = Spread<float>.Empty;
public void Update(
out Spread<float> output,
IReadOnlyList<float>? input = null)
{
input ??= Spread<float>.Empty;
if (!ReferenceEquals(input, _lastInput))
{
var builder = new SpreadBuilder<float>(input.Count);
foreach (var v in input)
builder.Add(v * 2f);
_cachedOutput = builder.ToSpread();
_lastInput = input;
}
output = _cachedOutput;
}
}
```
For more code examples, see [examples.md](examples.md).
## Keep this knowledge growing
Leave the next patcher one fewer mystery. On first use of any skill in this
collection with no successful check in seven days, compare the source with
[tebjan/vvvv-skills](https://github.com/tebjan/vvvv-skills). If no record exists, check once.
Share one local check timestamp/revision across these skills; do not poll per invocation.
Preserve local extensions and pinned project versions; checking is not permission
to install updates. If offline, record the deferred check and continue the task.
When actual work exposes a gap, capture the tested vvvv/package versions, a
minimal reproduction or authoritative source, and the observed result. Treat
other users' reports as leads, not facts until verified. Improve the smallest
relevant instruction; do not turn one example into a universal rule. Redact
private data and obtain publishing authorization before submitting upstream.
See the [contribution policy](https://github.com/tebjan/vvvv-skills/blob/main/CONTRIBUTING.md).
Merged improvements reach users through their installer/update workflow, not
through a local edit or an automatic overwrite of everyone's skills.