We’re excited to share a developer preview release of Parallel Luau project! Thanks to @EthicalRobot and @machinamentum for working on this (@zeuxcg also helped but people seem to frown on third person references so he decided he shouldn’t mention it)
Just like Future Is Bright and Avatar Evolution from prior years, this is a developer preview release. It needs more polish before being ready to become a Studio Beta, and definitely more work before becoming a production feature, but we wanted to share the build with y’all before the end of the year. We’re excited to see feedback on what works, what doesn’t, and what could be improved!
For the impatient, you can download the builds here (updated 12/16/2020):
Windows build (179 MB)
macOS build (166 MB)
And you may want to check out this neat parallel ray tracer example: raytracer.rbxl (the script is in StarterGui/ScreenGui/Tracer)
Please note that by downloading these builds you agree to a limited terms use license that is standard for our developer preview builds.
It’s important to realize that this build doesn’t magically make your code run in parallel. We have a plan that involves introducing a new programming model that is friendly to parallelism; it works really well once you get used to it but may require a bit of a transition. We will have tutorials and documentation for this when the feature goes outside of the preview, but for now you’ll have to make do with the rest of the post
Actors
This release adds a new instance type, Actor; scripts that are located under Actor instances in the hierarchy gain capability for parallel execution - by default the code still runs on the single thread though.
Actor objects are necessary as they will become units of execution isolation in the future; e.g. in the initial releases all functionality to modify the instances is going to be locked from the parallel execution, but we’re going to unlock mutation for the Actor subtrees in the future for the scripts that reside under them.
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.
As a side note, Actors are the units of parallel execution but we recommend to create them based on logical units of work. 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 run scripts that belong to different actors in parallel, but wait for the parallel sections to finish executing 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 state 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
(note: there may be other properties that we haven’t identified yet as “unsafe to read in parallel” and reading some of them may crash Studio; we’re going to refine the list of properties that aren’t safe and expose this as part of the API dump in the future)
You can’t modify any properties at this time. In the future releases we’re going to unlock ability to change properties selectively as long as the instance is part of the same Actor’s hierarchy. To be able to perform mutation on the hierarchy, 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). In this release we’ve whitelisted the following methods; the method status wrt thread safety will be exposed in the API dump in the future, and this list will expand over time:
- 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 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)
Initially you should expect to have to put all of the code that changes Instance properties in the serial portion of the update, with future releases allowing you to move more code from the serial to 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
… won’t work on scripts inside Actors in this release. This is why this is a developer preview Some other parts of functionality may be disabled or unstable - please report issues with this release via DevForum.
We’re eager to hear your feedback after you’ve tried to use this a bit! Please note that this may not match your existing model of how threading could work - this is not WebWorkers, and this is not “oh I know let me just create a thread”. Trust us, there are deep and profound reasons for why these models didn’t work for us, they are documented in an internal 26-page technical specification for this feature that we’re slowly building the implementation of
This being a developer preview you should expect some features to be broken, some amount of stability issues, and some features to just be lacking. For the beta release we’re planning to address the stability issues, add a way to communicate data between scripts that survives the VM separation and is thread-safe, expose more thread-safe methods and generally improve on the feedback that we get from this.