After a flurry of bug fixes and stability improvements, we are happy to announce the Parallel Lua project has graduated from a developer preview release to a full fledged Studio Beta! Special thanks to all the developers who kicked the tires on the developer preview and provided feedback and demos.
Important Note: For now, Parallel Lua features will only work within Roblox Studio! If you publish a place that has Parallel Lua API calls, things will be very broken.
The Parallel Lua APIs can be enabled via the Roblox Studio Beta Features Menu
The Basics of Parallelism
In order for your script to run in parallel with another script two things must be true:
- The scripts must be parented under different Actor instances
- The thread within a script must be desynchronized either via
task.desynchronize()
orSignal::ConnectParallel
What are Actors?
Actors are a new Instance type that lets you chop up your place into logical chunks. These chunks mark parts of the data model tree as independent. By promising that you will not access anything outside that actor in a desynchronized thread, threads from other actors can run at the same time.
Actor inherits from Model, so you should be able to replace the top-level instance type for your cars / NPCs / other 3D entities with Actor with no changes in the scripts (but see caveat about stateful ModuleScripts later).
It’s important to mention that scripts that are part of the same Actor always execute sequentially with respect to each other. For example an NPC is probably a good candidate to become an Actor. Any parallel-enabled behavior scripts underneath a single NPC Actor will run serially on a single thread, but if you have multiple NPC Actors, each of them will run in parallel on its own thread.
As a side note, Actors are the units of parallel execution but we recommend creating them based on logical units of work. The Roblox engine will distribute them appropriately among threads and cores. For example, if you want to generate voxel terrain in parallel, it’s totally reasonable to use 64 Actors or more instead of just 4 even if you’re targeting 4-core systems. This is valuable for scalability of the system and allows us to distribute the work based on the capability of the underlying hardware.
Parallel (“desynchronized”) execution
Each script still runs serially by default, but scripts running inside Actors can switch to run in parallel by using task.desynchronize
function. This function is yieldable - it suspends execution of the current coroutine and resumes it at the next parallel execution opportunity.
It’s important to understand that regions of parallel execution (the scripts on various Actors) run in parallel, but the engine waits for all parallel sections to finish before proceeding with serial execution. In other words, to take advantage of this feature you can’t run a very long computation that takes seconds in parallel to the rest of the simulation - you have to break it into small pieces, but you can run these pieces on multiple cores. Your mental model should be “let me run updates for 1000 NPCs in parallel with each update potentially running on a separate core” instead of “let me run this really slow function that sequentially updates all NPC states in parallel to the rest of the world processing”.
During parallel execution, access to the Instance hierarchy is restricted. You should be able to read most objects of the hierarchy as usual, with the exception of some properties that aren’t safe to read:
- GuiBase2d.AbsolutePosition
- GuiBase2d.AbsoluteSize
- ScrollingFrame.AbsoluteWindowSize
- UIGridLayout.AbsoluteCellCount
- UIGridLayout.AbsoluteCellSize
- UIGridStyleLayout.AbsoluteContentSize
Currently you can’t modify any properties during the parallel phase. In the future releases we’re going to unlock the ability to modify the properties of the Actor’s instance hierarchy in a desynchronized (parallel) script. For now, to be able to modify any properties or state, you must switch back to the serial (“synchronized”) execution; you can do this by calling task.synchronize
, which will suspend execution of the current coroutine and resume it at the next serial execution opportunity.
Methods exposed on Instances are safe to call only if they have been explicitly whitelisted (because many of them perform mutation of the hierarchy). So far we’ve whitelisted the following methods; the method status with regard to thread safety is exposed in the API dump, however that info has not yet been added to the online API reference:
- Instance.IsA
- Instance.FindFirstChild
- Instance.FindFirstChildOfClass
- Instance.FindFirstChildWhichIsA
- Instance.FindFirstAncestor
- Instance.FindFirstAncestorOfClass
- Instance.FindFirstAncestorWhichIsA
- Instance.GetAttribute
- Instance.GetAttributes
- Instance.GetChildren
- Instance.GetDescendants
- Instance.GetFullName
- Instance.IsDescendantOf
- Instance.IsAncestorOf
- Part.GetConnectedParts
- Part.GetJoints
- Part.GetRootPart
- Part.GetMass
- Part.IsGrounded
- CollectionService.GetTagged
- CollectionService.GetTag
- CollectionService.HasTag
- Workspace.FindPartsInRegion3
- Workspace.Raycast
- Terrain.ReadVoxels
RBXScriptSignal:ConnectParallel
Instead of using task.desynchronize
in signals, you can alternatively use a new ConnectParallel method. This method will run your code in parallel when that signal is triggered, which is more efficient than using Connect
+ task.desynchronize
.
A common pattern for parallel execution that we expect to see is:
RunService.Heartbeat:ConnectParallel(function ()
... -- some parallel code that computes a state update
task.synchronize()
... -- some serial code that changes the state of instances
end)
For the time being you will need to put all of the code that changes Instance properties in the serial portion of the update, while future releases will allow you to move more code from the serial to the parallel portion.
ModuleScripts
Scripts that run in the same Actor are running in the same Luau VM, but scripts that run in different actors may run in different VMs. You can’t control the allocation of Actors to VMs or the total number of VMs - it depends on the number of cores the processor has and some other internal parameters.
When you require
a ModuleScript from a Script inside the Actor, the script is going to get loaded (& cached) in every VM it’s needed in. This means that if your ModuleScript has mutable state, this state will not be global to your game anymore - it will be global to a VM, and there may be multiple VMs at play.
We encourage use of ModuleScripts that don’t contain global state. In the future we’re going to provide a shared storage that will be thread-safe so that games that use parallel execution can use it to store truly global state, as well as ways to communicate between scripts safely using messages, but for now you should be aware of this gotcha.
Debugger
… doesn’t work on scripts inside Actors in this release. This is why this is a beta