Com o modelo de programação Luau Paralelo, você pode executar código em múltiplas threads simultaneamente, o que pode melhorar o desempenho da sua experiência. À medida que você expande sua experiência com mais conteúdo, pode adotar este 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 sua experiência tiver lógica ou conteúdo complexos, como personagens não-jogadores (NPCs), validação de raycasting e geração procedural, a execução sequencial pode causar lag para os seus usuários. Com o modelo de programação paralela, você pode dividir tarefas em vários scripts e executá-los em paralelo. Isso faz com que o código da sua experiência seja executado mais rapidamente, o que melhora 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 código em uma thread, isso não afeta outros códigos que estão sendo executados em paralelo. Isso reduz o risco de um bug em seu código corromper toda a experiência e minimiza o atraso para os usuários em servidores ativos quando você implementa 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 um evento remoto individual para cada usuário em paralelo, mas ainda requer que o código inicial seja executado de forma sequencial para alterar propriedades globais, o que é um padrão comum para execução paralela.
Na maioria das vezes, você precisará combinar fases sequenciais e paralelas para alcançar o resultado desejado, uma vez que atualmente há 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 utilização das APIs em paralelo, veja segurança de threads.
Dividir código em múltiplas threads
Para executar os scripts da sua experiência em múltiplas threads de forma concorrente, você precisa dividi-los em blocos lógicos 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 executando simultaneamente.
Instâncias de ator no lugar
Você pode colocar atores em contêineres adequados ou usá-los para substituir os tipos de instâncias de nível superior das 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 pertence ao seu ator ancestral mais próximo.

Desincronizar 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 de execução. Você precisa chamar task.desynchronize(), uma função que permite a suspensão da execução da coroutine atual para executar código em paralelo e retomar 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 seu código imediatamente em paralelo ao ser acionado. Você não precisa chamar task.desynchronize() dentro do callback do sinal.
Desincronizar uma Thread
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Algum código paralelo que computa uma atualização de estado
task.synchronize()
... -- Algum código sequencial que altera o estado das instâncias
end)
Scripts que são parte do mesmo ator sempre executam 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 do seu NPC em um só 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, veja Melhores práticas.


Segurança de threads
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 gravação. Se você as utilizar em seu código paralelo, o motor Roblox pode detectar automaticamente e prevenir esses acessos.
Os membros da API têm um nível de segurança de threads 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 threads para os membros da API na referência da API. Ao usá-los, você também deve considerar como chamadas da API ou alterações de propriedades podem interagir entre threads paralelas. Geralmente é seguro para múltiplos atores ler os mesmos dados que outros atores, mas não modificar 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 mensagem de ator para enviar mensagens a um ator usando scripts.
- Estrutura de dados de tabela compartilhada para compartilhar de forma eficiente uma grande quantidade de dados entre múltiplos atores em um estado compartilhado.
- Comunicação direta do modelo de dados para comunicação simples com restrições.
Você pode suportar múltiplos mecanismos para atender às suas necessidades de comunicação entre threads. Por exemplo, você pode enviar uma tabela compartilhada através da API de Mensagens de Atores.
Mensagem de ator
A API de mensagem 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 desta API é assíncrona, onde o remetente não bloqueia até que o receptor receba a mensagem.
Ao enviar mensagens usando esta API, você precisa definir um tópico para categorizar a mensagem. Cada mensagem só pode ser enviada a um único ator, mas esse ator pode ter internamente 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 exemplo a seguir mostra como usar Actor:SendMessage() para definir um tópico e enviar uma mensagem do lado do remetente:
Exemplo de Remetente de Mensagemlocal Workspace = game:GetService("Workspace")-- Envie duas mensagens para o ator trabalhador com um tópico de "Saudação"local workerActor = Workspace.WorkerActorworkerActor:SendMessage("Greeting", "Olá Mundo!")workerActor:SendMessage("Greeting", "Bem-vindo")print("Mensagens enviadas")
O exemplo a seguir mostra como usar Actor:BindToMessageParallel() para vincular um callback para um determinado tópico em um contexto paralelo do lado do receptor:
Exemplo de Receptor de Mensagem
-- Obtenha o ator ao qual este script está anexado
local actor = script:GetActor()
-- Vincule 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 precisam de um estado comum compartilhado entre múltiplas threads. Por exemplo, quando múltiplos atores trabalham em um estado comum do mundo que não está armazenado no modelo de dados.
Enviar uma tabela compartilhada para outro ator não cria 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 em 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, onde diferentes atores podem escrever e posteriormente ler propriedades ou atributos. No entanto, para manter a segurança de threads, 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 os scripts a sincronizar com frequência, o que pode impactar o desempenho dos seus scripts.
Exemplos
Validação de raycasting do lado do servidor
Para uma experiência de combate, você precisa habilitar raycasting para as armas dos seus usuários. Com o cliente simulando as armas para obter boa latência, o servidor precisa confirmar o acerto, o que envolve realizar raycasts e alguma quantidade de heurísticas que computam a velocidade esperada do personagem e verificam 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 daquele personagem conecta-se 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, os danos são descontados, o que envolve a alteração de propriedades, então isso é executado sequencialmente no início.
local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Cria um novo evento remoto e o parenta à ferramenta
remoteEvent.Name = "RemoteMouseEvent" -- Renomeia para que o script local possa procurar
remoteEvent.Parent = tool
local remoteEventConnection -- Cria 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: Executa o código de configuração de forma sequencial
local character = player.Character
-- Ignora o personagem do usuário durante o raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALELA: Realiza o raycast em paralelo
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Usado para estender o ray ligeiramente, já que a localização do clique pode estar levemente 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 -- Faz a explosão não letal
explosion.Position = clickLocation.Position
-- Vários 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
-- O seguinte verifica se a execução chegou a esta parte primeiro
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Destrói
end
end
end
end
-- Conecte o sinal sequencialmente inicialmente, uma vez 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 sua experiência, você pode povoar o mundo de forma dinâmica. A geração procedural tipicamente cria blocos de terreno independentes, com o gerador realizando cálculos relativamente complexos para colocação de objetos, uso de material e preenchimento de voxels. Executar 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 clona a si mesmo; 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
-- Parent todos os atores sob si mesmo
for _, actor in workers do
actor.Parent = script
end
-- Instrua 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)
-- Saia do script original; o resto 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
-- Vincule o callback para ser chamado em um 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 não yieldables.

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 de lógica, 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 ou mais em vez de apenas 4, mesmo que você esteja visando 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.