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 Luau Parallelo , puoi eseguire il codice su più thread contemporaneamente, il che può migliorare le prestazioni della tua esperienza. Man mano che espandi la tua esperienza con più contenuti, puoi adottare questo modello per 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 la tua esperienza ha una logica o un contenuto complesso, come i 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 suddividere le attività in più script ed eseguirli in parallelo. Ciò rende il tu

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. Ciò riduce il rischio di avere un bug nel tuo codice che corrompa l'intera esperienza e minimizza il ritardo per gli utenti nei server live quando invii un Aggiornarmento.

Adottare il modello di programmazione parallela non significa mettere tutto in più thread. Ad esempio, la Validazione Raycasting lato server imposta per ogni singolo utente un evento remoto in parallelo, ma richiede comunque che il codice iniziale venga eseguito in serie per cambiare 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 Thread Safety.

Dividere 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 2> Class.DataModel2> . Lavorano come unità di isolamento dell'esecuzione che distribuiscono il carico su più core in esecuzione contemporane

Posizionamento delle istanze dell'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 raycasters, quindi aggiungere gli script corrispondenti script .

An example of a Script under an Actor

Per la maggior parte delle situazioni, non dovresti 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 antenato attore più vicino.

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

Desincronizzazione dei thread

Anche se mettere gli script sotto gli attori concede loro la capacità di esecuzione parallela, per impostazione predefinita il codice viene ancora eseguito sul singolo thread in serie, il che non migliora le Prestazionedi runtime. È necessario chiamare il task.desynchronize(), una funzione yieldable che sospende l'esecuzione della coroutine corrente per l'esecuzione del codice in parallelo e

In alternativa, puoi utilizzare il metodo RBXScriptSignal:ConnectParallel() quando vuoi pianificare una richiamata del segnale per eseguire immediatamente il tuo codice in parallelo al suo attivarsi. Non è necessario chiamare task.desynchronize() all'interno della richiamata 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 sempre eseguono sequenzialmente 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 <

Codice parallelo negli attori in esecuzione in serie in un unico thread
Codice parallelo negli attori in esecuzione simultaneamente in più thread

Thread sicurezza

Durante l'esecuzione parallela, puoi accedere alla maggior parte delle istanze della gerarchia DataModel come al solito, ma alcune proprietà e funzioni API non sono sicure da leggere o scrivere. Se le usi nel tuo codice parallelo, il motore Roblox può rilevare automaticamente e prevenire 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 la seguente tabella mostra:

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.
Cassaforte 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 leggano gli stessi dati di altri attori ma non modifichino lo stato di altri attori.

Cross-Thread Comunicazione

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:

  • API per l'invio di messaggi a un attore utilizzando gli script.
  • Tabella condivisa struttura dei dati per la condivisione efficiente di una grande quantità di dati tra più attori in uno stato condiviso.
  • Comunicazione diretta del modello di dati per una comunicazione semplice con restrizioni.

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

Messaggi dell'attore

L'API Actor Messaging 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:

mittente del messaggio di esempio

-- 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 associare un callback per un determinato argomento in un contesto parallelo sul Terminaredel ricevitore:

Ricetrasmittente del messaggio di esempio

-- Ottieni l'attore a cui questo script è parented to
local actor = script:GetActor()
-- Leggi una richiamata per il tema "Saluto"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")

Tavola condivisa

SharedTable è una struttura dati simile a una tabella 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 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 parte di 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 dei 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 fornito con restrizioni e può forzare gli script a sincronizzarsi frequentemente, il che può influire sulle prestazioni dei tuoi script.

Esempi

Validazione Raycasting 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 coinvolge il fare raycast e una certa quantità di euristica che calcola la velocità dei personaggi prevista e guarda 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 il 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, viene detratto il danno, che comporta la modifica delle proprietà, quindi viene eseguito inizialmente in serie.


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 remota
-- Funzione che ascolta un evento remoto
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIAL: Esegui il codice di configurazione in 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 -- Utilizzato per estendere leggermente il raggio poiché la posizione di clic potrebbe essere leggermente offsetta 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 seguente modifica lo stato al di fuori dell'attore
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Rendi l'esplosione non mortale
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
-- I seguenti controlli doppi controllano che l'esecuzione abbia raggiunto prima questa parte
if hitPart.Parent then
explosion.Parent = workspace
hitPart:Destroy() -- Distruggilo
end
end
end
end
-- Connetti il segnale in serie inizialmente poiché alcuni codici di configurazione non sono in grado di essere eseguiti 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 in genere crea pezzi 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. L'esecuzione del codice di generazione in parallelo può migliorare l'efficienza del processo. Il seguente esempio di codice serve da 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 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 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
-- Individua il callback da eseguire in parallelo
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 Lua:

  • Evita calcoli lunghi — Anche in parallelo, i calcoli lunghi possono bloccare l'esecuzione di altri script e causare lag. 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

    This doesn't mean you should use as many Class.Actor