Com o modelo de programação Luau Paralelo, você pode executar código em múltiplas threads simultaneamente, o que pode melhorar o desempenho do seu jogo. À medida que você expande seu jogo com mais conteúdo, pode adotar esse modelo para ajudar a manter o desempenho e a segurança de seus scripts Luau.
Modelo de programação paralela
Por padrão, os scripts são executados sequencialmente. Se o seu jogo possui lógica ou conteúdo complexos, como personagens não jogáveis (NPCs), validação de raycasting e geração procedural, a execução sequencial pode causar lag para seus usuários. Com o modelo de programação paralela, você pode dividir tarefas em múltiplos scripts e executá-los em paralelo. Isso faz com que o código do seu jogo seja executado mais rapidamente, melhorando a experiência do usuário.
O modelo de programação paralela também adiciona benefícios de segurança ao seu código. Ao dividir o código em múltiplas threads, quando você edita o código em uma thread, isso não afeta o outro código que está sendo executado em paralelo. Isso reduz o risco de um bug em seu código corromper todo o jogo e minimiza o atraso para os usuários em servidores ao vivo quando você publica uma atualização.
Adotar o modelo de programação paralela não significa colocar tudo em múltiplas threads. Por exemplo, a validação de raycasting do lado do servidor define para cada usuário individual um evento remoto em paralelo, mas ainda requer que o código inicial seja executado sequencialmente para alterar propriedades globais, que é um padrão comum para execução paralela.
Na maioria das vezes, você precisa combinar fases sequenciais e paralelas para alcançar a saída desejada, uma vez que atualmente existem algumas operações não suportadas em paralelo que podem impedir a execução de scripts, como modificar instâncias em fases paralelas. Para mais informações sobre o nível de uso das APIs em paralelo, consulte segurança de thread.
Dividir código em múltiplas threads
Para executar os scripts do seu jogo em múltiplas threads simultaneamente, você precisa dividi-los em partes lógicas sob diferentes atores no modelo de dados. Os atores são representados por instâncias de Actor que herdam de DataModel. Eles funcionam como unidades de isolamento de execução que distribuem a carga entre múltiplos núcleos que estão sendo executados simultaneamente.
Colocar instâncias de ator
Você pode colocar atores em contêineres apropriados ou usá-los para substituir os tipos de instância de nível superior de suas entidades 3D, como NPCs e raycasters, e então adicionar scripts correspondentes.

Na maioria das situações, você não deve colocar um ator como filho de outro ator no modelo de dados. No entanto, se você decidir colocar um script aninhado dentro de múltiplos atores para seu caso de uso específico, o script é de propriedade do ator ancestral mais próximo.

Dessincronizar threads
Embora colocar scripts sob atores conceda a eles a capacidade de execução paralela, por padrão o código ainda é executado em uma única thread sequencialmente, o que não melhora o desempenho em tempo de execução. Você precisa chamar task.desynchronize(), uma função que pode ser suspensa que suspende a execução da coroutine atual para executar código em paralelo e a retoma na próxima oportunidade de execução paralela. Para mudar um script de volta para execução sequencial, chame task.synchronize().
Alternativamente, você pode usar o método RBXScriptSignal:ConnectParallel() quando quiser agendar um callback de sinal para executar imediatamente seu código em paralelo ao ser acionado. Você não precisa chamar task.desynchronize() dentro do callback de sinal.
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Algum código paralelo que calcula uma atualização de estado
task.synchronize()
... -- Algum código sequencial que altera o estado das instâncias
end)Scripts que fazem parte do mesmo ator sempre são executados sequencialmente em relação uns aos outros, então você precisa de múltiplos atores. Por exemplo, se você colocar todos os scripts de comportamento habilitados para paralelismo para seu NPC em um único ator, eles ainda serão executados sequencialmente em uma única thread, mas se você tiver múltiplos atores para diferentes lógicas de NPC, cada um deles será executado em paralelo em sua própria thread. Para mais informações, consulte Melhores práticas.


Segurança de thread
Durante a execução paralela, você pode acessar a maioria das instâncias da hierarquia DataModel como de costume, mas algumas propriedades e funções da API não são seguras para leitura ou escrita. Se você as usar em seu código paralelo, o motor Roblox pode detectar automaticamente e impedir esses acessos de ocorrerem.
Os membros da API têm um nível de segurança de thread que indica se e como você pode usá-los em seu código paralelo, como a tabela a seguir mostra:
| Nível de segurança | Para propriedades | Para funções |
|---|---|---|
| Inseguro | Não pode ser lido ou escrito em paralelo. | Não pode ser chamado em paralelo. |
| Leitura Paralela | Pode ser lido, mas não escrito em paralelo. | N/A |
| Local Seguro | Pode ser usado dentro do mesmo Ator; pode ser lido, mas não escrito por outros Atores em paralelo. | Pode ser chamado dentro do mesmo Ator; não pode ser chamado por outros Atores em paralelo. |
| Seguro | Pode ser lido e escrito. | Pode ser chamado. |
Você pode encontrar tags de segurança de thread para membros da API na referência da API. Ao usá-los, você também deve considerar como chamadas de API ou alterações de propriedades podem interagir entre threads paralelas. Normalmente, é seguro que múltiplos atores leiam os mesmos dados que outros atores, mas não modifiquem o estado de outros atores.
Comunicação entre threads
No contexto de multithreading, você ainda pode permitir que scripts em diferentes atores se comuniquem entre si para trocar dados, coordenar tarefas e sincronizar atividades. O motor suporta os seguintes mecanismos para comunicação entre threads:
- API de mensagens de ator para enviar mensagens a um ator usando scripts.
- Estrutura de dados de tabela compartilhada para compartilhar eficientemente uma grande quantidade de dados entre múltiplos atores em um estado compartilhado.
- Comunicação de modelo de dados direto para comunicação simples com restrições.
Você pode suportar múltiplos mecanismos para acomodar suas necessidades de comunicação entre threads. Por exemplo, você pode enviar uma tabela compartilhada através da API de Mensagens de Atores.
Mensagens de ator
A API de mensagens de ator permite que um script, seja em um contexto sequencial ou paralelo, envie dados a um ator no mesmo modelo de dados. A comunicação através dessa API é assíncrona, na qual o remetente não bloqueia até que o receptor receba a mensagem.
Ao enviar mensagens usando essa API, você precisa definir um tópico para categorizar a mensagem. Cada mensagem pode ser enviada apenas a um único ator, mas esse ator pode internamente ter múltiplos callbacks vinculados a uma mensagem. Apenas scripts que são descendentes de um ator podem receber mensagens.
A API possui os seguintes métodos:
- Actor:SendMessage() para enviar uma mensagem a um ator.
- Actor:BindToMessage() para vincular um callback Luau a uma mensagem com o tópico especificado em um contexto sequencial.
- Actor:BindToMessageParallel() para vincular um callback Luau a uma mensagem com o tópico especificado em um contexto paralelo.
O seguinte exemplo mostra como usar Actor:SendMessage() para definir um tópico e enviar uma mensagem do lado do remetente:
local Workspace = game:GetService("Workspace")
-- Enviar duas mensagens para o ator trabalhador com um tópico de "Saudação"
local workerActor = Workspace.WorkerActor
workerActor:SendMessage("Greeting", "Olá Mundo!")
workerActor:SendMessage("Greeting", "Bem-vindo")
print("Mensagens enviadas")O seguinte exemplo mostra como usar Actor:BindToMessageParallel() para vincular um callback para um determinado tópico em um contexto paralelo do lado do receptor:
-- Obter o ator ao qual este script está parentado
local actor = script:GetActor()
-- Vincular um callback para o tópico de mensagem "Saudação"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Vinculado a mensagens")Tabela compartilhada
SharedTable é uma estrutura de dados semelhante a uma tabela acessível a partir de scripts executando sob múltiplos atores. É útil para situações que envolvem uma grande quantidade de dados e requerem um estado compartilhado comum entre múltiplas threads. Por exemplo, quando múltiplos atores trabalham em um estado de mundo comum que não está armazenado no modelo de dados.
Enviar uma tabela compartilhada para outro ator não faz uma cópia dos dados. Em vez disso, tabelas compartilhadas permitem atualizações seguras e atômicas por múltiplos scripts simultaneamente. Cada atualização a uma tabela compartilhada por um ator é imediatamente visível para todos os atores. Tabelas compartilhadas também podem ser clonadas em um processo eficiente em termos de recursos que utiliza compartilhamento estrutural em vez de copiar os dados subjacentes.
Comunicação direta do modelo de dados
Você também pode facilitar a comunicação entre múltiplas threads diretamente usando o modelo de dados, no qual diferentes atores podem escrever e subsequentemente ler propriedades ou atributos. No entanto, para manter a segurança de thread, scripts executando em paralelo geralmente não podem escrever no modelo de dados. Portanto, usar diretamente o modelo de dados para comunicação vem com restrições e pode forçar scripts a sincronizar frequentemente, o que pode impactar o desempenho de seus scripts.
Exemplos
Validação de raycasting do lado do servidor
Para um jogo de luta e batalha, você precisa habilitar raycasting para as armas de seus usuários. Com o cliente simulando as armas para alcançar uma boa latência, o servidor precisa confirmar o acerto, o que envolve fazer raycasts e algum nível de heurísticas que computam a velocidade esperada do personagem e observam o comportamento passado.
Em vez de usar um único script centralizado que se conecta a um evento remoto que os clientes usam para comunicar informações de acerto, você pode executar cada processo de validação de acerto no lado do servidor em paralelo, com cada personagem de usuário tendo um evento remoto separado.
O script do lado do servidor que é executado sob o Actor desse personagem se conecta a este evento remoto usando uma conexão paralela para executar a lógica relevante para confirmar o acerto. Se a lógica encontrar uma confirmação de um acerto, o dano é deduzido, o que envolve mudar propriedades, então isso é executado sequencialmente inicialmente.
local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Criar novo evento remoto e parentá-lo à ferramenta
remoteEvent.Name = "RemoteMouseEvent" -- Renomeá-lo para que o script local possa procurá-lo
remoteEvent.Parent = tool
local remoteEventConnection -- Criar uma referência para a conexão do evento remoto
-- Função que escuta um evento remoto
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SEQUENCIAL: Executar código de configuração sequencialmente
local character = player.Character
-- Ignorar o personagem do usuário enquanto faz raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALELO: Realizar o raycast em paralelo
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Usado para estender o raio ligeiramente, já que a localização do clique pode estar ligeiramente deslocada do objeto
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")
-- SEQUENCIAL: O código abaixo modifica o estado fora do ator
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Fazer a explosão não letal
explosion.Position = clickLocation.Position
-- Múltiplos atores poderiam pegar a mesma parte em um raycast e decidir destruí-la
-- Isso é perfeitamente seguro, mas resultaria em duas explosões ao mesmo tempo em vez de uma
-- A seguir, uma dupla verificação garante que a execução chegou a esta parte primeiro
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Destruir
end
end
end
end
-- Conectar o sinal sequencialmente inicialmente, já que algum código de configuração não pode ser executado em paralelo
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)Geração de terreno procedural do lado do servidor
Para criar um vasto mundo para seu jogo, você pode povoar o mundo dinamicamente. A geração procedural normalmente cria pedaços de terreno independentes, com o gerador realizando cálculos relativamente intrincados para colocação de objetos, uso de materiais e preenchimento de voxels. Executar o código de geração em paralelo pode aumentar a eficiência do processo. O seguinte exemplo de código serve como um exemplo.
-- A execução paralela requer o uso de atores
-- Este script se clona; o original inicia o processo, enquanto os clones atuam como trabalhadores
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
-- Parentar todos os atores sob si mesmo
for _, actor in workers do
actor.Parent = script
end
-- Instruir os atores a gerar terreno enviando mensagens
-- Neste exemplo, os atores são escolhidos aleatoriamente
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)
-- Sair do script original; o restante do código é executado em cada ator
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
-- Vincular o callback para ser chamado no contexto de execução paralela
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)
-- Atualmente, WriteVoxels() deve ser chamado na fase sequencial
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)Melhores práticas
Para aplicar os máximos benefícios da programação paralela, consulte as seguintes melhores práticas ao adicionar seu código Luau:
Evite Cálculos Longos — Mesmo em paralelo, cálculos longos podem bloquear a execução de outros scripts e causar lag. Evite usar programação paralela para lidar com um grande volume de cálculos longos e que não podem ser interrompidos.

Use o Número Certo de Atores — Para o melhor desempenho, use mais Atores. Mesmo que o dispositivo tenha menos núcleos do que Atores, a granularidade permite um balanceamento de carga mais eficiente entre os núcleos.

Isso não significa que você deve usar o máximo possível de Atores. Você ainda deve dividir o código em Atores com base em unidades lógicas, em vez de quebrar o código com lógica conectada em diferentes Atores. Por exemplo, se você quiser habilitar a validação de raycasting em paralelo, é razoável usar 64 Atores e mais, em vez de apenas 4, mesmo que você esteja mirando em sistemas de 4 núcleos. Isso é valioso para a escalabilidade do sistema e permite que ele distribua o trabalho com base na capacidade do hardware subjacente. No entanto, você também não deve usar muitos Atores, que são difíceis de manter.