パラレル Luau

*このコンテンツは、ベータ版のAI(人工知能)を使用して翻訳されており、エラーが含まれている可能性があります。このページを英語で表示するには、 こちら をクリックしてください。

パラレル Luau プログラミングモデルを使用すると、コードを複数のスレッドで同時に実行できます。これにより、エクスペリエンスのパフォーマンスを向上させることができます。エクスペリエンスにコンテンツを追加するにつれて、このモデルを採用することで、Luauスクリプトのパフォーマンスと安全性を維持できます。

パラレルプログラミングモデル

デフォルトでは、スクリプトは連続して実行されます。NPC などのユーザーに適用される複雑なロジックやコンテンツを持つエクスペリエンスの場合、逐次実行によりユーザーに遅れが生じる可能性があります。パラレルプログラミングモデルでは、タスクを複数のスクリプトに分割

並列プログラミングモデルはまた、コードに安全性のメリットを追加します。コードを複数のスレッドに分割することにより、1つのスレッドでコードを編集すると、並列で実行されている他のコードには影響しません。これにより、コードの 1つのバグが全体のエクスペリエンスを損なうリスクが軽減され、更アップデートを押すときにラ

並列プログラミングモデルを採用することは、すべてを複数のスレッドに入れることを意味しません。たとえば、「サーバー側レイキャスト検証」は、個々のユーザーにリモートイベントを並行して設定しますが、グローバルプロパティを変更するために最初のコードを連続して実行する必要があります。これは並列実行の共通

ほとんどの場合、並列フェーズでインスタンスを変更するなど、並列でサポートされていない操作があり、スクリプトの実行を妨げる可能性があるため、シリアルフェーズとパラレルフェーズを組み合わせて、目的の出力を達成する必要があります。並列 API の使用レベルに関する詳細は、スレッドセーフティを参照してください。

コードを複数のスレッドに分割する

エクスペリエンスのスクリプトを複数のスレッドで同時に実行するには、データモデルの異なる アクター の下で論理チャンクに分割する必要があります。アクターは Class.DataModel から継承された Actor インスタンスで表現されます。それ

アクターインスタンスを配置する

アクターを適切なコンテナに入れたり、NPC やレイキャスターなどの 3Dエンティティのトップレベルインスタンスタイプを置き換えるために使用したり、対応する スクリプト を追加できます。

An example of a Script under an Actor

ほとんどの状況では、アクターを別のアクターの子としてデータモデルに入れるべきではありません。ただし、特定のユースケースのために複数のアクター内にネストされたスクリプトを置くことを決めた場合、スクリプトは最も近い祖先アクターが所有しています。

A tree of actors and scripts that shows how a script is owned by its closest actor

スレッドの非同期化

スクリプトをアクターの下に置くと、並列実行の履行能が提供されますが、デフォルトではコードは単一スレッドで連続して実行されますが、ランタイムのパフォーマンスは向上しません。ランタイムパフォーマンスを向上させるためには、「Library.task.desynchron

あるいは、シグナルコールバックをスケジュールしたいときに RBXScriptSignal:ConnectParallel() メソッドを使用して、トリガー時にすぐにコードを並行して実行することもできます。シグナルコールバック内で task.desynchronize() を呼び出す必要はありません。

スレッドを非同期化

local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- 状態更アップデートを計算するパラレルコード
task.synchronize()
... -- インスタンスの状態を変更する一部のシリアルコード
end)

同じアクターの一部であるスクリプトは常にお互いに順番に実行されるため、複数のアクターが必要になります。たとえば、NPC の並列対応行動スクリプトをすべて 1つのアクターに入れると、1つのスレッドで連続して実行されますが、異なるNPCロジックのアクターが複数ある場合は、それ

1つのスレッドで連続して実行されているアクターの並列コード
複数のスレッドで同時に実行されているアクターの並列コード

スレッドセーフティ

並列実行中、DataModel 階層のほとんどのインスタンスに通常どおりアクセスできますが、一部のAPIプロパティと関数は並列読み書きが安全でない場合があります。これらを並列コードで使用すると、Roblox エンジンが自動的にこれらのアクセスを検出し、防止することができます。

API メンバーにはスレッドセーフティレベルがあり、次の表に示すように、パラレルコードで使用できるかどうか、およびどのように使用できるかを示します:

安全レベルプロパティの場合機能用
安全でない 並行して読んだり書いたりすることはできません。同時に呼び出すことはできません。
パラレル読み取りを読む 並行して読み取るが、書き込めない。N/A
ローカルの安全 同じアクター内で使用できます。並行して他の Actors によって読み取ることができますが、書き込みはできません。同じアクター内で呼び出すことができます; 別の Actors に並行して呼び出すことはできません。
安全 読み込みと書き込みが可能。呼び出すことができます。

API メンバー用のスレッドセーフティタグは、API リファレンス で見つけることができます。当該を使用するときは、API 呼び出しやプロパティの変更が並列スレッド間でどのようにインタラクトするかを考慮する必要があります。通常、複数のアクターが他のアクターと同じデータを読むのは安全ですが、他のアクターの状態を変更することは

クロススレッドコミュニケーション

マルチスレッドコンテキストでは、異なるアクターのスクリプトが互いに通信してデータを交換したり、タスクを調整したり、アクティビティを同期したりすることができます。エンジンはクロススレッド通信のための次のメカニズムをサポートしています:

クロススレッド通信のニーズに対応するために、複数のメカニズムをサポートできます。たとえば、Actor Messaging API を介して共有テーブルを送信できます。

俳優メッセージ

アクターメッセージング APIは、シリアルまたはパラレルコンテキストのいずれかでスクリプトが同じデータモデルのアクターにデータを送信できるようにします。この API を介した通信は非同期であり、送信者は受信者がメッセージを受信するまでブロックしません。

この API を使用してメッセージを送信すると、メッセージをカテゴリに分類するための トピック を定義する必要があります。各メッセージは単一のアクターにのみ送信できますが、そのアクターは内部的にメッセージにバインドされた複数のコールバックを持つことができます。アクターの子孫であるスクリプトのみがメッセージを受信でき

API には次のメソッドがあります:

  • Actor:SendMessage() アクターにメッセージを送信する。
  • Actor:BindToMessage() ルアウコールバックをシリアルコンテキストで指定されたトピックのメッセージにバインディングします。
  • Actor:BindToMessageParallel() ルアウコールバックをパラレルコンテキストで指定されたトピックのメッセージにバインディングします。

次の例では、Actor:SendMessage() を使用してトピックを定義し、送信者側にメッセージを送信する方法を示しています:

メッセージ送信者の例

-- 「挨拶」のトピックでワーカーアクターに 2つのメッセージを送信する
local workerActor = workspace.WorkerActor
workerActor:SendMessage("Greeting", "Hello World!")
workerActor:SendMessage("Greeting", "Welcome")
print("Sent messages")

次の例では、Actor:BindToMessageParallel() を使用して、受信者側の並列コンテキストで特定のトピックのコールバックをバインドする方法を示しています:

メッセージ受信機の例

-- このスクリプトの親になった俳優を取得する
local actor = script:GetActor()
-- 「挨拶」メッセージトピックのコールバックをバインド
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")

共有テーブル

SharedTable は、複数のアクターの下で実行されているスクリプトからアクセス可能なテーブルのようなデータ構造です。これは、大量のデータを含んでおり、複数のスレッド間で共通の共有状態を必要とする状況に有用です。たとえば、データモデルに保存されていない共通のワールド状態に複数のアクターが作業する場合な

共有テーブルを別のアクターに送信すると、データのコピーが作成されるのではなく、複数のスクリプトによる安全かつアトミックな更新をサポートされた共有テーブルを許可します。一つのアクターによって更新された共有テーブルは、即座に全てのアクターに見えるようになります。共有テーブルは、基本となるデータをコピーする代わりに

ダイレクトデータモデル通信

データモデルを使用して、複数のスレッド間の通信を直接促進することもできます。ここでは、さまざまなアクターがプロパティまたは属性を書き込んでから読み取ることで通信を行うことができます。ただし、スレッドセーフを保つために、並行して実行されているスクリプトは、一般にデータモデルに書き込むことができ

サーバー側レイキャスト検証

戦闘とバトル体験には、ユーザーの武器に「イキャスティング」を有効にする必要があります。 クライアントが武器をシミュレートして良好なレイテンシーを達成すると、サーバーはヒットを確認する必要があります。これには、レイキャストと予想される文字速度を計算するいくつかのヒューリスティックを行い、過去の動作を確認す

クライアントがヒット情報を伝達するために使用する単一の集中型スクリプトを使用する代わりに、サーバー側で各ヒット検証プロセスを並行して実行し、すべてのユーザーキャラクターが個別のリモートイベントを持つことができます。

そのキャラクターの Actor の下で実行されるサーバーサイドスクリプトは、パラレル接続を使用してこのリモートイベントに接続し、ヒットを確認するための関連するロジックを実行します。ロジックがヒットを確認すると、ダメージが差し引かれます。これはプロパティを変更することを含むので、最初は連続して実行され


local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- 新しいリモートイベントを作成し、ツールに親を付ける
remoteEvent.Name = "RemoteMouseEvent" -- ローカルスクリプトがそれを探すことができるように名前を変更
remoteEvent.Parent = tool
local remoteEventConnection -- リモートイベント接続の参照を作成
-- リモートイベントをリスニングする関数
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- シリアル: シリアルでセットアップコードを実行
local character = player.Character
-- レイキャスト中にユーザーのキャラクターを無視する
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- パラレル:レイキャストをパラレルで実行
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- クリック位置がオブジェクトから若干オフセットされているため、レイを若干拡張する
local lookDirection = (1 + epsilon) * (clickLocation.Position - origin)
local raycastResult = workspace:Raycast(origin, lookDirection, params)
if raycastResult then
local hitPart = raycastResult.Instance
if hitPart and hitPart.Name == "block" then
local explosion = Instance.new("Explosion")
-- SERIAL: コードは、アクター以外の状態を変更します
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- 爆発を死亡しないようにする
explosion.Position = clickLocation.Position
-- 複数のアクターがレイキャストで同じ部分を取得し、破壊することに同意する
-- 完全に安全ですが、1 回ではなく 2 回の爆発になります
-- 以下のダブルチェックは、実行が最初にこの部分に到達することを確認します
if hitPart.Parent then
explosion.Parent = workspace
hitPart:Destroy() -- 破壊する
end
end
end
end
-- 最初はシグナルをシリアルに接続して、いくつかの設定コードが並行して実行できないため
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)

サーバー側の手続き型地形生成

あなたの経験のための広大な世界を作成するには、世界を動的に満たすことができます。手続き型生成は通常、オブジェクトの配置、材料の使用、およびボクセル充填のための比較的複雑な計算を行うジェネレータで、独立した地形チャンクを作成します。生成コードを並行して実行すると、プロセスの効率が向上します。次のコ


-- 並列実行にはアクターの使用が必要
-- このスクリプトは自分をクローンします; オリジナルはプロセスを起動し、クローンは作業員として動作します
local actor = script:GetActor()
if actor == nil then
local workers = {}
for i = 1, 32 do
local actor = Instance.new("Actor")
script:Clone().Parent = actor
table.insert(workers, actor)
end
-- すべてのアクターを自分の下に置く
for _, actor in workers do
actor.Parent = script
end
-- メッセージを送信してアクターに地形を生成するように促す
-- この例では、アクターはランダムに選択されます
task.defer(function()
local rand = Random.new()
local seed = rand:NextNumber()
local sz = 10
for x = -sz, sz do
for y = -sz, sz do
for z = -sz, sz do
workers[rand:NextInteger(1, #workers)]:SendMessage("GenerateChunk", x, y, z, seed)
end
end
end
end)
-- オリジナルのスクリプトから退出します。コードの残りはそれぞれのアクターで実行されます
return
end
function makeNdArray(numDim, size, elemValue)
if numDim == 0 then
return elemValue
end
local result = {}
for i = 1, size do
result[i] = makeNdArray(numDim - 1, size, elemValue)
end
return result
end
function generateVoxelsWithSeed(xd, yd, zd, seed)
local matEnums = {Enum.Material.CrackedLava, Enum.Material.Basalt, Enum.Material.Asphalt}
local materials = makeNdArray(3, 4, Enum.Material.CrackedLava)
local occupancy = makeNdArray(3, 4, 1)
local rand = Random.new()
for x = 0, 3 do
for y = 0, 3 do
for z = 0, 3 do
occupancy[x + 1][y + 1][z + 1] = math.noise(xd + 0.25 * x, yd + 0.25 * y, zd + 0.25 * z)
materials[x + 1][y + 1][z + 1] = matEnums[rand:NextInteger(1, #matEnums)]
end
end
end
return {materials = materials, occupancy = occupancy}
end
-- コールバックをパラレル実行コンテキストで呼び出すにバインド
actor:BindToMessageParallel("GenerateChunk", function(x, y, z, seed)
local voxels = generateVoxelsWithSeed(x, y, z, seed)
local corner = Vector3.new(x * 16, y * 16, z * 16)
-- 現在、WriteVoxels() はシリアルフェーズで呼び出されなければなりません
task.synchronize()
workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)

ベストプラクティス

並列プログラミングのメリットを最大限に活用するには、Lua コードを追加するときに次のベストプラクティスを参照してください:

  • 長い計算を避ける — 並列でも、長い計算は他のスクリプトの実行をブロックし、ラグを引き起こす可能性があります。長くて頑固な計算を大量に処理するために並列プログラミングを使用しないでください。

    Diagram demonstrating how overloading the parallel execution phase can still cause lag
  • アクターの適切な数を使用する — 最高のパフォーマンスを得るには、より多くの Actors を使用してください。デバイスのコアが Actors よりも少ない場合でも、粒度はコア間のより効率的なロードバランシングを可能にします。

    Demonstration of how using more actors balances the load across cores

    これは、できるだけ多くの Class.Actor