Luau paralelo

*Este contenido se traduce usando la IA (Beta) y puede contener errores. Para ver esta página en inglés, haz clic en aquí.

Con el modelo de programación Parallel Luau , puedes ejecutar código en múltiples hilos simultáneamente, lo que puede mejorar el rendimiento de tu experiencia.A medida que expandes tu experiencia con más contenido, puedes adoptar este modelo para ayudar a mantener el rendimiento y la seguridad de tus scripts de Luau.

Modelo de programación en paralelo

Por defecto, los scripts se ejecutan de forma secuencial.Si tu experiencia tiene lógica o contenido complejo, como personajes no jugadores (NPC), validación de raycasting y generación procedural, entonces la ejecución secuencial podría causar retraso para tus usuarios.Con el modelo de programación paralela, puedes dividir las tareas en múltiples scripts y ejecutarlos en paralelo.Esto hace que el código de tu experiencia se ejecute más rápido, lo que mejora la experiencia del usuario.

El modelo de programación paralela también agrega beneficios de seguridad a su código.Al dividir el código en múltiples subprocesos, cuando editas código en un subproceso, no afecta a otro código que se ejecuta en paraleloEsto reduce el riesgo de tener un error en tu código que corrompa toda la experiencia y minimiza el retraso para los usuarios en los servidores en vivo cuando envías una actualización.

Adoptar el modelo de programación paralela no significa poner todo en múltiples subprocesos.Por ejemplo, la validación de lanzamiento de rayos del lado del servidor establece a cada usuario individual un evento remoto en paralelo, pero aún requiere que el código inicial se ejecute en serie para cambiar las propiedades globales, que es un patrón común para la ejecución paralela

La mayoría de las veces necesitas combinar fases seriales y paralelas para lograr el resultado deseado, ya que actualmente hay algunas operaciones que no se admiten en paralelo que pueden impedir que se ejecuten los scripts, como modificar instancias en fases paralelas.Para obtener más información sobre el nivel de uso de las API en paralelo, vea seguridad del hilo.

Divide el código en múltiples hilos

Para ejecutar los scripts de tu experiencia en múltiples hilos simultáneamente, debes dividirlos en pedazos lógicos bajo diferentes actores en el modelo de datos .Los actores se representan por Actor instancias que heredan de DataModel.Funcionan como unidades de aislamiento de ejecución que distribuyen la carga entre múltiples núcleos que funcionan simultáneamente.

Coloque instancias de actor

Puedes poner actores en contenedores apropiados o usarlos para reemplazar los tipos de instancia de alto nivel de tus entidades 3D, como NPCs y lanzadores de rayos, luego agregar los scripts correspondientes.

An example of a Script under an Actor

Para la mayoría de las situaciones, no deberías poner a un actor como hijo de otro actor en el modelo de datos.Sin embargo, si decide colocar un script anidado dentro de múltiples actores para su caso de uso específico, el script es propiedad de su actor ancestro más cercano.

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

Dessincronizar subprocesos

Aunque poner scripts bajo actores les otorga la capacidad de ejecución paralela, por defecto el código aún se ejecuta en el hilo único en serie, lo que no mejora el rendimiento de ejecución.Necesitas llamar a la función task.desynchronize(), una función disponible que suspende la ejecución de la coroutine actual para ejecutar código en paralelo y lo reanuda en la próxima oportunidad de ejecución paralela.Para cambiar un script de ejecución serial a ejecución secuencial, llame a task.synchronize() .

Alternativamente, puede usar el método RBXScriptSignal:ConnectParallel() cuando desee programar una llamada de señal para ejecutar inmediatamente su código en paralelo al activarse.No necesitas llamar a task.desynchronize() dentro de la llamada de señal.

Dessincronizar un hilo

local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Algún código paralelo que compute una actualización de estado
task.synchronize()
... -- Algún código serial que cambie el estado de las instancias
end)

Los scripts que forman parte del mismo actor siempre se ejecutan secuencialmente el uno con respecto al otro, por lo que necesitas múltiples actores.Por ejemplo, si pones todos los scripts de comportamiento habilitados en paralelo para tu NPC en un actor, aún se ejecutan en serie en un solo hilo, pero si tienes múltiples actores para diferentes lógicas de NPC, cada uno se ejecuta en paralelo en su propio hilo.Para obtener más información, vea Mejores prácticas.

Parallel code in Actors running serially in a single thread
Parallel code in Actors running simultaneously in multiple threads

Seguridad de subproceso

Durante la ejecución paralela, puedes acceder a la mayoría de las instancias de la jerarquía DataModel como de costumbre, pero algunas propiedades y funciones de la API no son seguras para leer o escribir.Si los usas en tu código paralelo, el motor de Roblox puede detectar y prevenir automáticamente que ocurran estos accesos.

Los miembros de la API tienen un nivel de seguridad de subproceso que indica si y cómo puedes usarlos en tu código paralelo, como muestra la siguiente tabla

Nivel de seguridadPara las propiedadesPara las funciones
Inseguro No se puede leer o escribir en paralelo.No se puede invocar en paralelo.
Leer paralelo Se puede leer, pero no se puede escribir en paraleloN/A
Caja fuerte local Se puede utilizar dentro del mismo actor; se puede leer pero no escribir por otros Actors en paralelo.Se puede llamar dentro del mismo actor; no se puede llamar por otros Actors en paralelo.
Caja fuerte Se puede leer y escribir.Se puede llamar.

Puedes encontrar etiquetas de seguridad de subproceso para miembros de la API en la referencia de API.Al usarlos, también debe considerar cómo las llamadas de API o los cambios de propiedad podrían interactuar entre hilos paralelosPor lo general, es seguro para múltiples actores leer los mismos datos que otros actores, pero no modificar el estado de otros actores.

Comunicación entre subprocesos

En el contexto de multihilo, aún puedes permitir que los scripts de diferentes actores se comuniquen entre sí para intercambiar datos, coordinar tareas y sincronizar actividades.El motor soporta los siguientes mecanismos para la comunicación entre subprocesos:

Puedes apoyar múltiples mecanismos para satisfacer tus necesidades de comunicación entre subprocesos.Por ejemplo, puedes enviar una tabla compartida a través de la API de mensajes de actor.

Mensajería del actor

La API de mensajería de actores permite que un script, ya sea en un contexto serial o paralelo, envíe datos a un actor en el mismo modelo de datos.La comunicación a través de esta API es asíncrona, en la que el remitente no bloquea hasta que el receptor reciba el mensaje.

Al enviar mensajes usando esta API, necesitas definir un tema para categorizar el mensaje.Cada mensaje solo se puede enviar a un actor único, pero ese actor puede tener internamente múltiples llamadas de devolución vinculadas a un mensaje.Solo los scripts que son descendientes de un actor pueden recibir mensajes.

La API cuenta con los siguientes métodos:

  • Actor:SendMessage() para enviar un mensaje a un actor.
  • Actor:BindToMessage() para vincular una llamada de devolución de llamada Luau a un mensaje con el tema especificado en un contexto serial.
  • Actor:BindToMessageParallel() para vincular una llamada de devolución de llamada Luau a un mensaje con el tema especificado en un contexto paralelo.

El siguiente ejemplo muestra cómo usar Actor:SendMessage() para definir un tema y enviar un mensaje en el lado del remitente:

Remitente de mensajes de ejemplo

local Workspace = game:GetService("Workspace")
-- Envíe dos mensajes al actor trabajador con un tema de "Saludo"
local workerActor = Workspace.WorkerActor
workerActor:SendMessage("Greeting", "Hello World!")
workerActor:SendMessage("Greeting", "Welcome")
print("Sent messages")

El siguiente ejemplo muestra cómo usar Actor:BindToMessageParallel() para vincular un llamado de devolución de llamada para un determinado tema en un contexto paralelo en el lado del receptor:

Ejemplo de receptor de mensaje

-- Obtener el actor al que este guión está asociado
local actor = script:GetActor()
-- Vincular una llamada de devolución para el tema de mensaje "Saludo"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")

Tabla compartida

SharedTable es una estructura de datos tabular accesible desde scripts que se ejecutan bajo múltiples actores.Es útil para situaciones que implican una gran cantidad de datos y requieren un estado compartido común entre múltiples subprocesos.Por ejemplo, cuando múltiples actores trabajan en un estado mundial común que no se almacena en el modelo de datos.

Enviar una tabla compartida a otro actor no crea una copia de los datos.En lugar de eso, las tablas compartidas permiten actualizaciones seguras y atómicas por parte de múltiples scripts simultáneamenteCada actualización de una tabla compartida por un actor es inmediatamente visible para todos los actores.Las tablas compartidas también se pueden clonar en un proceso eficiente de recursos que utiliza la compartición estructural en lugar de copiar los datos subyacentes.

Comunicación directa del modelo de datos

También puedes facilitar la comunicación entre múltiples hilos directamente usando el modelo de datos, en el que diferentes actores pueden escribir y luego leer propiedades o atributos.Sin embargo, para mantener la seguridad del subproceso, los scripts que se ejecutan en paralelo generalmente no pueden escribir en el modelo de datosAsí que usar directamente el modelo de datos para la comunicación viene con restricciones y puede obligar a los scripts a sincronizarse con frecuencia, lo que puede afectar el rendimiento de sus scripts.

Ejemplos

Validación de lanzamiento de rayos del lado del servidor

Para una experiencia de combate y batalla, debes habilitar raycasting para las armas de tus usuarios.Con el cliente que simula las armas para lograr una buena latencia, el servidor debe confirmar el golpe, lo que implica hacer raycasts y una cantidad de heurísticas que calculan la velocidad esperada del personaje y miran el comportamiento pasado.

En lugar de usar un solo guión centralizado que se conecta a un evento remoto que los clientes usan para comunicar la información de los golpes, puedes ejecutar cada proceso de validación de golpes en el lado del servidor en paralelo con cada personaje de usuario que tenga un evento remoto separado.

El script del lado del servidor que se ejecuta bajo el evento remoto de ese personaje Actor se conecta a este evento remoto utilizando una conexión paralela para ejecutar la lógica relevante para confirmar el golpe.Si la lógica encuentra una confirmación de un golpe, se deduce el daño, lo que implica cambiar las propiedades, por lo que se ejecuta en serie inicialmente.


local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Crear un nuevo evento remoto y asignarlo a la herramienta
remoteEvent.Name = "RemoteMouseEvent" -- Renómlalo para que el script local pueda buscarlo
remoteEvent.Parent = tool
local remoteEventConnection -- Crea una referencia para la conexión de evento remoto
-- Función que escucha un evento remoto
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIAL: Ejecute el código de configuración en serie
local character = player.Character
-- Ignora el carácter del usuario al lanzar rayos
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALELO: Realizar el raycast en paralelo
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Se usa para extender el rayo ligeramente ya que la ubicación del clic podría estar ligeramente desplazada desde el 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: El código siguiente modifica el estado fuera del actor
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Haz que la explosión no sea mortal
explosion.Position = clickLocation.Position
-- Múltiples actores podrían obtener la misma parte en un lanzamiento de rayos y decidir destruirla
-- Esto es perfectamente seguro, pero daría como resultado dos explosiones a la vez en lugar de una
-- Los siguientes controles dobles aseguran que la ejecución llegue a esta parte primero
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Destrúmpelo
end
end
end
end
-- Conecte la señal en serie inicialmente ya que cierto código de configuración no es capaz de ejecutarse en paralelo
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)

Generación de terreno procedural del lado del servidor

Para crear un vasto mundo para tu experiencia, puedes poblar el mundo de forma dinámica.La generación procedural crea típicamente trozos de terreno independientes, con el generador realizando cálculos relativamente intrincados para la colocación de objetos, el uso de materiales y el relleno de vóxeles.La ejecución del código de generación en paralelo puede mejorar la eficiencia del procesoEl siguiente ejemplo de código sirve como ejemplo.


-- La ejecución paralela requiere el uso de actores
-- Este guión se clona a sí mismo; el original inicia el proceso, mientras que los clones actúan como trabajadores
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
-- Padre de todos los actores bajo sí mismo
for _, actor in workers do
actor.Parent = script
end
-- Instruye a los actores para que generen terreno enviando mensajes
-- En este ejemplo, los actores se eligen al azar
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)
-- Salir del guión original; el resto del código se ejecuta en cada actor
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 la llamada de devolución a ser llamada en contexto de ejecución 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)
-- Actualmente, WriteVoxels() debe ser llamado en la fase serial
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)

Mejores prácticas

Para aplicar los máximos beneficios de la programación paralela, consulte las siguientes mejores prácticas al agregar su código de Luau:

  • Evite las largas computaciones — Incluso en paralelo, las largas computaciones pueden bloquear la ejecución de otros scripts y causar retrasos.Evite utilizar programación paralela para manejar un gran volumen de cálculos largos e inflexibles.

    Diagram demonstrating how overloading the parallel execution phase can still cause lag
  • Usa el número correcto de actores — Para obtener el mejor rendimiento, usa más Actors.Incluso si el dispositivo tiene menos núcleos que Actors, la granularidad permite un balanceo de carga más eficiente entre los núcleos.

    Demonstration of how using more actors balances the load across cores

    Esto no significa que debas usar tantos Actors como sea posible.Todavía deberías dividir el código en Actors según las unidades lógicas en lugar de romper el código con lógica conectada a diferentes Actors .Por ejemplo, si quieres habilitar la validación de raycasting en paralelo, es razonable usar 64 Actors y más en lugar de solo 4, incluso si estás apuntando a sistemas de cuatro núcleos.Esto es valioso para la escalabilidad del sistema y le permite distribuir el trabajo en función de la capacidad del hardware subyacenteSin embargo, tampoco deberías usar demasiados Actors, que son difíciles de mantener.