😸

Verse言語の設計思想を読み解きたい(10)非同期処理② 非同期式と非同期コンテキスト

2023/04/12に公開

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

今回は「非同期式」と、非同期式を記述できる「非同期コンテキスト」について。
https://dev.epicgames.com/documentation/ja-jp/uefn/concurrency-overview-in-verse

イミディエイト式と非同期式

Verseの式は、「イミディエイト式(immediate expressions)」と「非同期式(async expressions)」のいずれかに分類されます。

ざっくり言うと、処理が1フレーム中に終わる式がイミディエイト式、処理が2フレーム以上にまたがる可能性がある[1]式が非同期式です。

余談:イミディエイト式の訳語について。

「イミディエイト式」も上手い事日本語に訳したいんですが、しっくり来る言葉を思いつきませんでした[2]。そんなに使う機会は無いので、カタカナで書くことにします。

シミュレーションアップデート

これまでイミディエイト式/非同期式の最小実行単位は「1フレーム」だと説明してきました。この呼称はゲームエンジンによってまちまちで、フレームの他にも「ティック(tick)」「アップデート(update)」「ゲームループ(game loop)」などとも呼ばれます。

Verseではこの最小実行単位を「シミュレーションアップデート(simulation update)」と呼びます。「フレーム」でいいじゃないかとも思いますが、実際には秒間あたりの描画回数とコードの実行回数は一致しない事があり[3]、区別する事にしたのかと思われます。

また、Verseは将来的に汎用プログラミング言語としての普及を目指しており[4]、環境依存のない汎用的な名称が必要だったのかなと想像します。正直カタカナで書くと長いんですが、これもそんなに頻出する用語でも無いとは思います。

suspendsエフェクト指定子と非同期関数

suspendsエフェクト指定子を付与した関数は「非同期関数(async expression)」になります。非同期関数を含む式は非同期式になります。逆に言えば、非同期関数を含まない式は全てイミディエイト式という事になります。

以下のサンプルコードはOnBegin非同期関数を定義しています。

OnBegin<override>()<suspends> : void =
    HideAllPlatforms()

サスペンド式

非同期関数内で、現在のフレーム中の処理を一時停止して他の処理に切り替えるには「サスペンド式(suspend expression)」を呼び出します。
現時点では、サスペンド式は組み込みの関数としてのみ提供されていて、ユーザーが独自に作る事は出来ないようです[5]

例えば、Sleep()非同期関数はサスペンド式です。実行すると処理が中断され、引数で指定した秒数後のフレームから処理を再開します。
サスペンド式としては他にも、オブジェクトの座標を指定秒数かけて移動するMoveTo()非同期関数などがあります。

非同期コンテキスト

非同期関数のボディ[6]は「非同期コンテキスト(async cnotext)」です。非同期式は非同期コンテキスト内でのみ記述出来ます(非同期コンテキスト内にイミディエイト式を記述する事もできます)。

非同期コンテキストのサンプルコードを見てみましょう。

OnBegin<override>()<suspends> : void =
    Print("A")
    Sleep(5.0)
    Print("B")

UEFNでのVerseのエントリポイントとして使用されるOnBegin関数は非同期関数なので、ボディに非同期式を記述できます。実行すると、ログに"A"を出力したのちにいったん処理が中断され、5秒後に再開して"B"を出力します。

なお非同期式は、イミディエイト式と同じように値を返す事ができます[7]。「値が返った時=非同期式の処理が終わった時」と考えて良いです。逆に言えば、非同期式は処理が終わるまで値を返しませんし、処理が次の式へ進む事もありません。

非同期コンテキスト自体は、コード内で複数の処理を同時に実行する仕組みではない点に注意してください。複数の処理を同時に実行するには、後述する並行処理式を使用します。

アトミック

非同期コンテキスト内において、連続するイミディエイト式は1つの塊として扱われ、途中で処理が切り替わらずに実行される事が保証されます。これを「コードが アトミック(atomic) である」と言います。

並行処理式

複数の非同期式を同時に(つまり、並行処理で)動作させるには「並行処理式(Concurrency Expression)」を使用します。並行処理式のコードブロックに複数の非同期式を記述すると、それらは同時に実行されます。

実行される非同期式の振る舞いの違いに応じて、複数の並行処理式が提供されています。また、それらは機能に応じて「構造化並行処理式(Structured Concurrency Expression)」と「非構造化並行処理式(Unstructured Concurrency Expression)」の2つに分類されます。

以下に並行処理式の一覧を示します。なお、並行処理式ではないのですが、同系統の構文を持つblock式もリストに加えておきます。

  • イミディエイト処理式(Immediately Expression)[8]
    • block
  • 構造化並行処理式(コードブロックを取る並行処理式)
    • sync
    • race
    • rush
    • branch
  • 非構造化並行処理式(式を一つだけ取る並行処理式)
    • spawn

次回からこれらの並行処理式を見ていきます。

続き

https://zenn.dev/t_tutiya/articles/394e71dab546c2


お知らせ

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

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

#Verse #UEFN #Fortnite #Verselang #UnrealEngine

宣伝

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

脚注
  1. 1フレーム中に終わる場合もあります ↩︎

  2. 「即時式」と訳される事もあります。公式ドキュメントでは"immidiate式" ↩︎

  3. 処理速度の問題で描画が飛ばされたり、その逆が起きる事があるため ↩︎

  4. 多分 ↩︎

  5. 明文化はされていません。Await()関数を使えば行けるのかもしれないけど未検証。後日試します。 ↩︎

  6. body. 関数の実装部の事 ↩︎

  7. sleep()非同期関数の返し値はvoidです ↩︎

  8. このブログの造語です。公式ドキュメントではblock式を分類する用語は用意されていません ↩︎

Discussion