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 expérience. Au fur et à mesure que vous développez votre expérience 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 expérience comporte une logique ou un contenu complexe, tel que des personnages non joueurs (PNJ), une validation de raycasting, et une génération procédurale, alors une exécution séquentielle pourrait causer 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 d'expérience 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 les autres codes s'exécutant en parallèle. Cela réduit le risque d'avoir un bug dans votre code corrompant l'ensemble de l'expérience 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 tout mettre dans plusieurs threads. Par exemple, la validation de raycasting côté serveur assigne à chaque utilisateur individuel un événement distant en parallèle, mais nécessite tout de même que le code initial s'exécute en série pour changer les propriétés globales, ce qui est un modèle commun pour l'exécution parallèle.

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

Diviser le code en plusieurs threads

Pour exécuter les scripts de votre expérience sur 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 distribuent la charge sur plusieurs cœurs tournant simultanément.

Placer les instances d'acteur

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

Un exemple d'un 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 est possédé par son acteur ancêtre le plus proche.

Un arbre d'acteurs et de scripts montrant comment un script est possédé par 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 le même 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 du coroutine actuel pour exécuter du code en parallèle et le reprend à la prochaine opportunité d'exécution parallèle. Pour changer un script de nouveau en exécution séquentielle, appelez task.synchronize().

Alternativement, vous pouvez utiliser la méthode RBXScriptSignal:ConnectParallel() lorsque vous voulez planifier un rappel de signal pour exécuter immédiatement votre code en parallèle dès que ce signal est déclenché. 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 change 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, vous avez donc 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, voir 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

Lors de 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 API ne sont pas sûres à lire ou à écrire. Si vous les utilisez dans votre code parallèle, le moteur Roblox peut automatiquement détecter 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
Securité localePeut être utilisé dans le même acteur ; peut être lu mais pas écrit par d'autres Actors en parallèle.Peut être appelé dans le même acteur ; ne peut pas être appelé par d'autres Actors en parallèle.
SûrPeut ê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 devriez également tenir compte de la façon dont les appels d'API ou les changements de propriété 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 de la 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 soutenir 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, soit dans un contexte séquentiel soit parallèle, d'envoyer des données à un acteur dans le même modèle de données. La communication par le biais de cette API est asynchrone, dans laquelle l'émetteur ne bloque pas jusqu'à ce que le récepteur reçoive le message.

Lors de l'envoi de messages en utilisant 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 internes associé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'émetteur :

Exemple d'émetteur de message
local Workspace = game:GetService("Workspace")
-- Envoyer deux messages à l'acteur travailleur 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 similaire à un tableau accessible depuis des scripts exécutés sous plusieurs acteurs. C'est utile pour des situations qui impliquent une grande quantité de données et nécessitent un état commun partagé 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 fait pas une 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 exécutés en parallèle ne peuvent généralement pas écrire dans le modèle de données. Donc, utiliser directement le modèle de données pour la communication vient avec 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 une expérience de combat et de bataille, vous devez activer le raycasting pour les armes de vos utilisateurs. Avec le client simulant les armes pour atteindre une bonne latence, le serveur doit confirmer le coup, ce qui implique de faire des raycasts et une certaine quantité d'heuristique pour calculer la vélocité attendue du personnage, et examiner le comportement passé.

Au lieu d'utiliser un script centralisé unique qui se connecte à un événement distant que les clients utilisent pour communiquer les 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 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ée un nouvel événement distant et le parenté à l'outil
remoteEvent.Name = "RemoteMouseEvent" -- Renommez-le pour que le script local puisse le chercher
remoteEvent.Parent = tool
local remoteEventConnection -- Crée une référence pour la connexion à l'événement distant
-- Fonction qui écoute un événement distant
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SÉQUENTIEL: Exécuter le code d'initialisation en séquentiel
local character = player.Character
-- Ignorer le personnage de l'utilisateur lors de la validation de 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 la position du clic pourrait être légèrement décalée de 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 aboutirait à deux explosions simultanées au lieu d'une
-- La suite vérifie que l'exécution est arrivée à cette partie en premier
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Détruisez-le
end
end
end
end
-- Connectez le signal en séquentiel au départ car le code d'initialisation ne peut 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 expérience, 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, l'algorithme de génération effectuant des calculs relativement complexes pour le placement d'objets, l'utilisation de matériaux, et le remplissage de 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 eux-mêmes
for _, actor in workers do
actor.Parent = script
end
-- Ordre aux acteurs de 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)
-- Sortir du 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 qu'il soit 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 calculs longs — Même en parallèle, les calculs longs peuvent bloquer l'exécution d'autres scripts et provoquer des ralentissements. Evitez d'utiliser la programmation parallèle pour gérer un volume important de calculs longs et non yieldables.

    Diagramme montrant comment la surcharge de la phase d'exécution parallèle peut toujours causer des ralentissements
  • Utilisez le bon nombre d'acteurs — Pour les meilleures performances, utilisez plus de Actors. Même si l'appareil a moins de cœurs que de Actors, la granularité permet un meilleur équilibrage de la charge 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 devriez encore diviser le code en Actors basés sur des unités de logique plutôt que de briser le code avec une logique connectée à différents Actors. Par exemple, si vous voulez activer la validation du raycasting en parallèle, il est raisonnable d'utiliser 64 Actors et plus au lieu de juste 4, même si vous ciblez des systèmes à 4 cœurs. Cela est précieux pour l'évolutivité du système et permet de distribuer le travail basé sur la capacité du matériel sous-jacent. Cependant, vous ne devriez 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.