mcpField
V
vvvv-spreads

vvvv-spreads

github.com/tebjan开发3493 次安装2 天前更新
代码仓库

关于 vvvv-spreads

vvvv-spreads 是帮助开发者在 vvvv 与 C# 中处理 Spread 集合的技能,涵盖创建、访问和常见变换,并强调不可变性及减少分配的性能做法。

主要功能

  1. 01使用 SpreadBuilder 创建集合
  2. 02掌握元素访问与循环索引
  3. 03实现映射、筛选、合并和累积
  4. 04按用途选择集合输入类型
  5. 05通过缓存和 Span 减少分配

适用场景

  1. 01转换或筛选节点输入集合
  2. 02处理多个集合的逐项运算
  3. 03为逐帧模拟数据优化输出
  4. 04避免热点路径中的额外分配

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/vvvv-spreads确认已加载。

附带文件examples.md

详细介绍

vvvv Spreads

什么是 Spread

Spread<T> 是 vvvv 的不可变集合类型,概念上类似于 ImmutableArray<T>。节点以 Spread<T> 的形式返回集合;它们不会将 Spread<T> 作为输入类型(参见集合输入)。

主要特性:

  • 不可变 — 永远不要原地修改,始终创建新的 Spread
  • 值语义 — 包含相同元素的两个 Spread 被视为相等
  • 循环索引 — 在视觉化补丁中,索引会循环回绕(C# API 中不会)
  • 永不为 null — 使用 Spread<T>.Empty 代替 null

创建 Spread

SpreadBuilder(首选方法)
var builder = new SpreadBuilder<float>(expectedCount);
for (int i = 0; i < count; i++)
    builder.Add(ComputeValue(i));
Spread<float> result = builder.ToSpread();
从现有数据创建
// 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();

访问元素

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

C# 中的常见模式

Map(转换每个元素)
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(同时处理两个 Spread)
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(累计总和)
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:高性能替代方案

对于热路径输出(例如每帧模拟数据),ReadOnlySpan<T> 可以完全避免分配:

[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();
    }
}

集合输入:绝不使用 Spread<T>

节点需要输入类型
数量或索引访问IReadOnlyList<T>
仅用于迭代IEnumerable<T>

Spread<T> 可连接到两者(它实现了 IReadOnlyList<T>),数组、列表以及用户接入的其他所有集合也一样。类型为 Spread<T> 的输入无法接受其他类型,并会强制进行转换。输出仍使用 Spread<T>。

VL.StandardLibs 中唯一声明 Spread<T> 参数的地方是集合引脚组(PinGroupKind.Collection,带添加/删除按钮的引脚)。为引脚组指定其他类型前,请先检查。

性能规则

  • 预分配 builder:已知数量时,使用 new SpreadBuilder<T>(expectedCount)
  • 热路径中不使用 LINQ:.Where()、.Select()、.ToList() 会产生隐式分配
  • 缓存 Spread:如果输出没有变化,就返回缓存的 Spread 引用
  • 避免重复调用 .ToSpread():只构建一次并输出结果
  • 处理大型 Spread:考虑在内部使用 Span<T>,在 API 边界处转换为 Spread
  • Spread 变更检测:由于 Spread 不可变,引用相等性(!= 或 ReferenceEquals)已足够——如果引用发生变化,内容就发生了变化

ProcessNodes 中的 Spread

[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;
    }
}

更多代码示例请参阅 examples.md。

持续积累知识

让下一个补丁制作者少一个疑问。首次使用本集合中的任意技能时,如果过去七天内没有成功检查记录,请将其与 tebjan/vvvv-skills 中的源内容进行比较。如果没有记录,只检查一次。 在这些技能之间共享一个本地检查时间戳/修订版本;不要在每次调用时都检查。 保留本地扩展和固定的项目版本;检查并不代表获准安装更新。如果处于离线状态,请记录延迟的检查并继续任务。

实际工作发现知识缺口时,请记录经过测试的 vvvv/包版本、最小复现案例或权威来源,以及观察到的结果。将其他用户的报告视为线索,在验证前不要当作事实。改进最小范围内的相关说明;不要将单个示例变成普遍规则。提交上游前,请清除私人数据并获得发布授权。 请参阅贡献政策。 合并后的改进会通过用户的安装程序/更新工作流触达用户,而不是通过本地编辑或自动覆盖所有人的技能。

评论

支持 Markdown