Maximizing Performance with Parallel Lua

Multithreading in Roblox: A Guide to Parallel Lua

by Vor_Der

Author Avatar

Introduction to the Parallel Lua programming model

What is Parallel Lua?

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.


What are the benefits of Parallel Lua?

There are several benefits to using the Parallel Lua programming model:


Splitting Code into Multiple Threads

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.

Placing actor Instances

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.

image|100x100, 50%

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.

image|100x100, 50%

Desynchronizing Threads

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.


Thread Safety

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.

Safety Levels

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.

Example: Server-side Raycasting Validation

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)


Best practices

When adding your Lua code, keep the following practices in mind to reap the full benefits of parallel programming:

Avoiding Long Computations

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.

Using an Appropriate Number of Actors

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.

image|100x100, 180% -- 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.

image|100x100, 10%

View in-game to comment, award, and more!