vvvv-spreads について
vvvv-spreadsは、vvvvやC#でSpreadコレクションを扱う開発者向けのスキルです。作成、アクセス、基本的な変換に加え、不変性とメモリ割り当て削減の指針を扱います。
主な機能
- 01SpreadBuilderによるコレクション作成
- 02要素アクセスと循環インデックスの理解
- 03Map、Filter、Zip、累積処理の実装
- 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 のスプレッド
スプレッドとは
Spread<T> は vvvv のイミュータブルなコレクション型で、概念的には ImmutableArray<T> に似ています。ノードはコレクションを Spread<T> として返しますが、Spread<T> を入力型として受け取ることはありません(コレクション入力 を参照)。
主な特性:
- イミュータブル — その場で変更されることはなく、常に新しいスプレッドが作成されます
- 値セマンティクス — 同じ要素を持つ 2 つのスプレッドは等しいとみなされます
- 循環インデックス — ビジュアルパッチでは、インデックスはラップアラウンドします(C# API では行われません)
- null にならない —
nullの代わりにSpread<T>.Emptyを使用します
スプレッドの作成
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(2 つのスプレッドをまとめて処理)
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、追加/削除ボタンのピン)です。ピングループに別の型を割り当てる前に確認してください。
パフォーマンスのルール
- ビルダーを事前確保する: 件数がわかっている場合は
new SpreadBuilder<T>(expectedCount)を使用します - ホットパスで LINQ を使わない:
.Where()、.Select()、.ToList()は隠れたアロケーションを発生させます - スプレッドをキャッシュする: 出力が変わらない場合は、キャッシュされたスプレッド参照を返します
- 繰り返しの
.ToSpread()を避ける: 一度構築し、その結果を出力します - 大きなスプレッドの場合: 内部で
Span<T>の使用を検討し、API の境界で Spread に変換します - スプレッドの変更検出: スプレッドはイミュータブルであるため、参照の等価性(
!=またはReferenceEquals)で十分です — 参照が変わっていれば、内容も変わっています
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;
}
}
その他のコード例については、examples.md を参照してください。
この知識を成長させ続ける
次にパッチを組む人に、謎をひとつ減らしてあげましょう。このコレクション内のいずれかのスキルを初めて使用し、7 日以内に成功した確認がない場合は、ソースを tebjan/vvvv-skills と比較してください。記録が存在しない場合は、一度だけ確認してください。これらのスキル全体で 1 つのローカル確認タイムスタンプ/リビジョンを共有し、呼び出しごとにポーリングしないでください。ローカルの拡張機能と固定されたプロジェクトのバージョンは保持してください。確認することは、更新をインストールする許可ではありません。オフラインの場合は、延期した確認を記録し、タスクを続行してください。
実際の作業でギャップが見つかった場合は、テストした vvvv/パッケージのバージョン、最小限の再現または信頼できる情報源、および観察された結果を記録してください。他のユーザーの報告は、検証されるまでは事実ではなく手がかりとして扱ってください。最も小さな関連する指示を改善してください。1 つの例を普遍的なルールにしてはいけません。プライベートデータを伏せ、アップストリームに送信する前に公開の許可を得てください。コントリビューションポリシーを参照してください。マージされた改善は、ローカルの編集や全員のスキルの自動上書きではなく、各ユーザーのインストーラー/アップデートワークフローを通じてユーザーに届きます。