🐙

[#UEFN][#Verse]UEFNのカスタムイベント実装を考える[5] ペイロードを送信する

2023/08/22に公開

前回はこちら
https://zenn.dev/t_tutiya/articles/8a531b039e473e

今回は前回の記事で触れなかった「ペイロード」について。
その前に前回説明が不足していた箇所についてフォローアップします。

前回のフォローアップ:空引数とtuple()の関係について

前回の記事は、ヘルパー関数を使えばパラメータを持たない(正確には「要素数ゼロのtuple型」を持つ)eventオブジェクトを定義出来るという物でした。

ところで、第3回のサンプルコードには以下の様な行があります。Event定数はevent(tuple())クラスのインスタンスが定義されています。

Event.Signal()

event.Signal()のインターフェイスは以下の様に定義されています。

Signal<native><override>(Val:t):void

Valeventオブジェクトに渡すペイロードです。「ペイロード」については後述するとして、ここでは関数呼び出しのインターフェイスについて注目します。

先述したように、引数Valのパラメータ型tは、ここではtuple()にパラメタライズされています。しかし関数呼び出しの式はEvent.Signal()となっており、引数が記述されていません。引数の個数が一致していないのに、なぜコンパイルエラーにならないのでしょうか?

これは、恐らくですが、Verseでは関数呼び出し時の引数群を内部的にtuple型に格納していると思われ、その結果、引数の無い関数呼び出しも要素数ゼロのtuple型に変換されるので、インターフェイスが整合するのだと思われます。

この仕様は明言されていないのでいずれも想像の域を出ませんが、引数におけるtupleの自動展開という仕様があり、そこから類推しています。
https://dev.epicgames.com/documentation/en-us/uefn/tuple-in-verse#tupleexpansion

フォローアップはここまで。

承前:「ペイロード」という用語について(豆知識)

「ペイロード(Payload.)」とはなんでしょう。IT用語辞典 e-wordsから引用します。

https://e-words.jp/w/ペイロード.html

ペイロードとは、有料荷重、有効搭載量、最大積載量、積載物などの意味を持つ英単語。
通信・ネットワークの分野において、送受信されるデータの伝送単位(パケットやデータ
グラムなど)のうち、宛先などの制御情報を除いた、相手に送り届けようとしている正味
のデータ本体のことをペイロードという。

プログラミングでは「通信で送受信するデータ本体」を指してペイロードと言うようです。ちなみに土屋は「このロケットのペイロードはxxトンなので」という文脈で聞いたことがあります。

eventオブジェクトのペイロード

eventオブジェクトを使うと、あるタスクの中でAwait()を実行して待機状態に移行させ、その後で外部からSignal()を実行してそのタスクの待機状態を解除するという制御が行えます。

この時、Signal()の引数に値を渡すと、その値をAwait()の戻り値として受け取る事ができます。挙動としては外部からタスクに向かって値を送信しているように見えます。なので、この送信する値の事を「ペイロード」と呼んでいます。

イベントクラスのインターフェイスを再確認しましょう。

event<native><public>(t:type)<computes> := class(signalable(t), awaitable(t)):
    Await<native><override>()<suspends>:t
    Signal<native><override>(Val:t):void

ペイロードとして渡す値の型はeventクラスのインスタンスを生成する時にパラメータで指定します。上記を見ると、パラメータ型tSignal()の引数の型とAwait()の戻り値の型にそれぞれ設定されているのが分かります。

ペイロードを使ったイベント処理を実装する

では実際にペイロードを使うイベント処理を実装してみましょう。
前回の記事を書いてる最中に「このペイロードの仕組みを使えば、最初のサンプルコードでagentを取り回していた面倒な箇所をシンプルに書けるのでは?」と気づき、実践してみました。

サンプルコードはボタンデバイスが押下されたタイミングでスイッチデバイスのON/OFFをトグルするという物でした。スイッチをトグルするのはswitch_device.ToggleState()メソッドを実行すれば良いのですが、このメソッドは引数に実行者となるagentが必要です。その為、呼び出し元からタスクに向かってagentへの参照を通知する必要があったわけです。これをペイロードで行います。

以下、以前のサンプルコードとの差分箇所について、処理順に解説します。完全なサンプルコードは最後にアップしています。

Event<public>:event(agent) = event(agent){}

eventクラスのパラメータにagentを設定してインスタンスを生成、Event定数を定義します。以前はevent()ヘルパー関数呼び出しでしたが、今回は直接クラス生成しています(ややこしいですね!)。

Subscribe(CustomEvent: custom_event)<suspends>:void=
    Agent := CustomEvent.Event.Await()

生成されたタスクをevent.Await()で待機状態に移行します。右辺の評価時に待機状態に入っているので、この段階では式は値を返しておらず、Agent定数も(まだ)定義されていない点に注意して下さい。

Emit<public>(Agent:agent):void=
    Event.Signal(Agent)

さて、ボタンが押されると、格納されているcustom_eventオブジェクトそれぞれについてcustom_event.Emit()が呼びだされます。以前はここでメンバ変数にAgentを格納していましたが、今回はSingal()メソッドにAgentを渡します(逆にメンバ変数は用意していません)。

Agent := CustomEvent.Event.Await()
CustomEvent.Switch.ToggleState(Agent)

ぐるりと一周してきました。Signal()の実行によってタスクの待機状態は解除されます。Signal()の引数に設定したagentオブジェクト(これがペイロードです)は、Await()の戻り値として送信され、Agent定数に定義されます。この値を使ってswitch_devince.ToggleState()を実行します。

というわけで、agentを取り回していた処理を、奇麗にペイロードに置き換える事が出来たのでした。

サンプルコード

using { /Verse.org/Simulation/Tags }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }

custom_event := class():
    Switch :switch_device

    Event<public>:event(agent) = event(agent){}

    Emit<public>(Agent:agent):void=
        Event.Signal(Agent)

pub_test_device := class(creative_device):

    @editable MyButton : button_device = button_device{}
    @editable Switches : []switch_device = array{switch_device{}}

    var SwitchEvents: []custom_event = array{}

    Subscribe(CustomEvent: custom_event)<suspends>:void=
        Agent := CustomEvent.Event.Await()
        CustomEvent.Switch.ToggleState(Agent)
        
        spawn{Subscribe(CustomEvent)}

    OnBegin<override>()<suspends>:void=
        MyButton.InteractedWithEvent.Subscribe(HandleButtonInteraction)

        for:
            Switch : Switches
        do:
            EventObj : custom_event = custom_event{Switch := Switch}
            set SwitchEvents += array{EventObj}
            spawn{Subscribe(EventObj)}

    HandleButtonInteraction(Agent : agent) : void =
        for:
            SwitchEvent : SwitchEvents
        do:
            SwitchEvent.Emit(Agent)

おわりに

元のサンプルコードを書いていた時「なんでagentを上手い事取り回せないんだ!」と憤慨していたのですが、方法はちゃんと用意されていたという話でしたすみません。

ここまで来たらeventオブジェクト自体をスクラッチで実装出来るんじゃないかという気もします(出来ない気もします)。次回はそれに挑戦して上手く行けばイベント編は完結の予定(上手く行かなければ今回で完結)。

続き

https://zenn.dev/t_tutiya/articles/2f7b4ca32f70ff

お知らせ

verse言語とUEFNの記事を他にも書いているので御覧下さい。
https://zenn.dev/t_tutiya

最後まで読んで頂きありがとうございました。この記事がお役に立てたようであれば、是非LIKEとフォローをお願いします(今後の執筆のモチベーションに繋がります)。

#Verse #UEFN #Fortnite #Verselang #UnrealEngine

宣伝

「Unityシェーダープログラミングの教科書」シリーズ1~5をBOOTHで頒布中です。
https://s-games.booth.pm/

Discussion