🤖

[UEFN][verse]グローバル変数を使いたい[3]weak_mapとsession

2023/08/29に公開

軽くお知らせ

まだ先の話ですが、いずれVerseの記事は土屋がメインでやっているはてなブログの方にアップしていく予定でいます(シリーズで書いている物はキリの良い所までzennに上げます)。

今回はVerseでグローバル変数を使いたいという話の3回目です。前回はこちら(タイトル変えました)。
https://zenn.dev/t_tutiya/articles/0a19265f905640

weak_map

v25.20から、weak_mapというコンテナ型が追加されました。

https://dev.epicgames.com/documentation/en-us/uefn/map-in-verse#weakmap

日本語ドキュメントにはまだweak_mapの記載がありません[1]

weak_mapはグローバル変数として使用する事が許可されている変数型です。また、ラウンドを超えて継続的に値を保持する機能を持っています(ただし、こちらについてはv25.40現在は正しく機能していません)。

今回はこのweak_mapについて現在分かっている事をまとめました。

基本説明

まず、weak_mapの機能について確認します。weak_mapは、組み込みのコンテナ型であるmapのスーパータイプです。

「スーパータイプ(supertype)」は、クラスにおける「スーパークラス(≒親クラス≒派生元クラス)」の型(type)バージョンだと考えて問題ありません。詳しくはこちらの記事を参照。

https://zenn.dev/t_tutiya/articles/71010a30e35dcc

weak_mapは「弱い(weak)map」という名前の通り、mapの機能が限定された型です[2]

weak_mapは、mapと比較して以下が異なっています。

  • Lengthメンバを持たず、要素数を確認出来ない。
    • ←正確には、map.Length()は拡張メソッドのため、mapでしか使用できません。
  • イテレート出来ない
    • For式で巡回する事ができません
  • ConcatenateMaps()関数を適用出来ない
    • ConcatenateMaps()関数はmap同士を結合して新しいmapを定義する関数です。
  • グローバル変数として使用出来る
    • ←後述します

初期化と使用

実際の使い方は以下のようになります(公式ドキュメントから引用)。

ExampleFunction():void=
    var MyWeakMap:weak_map(int, int) = map{} 
    if: 
        set MyWeakMap[0] = 1 
    then:
        if (Value := MyWeakMap[0]):
            Print("Value of map at key 0 is {Value}")

ここでは、キーと値がどちらもint型のweak_mapであるMyWeakMap変数を定義し、値の設定と取得をしています。

weak_mapの型宣言はパラメータ型クラスと同じ仕様になっていて、mapの型宣言とは書き方が異なる点に注意が必要です。念のためmapの型宣言もおさらいしておきましょう。下記はキーと値がどちらもstringmapになります(公式ドキュメントから引用)。

ExampleMap : [string]string = map{"a" => "apple", "b" => "bear", "c" => "candy"}

また、初期化の際には=map{}を使います。これはweak_mapmapのスーパータイプである事によります。なのでこういう形の初期化もできます。

MyWeakMap:weak_map(int, int) = map{0 => 2} 

一方、以下の様に右辺でweak_mapを生成するような初期化はできません。

MyWeakMap:weak_map(int, int) = weak_map{} 

なぜこれが出来ないのかは想像しかできませんが。恐らくweak_mapは実際にはカスタムクラスで、純粋な型として定義されていないのだと思います。後述するようにweak_mapは処理系の都合で用意された部分があるため、言語仕様に組み込んでいないのでしょう[3]

グローバル変数としての利用(session型の使用)

weak_mapは、現時点のVerseにおいて唯一、moduleスコープで定義する事が出来る型です。つまり、weak_mapはグローバル変数として使用できる型であり、敢えて機能が制限されたmapを使う理由はここにあります[4]

前回、グローバル変数的な使い方が出来る唯一の方法としてタグを使う手法を紹介しました。weak_map型を使えばよりシンプルにグローバル変数へのアクセスが可能になります。

グローバル変数として定義する場合は以下の様に記述します(公式ドキュメントから引用)。

var GlobalInt:weak_map(session, int) = map{}
ExampleFunction():void=
  X := if (Y := GlobalInt[GetSession()]) then Y + 1 else 0
  if:
      set GlobalInt[GetSession()] = X
  Print({X})

このコードではキーがsession、値がintのweak_mapであるGlobalInt変数をモジュールスコープに定義しています。

モジュールスコープに定義する場合、キーはsessionでなければならず、それ以外の型を指定した場合は下図のようにコンパイルエラーになります。

sessionというのはverse.digest.verseファイルで定義されている組み込みの<unique>クラスで、今現在実行している「セッション(Session)」を表すuniqueなインスタンスを指定します。

この「セッション」という用語はどうも定義がないっぽいのですが、「ある島がサーバー上でインスタンス化してから、その島から全てのユーザーがいなくなりインスタンスが削除されるまで」を1セッションと考えて良いだろうと思います。

sessionインスタンスはGetSession()関数を通してのみ取得可能です。同一セッション内であれば常に同じインスタンスが返ると考えて良いでしょう。この仕組みにより、現在のセッションに割り当てられたグローバル変数にアクセスできるわけです。

仕組みを想像してみる

何故グローバル変数にアクセスするためにsessionを指定しなければならないのかですが、仕様から想像するに、モジュールスコープ(≒グローバル)に配置されたweak_mapは、サーバー上でインスタンス化されている全てのセッションで共有されているんじゃないかと思います。定数も共有出来るのでメモリ効率良さそうだし。

そして、その共有されているweak_mapから、自身のセッション用の値だけをアクセスする(≒他のセッションに干渉しない)ために、session変数をキーにするのが強制されているのかなと思います。

(工事中)ラウンドを超えた永続化は現時点では上手く動かない模様

※土屋自身がラウンドをまたぐコードをまだ書いていないため、この稿は未検証です。ご注意ください。

(23/08/29 時間が無くてここは作業中)

お知らせ

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

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

#Verse #UEFN #Fortnite #Verselang #UnrealEngine

宣伝

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

脚注
  1. ドキュメント更新時の翻訳ワークフローに不具合があるのだと思われる ↩︎

  2. weak_mapの機能を拡張した物がmap」とした方がより正確かもしれません。 ↩︎

  3. 推測です ↩︎

  4. mapより速度やメモリ効率が良い可能性はあるが明言はされていない ↩︎

Discussion