Con il modello di programmazione Luau Parallelo, puoi eseguire codice su più thread simultaneamente, il che può migliorare le prestazioni del tuo gioco. Man mano che espandi il tuo gioco con più contenuti, puoi adottare questo modello per aiutare a mantenere le prestazioni e la sicurezza dei tuoi script Luau.
Modello di programmazione parallela
Per impostazione predefinita, gli script vengono eseguiti in modo sequenziale. Se il tuo gioco ha logiche o contenuti complessi, come personaggi non giocanti (NPC), validazione di raycasting e generazione procedurale, l'esecuzione sequenziale potrebbe causare ritardi per i tuoi utenti. Con il modello di programmazione parallela, puoi dividere i compiti in più script ed eseguirli in parallelo. Questo fa sì che il codice del tuo gioco venga eseguito più velocemente, migliorando l'esperienza dell'utente.
Il modello di programmazione parallela aggiunge anche vantaggi di sicurezza al tuo codice. Dividendo il codice in più thread, quando modifichi il codice in un thread, non influisce su altri codici in esecuzione in parallelo. Questo riduce il rischio che un bug nel tuo codice corrompa l'intero gioco e minimizza il ritardo per gli utenti nei server live quando pubblichi un aggiornamento.
Adottare il modello di programmazione parallela non significa mettere tutto in più thread. Ad esempio, la validazione di raycasting lato server imposta ogni singolo utente un evento remoto in parallelo, ma richiede comunque che il codice iniziale venga eseguito in modo seriale per modificare le proprietà globali, che è un modello comune per l'esecuzione parallela.
Nella maggior parte dei casi, è necessario combinare fasi seriali e parallele per ottenere l'output desiderato, poiché attualmente ci sono alcune operazioni non supportate in parallelo che possono impedire l'esecuzione degli script, come la modifica delle istanze nelle fasi parallele. Per ulteriori informazioni sul livello di utilizzo delle API in parallelo, vedere sicurezza dei thread.
Dividi il codice in più thread
Per eseguire gli script del tuo gioco in più thread contemporaneamente, devi dividerli in blocchi logici sotto diversi attori nel modello dei dati. Gli attori sono rappresentati da istanze Actor che ereditano da DataModel. Funzionano come unità di isolamento dell'esecuzione che distribuiscono il carico su più core in esecuzione simultaneamente.
Posiziona le istanze degli attori
Puoi mettere gli attori in contenitori appropriati o usarli per sostituire i tipi di istanza di livello superiore delle tue entità 3D come NPC e raycaster, quindi aggiungere script corrispondenti.

Nella maggior parte delle situazioni, non dovresti mettere un attore come figlio di un altro attore nel modello dei dati. Tuttavia, se decidi di posizionare uno script annidato all'interno di più attori per il tuo caso d'uso specifico, lo script è di proprietà del suo attore antenato più vicino.

Desincronizza i thread
Sebbene mettere gli script sotto gli attori consenta loro la capacità di esecuzione parallela, per impostazione predefinita il codice viene comunque eseguito su un singolo thread in modo seriale, il che non migliora le prestazioni di runtime. Devi chiamare task.desynchronize(), una funzione yieldable che sospende l'esecuzione della coroutine corrente per eseguire codice in parallelo e lo riprende alla successiva opportunità di esecuzione parallela. Per passare un script all'esecuzione seriale, chiama task.synchronize().
In alternativa, puoi utilizzare il metodo RBXScriptSignal:ConnectParallel() quando desideri pianificare un callback di segnale per eseguire immediatamente il tuo codice in parallelo al momento dell'attivazione. Non è necessario chiamare task.desynchronize() all'interno del callback del segnale.
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Alcuni codici paralleli che calcolano un aggiornamento di stato
task.synchronize()
... -- Alcuni codici seriali che modificano lo stato delle istanze
end)Gli script che fanno parte dello stesso attore vengono sempre eseguiti sequenzialmente l'uno rispetto all'altro, quindi hai bisogno di più attori. Ad esempio, se metti tutti gli script di comportamento abilitati per il parallelo per il tuo NPC in un attore, essi verranno comunque eseguiti in modo seriale su un singolo thread, ma se hai più attori per la logica di diversi NPC, ognuno di essi verrà eseguito in parallelo sul proprio thread. Per ulteriori informazioni, vedere Migliori pratiche.


Sicurezza dei thread
Durante l'esecuzione parallela, puoi accedere alla maggior parte delle istanze della gerarchia DataModel come al solito, ma alcune proprietà e funzioni delle API non sono sicure da leggere o scrivere. Se le utilizzi nel tuo codice parallelo, il motore Roblox può rilevare automaticamente e prevenire questi accessi.
I membri delle API hanno un livello di sicurezza dei thread che indica se e come puoi usarli nel tuo codice parallelo, come mostrato nella seguente tabella:
| Livello di sicurezza | Per le proprietà | Per le funzioni |
|---|---|---|
| Non sicuro | Non può essere letto o scritto in parallelo. | Non può essere chiamato in parallelo. |
| Leggi Parallelo | Può essere letto ma non scritto in parallelo. | N/A |
| Sicuro Locale | Può essere utilizzato all'interno dello stesso Attore; può essere letto ma non scritto da altri Attori in parallelo. | Può essere chiamato all'interno dello stesso Attore; non può essere chiamato da altri Attori in parallelo. |
| Sicuro | Può essere letto e scritto. | Può essere chiamato. |
Puoi trovare i tag di sicurezza dei thread per i membri delle API nella riferimento API. Quando li utilizzi, dovresti anche considerare come le chiamate API o le modifiche alle proprietà potrebbero interagire tra i thread paralleli. Di solito è sicuro per più attori leggere gli stessi dati di altri attori, ma non modificare lo stato di altri attori.
Comunicazione tra thread
Nel contesto del multithreading, puoi comunque consentire agli script in diversi attori di comunicare tra loro per scambiare dati, coordinare compiti e sincronizzare attività. Il motore supporta i seguenti meccanismi per la comunicazione tra thread:
- API di messaggistica degli attori per inviare messaggi a un attore utilizzando script.
- Struttura dati Shared table per condividere in modo efficiente una grande quantità di dati tra più attori su uno stato condiviso.
- Comunicazione diretta del modello dei dati per una comunicazione semplice con restrizioni.
Puoi supportare più meccanismi per soddisfare le tue esigenze di comunicazione tra thread. Ad esempio, puoi inviare una tabella condivisa tramite l'API di Messaggistica degli Attori.
Messaggistica degli attori
L'API di messaggistica degli attori consente a uno script, sia in un contesto seriale che parallelo, di inviare dati a un attore nello stesso modello dei dati. La comunicazione tramite questa API è asincrona, in cui il mittente non blocca fino a quando il ricevitore non riceve il messaggio.
Quando invii messaggi utilizzando questa API, devi definire un argomento per categorizzare il messaggio. Ogni messaggio può essere inviato solo a un singolo attore, ma quell'attore può avere internamente più callback legati a un messaggio. Solo gli script che sono discendenti di un attore possono ricevere messaggi.
L'API ha i seguenti metodi:
- Actor:SendMessage() per inviare un messaggio a un attore.
- Actor:BindToMessage() per legare un callback Luau a un messaggio con l'argomento specificato in un contesto seriale.
- Actor:BindToMessageParallel() per legare un callback Luau a un messaggio con l'argomento specificato in un contesto parallelo.
Il seguente esempio mostra come utilizzare Actor:SendMessage() per definire un argomento e inviare un messaggio dal lato del mittente:
local Workspace = game:GetService("Workspace")
-- Invia due messaggi all'attore lavoratore con un argomento di "Saluto"
local workerActor = Workspace.WorkerActor
workerActor:SendMessage("Saluto", "Ciao Mondo!")
workerActor:SendMessage("Saluto", "Benvenuto")
print("Messaggi inviati")Il seguente esempio mostra come utilizzare Actor:BindToMessageParallel() per legare un callback per un certo argomento in un contesto parallelo dal lato del ricevitore:
-- Ottieni l'attore a cui è associato questo script
local actor = script:GetActor()
-- Lega un callback per l'argomento del messaggio "Saluto"
actor:BindToMessageParallel("Saluto", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Legato ai messaggi")Tabella condivisa
SharedTable è una struttura dati simile a una tabella accessibile da script in esecuzione sotto più attori. È utile per situazioni che coinvolgono una grande quantità di dati e richiedono uno stato condiviso comune tra più thread. Ad esempio, quando più attori lavorano su uno stato del mondo comune che non è memorizzato nel modello dei dati.
Inviare una tabella condivisa a un altro attore non crea una copia dei dati. Invece, le tabelle condivise consentono aggiornamenti sicuri e atomici da parte di più script simultaneamente. Ogni aggiornamento a una tabella condivisa da un attore è immediatamente visibile a tutti gli attori. Le tabelle condivise possono anche essere clonate in un processo efficiente in termini di risorse che utilizza la condivisione strutturale anziché copiare i dati sottostanti.
Comunicazione diretta del modello dei dati
Puoi anche facilitare la comunicazione tra più thread direttamente utilizzando il modello dei dati, in cui diversi attori possono scrivere e successivamente leggere proprietà o attributi. Tuttavia, per mantenere la sicurezza dei thread, gli script in esecuzione in parallelo generalmente non possono scrivere nel modello dei dati. Quindi, utilizzare direttamente il modello dei dati per la comunicazione comporta restrizioni e può costringere gli script a sincronizzarsi frequentemente, il che può influire sulle prestazioni dei tuoi script.
Esempi
Validazione di raycasting lato server
Per un gioco di combattimento e battaglia, devi abilitare il raycasting per le armi dei tuoi utenti. Con il client che simula le armi per ottenere una buona latenza, il server deve confermare il colpo, il che comporta eseguire raycast e una certa quantità di euristiche che calcolano la velocità attesa del personaggio e guardano il comportamento passato.
Invece di utilizzare un singolo script centralizzato che si connette a un evento remoto che i client utilizzano per comunicare le informazioni sui colpi, puoi eseguire ogni processo di validazione del colpo sul lato server in parallelo, con ogni personaggio utente che ha un evento remoto separato.
Lo script lato server che viene eseguito sotto l'Actor di quel personaggio si connette a questo evento remoto utilizzando una connessione parallela per eseguire la logica pertinente per confermare il colpo. Se la logica trova una conferma di un colpo, il danno viene dedotto, il che comporta la modifica delle proprietà, quindi viene eseguito inizialmente in modo seriale.
local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Crea un nuovo evento remoto e impostalo come genitore dello strumento
remoteEvent.Name = "RemoteMouseEvent" -- Rinominalo in modo che lo script locale possa cercarlo
remoteEvent.Parent = tool
local remoteEventConnection -- Crea un riferimento per la connessione dell'evento remoto
-- Funzione che ascolta un evento remoto
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIAL: Esegui codice di configurazione in modo seriale
local character = player.Character
-- Ignora il personaggio dell'utente durante il raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALLEL: Esegui il raycast in parallelo
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Usato per estendere leggermente il ray poiché la posizione del clic potrebbe essere leggermente spostata dall'oggetto
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: Il codice sottostante modifica lo stato al di fuori dell'attore
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Rendi l'esplosione non letale
explosion.Position = clickLocation.Position
-- Più attori potrebbero ottenere la stessa parte in un raycast e decidere di distruggerla
-- Questo è perfettamente sicuro ma comporterebbe due esplosioni contemporaneamente invece di una
-- Il seguente doppio controlla che l'esecuzione sia arrivata prima a questa parte
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Distruggilo
end
end
end
end
-- Collega il segnale inizialmente in modo seriale poiché alcuni codici di configurazione non possono essere eseguiti in parallelo
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)Generazione procedurale del terreno lato server
Per creare un vasto mondo per il tuo gioco, puoi popolare il mondo dinamicamente. La generazione procedurale crea tipicamente chunk di terreno indipendenti, con il generatore che esegue calcoli relativamente complessi per il posizionamento degli oggetti, l'uso dei materiali e il riempimento dei voxel. Eseguire il codice di generazione in parallelo può migliorare l'efficienza del processo. Il seguente esempio di codice serve come esempio.
-- L'esecuzione parallela richiede l'uso di attori
-- Questo script si clona; l'originale avvia il processo, mentre i cloni agiscono come lavoratori
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
-- Imposta tutti gli attori sotto se stesso
for _, actor in workers do
actor.Parent = script
end
-- Istruisci gli attori a generare terreno inviando messaggi
-- In questo esempio, gli attori vengono scelti casualmente
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)
-- Esci dallo script originale; il resto del codice viene eseguito in ciascun attore
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
-- Lega il callback per essere chiamato nel contesto di esecuzione parallela
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)
-- Attualmente, WriteVoxels() deve essere chiamato nella fase seriale
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)Migliori pratiche
Per applicare i massimi benefici della programmazione parallela, fai riferimento alle seguenti migliori pratiche quando aggiungi il tuo codice Luau:
Evita calcoli lunghi — Anche in parallelo, calcoli lunghi possono bloccare l'esecuzione di altri script e causare ritardi. Evita di utilizzare la programmazione parallela per gestire un grande volume di calcoli lunghi e non yieldable.

Usa il numero giusto di attori — Per le migliori prestazioni, utilizza più Attori. Anche se il dispositivo ha meno core rispetto a Attori, la granularità consente un bilanciamento del carico più efficiente tra i core.

Questo non significa che dovresti usare il maggior numero possibile di Attori. Dovresti comunque dividere il codice in Attori basati su unità logiche piuttosto che rompere il codice con logica connessa in diversi Attori. Ad esempio, se desideri abilitare la validazione di raycasting in parallelo, è ragionevole utilizzare 64 Attori e più invece di solo 4, anche se stai mirando a sistemi a 4 core. Questo è prezioso per la scalabilità del sistema e consente di distribuire il lavoro in base alla capacità dell'hardware sottostante. Tuttavia, non dovresti nemmeno utilizzare troppi Attori, che sono difficili da mantenere.