关于 vvvv-spreads
vvvv-spreads 是帮助开发者在 vvvv 与 C# 中处理 Spread 集合的技能,涵盖创建、访问和常见变换,并强调不可变性及减少分配的性能做法。
主要功能
- 01使用 SpreadBuilder 创建集合
- 02掌握元素访问与循环索引
- 03实现映射、筛选、合并和累积
- 04按用途选择集合输入类型
- 05通过缓存和 Span 减少分配
适用场景
- 01转换或筛选节点输入集合
- 02处理多个集合的逐项运算
- 03为逐帧模拟数据优化输出
- 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/包版本、最小复现案例或权威来源,以及观察到的结果。将其他用户的报告视为线索,在验证前不要当作事实。改进最小范围内的相关说明;不要将单个示例变成普遍规则。提交上游前,请清除私人数据并获得发布授权。 请参阅贡献政策。 合并后的改进会通过用户的安装程序/更新工作流触达用户,而不是通过本地编辑或自动覆盖所有人的技能。