mcpField
V
vvvv-spreads

vvvv-spreads

github.com/tebjanDevelopment3493 installsUpdated 2d ago
Repository

About vvvv-spreads

vvvv-spreads is a skill for developers working with Spread collections in vvvv and C#. It covers creation, access, and common transformations, with guidance on immutability and reducing allocations.

Key features

  1. 01Create collections with SpreadBuilder
  2. 02Understand element access and cyclic indexing
  3. 03Implement map, filter, zip, and accumulation
  4. 04Choose collection input types by use
  5. 05Reduce allocations with caching and Span

Use cases

  1. 01Transform or filter node input collections
  2. 02Process element-wise operations across collections
  3. 03Optimize output for per-frame simulation data
  4. 04Avoid extra allocations in hot paths

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/tebjan/vvvv-skills --skill vvvv-spreads

After installing, type/vvvv-spreadsin Claude Code to confirm it loaded.

Bundled filesexamples.md

Details

From the project README

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)
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
// 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

// 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)
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
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)
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)
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:

[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 needsInput type
the count, or index accessIReadOnlyList<T>
only to iterateIEnumerable<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

[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.

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. 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. Merged improvements reach users through their installer/update workflow, not through a local edit or an automatic overwrite of everyone's skills.

Comments

Markdown supported