Paralelo Luau

*Este conteúdo é traduzido por IA (Beta) e pode conter erros. Para ver a página em inglês, clique aqui.

Com o modelo de programação Parallel Luau , você pode executar código em vários subprocessos simultaneamente, o que pode melhorar o desempenho de sua experiência.À medida que você expande sua experiência com mais conteúdo, você pode adotar esse modelo para ajudar a manter o desempenho e a segurança de seus scripts Luau.

modelode programação paralela

Por padrão, os scripts são executados sequencialmente.Se a sua experiência tiver lógica ou conteúdo complexos, como personagens não jogáveis (NPCs), validação de raycasting e geração procedural, então a execução sequencial pode causar lag para 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 execute mais rápido, 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 vários subprocessos, quando você edita o código em um Subprocesso, isso não afeta outro código em execução em paralelo.Isso reduz o risco de ter um bug em seu código corrompendo toda a experiência e minimiza o atraso para os usuários em servidores ao vivo quando você envia uma atualização.

Adotar o modelo de programação paralela não significa colocar tudo em vários subprocessos.Por exemplo, a validação de lançamento de raios do lado do servidor define cada usuário individual um evento remoto em paralelo, mas ainda requer que o código inicial seja executado em série para alterar propriedades globais, que é um padrão comum para a execução em paralelo.

Na maioria das vezes, você precisa combinar fases seriais e paralelas para obter o saídadesejado, pois atualmente há algumas operações que não sã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 de APIs em paralelo, veja segurança do subprocesso.

Dividir código em vários subprocessos

Para executar os scripts da sua experiência em múltiplos subprocessos simultaneamente, você precisa dividi-los em pedaços lógicos sob diferentes atores no modelo de dados .Os atores são representados por Actor instâncias que herdam de DataModel.Eles funcionam como unidades de isolamento de execução que distribuem a carga entre vários núcleos executados simultaneamente.

Coloque instâncias de ator

Você pode colocar atores em contêineres apropriados ou usá-los para substituir os tipos de instância de alto nível de suas entidades 3D, como NPCs e raycasters, e então adicionar os scripts correspondentes.

An example of a Script under an Actor

Para a 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 em múltiplos atores para o seu caso de uso específico, o script é de propriedade de seu ator ancestral mais próximo.

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

Dessincronizar threads

Embora colocar scripts sob atores lhes conceda a capacidade de execução paralela, por padrão o código ainda é executado em um único subprocesso em série, o que não melhora o performancedo tempo de execução.Você precisa chamar o task.desynchronize(), uma função yieldable que suspende a execução da corrotina atual para executar código em paralelo e retoma na próxima oportunidade de execução paralela.Para trocar um script de volta para a execução serial, chame task.synchronize() .

Alternativamente, você pode usar o método RBXScriptSignal:ConnectParallel() quando quiser agendar um retorno de sinal para executar imediatamente seu código em paralelo após o disparo.Você não precisa chamar task.desynchronize() dentro do retorno de chamada de sinal.

Dessincronizar um Subprocesso

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 serial que muda 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 vários atores.Por exemplo, se você colocar todos os scripts de comportamento habilitados em paralelo para o seu NPC em um ator, eles ainda serão executados em série em um único Subprocesso, mas se você tiver vários atores para diferentes lógicas de NPC, cada um deles será executado em paralelo em seu próprio Subprocesso.Para mais informações, veja Melhores práticas.

Código paralelo em Atores rodando em série em um único subprocesso
>

Código paralelo em Atores rodando simultaneamente em vários subprocessos
>

Segurança do subprocesso

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 ler ou escrever.Se você usá-los em seu código paralelo, o Roblox Engine pode detectar e impedir automaticamente que esses acessos ocorram.

Os membros da API têm um nível de segurança de subprocesso que indica se e como você pode usá-los em seu código paralelo, como mostra a seguinte tabela:

Nível de segurançaPara propriedadesPara funções
Perigoso 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
Segurança Local Pode ser usado dentro do mesmo Actor; pode ser lido, mas não escrito por outro Actors em paralelo.Pode ser chamado dentro do mesmo Actor; não pode ser chamado por outro Actors em paralelo.
Segura Pode ser lido e escrito.Pode ser chamado.

Você pode encontrar tags de segurança de subprocesso para membros da API na referência da API.Ao usá-los, você também deve considerar como as chamadas da API ou as alterações de propriedade podem interagir entre threads paralelos.Normalmente, é seguro para vários atores lerem os mesmos dados que outros atores, mas não modificarem o estado de outros atores.

Comunicação entre subprocessos

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 subprocessos:

Você pode suportar vários mecanismos para acomodar suas necessidades de comunicação entre subprocessos.Por exemplo, você pode enviar uma tabela compartilhada através da API (Interface de Programação para Aplicações)de Mensageria de Ator.

Mensageria de ator

A API de mensageria do ator permite que um script, seja em um contexto serial ou paralelo, envie dados para um ator no mesmo modelo de dados.A comunicação através desta API é assíncrona, na qual o remetente não bloqueia até que o receptor receba a mensagem.

Ao enviar mensagens usando essa API (Interface de Programação para Aplicações), você precisa definir um tópico para categorizar a mensagem.Cada mensagem só pode ser enviada para um único ator, mas esse ator pode internamente ter vários retornos vinculados a uma mensagem.Apenas scripts que são descendentes de um ator podem receber mensagens.

A API tem os seguintes métodos:

O seguinte exemplo mostra como usar Actor:SendMessage() para definir um tópico e enviar uma mensagem no terminar/parar/sairdo remetente:

Exemplo de Remetente de Mensagem

local Workspace = game:GetService("Workspace")
-- Envie duas mensagens para o ator trabalhador com um tópico de "Saudação"
local workerActor = Workspace.WorkerActor
workerActor:SendMessage("Greeting", "Hello World!")
workerActor:SendMessage("Greeting", "Welcome")
print("Sent messages")

O seguinte exemplo mostra como usar Actor:BindToMessageParallel() para vincular um retorno de chamada para um determinado tópico em um contexto paralelo no terminar/parar/sairdo receptor:

Exemplo de Receptor de Mensagem

-- Obtenha o ator ao qual este script está ligado
local actor = script:GetActor()
-- Vincule um retorno de chamada para o tópico de mensagem "Saudação"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")

Tabela compartilhada

SharedTable é uma estrutura de dados semelhante a uma tabela acessível a partir de scripts executados sob múltiplos atores.É útil para situações que envolvem uma grande quantidade de dados e exigem um estado compartilhado comum entre vários subprocessos.Por exemplo, quando vários atores trabalham em um estado mundial 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, as tabelas compartilhadas permitem atualizações seguras e atômicas por vários scripts simultaneamente.Cada atualização para uma tabela compartilhada por um ator é imediatamente visível para todos os atores.As tabelas compartilhadas também podem ser clonadas em um processo eficiente de recursos que utiliza a partilha estrutural em vez de copiar os dados subjacentes.

Comunicação direta do modelo de dados

Você também pode facilitar a comunicação entre vários subprocessos diretamente usando o modelo de dados, no qual diferentes atores podem escrever e posteriormente ler propriedades ou atributos.No entanto, para manter a segurança do subprocesso, scripts em execução em paralelo geralmente não podem escrever no modelo de dados.Então, usar diretamente o modelo de dados para comunicação vem com restrições e pode forçar scripts a sincronizarem frequentemente, o que pode afetar o desempenho de seus scripts.

Exemplos

Validação de raycasting do lado do servidor

Para uma experiência de luta e batalha, você precisa habilitar lançamento de raios para as armas de seus usuários.Com o cliente simulando as armas para alcançar boa latência, o servidor tem que confirmar o hit, o que envolve fazer raycasts e uma quantidade de heurísticas que calculam a velocidade esperada do personagem e olhar para 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 hit, você pode executar cada processo de validação de hit 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 evento remoto de Actor desse personagem se conecta a este evento remoto usando uma conexão paralela para executar a lógica relevante para confirmar o hit.Se a lógica encontrar uma confirmação de um hit, o dano é deduzido, o que envolve a mudança de propriedades, então ele é executado em série inicialmente.


local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Criar um novo evento remoto e associá-lo à ferramenta
remoteEvent.Name = "RemoteMouseEvent" -- Renomeie-o para que o script local possa procurá-lo
remoteEvent.Parent = tool
local remoteEventConnection -- Crie uma referência para a conexão de evento remoto
-- Função que ouve um evento remoto
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIAL: Executar código de configuração em serial
local character = player.Character
-- Ignorar o personagem do usuário ao fazer raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALELA: Realize o raycast em paralelo
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Usado para estender o raio ligeiramente, pois 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")
-- SERIAL: O código abaixo modifica o estado fora do ator
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Torne a explosão não mortal
explosion.Position = clickLocation.Position
-- Múltiplos atores podem obter a mesma parte em um raycast e decidir destruí-la
-- Isso é perfeitamente seguro, mas resultaria em duas explosões de uma vez ao invés de uma
-- As seguintes verificações duplas que a execução chegou a esta parte primeiro
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Destrua-o
end
end
end
end
-- Conecte o sinal em serial inicialmente, pois algum código de configuração não é capaz de executar 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 preencher o mundo dinamicamente.A geração procedural normalmente cria pedaços de terreno independentes, com o gerador executando cálculos relativamente intrincados para a colocação de objetos, uso de materiais e preenchimento de voxel.Executar código de geração em paralelo pode aumentar a eficiência do processo.O seguinte exemplo de código serve como 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
-- Parente 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)
-- Saída 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 retorno de chamada a 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, o WriteVoxels() deve ser chamado na fase serial
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 o 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 inflexíveis.

    Diagram demonstrating how overloading the parallel execution phase can still cause lag
  • Use o número correto de atores — Para o melhor performance, use mais Actors .Mesmo que o dispositivo tenha menos núcleos que Actors, a granularidade permite um balanceamento de carga mais eficiente entre os núcleos.

    Demonstration of how using more actors balances the load across cores

    Isso não significa que você deve usar o máximo de Actors possível.Você ainda deve dividir o código em Actors com base em unidades lógicas, em vez de quebrar o código com lógica conectada a diferentes Actors.Por exemplo, se você quiser habilitar a validação de raycasting em paralelo, é razoável usar 64 e mais em vez de apenas 4, mesmo que você esteja alvo de 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 Actors, que são difíceis de manter.