📊

【Roblox】マルチスレッド処理

2024/10/04に公開

はじめに

ゲーム開発において処理の高速化の手法として使われることが多いマルチスレッド処理ですが、Robloxの開発で使用されるLuauでも実装することができます。今回はマルチスレッド処理の実装方法について紹介します。

Robloxバージョン:0.641.2.6410741

マルチスレッド処理とは

マルチスレッドについて一応軽く説明しておきます。本来スクリプトは順番に実行されるものですが、この方法だとゲーム内に多くのコンテンツが配置されたときに処理時間が長くなってしまい、遅延が発生してしまいます。そこでマルチスレッド処理ではタスクを複数のスレッドを使用して同時に実行することで処理時間を短くすることができます。
Robloxでのマルチスレッド処理は「Parallel Luau」を使用することで実装することができます。

Parallel Luauの実装

マルチスレッド処理を実装するにはスクリプトをActorインスタンスの子オブジェクトにする必要があります。

Actorインスタンスの子オブジェクトに配置したスクリプトがすべて並列処理になるわけではなく、スクリプトからtask.desynchronize()task.synchronize()を使ってシリアル実行と並列実行を切り替えることができます。

--[[
    この間シリアル実行
]]
task.desynchronize() -- 並列実行に切替
--[[
    この間並列実行
]]
task.synchronize() -- シリアル実行に切替
--[[
    この間シリアル実行
]]

シグナルコールバックをスケジュールして、トリガー時にコードを並列実行する場合は:ConnectParallel()メソッドを使用できます。

local RunService = game:GetService("RunService")

RunService.Heartbeat:ConnectParallel(function()
    --[[
        この間並列実行
    ]]
    task.synchronize()
    --[[
        この間シリアル実行
    ]]
end)

スレッドセーフティ

並列実行中は、通常時と同様にDataModel階層のほとんどのインスタンスにアクセスできますが、一部のAPIプロパティと関数は安全に読み書きできません。

安全レベル プロパティ 関数
Unsafe 並列実行から読み取りと書き込みを行うことはできません。 並列実行から呼び出すことができません。
ReadParallel 並列実行から読み取ることはできますが、書き込むことはできません。 該当なし。
LocalSafe 同じアクター内で使用できます。ほかのアクターから並列して読み取ることはできますが、書き込むことはできません。 同じアクター内で呼び出すことができますが、ほかのアクターから並列して呼び出すことはできません。
Safe 読み取りと書きことが可能です。 呼び出すことができます。

APIメンバーのスレッドセーフティタグは、APIリファレンスで確認できます。

上記はHumanoidのプロパティです。
「並列読みとり」と書かれているものが表の「ReadParallel」に該当するものになり、読み取りのみ可能になっています。場合によってはAPIリファレンスに記載のないものがありますが、基本的には「Unsafe」に設定されています。

スレッド間通信

Actor Messaging API

シリアルまたは並列コンテキストのスクリプトから、同じデータ モデル内のアクターにデータを送信できます。この API を介した通信は非同期で実行されます。
メッセージを分類するためのトピックを定義する必要があります。
各メッセージは1つのアクターにのみ送信できますが、そのアクターは内部的にメッセージにバインドされた複数のコールバックを持つことができます。

メッセージの送信

-- メッセージを送信するアクターを取得
local workerActor = workspace.WorkerActor

-- アクターにメッセージを送信
workerActor:SendMessage("topic_01", "Hello World!")

メッセージの受信

local actor = script:GetActor()

-- シリアル実行する場合
actor:BindToMessage("topic_01", function(receiveMessage)
    print(actor.Name, "-", receiveMessage)
end)

-- 並列実行する場合
actor:BindToMessageParallel("topic_01", function(receiveMessage)
    print(actor.Name, "-", receiveMessage)
end)

SharedTable

複数のアクターで実行されているスクリプトからアクセスできるテーブルのようなデータ構造です。SharedTableを別のアクターに送信しても、データのコピーは作成されません。1つのアクターによるSharedTableの更新はすべてのアクターに即座に反映されます。
SharedTableを共有するにはSharedTableRegistryを使用します。

テーブルの作成

local SharedTableRegistry = game:GetService("SharedTableRegistry")

local sharedTable = SharedTable.new()
sharedTable[1] = "AAA"
sharedTable["x"] = 1234

-- SharedTableRegistryにテーブルを登録
-- 第1引数:テーブル名 第2引数:SharedTable
SharedTableRegistry:SetSharedTable("TestTable", sharedTable)

テーブルの使用

local SharedTableRegistry = game:GetService("SharedTableRegistry")

-- テーブルの取得
-- 第1引数:テーブル名
local sharedTable = SharedTableRegistry:GetSharedTable("TestTable")

print(sharedTable)
--▼ {
--      [1] = "AAA",
--      ["x"] = 1234
--  }

ダイレクトデータモデルの送信

DataModelを使用して、複数のスレッド間の通信を容易にすることもできます。これにより、異なるアクターがプロパティや属性を書き込み、読み取ることができます。ただし、スレッドの安全性を維持するために、並行して実行されるスクリプトは通常DataModelに書き込むことができません。
通信にDataModelを直接使用すると制限が伴い、スクリプトが頻繁に同期される可能性があり、スクリプトのパフォーマンスに影響を与える可能性があります。

まとめ

今回はマルチスレッド処理について紹介しました。
並列実行に対応しているAPIがまだ少ない状態ではありますが、Robloxでは多くの処理を同時に行う必要があることが多いので並列処理を上手く使って、処理の高速化を図っていきましょう。
お読みいただきありがとうございました。

参考

https://create.roblox.com/docs/ja-jp/scripting/multithreading
https://create.roblox.com/docs/ja-jp/reference/engine/datatypes/SharedTable
https://create.roblox.com/docs/ja-jp/reference/engine/classes/SharedTableRegistry

ランド・ホー Roblox開発チーム

Discussion