Luau parallèle

*Ce contenu est traduit en utilisant l'IA (Beta) et peut contenir des erreurs. Pour consulter cette page en anglais, clique ici.

Avec le modèle de programmation Luau parallèle, vous pouvez exécuter du code sur plusieurs threads simultanément, ce qui peut améliorer les performances de votre jeu. À mesure que vous développez votre jeu avec plus de contenu, vous pouvez adopter ce modèle pour aider à maintenir les performances et la sécurité de vos scripts Luau.

Modèle de programmation parallèle

Par défaut, les scripts s'exécutent de manière séquentielle. Si votre jeu a une logique ou un contenu complexe, comme des personnages non-joueurs (PNJ), une validation de raycasting et une génération procédurale, alors l'exécution séquentielle peut provoquer des ralentissements pour vos utilisateurs. Avec le modèle de programmation parallèle, vous pouvez diviser les tâches en plusieurs scripts et les exécuter en parallèle. Cela permet à votre code de jeu de s'exécuter plus rapidement, ce qui améliore l'expérience utilisateur.

Le modèle de programmation parallèle ajoute également des avantages en matière de sécurité à votre code. En divisant le code en plusieurs threads, lorsque vous modifiez le code dans un thread, cela n'affecte pas le reste du code s'exécutant en parallèle. Cela réduit le risque qu'un bug dans votre code corrompe l'ensemble du jeu et minimise le délai pour les utilisateurs sur les serveurs en direct lorsque vous déployez une mise à jour.

Adopter le modèle de programmation parallèle ne signifie pas mettre tout dans plusieurs threads. Par exemple, la validation de raycasting côté serveur attribue à chaque utilisateur un événement distant en parallèle, mais nécessite toujours que le code initial s'exécute de manière séquentielle pour modifier les propriétés globales, ce qui est un modèle courant pour l'exécution parallèle.

La plupart du temps, vous devez combiner des phases séquentielles et parallèles pour obtenir le résultat souhaité, car actuellement, certaines opérations ne sont pas prises en charge en parallèle et peuvent empêcher les scripts de s'exécuter, comme la modification d'instances dans des phases parallèles. Pour plus d'informations sur le niveau d'utilisation des API en parallèle, consultez la sécurité des threads.

Diviser le code en plusieurs threads

Pour exécuter les scripts de votre jeu dans plusieurs threads simultanément, vous devez les diviser en morceaux logiques sous différents acteurs dans le modèle de données. Les acteurs sont représentés par des instances Actor héritant de DataModel. Ils fonctionnent comme des unités d'isolation d'exécution qui répartissent la charge sur plusieurs cœurs fonctionnant simultanément.

Placer des instances d'acteurs

Vous pouvez placer des acteurs dans des conteneurs appropriés ou les utiliser pour remplacer les types d'instances de niveau supérieur de vos entités 3D telles que les PNJ et les raycasters, puis ajouter les scripts correspondants.

Un exemple de Script sous un Acteur

Dans la plupart des situations, vous ne devriez pas mettre un acteur comme enfant d'un autre acteur dans le modèle de données. Cependant, si vous décidez de placer un script imbriqué dans plusieurs acteurs pour votre cas d'utilisation spécifique, le script appartient à son acteur ancêtre le plus proche.

Un arbre d'acteurs et de scripts qui montre comment un script appartient à son acteur le plus proche

Désynchroniser les threads

Bien que placer des scripts sous des acteurs leur confère la capacité d'exécution parallèle, par défaut, le code s'exécute toujours sur un seul thread de manière séquentielle, ce qui n'améliore pas les performances d'exécution. Vous devez appeler task.desynchronize(), une fonction yieldable qui suspend l'exécution de la coroutine actuelle pour exécuter du code en parallèle et la reprend à la prochaine opportunité d'exécution parallèle. Pour revenir à l'exécution séquentielle, appelez task.synchronize().

Alternativement, vous pouvez utiliser la méthode RBXScriptSignal:ConnectParallel() lorsque vous souhaitez planifier un rappel de signal pour exécuter immédiatement votre code en parallèle lors du déclenchement. Vous n'avez pas besoin d'appeler task.desynchronize() à l'intérieur du rappel de signal.

Désynchroniser un Thread
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Du code parallèle qui calcule une mise à jour d'état
task.synchronize()
... -- Du code séquentiel qui modifie l'état des instances
end)

Les scripts qui font partie du même acteur s'exécutent toujours séquentiellement les uns par rapport aux autres, donc vous avez besoin de plusieurs acteurs. Par exemple, si vous placez tous les scripts de comportement activés en parallèle pour votre PNJ dans un seul acteur, ils s'exécutent toujours séquentiellement sur un seul thread, mais si vous avez plusieurs acteurs pour différentes logiques de PNJ, chacun d'eux s'exécute en parallèle sur son propre thread. Pour plus d'informations, consultez les meilleures pratiques.

Code parallèle dans des Acteurs s'exécutant séquentiellement sur un seul thread
Code parallèle dans des Acteurs s'exécutant simultanément sur plusieurs threads

Sécurité des threads

Pendant l'exécution parallèle, vous pouvez accéder à la plupart des instances de la hiérarchie DataModel comme d'habitude, mais certaines propriétés et fonctions de l'API ne sont pas sûres à lire ou à écrire. Si vous les utilisez dans votre code parallèle, le moteur Roblox peut détecter automatiquement et empêcher ces accès de se produire.

Les membres de l'API ont un niveau de sécurité des threads qui indique si et comment vous pouvez les utiliser dans votre code parallèle, comme le montre le tableau suivant :

Niveau de sécuritéPour les propriétésPour les fonctions
Non sécuriséNe peut pas être lu ou écrit en parallèle.Ne peut pas être appelé en parallèle.
Lecture parallèlePeut être lu mais pas écrit en parallèle.N/A
Local sécuriséPeut être utilisé au sein du même Acteur ; peut être lu mais pas écrit par d'autres Actors en parallèle.Peut être appelé au sein du même Acteur ; ne peut pas être appelé par d'autres Actors en parallèle.
SécuriséPeut être lu et écrit.Peut être appelé.

Vous pouvez trouver des étiquettes de sécurité des threads pour les membres de l'API dans la référence API. Lorsque vous les utilisez, vous devez également considérer comment les appels d'API ou les modifications de propriétés pourraient interagir entre les threads parallèles. En général, il est sûr pour plusieurs acteurs de lire les mêmes données que d'autres acteurs, mais pas de modifier l'état d'autres acteurs.

Communication entre threads

Dans le contexte du multithreading, vous pouvez toujours permettre aux scripts dans différents acteurs de communiquer entre eux pour échanger des données, coordonner des tâches et synchroniser des activités. Le moteur prend en charge les mécanismes suivants pour la communication entre threads :

  • API de messagerie des acteurs pour envoyer des messages à un acteur à l'aide de scripts.
  • Structure de données de table partagée pour partager efficacement une grande quantité de données entre plusieurs acteurs sur un état partagé.
  • Communication directe du modèle de données pour une communication simple avec des restrictions.

Vous pouvez prendre en charge plusieurs mécanismes pour répondre à vos besoins de communication entre threads. Par exemple, vous pouvez envoyer une table partagée via l'API de messagerie des acteurs.

Messagerie des acteurs

L'API de messagerie des acteurs permet à un script, que ce soit dans un contexte séquentiel ou parallèle, d'envoyer des données à un acteur dans le même modèle de données. La communication via cette API est asynchrone, dans laquelle l'expéditeur ne bloque pas jusqu'à ce que le destinataire reçoive le message.

Lors de l'envoi de messages à l'aide de cette API, vous devez définir un sujet pour catégoriser le message. Chaque message ne peut être envoyé qu'à un seul acteur, mais cet acteur peut avoir plusieurs rappels liés à un message. Seuls les scripts qui sont des descendants d'un acteur peuvent recevoir des messages.

L'API a les méthodes suivantes :

L'exemple suivant montre comment utiliser Actor:SendMessage() pour définir un sujet et envoyer un message du côté de l'expéditeur :

Exemple d'Expéditeur de Message
local Workspace = game:GetService("Workspace")
-- Envoyer deux messages à l'acteur de travail avec un sujet de "Salutation"
local workerActor = Workspace.WorkerActor
workerActor:SendMessage("Greeting", "Bonjour le monde !")
workerActor:SendMessage("Greeting", "Bienvenue")
print("Messages envoyés")

L'exemple suivant montre comment utiliser Actor:BindToMessageParallel() pour lier un rappel pour un certain sujet dans un contexte parallèle du côté du récepteur :

Exemple de Récepteur de Message
-- Obtenir l'acteur auquel ce script est parenté
local actor = script:GetActor()
-- Lier un rappel pour le sujet de message "Salutation"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Lié aux messages")

Table partagée

SharedTable est une structure de données de type table accessible depuis des scripts s'exécutant sous plusieurs acteurs. Elle est utile pour les situations impliquant une grande quantité de données et nécessitant un état partagé commun entre plusieurs threads. Par exemple, lorsque plusieurs acteurs travaillent sur un état de monde commun qui n'est pas stocké dans le modèle de données.

Envoyer une table partagée à un autre acteur ne crée pas de copie des données. Au lieu de cela, les tables partagées permettent des mises à jour sûres et atomiques par plusieurs scripts simultanément. Chaque mise à jour d'une table partagée par un acteur est immédiatement visible par tous les acteurs. Les tables partagées peuvent également être clonées dans un processus efficace en ressources qui utilise le partage structurel au lieu de copier les données sous-jacentes.

Communication directe du modèle de données

Vous pouvez également faciliter la communication entre plusieurs threads directement en utilisant le modèle de données, dans lequel différents acteurs peuvent écrire et ensuite lire des propriétés ou des attributs. Cependant, pour maintenir la sécurité des threads, les scripts s'exécutant en parallèle ne peuvent généralement pas écrire dans le modèle de données. Ainsi, l'utilisation directe du modèle de données pour la communication comporte des restrictions et peut forcer les scripts à se synchroniser fréquemment, ce qui peut affecter les performances de vos scripts.

Exemples

Validation de raycasting côté serveur

Pour un jeu de combat et de bataille, vous devez activer le raycasting pour les armes de vos utilisateurs. Avec le client simulant les armes pour obtenir une bonne latence, le serveur doit confirmer le coup, ce qui implique de faire des raycasts et une certaine quantité d'heuristiques qui calculent la vitesse attendue du personnage et examinent le comportement passé.

Au lieu d'utiliser un seul script centralisé qui se connecte à un événement distant que les clients utilisent pour communiquer des informations de coup, vous pouvez exécuter chaque processus de validation de coup côté serveur en parallèle, chaque personnage utilisateur ayant un événement distant séparé.

Le script côté serveur qui s'exécute sous l'Actor de ce personnage se connecte à cet événement distant en utilisant une connexion parallèle pour exécuter la logique pertinente pour confirmer le coup. Si la logique trouve une confirmation d'un coup, les dégâts sont déduits, ce qui implique de modifier des propriétés, donc cela s'exécute séquentiellement au départ.

local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Créer un nouvel événement distant et le parenté à l'outil
remoteEvent.Name = "RemoteMouseEvent" -- Le renommer pour que le script local puisse le rechercher
remoteEvent.Parent = tool
local remoteEventConnection -- Créer une référence pour la connexion de l'événement distant
-- Fonction qui écoute un événement distant
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SÉQUENTIEL : Exécuter le code de configuration de manière séquentielle
local character = player.Character
-- Ignorer le personnage de l'utilisateur lors du raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALLÈLE : Effectuer le raycast en parallèle
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Utilisé pour étendre légèrement le rayon car l'emplacement du clic peut être légèrement décalé par rapport à l'objet
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")
-- SÉQUENTIEL : Le code ci-dessous modifie l'état en dehors de l'acteur
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Rendre l'explosion non mortelle
explosion.Position = clickLocation.Position
-- Plusieurs acteurs pourraient obtenir la même pièce dans un raycast et décider de la détruire
-- Cela est parfaitement sûr mais cela entraînerait deux explosions en même temps au lieu d'une
-- La vérification suivante s'assure que l'exécution est arrivée à cette partie en premier
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Détruire
end
end
end
end
-- Connecter le signal de manière séquentielle au départ car certains codes de configuration ne peuvent pas s'exécuter en parallèle
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)

Génération de terrain procédural côté serveur

Pour créer un vaste monde pour votre jeu, vous pouvez peupler le monde de manière dynamique. La génération procédurale crée généralement des morceaux de terrain indépendants, le générateur effectuant des calculs relativement complexes pour le placement des objets, l'utilisation des matériaux et le remplissage des voxels. Exécuter le code de génération en parallèle peut améliorer l'efficacité du processus. L'exemple de code suivant sert d'exemple.

-- L'exécution parallèle nécessite l'utilisation d'acteurs
-- Ce script se clone ; l'original initie le processus, tandis que les clones agissent comme des travailleurs
local Workspace = game:GetService("Workspace")
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
-- Parent tous les acteurs sous soi
for _, actor in workers do
actor.Parent = script
end
-- Instruire les acteurs à générer du terrain en envoyant des messages
-- Dans cet exemple, les acteurs sont choisis au hasard
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)
-- Quitter le script original ; le reste du code s'exécute dans chaque acteur
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
-- Lier le rappel pour être appelé dans le contexte d'exécution parallèle
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)
-- Actuellement, WriteVoxels() doit être appelé dans la phase séquentielle
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)

Meilleures pratiques

Pour tirer le maximum de bénéfices de la programmation parallèle, référez-vous aux meilleures pratiques suivantes lors de l'ajout de votre code Luau :

  • Évitez les longues computations — Même en parallèle, les longues computations peuvent bloquer l'exécution d'autres scripts et provoquer des ralentissements. Évitez d'utiliser la programmation parallèle pour gérer un grand volume de calculs longs et non yieldables.

    Diagramme démontrant comment la surcharge de la phase d'exécution parallèle peut encore provoquer des ralentissements
  • Utilisez le bon nombre d'acteurs — Pour de meilleures performances, utilisez plus de Actors. Même si l'appareil a moins de cœurs que Actors, la granularité permet un équilibrage de charge plus efficace entre les cœurs.

    Démonstration de la façon dont l'utilisation de plus d'acteurs équilibre la charge entre les cœurs

    Cela ne signifie pas que vous devez utiliser autant de Actors que possible. Vous devez toujours diviser le code en Actors en fonction des unités logiques plutôt que de casser le code avec une logique connectée à différents Actors. Par exemple, si vous souhaitez activer la validation de raycasting en parallèle, il est raisonnable d'utiliser 64 Actors et plus au lieu de seulement 4, même si vous ciblez des systèmes à 4 cœurs. Cela est précieux pour la scalabilité du système et permet de répartir le travail en fonction des capacités du matériel sous-jacent. Cependant, vous ne devez pas non plus utiliser trop de Actors, ce qui est difficile à maintenir.

©2026 Société Roblox. Roblox, le logo Roblox et Powering Imagination font partie de nos marques déposées aux États-Unis et dans d'autres pays.