😽

[UEFN][Verse]logクラスを使って失敗コンテキスト内でもコンソール出力デバッグを可能にする

2023/08/15に公開

イベント実装の続きを書いているのですが、このスニペット[1]を見て「logクラスなんてあったの!?」と驚いたので記事にまとめました。

https://dev.epicgames.com/community/snippets/p8N2/fortnite-project-wide-logger

logクラスとは

Verseはサーバーサイドで実行されている事もあり、現状ではステップ実行する方法がありません。その為、デバッグは主にPrint()関数を使う原始的な方法で行う必要があります。

ところが、Print()メソッドは<no_rollback>が指定されていて、失敗コンテキスト内では実行する事ができません。例えば、if式の述語部で複数の条件を記述した時、どの条件で失敗したのかをPrint()で確認するとうい事が出来ないのです。

ではどうするのかというと、Print()メソッドの代わりに、ログの仕組みを使用します。ログはPrint()メソッドと同じようにコンソールに文字列を出力する仕組みで、かつ、<no_rollback>の制限がありません(どうしてPrint()ではNGでログではOKなのかはよくわからない)。

ログはlog/log_channnel/log_levelの3つのクラスで構成されます。

https://dev.epicgames.com/documentation/en-us/uefn/verse-api/unrealenginedotcom/temporary/diagnostics/log

https://dev.epicgames.com/documentation/en-us/uefn/verse-api/unrealenginedotcom/temporary/diagnostics/log_channel

https://dev.epicgames.com/documentation/en-us/uefn/verse-api/unrealenginedotcom/temporary/diagnostics/log_level

サンプルコード

サンプルコードは以下にになります。

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

#A:ログチャンネル定義
my_log_channel<public> := class(log_channel):
 
log_test := class(creative_device):
    @editable MyButton : button_device = button_device{}

	#B:ログ出力
    ProjectLog<public>(Message:[]char, ?Level:log_level = log_level.Normal)<transacts>:void=
        Logger := log{Channel := my_log_channel}
        Logger.Print(Message, ?Level := Level)

    OnBegin<override>()<suspends>:void=
        #ボタンにイベントハンドラを登録
        MyButton.InteractedWithEvent.Subscribe(HandleButtonInteraction)
    
    #C:ボタンがインタラクトしたら実行される
    HandleButtonInteraction(Agent : agent) : void =
        if:
            ProjectLog("log_level.Normal")
            ProjectLog("log_level.Warning" , ?Level := log_level.Warning)
            0=0
            ProjectLog("log_level.Error" , ?Level := log_level.Error)
        then:
            Print("Success")
        else:
            Print("Fail")

log_testデバイスをシーンに配置し、詳細タブでボタンを設定します。ゲームを開始してボタンにインタラクトすると、コンソールに以下の様に出力されます。

最初の3行が、log出力された文字列になります。

コード解説

主要な処理について解説します。

#A:ログチャンネル定義
my_log_channel<public> := class(log_channel):

ログ出力する場合、そのログが属するチャンネルを設定する必要があります。ログチャンネルは、log_channelクラスを継承したクラスとして定義します。ここではmy_log_channelログチャンネルを定義しています。ちなみにですが、行末の":"を忘れると構文エラーになるのでご注意ください。

#B:ログ出力
ProjectLog<public>(Message:[]char, ?Level:log_level = log_level.Normal)<transacts>:void=
    Logger := log{Channel := my_log_channel}
    Logger.Print(Message, ?Level := Level)

ログ出力するメソッドです。logクラスはアーキタイプでmy_log_channelをログチャンネルに指定しています。log#Printメソッドにはlog_level列挙体を使って、ログのレベルを指定できます。
log_level列挙体は現状5個のレベルが定義されていますが、機能するのはNormal/Warning/Errorの3個のみのようです[2]

#C:ボタンがインタラクトしたら実行される
HandleButtonInteraction(Agent : agent) : void =
    if:
        ProjectLog("log_level.Normal")
        ProjectLog("log_level.Warning" , ?Level := log_level.Warning)
        0=0
        ProjectLog("log_level.Error" , ?Level := log_level.Error)
    then:
        Print("Success")
    else:
        Print("Fail")

ここでログを出力しています。途中で0=0という式が入っていますが、これはif式の中には最低でも一個は失敗許容式が必要な為です。この式を0=1に買えて再実行すると、出力は以下の様に変化します。

先程と異なり、ログは2個しか出力されず、順番も逆で、かつ文中に"(Rolling Back)"と書かれています。これは、0=1が評価されたタイミングでif式が失敗し、処理が(実行順と逆順に)ロールバックされた事を示しています。これにより、if式が失敗したのが3つめのProjectLog()メソッドの直前、つまり0=1のタイミングであると分かるわけです。

終わりに

土屋自身はそこまで複雑なif式を書いていないので、このようなログ機構が必要になった事はまだ無いのですが、ログレベルの指定も出来ますし、覚えておくと便利なのではないでしょうか。多分。

お知らせ

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

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

#Verse #UEFN #Fortnite #Verselang #UnrealEngine

脚注
  1. 「部分的なソースコード」くらいの意味 ↩︎

  2. UEFNエディタの設定を変えればいいのかもしれないが未調査 ↩︎

Discussion