VL の概念
VL には vvvv には無い概念がいろいろあります。
VL を実際に触る前に、まずは概念の理解をすると理解が捗ります。
vvvv ユーザは他の言語を知らない方も多いと思うので、出来るだけ易しく解説しています。
とはいえ、説明が分かりにくく他の言語で慣れていないとピンと来ないものもあると思います。
後々サンプルと一緒に見ていったりする事で理解できると思うので、後々出てきた時に拒絶反応を起こさずに、どこかで聞いた事はあるなとなってもらえればと。
全体像
下の図がわかりやすいです。
引用元:The Gray Book > Project Structure https://gitbook.com/book/vvvv/the-gray-book/details
-
VLドキュメント(VL Document)
- VLプロジェクトのルートになるものです。
- 複数のパッチを含める事ができます。
- Alt+P で今開いているパッチのドキュメントパッチを開くことができます。
-
依存関係(Dependencies)
- VL ドキュメントは他のVLドキュメントやDLLを使用でき、それに「依存している」といいます。
-
パッチ(Patch)
- 次に詳しく書いていきます。
- .vlimport
基本
-
データフロー(Data Flow)
- 文脈で少し変わりますが、言葉を聞いた時にデータが流れているイメージを思い浮かべればよいです。
- vvvv や VL ではデータの流れを中心に考えます。
-
ノード(Node)
- 一個一個の箱。同vvvv
-
リンク(Link)
- ノードをつなぐ線。同vvvv
-
スプレッド(Spread)
- vvvv におけるのデータの基本概念で、データの配列的な感じのものです。
- vvvv の世界ではデータを暗黙的に Spread に変換する仕組みなどが動いています。
- VL ではあくまで(少し優遇された)型の一つでしかないです。
-
ノードブラウザ(NodeBrowser)
- 左ダブルクリックすると出てくるやつ
-
データハブ/ピン(Data Hub/Pin)
- リンクのつながる場所
- vvvv でいうピンです。VL でもピンとも呼びます。
-
データソース(Data Source)
- データの入口
-
データシンク(Data Sink)
- データの出口
-
型/型定義(Type/Type Definition)
- 「かた」「かたていぎ」と読みます。普通にタイプとも言います。
- 複数のデータをまとめ、それに対する操作などを持ちます。
- 他の言語でいう、型やクラスの事です。
- 全てのデータは型を持ちます。
- 例えば、この文字列はStringという型です、とか、この数値はValue型です、とか言います。
- 例えば、vvvv で AsString(Value)や AsString(Raw) などがありますが、それぞれ元になるデータの型が Value か Raw かなどで使い分けます。
- 言語にあらかじめ存在する型もありますが、自分で型を定義することもできます。
- 例えば、パーティクルを作ろうとした時に、Particleという型を作り、位置や色情報を持ち、位置の更新などの操作を持つ「型」を定義すれば使いやすく分かりやすくなる、みたいな。
-
インスタンス(Instance)
- ある型の具体的なデータの事です。日本語で「実体」などとも言います。
- 型はそのデータの構造で、具体的なデータではありません。
- 上の例でいう「Particle という型」はあくまで、位置や色情報や更新の操作が定義されていているものを指し、「Particle という型のインスタンス」というと実際にどの位置にいて、この色だ、みたいなデータを持てます。
-
プロパティ(Property)
- そのインスタンスの持っている値などの情報の事です。
- 「メンバ変数」「フィールド」などとも言います。厳密にいうと少し意味合いが違いますが、VLにおいて話を聞いたりドキュメントを読む際には厳密な使い分けは意識しなくて大丈夫です。
- VL では「パッド(後述)」という物がプロパティにアクセスするための物なので、文脈によりパッド=プロパティとして話す事もありそうです。
-
フィールド(Field)
- 文脈によって二つ意味があります。
-
- プロパティの事
-
- パッチを作っていく領域の事
-
ステートレス(Stateless)
- 状態を持たない事を意味します。状態とは上述のプロパティなどを指します。
- 「ステートレスなノード」というと、状態を持たず、入力に対して出力が常に同じなノードという意味合いです。
- 例えば「+」や「AsString」ノードなどは、出力が入力にしか依存していません。
-
ステートフル(Stateful)
- 状態を持つ事を意味します
- 「ステートフルなノード」というと、状態を持ち、その状態によって入力が同じでも出力が異なります。
- 例えば「S+H」「TogEdge」「Damper」ノードなどは、前回までの入力によってノード内の状態が変わる事によって、同じ入力だとしても状態によって出力が状態によって変わります。
- ステートレスが必要ないのではと思うかもしれませんが、状態を持たないという性質にも利点があり、簡単に言うとバグが入りにくかったり、テストがしやすかったり、動作が理解しやすかったりします。
- ステートフルな場合、この状態の時だけ起きる不具合、みたいなのがあり得ます。
-
イミュータブル(Immutable)
- 単語としては「不変」いう意味です。
- イミュータブルな型などと使います。
- 例えば C# の String という型はイミュータブルです。
- 何がイミュータブル(不変)なのかというと、例えば"ABC"という string 型のインスタンスXがあったとして、この最後の文字を'C'から'D'に変更したい場合、直接Xを編集することができません。どうするかというとXの最後の文字を'D'に置き換えた新しいインスタンスを作る必要があります。
- ちなみに、データフローというパラダイムにマッチしたり、並行実行の利点もあるので vl はデータは基本的に immutable です。数値も文字列も色も。
-
ミュータブル(Mutable)
- 単語としては「可変」という意味です。
- ミュータブルな型などと言います。
- 例えば c++ の string という型はミュータブルです。
- 上の例と同じく、"ABC"という string 型のインスタンスXがあったとして、X[2]='D'とする事ができます。
-
メンバー(Member)
- VL において、メンバーとは他の言語でいうメンバ関数を指します。
- このパッチに対しての操作、例えばCreateやUpdateなどがメンバーになります。
- 自分でメンバーを作ることもできます。
- 一つのパッチに複数のメンバーを定義でき、ある部分がこのメンバーの実行時に動くのかを設定でき、「このノード/パッド/インプットを、このメンバに割り当てる(アサイン/Assignする)」などと表現します。
- アサインする事でメンバの動きが定義されます。
- メンバーオペレーション(Member Operation) とも呼びます。
-
ジェネリクス/ジェネリック(Generics/Generic)
- 型を限定しない操作などを定義できます。
- ジェネリックなノードなどと言う時、受け取る型を限定せず汎用的に使えるノードという意味です。
- 例えば「+」というジェネリックなノードがあったとして、String+StringとInt+Intなどが一つの定義でどちらも実行可能になります。
- ジェネリクスというと、そういう事をサポートする機能の総称を指します。
- 「VLはジェネリクスをサポートしているので、ジェネリックなノードを作れる」みたいに言います。
-
非同期(Async/Asynchronous)
- 反対語は同期(Sync/Synchronous)です。
- ある処理AとBがあったとして、「Aが完了した後にBを実行する」というのを同期実行といい、「Aが完了する前にBも実行する」というのを非同期と言います。
- マルチスレッドで処理を行う、みたいなイメージです。
- 例えば vvvv で FileTexture でファイル読み込みをした時に、「Load In Background=0」だと読み込み完了まで少し止まったりします。これは同期的にファイルを読み込んでいるからです。一方「Load In Background=1」を1にすると裏側で読み込みをしてくれます。これは「非同期にファイルを読み込んでる」といいます。
- タイミングを合わせる、みたいな意味でも同期という言葉を使います。
- VLでは非同期な処理をかけます
-
ステップ実行(Stepping Execution)
- VL ではデバッグのためにステップ実行が可能です。
- ステップ実行は1フレームずつ実行し結果を確認できます。
-
世界(World)
- 「vvvv の世界」「VL の世界」などと使います。スラングですがよく使われます。
- vvvv 界隈以外でも「CPUの世界」と「GPUの世界」みたいな感じで言ったりします。
- vvvv と VL はそれぞれの環境とか制約とかが別物で、それをふわっと丸っと指す感じの意味になります。
- VL のデータを vvvv の世界に持ってくるとか、VL の型を vvvv の世界で使えるようにする、みたいに言います。
- 非同期で動いているリージョンなどは、「async の世界」とかも言ったりします。
-
リアクティブ(Reactive)
- リアクティブプログラミングというパラダイムを指す
- VL だと Observable とかが絡むものとか
- 非同期なデータストリーミングを扱えたり、イベントにより何か起きるなどする奴
- (Reactive)とかついているノードはこれ向けのノード
-
抽象/抽象化(Abstract/Abstraction)
- Sequence と Spread を抽象化したものとして Sequence があるとか
- 実際の処理を持たないものを抽象クラスと言ったりします
- ソフトウェア開発においてとても大事な概念なのですが、一般的な意味での抽象化の意味だと思って、雰囲気さえ掴んでもらえればそんなに困らないのではと思います。
パッチ
パッチの種類
-
ドキュメントパッチ
- = VL ドキュメント
-
データタイプパッチ
- Process/Record/Class の総称
-
ドキュメントの構造を作るためのパッチ
-
グループ(Group)
- 単純に人間が読みやすいようにまとめれるだけ。
- ノードブラウザでの見え方などは変わらない
-
カテゴリ(Category)
- 親パッチに紐づく、カテゴリの階層を作れる
- ノードブラウザにもそう表示される
- 見え方的に頭に「.」がつく
-
フルカテゴリ(FullCategory)
- Categoryみたいなものだが、
- 親パッチに紐づかない
- 見え方的に頭に「:」がつく
-
グループ(Group)
リージョン(Region)
VL における新しい概念です。
リージョンにはいくつか種類があります。
- IFリージョン
- ForEachリージョン
- Repeatリージョン
- Delegateリージョン
- Operationリージョン
- Whereリージョン
- AsyncLoop/Taskリージョン
- などなど
リージョンによって vvvv では出来なかったループや非同期処理などが可能になります。
大事な所なのでリージョンを解説する動画やドキュメントを作っていきますが、とりあえず雰囲気を載せます。
例えば、下の図は「IFリージョン」です。
IFリージョンはCondition(True/False)を取り、Condition=Trueの時のみ内部が実行されます。
上の例ではCondition=Trueの時に100が、Condition=Falseの時に200を返しています。
下の図は「ForEachリージョン」です。
Spreadを受け取り、各要素に対して操作を行い、その結果をSpreadとして返します。
上の例では入力を2倍しています。
リージョン関係の用語
- スプライサ(Splicer)
- アキュミュレータ(Accumulator)
-
特殊ピン
- リージョン内で使える特殊なピンが存在する
-
Keepピン
- リージョンでこれが False だとその結果は Splicer に追加されない
- Accumulator には影響を与えない
-
Breakピン
- これが True だとループを中断し途中で抜ける
-
Indexピン
- 何回めのループかが入っている
パッド(Pad)
パッドとは、プロパティにアクセス(読み書き)するためのものです。
例えば以下の図での「Color」という丸いものがパッドです。
前提として、赤い部分はCreateメンバ(上述)にアサイン(上述)されていて、青い部分はUpdateメンバにアサインされています。
で、この図の意味は、
Create時には Color プロパティにランダムな値(1つの実数値)を設定する(赤い部分)。パッドをデータシンクとして扱えば、その名前のプロパティに値が設定されます。
Update時には Color プロパティを取得して色として返す。パッドをデータソースとして扱えば、その名前のプロパティの値が取得できます。
また、これを簡潔に書くと下の図のようになります。
上二つの画像で行っている事は完全に同じです。
オペレーション(Operation)
オペレーションという際には、基本的にはこの 静的オペレーション(Static Operation) というものを指します。
ステートレスな操作を行うノードを作れます。
例)
左側で「Adder」というオペレーションを定義して、右側では「Adder」をノードとして使用しています。
vvvv 側からも使用可能です(ノードブラウザに出てきます)。
オペレーションの中でステートフルな操作(例えばPadを置くとか、S+Hノードなどのステートフルなノードを置くとか)をしようとするとエラーが出ます。
ドキュメントパッチorグループパッチで定義でき、デフォルトで Generic です。
プロセス(Process)
ステートフルな操作を行うノードです。
MyS+Hという「S+H」ノード的な動きをするノードを作ってみます。
Create時には、DefaultValueをvalueプロパティに設定しています。
Update時には、set が ON であれば value プロパティに input を設定、set が OFF であれば value プロパティはそのままにしています。(Switch を使っていますが、もちろん前述のIFリージョンでも書くことができます。)
このようなプロセスを定義すると、以下の図のように使えます。
レコード(Record)
immutable な型を作れます。
例えば、ColorGeneratorというRecordを定義します。
これはHueというプロパティを持ち、最初にランダムで初期化、その値をGetColorで色として取得、GetHueではHueの値だけ取得できます。
これは下のように使用できます。
そして、ここでCreateしたinstanceはイミュータブルです。
クラス(Class)
mutable な型を作れます。
上のColorGeneratorというクラスを定義します。
左上の「Record」が「Class」になっただけです。
これを使用してみると、少し見た目が変わります。点線はイミュータブルなデータだということを示しています。
レコードのインスタンスはあるフレームにおいて instance をどこかで編集しようとも、instance のパッドから出てくるものは同じデータであることが保証されます。
一方、クラスのインスタンスは編集される可能性があります。
レコードはCreateなどでは値を取得でき、クラスはCreateなどでは参照を取得できる。と考えるといいのかなと思います。
基本的にレコードを使った方が予期せぬバグは減らせます。が、パフォーマンス的には無駄にコピーなどが発生しないので状況によってクラスを使うといいです。
Createメンバ
Create というメンバを使用してレコードやクラスを生成します。
デリゲート(Delegate)
デリゲートは処理を定義し、Invokeというノードで動的に呼び出せます。
例えば、下の左はInvokeで渡された値に10を足して返し、右は10をかけて返します。
デリゲートの利点として、デリゲートの処理内容に全く依存せず、「N個入力を取って、M個出力を出す何か」という一段階抽象的な形で扱えます。
下の図を見てください。
Switchでデリゲートを切り替えて、Invoke で呼び出しています。
ConditionがONの時には100(10*10)が、OFFの時には(10+10)が出力されます。
Invoke は入力が1個で出力が1個のデリゲートであれば同じものとして扱えるし、デリゲートはプロパティで持つ事などもできるので、何か起きた時に処理を切り替える、などの処理がとても自然にかけます。
よく使いそうな例としては、レコードなどにデリゲートをプロパティとして持たせ、外からそのデリゲートを切り替えることで、レコード内を一切修正せずにレコードの挙動が変更出来ます。
インタフェース(Interface)
レコードやクラスに共通の振る舞いをさせたい場合に使用します。
例えば、下のような InterfaceA を定義します。
メンバーには GetValue というメンバを定義しました。
次に、InterfaceA をインタフェースとして持つ、RecordA と RecordB を定義します。
どちらも Interfaces に InterfaceA があり、GetValue というメンバを定義しています。
RecordA の GetValue は 22 を返し、RecordB の GetValue は 47 を返します。
これをどのように使うかというと、下の図のように使えます。
GetValue ノードはどちらも InterfaceA の GetValue ノードですが、実際の挙動はそれぞれ RecordA/B で定義した挙動になっています。
InterfaceA の GetValue ノードは InterfaceA を実装しているものであれば区別せずに呼び出すことができます。
フォワード(Forward)
依存関係の時に解説します。
データ構造
-
コレクション
- SpreadとかListとかSetとかDictionaryとか、データのまとまりを表すものの総称
-
Spread
- データを取れるし、何個あるかわかるし、何が入っているか知っているコレクション
- イミュータブル型
-
SpreadBuilder
- Spreadを作るのに便利なもの
- Spread はイミュータブルなので、ループで一つ一つ追加とかするとコピーコストがかかる。そういう時には SpreadBuilder を使うと良い。
- ローカルで使う(ノードを行き来しない)のをおすすめしている
-
HashSet
- 数学的な意味での集合、Set、セット。
- HashSet 内には同じ値は一つしか存在しない
- 内部的に木構造なので、ある要素がそこに含まれるかどうかを判定するのが、計算量レベルで速い
- 一方、データの追加にも少しコストがかかる。
-
Sequence
- ForEachリージョンなどははsequenceを受け取ります
- 一つ一つみていけるコレクションの抽象です
- ノードを作るとき、入力がSequenceでよければそうした方が良い
-
List
- Repeatリージョンはリストを受け取ります
- Listはカウントを持つコレクションの抽象です
-
Spread/SpreadBuilder/HashSet は Sequence であり List です
-
Int(イント/Integer)
- 整数値のこと
- 例)1, 10, 30
-
Float(フロート/Floating Point Number)
- 浮動小数点数、簡単に言うと実数値のこと
- 例)0.123, 12.567
-
Bool(ブール/Bloolean)
- 真偽値
- True(真) / False(偽) のどちらかを取る
- Toggle とか Bang の出力
- VL において True = 1 で False = 0 です。