Parallel Luau

With the Parallel Luau programming model, you can run code on multiple threads simultaneously, which can improve the performance of your experience. As you expand your experience with more content, you can adopt this model to help maintain the performance and safety of your Luau scripts.

Parallel Programming Model

By default, scripts execute sequentially. If your experience has complex logic or content, such as non-player characters (NPCs), raycasting validation, and procedural generation, then sequential execution might cause lag for your users. With the parallel programming model, you can split tasks into multiple scripts and run them in parallel. This makes your experience code run faster, which improves the user experience.

The parallel programming model also adds safety benefits to your code. By splitting code into multiple threads, 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.

Adopting the parallel programming model doesn't mean to put everything in multiple threads. For example, the Server-side Raycasting Validation sets each individual user a remote event in parallel but still requires the initial code to run serially to change global properties, which is a common pattern for parallel execution.

Most times you need to combine serial and parallel phases to achieve your desired output, since currently there are some operations not supported in parallel that can prevent scripts from running, such as modifying instances in parallel phases. For more information on the level of usage of APIs in parallel, see Thread Safety.

Splitting Code into Multiple Threads

To run your experience's scripts in multiple threads concurrently, you need to split them into logical chunks under different Actor instances in the Explorer. Actors inherit from DataModel and work as units of execution isolation that distribute the load across multiple cores running simultaneously.

Placing Actor Instances

You can put Actors in proper folders or use them to replace the top-level instance types of your 3D entities such as NPCs and raycasters, then add corresponding Scripts, LocalScripts, and ModuleScripts.

An example of a Script under an Actor

For most situations, you shouldn't put an Actor instance underneath another Actor in the DataModel. However, if you decide to place a script nesting within multiple Actors for your specific use case, the script is owned by its closest ancestor Actor.

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

Desynchronizing Threads

Though putting scripts under Actor instances grants them the capability for parallel execution, by default the code still runs on the single thread serially, which doesn't improve the runtime performance. You need to call the task.desynchronize(), a yieldable function that suspends the execution of the current coroutine for running code in parallel and resumes it at the next parallel execution opportunity. To switch a script back to serial execution, call task.synchronize().

Alternatively, you can use RBXScriptSignal:ConnectParallel() method when you want to schedule a signal callback to immediately run your code in parallel upon triggering. You don't need to call task.desynchronize() inside the signal callback.

Desynchronize a Thread Using RBXScriptSignal:ConnectParallel()

1RunService.Heartbeat:ConnectParallel(function ()
2 ... -- some parallel code that computes a state update
3 task.synchronize()
4 ... -- some serial code that changes the state of instances
5end)
6

Scripts that are part of the same Actor always execute sequentially with respect to each other, so you need multiple Actors. For example, if you put all parallel-enabled behavior scripts for your NPC in one Actor, they still run serially on a single thread, but if you have multiple NPC Actors, each of them runs in parallel on its own thread. For more information, see Best Practices.

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

Thread Safety

During the parallel execution, you can access most instances of the DataModel hierarchy as usual, but some API properties and functions aren’t safe to read or write. If you use them in your parallel code, Roblox engine can automatically detect and prevent these accesses from occurring.

API members have a thread safety level that indicates whether and how you can use them in your parallel code, as the following table shows:

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
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.
Safe Can be read and written. Can be called.

You can find thread safety tags for API members on the API reference. When using them, you should also consider how API calls or property changes might interact between parallel threads. Usually it's safe for multiple Actors to read the same data as other Actors but not modify the state of other Actors.

Example: Server-side Raycasting Validation

For a fighting and battle experience, you need to enable raycasting for your users' weapons. With the client simulating the weapons to achieve good latency, the server has to confirm the hit, which involves doing raycasts and some amount of heuristics that compute expected character velocity, and look at past behavior.

Instead of 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 every user character having a separate remote event.

The server-side script that runs under that character’s Actor connects to this remote event using a parallel connection to run the relevant logic for confirming the hit. If the logic finds a confirmation of a hit, the damage is deducted, which involves changing properties, so it runs serially initially.


1local tool = script.Parent.Parent
2local remoteEvent = Instance.new("RemoteEvent") -- Create new remote event and parent it to the tool
3remoteEvent.Parent = tool
4remoteEvent.Name = "RemoteMouseEvent" -- Rename it so that the local script can look for it
5local remoteEventConnection -- Create a reference for the remote event connection
6
7-- Function which listens for a remote event
8local function onRemoteMouseEvent(player : Player, clickLocation:CFrame)
9
10 -- SERIAL: execute setup code in serial
11 local character = player.Character
12 -- Ignore the user's character while raycasting
13 local params = RaycastParams.new()
14 params.FilterType = Enum.RaycastFilterType.Blacklist
15 params.FilterDescendantsInstances = {character}
16
17 -- PARALLEL: Perform the raycast in parallel
18 task.desynchronize()
19 local origin = tool.Handle.CFrame.Position
20 local epsilon = 0.01 -- Used to extend the ray slightly since the click location might be slightly offset from the object
21 local lookDirection = (1 + epsilon) * (clickLocation.Position - origin)
22 local raycastResult = workspace:Raycast(origin, lookDirection, params)
23 if raycastResult then
24 local hitPart = raycastResult.Instance
25 if hitPart and hitPart.Name == "block" then
26 local explosion = Instance.new("Explosion")
27
28 -- SERIAL: the code below modifies state outside of the actor
29 task.synchronize()
30 explosion.DestroyJointRadiusPercent = 0 -- Make the explosion non-deadly
31 explosion.Position = clickLocation.Position
32
33 -- Multiple Actors could get the same part in a ray cast and decide to destroy it
34 -- this is actually perfectly safe, but it means that we'll see two explosions at once instead of one
35 -- so we can double check that we got to this part first here.
36 if hitPart.Parent then
37 explosion.Parent = workspace
38 hitPart:Destroy() -- destroy it
39 end
40 end
41 end
42end
43
44-- Connect the signal in serial initially since some setup code is not
45-- able to run in parallel.
46remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
47

Best Practices

To apply the maximum benefits of parallel programming, refer to the following best practices when adding your Lua code:

Avoiding Long Computations

Even in parallel, long computations can block execution of other scripts and cause lag. Avoid using parallel programming to handle a large volume of long, unyielding calculations.

A diagram demonstrating how overloading the parallel execution phase can still cause lag

Using the Right Number of Actors

For the best performance, use more Actors. Even if the device has fewer cores than Actors, the granularity allows for more efficient load balancing between the cores.

A demonstration of how using more Actors balances the load across cores

This doesn't mean you should use as many Actors as possible. You should still divide code into Actors based on logic units rather than breaking code with connected logic to different Actors.

For example, if you want to enable raycasting validation in parallel, it’s reasonable to use 64 Actors and more instead of just 4, even if you’re targeting 4-core systems. This is valuable for scalability of the system and allows it to distribute the work based on the capability of the underlying hardware. However, you also shouldn't use too many Actors, which are hard to maintain.