--- name: vvvv-spreads description: Helps write code using vvvv gamma's Spread immutable collection type and SpreadBuilder. Use when working with Spreads, collections, arrays, iteration, mapping, filtering, zipping, or converting between Span and Spread. license: CC-BY-SA-4.0 compatibility: Designed for coding AI agents assisting with vvvv gamma development metadata: author: Tebjan Halm version: "1.0" --- # vvvv Spreads ## What Are Spreads `Spread` is vvvv's immutable collection type, conceptually similar to `ImmutableArray`. It is the primary way to pass collections between nodes. 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.Empty` instead of `null` ## Creating Spreads ### SpreadBuilder (Primary Method) ```csharp var builder = new SpreadBuilder(expectedCount); for (int i = 0; i < count; i++) builder.Add(ComputeValue(i)); Spread result = builder.ToSpread(); ``` ### From Existing Data ```csharp // From array (extension method) Spread fromArray = new int[] { 1, 2, 3 }.ToSpread(); // From array (static factory) Spread fromResult = Spread.Create(resultArray); // Empty spread (NEVER use null) Spread empty = Spread.Empty; // Single element var single = new SpreadBuilder(1); single.Add(42f); Spread 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 Scale(Spread input, float factor = 1f) { var builder = new SpreadBuilder(input.Count); foreach (var value in input) builder.Add(value * factor); return builder.ToSpread(); } ``` ### Filter ```csharp public static Spread FilterAbove(Spread input, float threshold = 0.5f) { var builder = new SpreadBuilder(); foreach (var value in input) { if (value > threshold) builder.Add(value); } return builder.ToSpread(); } ``` ### Zip (Process Two Spreads Together) ```csharp public static Spread Add(Spread a, Spread b) { int count = Math.Max(a.Count, b.Count); var builder = new SpreadBuilder(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 RunningSum(Spread input) { var builder = new SpreadBuilder(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` avoids allocation entirely: ```csharp [ProcessNode] public class ParticleSimulator { private ParticleState[] _states; public ReadOnlySpan Update(SimulationConfig config, float deltaTime) { // Simulate into pre-allocated array — zero allocation Simulate(_states, config, deltaTime); return _states.AsSpan(); } } ``` Use `Spread` for infrequent config inputs; use `ReadOnlySpan` for high-frequency frame data. ## Performance Rules - **Pre-allocate builder**: `new SpreadBuilder(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` 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 Spread _lastInput = Spread.Empty; private Spread _cachedOutput = Spread.Empty; public void Update( out Spread output, Spread input = default) { input ??= Spread.Empty; if (!ReferenceEquals(input, _lastInput)) { var builder = new SpreadBuilder(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).