見出し画像

vvvv gamma入門 - 4

vvvv入門4回目です。前回は5種類あるDataType Patchの内、Process、Record、Classについて説明しました。
今回はInterfaceについてです。

Interface

Interfaceは公式ドキュメントでは正式には未サポートとだけ記載されていますが既に動作します。
役割としてはC#などの他のプログラミング言語におけるInterfaceの役割と同じです。

ステートパターン

Interfaceの使い方例です。ここではデザインパターンの1つであるステートパターンを実装してみます。

ここでは一定時間たつと形が変わるオブジェクトを考えてみます。
土台としてStrideのシーンを作っておきます。

ライトだけを置いたシーン
ライトだけを置いたシーン

次にステートパターンの登場人物を紹介します。
StateContext: ステートを管理します。
IState: ステートのInterfaceです。
MyBox、MySphere: IStateを実装し、ステートを表します。

IState

さっそくInterfaceを使ってみます。Interfaceの作り方はProcessなどの付くありかたと同じです。
慣習としてInterfaceの名前は接頭辞にIがついたり〇〇able(Serializableなど)といった名前にすることが多いです。ここではIStateという名前にします。

Interfaceはメソッドのシグネチャを指定するものです。実際の処理は記述しません。シグネチャとはOperationの名前や引数、返値などの組み合わせのことです。
ステートに遷移するためのGoNextState Operationと3Dオブジェクトを取得するGetModel Operationを定義します。

IState

Operation名が書かれている少し明るい矩形エリアはFrameと呼ばれるものです。
この様に処理内容を囲ってコメントの様に使ったり、スクリーンショットが取れたりと便利なものです。
Frameを置くにはNode BrowserでFrameを探して配置します。

Frame

StateContext

次にStateContext Classを実装します。
ステートを管理するクラスなので、PropertyにStateを持っています。
State Propertyは値が何も入っていないと都合が悪いのでCreate Operationで最初のステートを指定できるようにしておきます。
また、ステートのセッターであるSetState Operationとステートの変更を行うChange Operationがあります。

StateContext

MyBox、MySphere

最後にIStateの実装であるステートを表すClassを作成します。
手始めにBoxを表示するMyBox ClassとSphereを表示するMySphere Classを作成します。
それぞれInterfaceにIStateを指定します。
IStateで定義されているOperationの処理内容を実装します。

MySphereのIState実装

GoNextState OperationではStateContextのインスタンスを受け取って次のステートをセットする処理を書きます。
MyBoxでは次にMySphereに遷移したいので、StateContexのSetState OperationでMySphereのインスタンスをセットします。
MySphereではMyBoxのインスタンスをセットします。
GetModel OperationではそれぞれBoxとSphereを返す様にします。

メインパッチ

これで役者が揃ったのでメインパッチにノードを組んでいきましょう!
ステートの管理者であるStateContexをCreateします。最初のステートとしてMyBoxもCreateしてStateContextのCreateに渡しておきます。

StateContextをCreate

StateContextのCreate Operationの出力を名前の無いPadに繋ぎます。Anonymous Padと呼びます。

ここまでがメインパッチのCreate Operationです。
上記をドラッグで囲い、選択したのちにCreate Operationを割り当てます。

何故Anonymous Padが必要なのかというと、PadがあるとOperationの割り当て伝搬がそこで止められるのです。これはOperationの割り当て設定の際に超重要なため必ず覚えておきます。
この場合、Padがないと、これから実装するステートの切り替え処理までCreate Operationになってしまい、最初の1フレーム目しか実行されなくなってしまいます。
また、Process NodeもOperationの割り当てが出来ません。Process Nodeはそれに繋がるProcess Node以外のノードをOperationに割り当てることで間接的にそのOperationに割り当てられることになります。
この辺りは混乱しがちで毎回四苦八苦してしまいます。

以降は毎フレーム呼ばれる処理を実装していきます。
一定時間で3Dオブジェクトを切り替えたいのでLFOノードとIfを使ってこの様にノードを組みます。

If Region

If Regionは初登場です。指定した条件を満たす場合にだけIf Regionの中の処理が実行されます。
If Regionの中にStateContextのChange Operationを起きます。Regionの中にノードを入れるのはCtrlを押しながらドラッグします。
Ifの条件にLFOのOnNewCycle Outputをつなぐことで一定時間ごとにChange Operationが実行される様になります。
この様に一定時間ごとに何か処理をトリガーする場合にLFOノードのOnNewCycleを利用したイディオムはよく使うため覚えておくと良いです。

最後にStateContextのGetModel Operationを読んで現在のステートの3Dオブジェクトを取得し、シーンに置きます。

GetModel Operation

メインパッチは以上です。全体的にはこの様になっているはずです。

 メインパッチ
メインパッチ

メインのノードが組み上がったのでF9を押してパッチをRestartして処理を最初から実行してみます。

この様にBoxとSphereが定期的に入れ替わっていたら成功です!

3Dモデルが切り替わる

ステートを追加する

一般的に広く知られているデザインパターンは拡張性も優れています。
今の処理に追加して今度はConeも表示したくなったとしましょう。
同じ要領でMyCone Classを実装します。
MyConeの次のステートはMyBoxにして、MySphereの次のステートをMyConeに変えるだけで新しいステートを追加することができました。
どんどん新しいスタートが増やしたり、ランダムで次のステートを決めたりなど色々な拡張が容易に可能になっています。

Coneモデルの出力とMyBoxへの遷移
MySphereの次のステートをMyConeにする

Singletonパターン

現状の実装をよく見ると、ステートの変更時にその都度ステートクラスをCreateしています。

都度インスタンスを作成している

これはこれで良いのですが、その都度インスタンスを作るのはなんとなく無駄に感じますし、例えば1ループ前のステートの状態を引き継ぎたいケースに対応できません。

例えばMyBoxにSizeというPropertyを追加してBoxのサイズを指定できるようにしてみます。InputにCreate Operationを割り当てます。

CreateでSizeを指定する

このInputはConfigureでデフォルト値を設定できます。デフォルトが0だとうっかり見えなくなってしまうので1を指定しておきます。

デフォルト値の設定

メインパッチのCreateで2を指定してみます。

F9を押してパッチをRestartします。
1ループ目では大きなBoxが表示されますが2ループ目では大きさが戻ってしまっています。

そこでSingletonパターンというものを使ってみます。Singletonパターンを使うとアプリケーションで使われるインスタンスを必ず1つだけに保つ事が出来ます。恐らく最も良く使われるデザインパターンと言っても過言ではありません。
インスタンスが1つだけになるのでステートが変わっても1ループ前のステートのPropertyはリセットされずに保持されます。

vvvvでSingletonパターンを使うには以下の様にします。
まずステートクラスのインスタンスを取得するためのProcessを定義します。
MyBoxクラスのインスタンスを取得するProcessをMyBoxInstanceという名前で作成します。
SingleInstanceというRegionが用意されているのでその中でステートクラスをCreateし、出力します。
SingleInstance Regionの中で実行されたCreateは毎フレーム同じインスタンスを返す様になっています。

MyBoxInstance

各ステートクラスのGoNextState Operation内で次のステートクラスをCreateしていたものを、MyBoxInstanceノードで置き換えます。
Createで直接Size初期値を渡せなくなったのでSetSize Operationを追加しています。

メインパッチのCreate
MyConeのGetState Operation

本当に同じインスタンスが使われているか確認してみましょう。

F9でパッチをRestartします。2ループ目以降もBoxの大きさが引き継がれています。

ちなみにSingleInstance Regionで作成するインスタンスはClassを使用します。Recordでは同じインスタンスが返りません。
これはよく考えると当然なのですが、RecordはOperationの度に新しいインスタンスが返るという仕様になっているので、Recordのインスタンスをただ1つに制限することができないのです。

もう一工夫

ここまでで説明したSingletonパターンの実装は厳密な意味でのSingletonパターンにはなっていません。
C#などのプログラム言語ではどの様なインスタンスの作り方をしても必ずインスタンスが1つになる様に実装しますが、今回の実装方法ではMyBoxクラスのCreate Operationを直に呼んでしまうとインスタンスが無限に作れてしまいます。
自分一人で開発する場合は良いかもしれませんが、チームで開発する場合などは、何らかの方法でこのクラスはSingleton用のノードでインスタンスを取得してね、と言うことを周知しなければなりません。

そこでMyBoxクラスのCreateOperationを呼べない様にしてみます。
MyBoxInstanceのSingleInstance Regionの中をこの様にします。

Reflectionで無理やりMyBoxのインスタンスを作る

CreateInstanceノードにMyBoxの型情報を渡してインスタンスを作成しています。
MyBoxのCreate Operationは不要になったので削除します。これでNode BrowserでMyBoxのCreateを探しても出てこないので、気軽にMyBoxのインスタンスは作れなくなりました。

MyBoxを作ろうとしたときにCreateが無いので困惑するかもしれませんが、Singleton用のノードの名前を似たものにしておけば候補で表示されるので直感的に気づきやすいかもしれません。

MyBoxInstanceを見つけやすい

いかがでしたでしょうか。vvvvでもれっきとしたデザインパターンが実装できることがわかりました。これらはvvvv betaの頃には出来なかったものです。
他の言語で培われたノウハウをどんどんvvvvに持ってきましょう!
それではまた次の記事で。

もしこの記事があなたのお役に立てたなら幸いです。 よろしければサポートをお願いします。今後の制作資金にさせていただきます!