Luau parallelo

*Questo contenuto è tradotto usando AI (Beta) e potrebbe contenere errori. Per visualizzare questa pagina in inglese, clicca qui.

Con il modello di programmazione Parallel Luau , puoi eseguire il codice su più thread contemporaneamente, il che può migliorare le prestazioni della tua esperienza.Mentre espandi la tua esperienza con più contenuti, puoi adottare questo modello per aiutare a mantenere le prestazioni e la sicurezza dei tuoi script Luau.

modellodi programmazione parallela

Per impostazione predefinita, gli script vengono eseguiti in sequenza.Se la tua esperienza ha una logica o un contenuto complesso, come personaggi non giocatori (NPC), la convalida del raycasting e la generazione procedurale, l'esecuzione sequenziale potrebbe causare un ritardo per i tuoi utenti.Con il modello di programmazione parallela, puoi dividere le attività in più script e eseguirli in parallelo.Questo rende il tuo codice di esperienza più veloce, il che migliora 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 Filo, non influisce su altro codice in esecuzione in parallelo.Questo riduce il rischio di avere un bug nel tuo codice che corrompe l'intera esperienza e minimizza il ritardo per gli utenti nei server live quando spingi un Aggiornarmento.

Adottare il modello di programmazione parallela non significa mettere tutto in più thread.Ad esempio, la validazione del raycasting lato server imposta ogni singolo utente un evento remoto in parallelo ma richiede comunque che il codice iniziale venga eseguito in serie per modificare le proprietà globali, che è un modello comune per l'esecuzione parallela.

La maggior parte delle volte è necessario combinare fasi seriali e parallele per ottenere l'Outputdesiderato, poiché attualmente ci sono alcune operazioni non supportate in parallelo che possono impedire l'esecuzione degli script, come la modifica delle istanze in fasi parallele.Per ulteriori informazioni sul livello di utilizzo delle API in parallelo, vedi sicurezza del thread.

Dividi il codice in più thread

Per eseguire gli script della tua esperienza in più thread contemporaneamente, devi dividerli in blocchi logici sotto diversi attori nel modello di dati.Gli attori sono rappresentati da Actor istanze che ereditano da DataModel.Lavorano come unità di isolamento dell'esecuzione che distribuiscono il carico su più core in esecuzione contemporaneamente.

Posiziona istanze attore

Puoi mettere gli attori in contenitori appropriati o usarli per sostituire i tipi di istanza di alto livello delle tue entità 3D come NPC e lanciatori di raggi, quindi aggiungi gli script corrispondenti.

An example of a Script under an Actor

Per la maggior parte delle situazioni, non devi mettere un attore come figlio di un altro attore nel modello di 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.

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

Desincronizza thread

Anche se mettere gli script sotto gli attori garantisce loro la capacità di esecuzione parallela, per impostazione predefinita il codice viene ancora eseguito in serie sul singolo thread, il che non migliora le Prestazionedi runtime.Devi chiamare il task.desynchronize(), una funzione yieldable che sospende l'esecuzione della coroutine attuale per l'esecuzione del codice in parallelo e lo riprende alla prossima opportunità di esecuzione parallela.Per passare uno script alla esecuzione seriale, chiama task.synchronize() .

In alternativa, puoi utilizzare il metodo RBXScriptSignal:ConnectParallel() quando vuoi programmare una richiamata del segnale per eseguire immediatamente il tuo codice in parallelo al momento dell'attivazione.Non devi chiamare task.desynchronize() all'interno del richiamo del segnale.

Desincronizza un Thread

local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Qualche codice parallelo che calcola un aggiornamento di Aggiornarmento
task.synchronize()
... -- Qualche codice seriale che cambia lo stato delle istanze
end)

Gli script che fanno parte dello stesso attore vengono sempre eseguiti in sequenza l'uno rispetto all'altro, quindi hai bisogno di più attori.Ad esempio, se metti tutti gli script di comportamento abilitati in parallelo per il tuo NPC in un attore, vengono comunque eseguiti in serie su un singolo Filo, ma se hai più attori per una diversa logica NPC, ognuno di essi viene eseguito in parallelo sul proprio Filo.Per ulteriori informazioni, vedi Migliori pratiche.

Codice parallelo negli attori in esecuzione in serie in un singolo thread
>

Codice parallelo negli attori in esecuzione simultaneamente in più thread
>

Sicurezza del thread

Durante l'esecuzione parallela, puoi accedere alla maggior parte delle istanze della gerarchia DataModel come al solito, ma alcune proprietà e funzioni dell'API non sono sicure da leggere o scrivere.Se li utilizzi nel tuo codice parallelo, il motore Roblox può rilevare e prevenire automaticamente l'avvenimento di questi accessi.

I membri dell'API hanno un livello di sicurezza del thread che indica se e come puoi usarli nel tuo codice parallelo, come mostra la seguente tabella:

Livello di sicurezzaPer 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
Cassaforte locale Può essere utilizzato all'interno dello stesso attore; può essere letto ma non scritto da altri Actors in parallelo.Può essere chiamato all'interno dello stesso attore; non può essere chiamato da altri Actors in parallelo.
Sicuro Può essere letto e scritto.Può essere chiamato.

Puoi trovare i tag di sicurezza dei thread per i membri dell'API sul riferimento dell'API.Quando li utilizzi, dovresti anche considerare come le chiamate API o le modifiche delle proprietà potrebbero interagire tra thread paralleli.Di solito è sicuro che più attori leggono gli stessi dati di altri attori ma non modifichino lo stato di altri attori.

Comunicazione cross-thread

Nel contesto multithread, puoi comunque consentire agli script in diversi attori di comunicare tra loro per scambiare dati, coordinare le attività e sincronizzare le attività.Il motore supporta i seguenti meccanismi per la comunicazione cross-thread:

Puoi supportare più meccanismi per soddisfare le tue esigenze di comunicazione cross-thread.Ad esempio, puoi inviare una tabella condivisa tramite l'Actor Messaging API.

Messaggi dell'attore

L'API messaggi dell'attore consente a uno script, in un contesto seriale o parallelo, di inviare dati a un attore nello stesso modello di dati.La comunicazione attraverso questa API è asincrona, in cui il mittente non blocca fino a quando il destinatario non riceve il Messaggio.

Quando si inviano messaggi utilizzando questa API, è necessario definire un argomento per categorizzare il Messaggio.Ogni messaggio può essere inviato solo a un singolo attore, ma quell'attore può internamente avere più richiami legati a un Messaggio.Solo gli script che sono discendenti di un attore possono ricevere messaggi.

L'API ha i seguenti metodi:

L'esempio seguente mostra come utilizzare Actor:SendMessage() per definire un argomento e inviare un messaggio alla Terminaredel mittente:

Inviatore di messaggio di esempio

local Workspace = game:GetService("Workspace")
-- Invia due messaggi all'attore lavoratore con un argomento di "Saluto"
local workerActor = Workspace.WorkerActor
workerActor:SendMessage("Greeting", "Hello World!")
workerActor:SendMessage("Greeting", "Welcome")
print("Sent messages")

L'esempio seguente mostra come utilizzare Actor:BindToMessageParallel() per legare un callback per un determinato argomento in un contesto parallelo sul Terminaredel ricevitore:

Ricetrasmittente di messaggio di esempio

-- Ottieni l'attore a cui questo script è parented to
local actor = script:GetActor()
-- Lega un callback per il messaggio topic "Saluto"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")

Tavola condivisa

SharedTable è una struttura dati tabellare accessibile dagli 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 mondiale comune che non è memorizzato nel modello di dati.

L'invio di una tabella condivisa a un altro attore non crea una copia dei dati.Invece, le tabelle condivise consentono aggiornamenti sicuri e atomici da più script contemporaneamente.Ogni aggiornamento di 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 invece di copiare i dati sottostanti.

Comunicazione diretta del modello di dati

Puoi anche facilitare la comunicazione tra più thread direttamente utilizzando il modello di dati, in cui diversi attori possono scrivere e successivamente leggere proprietà o attributi.Tuttavia, per mantenere la sicurezza del thread, gli script in esecuzione in parallelo generalmente non possono scrivere nel modello di dati.Quindi l'utilizzo diretto del modello di dati per la comunicazione viene con restrizioni e può forzare gli script a sincronizzarsi frequentemente, il che può influire sulle prestazioni dei tuoi script.

Esempi

Validazione del lancio di raggi lato server

Per un'esperienza di combattimento e battaglia, devi abilitare 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, che implica l'esecuzione di raycast e una certa quantità di euristiche che calcolano la velocità del personaggio prevista e guardano al comportamento passato.

Invece di utilizzare un singolo script centralizzato che si connette a un evento remoto che i client utilizzano per comunicare le informazioni sugli hit, puoi eseguire ogni processo di convalida degli hit sul lato server in parallelo con ogni personaggio utente che ha un evento remoto separato.

Lo script lato server che viene eseguito sotto l'evento remoto di quel personaggio Actor 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 detratto, il che implica il cambio di proprietà, quindi viene eseguito in serie inizialmente.


local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Crea un nuovo evento remoto e affidalo allo strumento
remoteEvent.Name = "RemoteMouseEvent" -- Rinominalo in modo che lo script locale possa cercarlo
remoteEvent.Parent = tool
local remoteEventConnection -- Crea un riferimento per la connessione evento remoto
-- Funzione che ascolta un evento remoto
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIALE: Esegui il codice di configurazione in serie
local character = player.Character
-- Ignora il personaggio dell'utente durante il raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALLELO: Esegui il raycast in parallelo
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Utilizzato per estendere leggermente il raggio 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")
-- SERIALE: Il codice seguente modifica lo stato al di fuori dell'attore
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Rendi l'esplosione non mortale
explosion.Position = clickLocation.Position
-- Diversi attori potrebbero ottenere la stessa parte in un raycast e decidere di distruggerla
-- Questo è perfettamente sicuro ma si tradurrebbe in due esplosioni contemporaneamente invece di una
-- Le seguenti doppie verifiche che l'esecuzione deve arrivare a questa parte prima
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Distruggerlo
end
end
end
end
-- Collega il segnale in serie inizialmente poiché alcun codice di configurazione è in grado di eseguire in parallelo
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)

Generazione del terreno procedurale lato server

Per creare un vasto mondo per la tua esperienza, puoi popolare il mondo in modo dinamico.La generazione procedurale crea tipicamente blocchi di terreno indipendenti, con il generatore che esegue calcoli relativamente complessi per il posizionamento degli oggetti, l'utilizzo del materiale e il riempimento del 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 inizia 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
-- Genitore di tutti gli attori sotto sé
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 ogni 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 da chiamare 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, i 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 inflessibili.

    Diagram demonstrating how overloading the parallel execution phase can still cause lag
  • Usa il giusto numero di attori — Per le migliori Prestazione, usa più Actors .Anche se il dispositivo ha meno core di Actors , la granularità consente un bilanciamento del carico più efficiente tra i core.

    Demonstration of how using more actors balances the load across cores

    Questo non significa che dovresti usare il maggior numero di Actors possibile.Dovresti comunque dividere il codice in Actors in base alle unità logiche piuttosto che rompere il codice con logica connessa a diverse Actors .Ad esempio, se vuoi abilitare la validazione del raycasting in parallelo, è ragionevole utilizzare 64 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à del hardware sottostante.Tuttavia, non dovresti usare troppi Actors , che sono difficili da mantenere.