Parallel Lua is a programming model for writing code in the Lua programming language, which is used in Roblox experiences. It allows certain tasks to be run concurrently on multiple threads, rather than sequentially on a single thread. This can improve the performance of an experience by allowing complex tasks to be split up and run in parallel, rather than having to be completed in sequence.
There are several benefits to using the Parallel Lua programming model:
Improved performance: By running code concurrently on multiple threads, the Parallel Lua programming model can improve the overall performance of an experience by allowing tasks to be completed more quickly. In other words; It will allow you to create larger experiences without lag.
Increased safety: Splitting code into multiple threads also adds safety benefits to your code. When you edit code in one thread, it doesn't affect other code running in parallel. This reduces the risk of having one bug in your code corrupting the entire experience, and minimizes the delay for users in live servers when you push an update.
Easier to scale: As an experience grows and becomes more complex, the Parallel Lua programming model makes it easier to scale and maintain performance. By splitting code into multiple threads, it becomes easier to add new content and features without negatively impacting performance.
To use the Parallel Lua programming model, you need to split your code into logical chunks and place them under actor instances in the Roblox Explorer. Actors are special types of instances that act as units of execution isolation, allowing code to be distributed across multiple cores running simultaneously.
To place actor instances, you can either put them in proper folders or use them to replace the top-level instance types of your 3D entities, such as NPCs and raycasters. Then, you can add corresponding Scripts, LocalScripts, and ModuleScripts under the actor instances.
It's generally best not to put an actor instance underneath another actor in the DataModel, but in some cases you may want to nest scripts within multiple Actors for a specific use case. In this case, the script is owned by its closest ancestor actor.
By default, code placed under actor instances will still run on a single thread, even if it's in a parallel context. To enable parallel execution of a script, you need to call the task.desynchronize() function, which is a yieldable function that suspends the execution of the current coroutine and allows the code to be run in parallel.
Alternatively, you can use the RBXScriptSignal:ConnectParallel() method to schedule a signal callback to run your code in parallel upon triggering. This method allows you to immediately run your code in parallel when a signal is triggered, without needing to call task.desynchronize() inside the callback.
Desynchronize a Thread Using RBXScriptSignal:ConnectParallel()
RunService.Tire:ConnectParallel(function ()
... -- Some parallel code that computes a state change
print("Computing state update...")
-- Switch execution back to the main thread
task.synchronize()
... -- Some serial code that changes the state of instances
print("Updating state of instances...")
end)
Note that you cannot use the require() function in a desynchronized parallel phase. If you need to use a script that has been required in a serial context, you should require it first in a serial context before calling task.desynchronize().
Scripts within the same Actor always execute sequentially with respect to one another, necessitating the use of multiple Actors. For example, if you put all of your NPC's parallel-enabled behavior scripts in one Actor, they will still run serially on a single thread; however, if you have multiple NPC Actors, each will run in parallel on its own thread.
In the Parallel Lua programming model, it's important to ensure that your code is thread-safe, meaning that it can be run concurrently without causing issues such as race conditions or data corruption. To help with this, Roblox provides a concept called "safety levels" that allows you to specify the level of thread safety for different parts of your code.
There are four levels of thread safety:
Safety Level | For Properties | For Functions
Unsafe | Cannot be read or written. | Cannot be called.
Read Parallel | Can be read but not written. | N/A
Safe | Can be read and written. | Can be called.
Local Safe | Can be used within the same Actor; can be read but not written to by other Actors. | Can be called within the same Actor; cannot be called by other Actors.
It's important to be aware of the thread safety level of different operations when using the Parallel Lua programming model, as using unsafe operations in a parallel context can lead to problems.
Enabling raycasting for your users' weapons is required for a fighting and battle experience. With the client simulating the weapons for low latency, the server must confirm the hit, which requires raycasts and some heuristics that compute expected character velocity and look at past behavior.
Rather than using a single centralized script that connects to a remote event that clients use to communicate hit information, you can run each hit validation process on the server side in parallel, with each user character having its own remote event.
The server-side script that runs under that character's Actor establishes a parallel connection to this remote event in order to execute the relevant logic for confirming the hit. If the logic detects a hit confirmation, the damage is deducted, which involves changing properties, so it initially runs serially.
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Create new remote event and parent it to the tool
remoteEvent.Parent = tool
remoteEvent.Name = "RemoteMouseEvent" -- Rename it so that the local script can look for it
local remoteEventConnection -- Create a reference for the remote event connection
-- Function which listens for a remote event
local function onRemoteMouseEvent(player : Player, clickLocation:CFrame)
-- Execute setup code in serial
local character = player.Character
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Blacklist
params.FilterDescendantsInstances = {character}
-- Perform the raycast in parallel
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local extender = 0.01 -- Used to extend the ray slightly since the click location might be slightly offset from the object
local lookDirection = (1 + extender) * (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")
-- Modifies state outside of the actor
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Stop the explosion from killing you.
explosion.Position = clickLocation.Position
-- Multiple Actors could get the same part in a ray cast and decide to destroy it
-- this is actually perfectly fine, but it means that we'll see two explosions at once instead of one
-- so we can double check that we got to this part here first.
if hitPart.Parent then
explosion.Parent = workspace
hitPart:Destroy() -- Destroys it
end
end
end
end
-- Due to some setup code not being able to run in parallel, connect the signal in serial first.
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
When adding your Lua code, keep the following practices in mind to reap the full benefits of parallel programming:
When using parallel programming, it is important to avoid using it for long, unyielding calculations as these can block the execution of other scripts and cause lag.
To achieve the best performance, it is recommended to use a larger number of Actors, as this allows for more efficient load balancing between the cores of the device. However, it is important to divide code into Actors based on logic units rather than breaking code with connected logic into different Actors.
-- If the line on the graph is shorter, the task will be completed faster. Therefore, shorter lines represent a quicker completion time on the threads.
If you want to enable raycasting validation in parallel, for example, it's reasonable to use 64 Actors or more rather than just 4, even if you're targeting 4-core systems. This is useful for the system's scalability because it allows it to distribute work based on the capabilities of the underlying hardware. It is also important to not use too many Actors, as this can make your game hard to maintain.
I hope this tutorial provided a clear and simple introduction to the Parallel Lua programming model. Please leave a comment if you have any questions, have difficulty understanding the content, or believe there are any inaccuracies.