Parallel Luau [Version 2 Release]

Hi Developers,

In our initial release of Parallel Luau, there were many cases where communicating with an Actor or associated scripts could be inconvenient. Today, we have introduced two key features to make communication more convenient and to open up new ways to communicate efficiently; Actor Messaging API & SharedTable.

Previously, communication required storing state directly in the DataModel, or transmitting state using existing mechanisms (e.g. BindableEvents). So, while communication was possible, it was often inconvenient, and having Actors accessing shared state wasn’t really possible when running in parallel.

What’s New?

Actor Messaging API

The Actor Messaging API allows a script to send a message to an Actor within the same DataModel. Messages are sent asynchronously (i.e. the sender doesn’t block until the receiver receives the message).

Any script may send messages to an Actor, but only scripts that are descendants of an Actor are allowed to receive those messages. Since there are many different kinds of messages that one might want to send to an Actor, the messaging API uses a “topic” to identify the kind of message being sent and received.

Example message sender:

-- Send two messages to the worker actor with a topic of "Greeting"
local workerActor = workspace.WorkerActor
workerActor:SendMessage("Greeting", "Hello World!")
workerActor:SendMessage("Greeting", "Welcome")
print("Sent messages")

Example message receiver:

-- Get the actor this script is parented to
local actor = script:GetActor()
-- Bind a callback for the "Greeting" message topic
actor:BindToMessageParallel("Greeting", function(greetingString)
    print(actor.Name, "-", greetingString)
end)
print("Bound to messages")

SharedTable

Currently, scripts running under different Actors don’t have a way to share data efficiently. They can send data to one another, but sending data requires that data be copied. If large amounts of data are involved, sending the data can be inefficient. For many use cases, it is desirable to allow direct sharing of data. For example, multiple Actors may need to work on a common world state that is not stored in the DataModel. If this state requires a large amount of data it could be inefficient to send a copy to each Actor.

To support this, we are adding a new data type to Roblox called SharedTable. A SharedTable is a table-like data structure that can be accessed from scripts running under multiple Actors. Sending a SharedTable to another Actor (e.g., via the new Actor Messaging API) does not make a copy of the data; instead, SharedTables are effectively shared “by reference.” Multiple Actors can update the same SharedTable in parallel.

SharedTables have an API that is fairly similar to standard Luau tables. For example, they support reading and writing values using a key, with v = st[k] or st[k] = v. There are some restrictions on the kinds of data that can be stored in a SharedTable–notably, all keys must be either a string or a non-negative integer. SharedTables can be cloned, and clones use structural sharing to make them very inexpensive (data is not copied when a SharedTable is cloned).

Here are a few simple examples:

-- SharedTables can be used much like ordinary Luau tables:
local st = SharedTable.new()
st.a = 100
st["b"] = 200
st[1] = 300

-- SharedTables can be inexpensively cloned:
local clone = SharedTable.clone(st)

-- A global SharedTableRegistry is provided for "sharing" SharedTables:
local SharedTableRegistry = game:GetService("SharedTableRegistry")
SharedTableRegistry:SetSharedTable("MyData", st)
local myData = SharedTableRegistry:GetSharedTable("MyData")
myData[2] = 400

You can find out more about the features supported by SharedTable, along with concurrency and atomicity guarantees and a variety of code samples in the SharedTable documentation.

Support for this new data type has been added to Roblox Studio. Printing a SharedTable will cause it to be displayed in the output window as you would expect.

Printing a SharedTable:

Changes and Improvements

Scheduling Changes

For this release of Parallel Luau we made changes to scheduling which have these benefits:

  1. We modified parallel signal scheduling so that parallel scripts run at the same “resumption points” as deferred threads. This means that the scheduling of all signal types is much more consistent now.

  2. This also means that if a Luau script performs a “ConnectParallel” call on a RunService event, the parallel callback will occur during that engine phase. Therefore scripts now have much more control over when parallel callbacks will execute during the frame.

We execute “deferred” Luau signals many times during a frame, so running parallel scripts only once per frame seems inconsistent.

Internal engine systems may also want to execute Luau scripts in parallel but require those scripts to run at specific times during the frame. Of course, if we want to do this internally there is a good chance that Roblox developers will also want this ability.

More Thread-safe APIs

A lot of the discussion so far has been focused on improvements to the base programming model available to scripts. However, having a great programming model still isn’t very useful if scripts running in parallel can’t call the APIs that they need to use. So another area of improvement we focused on recently was making more of Roblox APIs thread safe so they can be called from scripts running in parallel.

Here’s a list of some of the APIs we have made accessible to parallel scripts as Safe since our initial release:

Click here to view the list.
  • Instance:GetActor
  • BasePart:GetNetworkOwner
  • BasePart:GetNetworkOwnershipAuto
  • BasePart:CanCollideWith
  • BasePart:GetVelocityAtPosition
  • Workspace:GetPhysicsThrottling
  • Workspace:GetNumAwakeParts
  • Workspace:GetServerTimeNow
  • Workspace:GetRealPhysicsFPS
  • Player:GetNetworkPing
  • Players:GetPlayerByUserId
  • Humanoid:GetStateEnabled
  • Humanoid:GetState
  • Terrain:GetMaterialColor
  • HTTPService:JSONDecode
  • HTTPService:JSONEncode
  • HTTPService:UrlEncode
  • HTTPService:GenerateGUID
  • RunService:IsServer
  • RunService:IsClient
  • RunService:IsStudio
  • RunService:IsRunMode
  • RunService:IsEdit
  • PVInstance:GetPivot

Roblox has a large number of APIs, and we realize that there are still a lot of APIs and properties that can be made thread safe. Please let us know on the dev forums if there are specific APIs that could be made more accessible to increase your adoption of Parallel Luau. Knowing what APIs are the most valuable to make accessible (whether that is Read Safe, Local Safe, or Safe) from Parallel Luau will help us prioritize work to make the necessary changes.

Improving Some Behavior from Our Initial Release

Over time, we’ve realized that some of the task library APIs have behavior that can be unexpected or surprising. As part of this release, we are making the following changes.

task.synchronize() and task.desynchronize() may now only be called from scripts that are descendants of an Actor. Previously, it was possible for a script that was not a descendant of an Actor to call these APIs. The resulting behavior was likely surprising: A script that is not a descendant of an Actor can never run in parallel, so if such a script called task.desynchronize(), that call would have no effect and the script would continue running in serial.

We will be changing the behavior of these APIs so that an error is raised if they are called from such a script.

task.defer(), task.delay(), and task.wait() now resume in the same context (serial or parallel) that they were called in. Previously, these APIs would all resume in the serial context, even when called from a script running in the parallel context. This resulted in surprising, unexpected context changes. Ideally, all context changes should be explicit, e.g., via a call to task.synchronize() or task.desynchronize().

We will be changing the behavior of these APIs so that they will always resume in the same context that they were called in. When one of these APIs is called from the parallel context, the script will be resumed in the parallel context. When called from the serial context, the behavior is unchanged, and the script will be resumed in the serial context.

While these are breaking API changes, we believe they will make the API behavior more consistent. We have metrics that indicate that only a handful of experiences will be impacted. To reduce the impact and help you to update your scripts, today we are only enabling warnings for scripts that would be broken by these changes. The warnings will appear in the console, along with other diagnostic messages. We will follow up with another post announcing a date for the behavioral changes in the near future.

Additional Information

We have also made some optimizations to reduce the overhead in Parallel Luau to improve performance. For example, some of the thread-safety checks decreased performance in our micro benchmarks even for scripts not running in parallel.

We have also made numerous minor fixes for issues that impacted Parallel Luau.

We have updated and expanded our documentation for Parallel Luau:

What’s next for Parallel Luau

We know that there is still more work to do to improve Parallel Luau ever further. Here are some areas we hope to address in the future:

  • Provide more tutorials for developers to see how Parallel Luau can be used in their experiences
  • Continue to optimize Parallel Luau to reduce overhead and improve performance
  • Make even more APIs available from parallel scripts
  • Work on fundamental internal engine changes to enable more parallelism
  • And of course address your feedback…

Availability

Parallel Luau Version 2 features are available in the Roblox 576 (and later) release. You may begin trying out these features in Roblox Studio immediately. However, please be aware that some mobile and console platforms may not be updated to this release at the time of this announcement. Those platforms will be able to use Parallel Luau Version 2 features as they update to the 576 release in the coming weeks.

Feedback

We are looking forward to hearing your feedback on Parallel Luau. Please let us know what you like about our latest release and what areas are causing difficulties. We’d love to hear about how you are using the system as well as what future plans you may have. Your feedback is instrumental in guiding what we do next!

Thank you.

205 Likes

This topic was automatically opened after 10 minutes.

The addition of these cross-actor APIs is so helpful! I’ve been meaning for a way to cross state between actors and my non-actor code as I’ve begun making the switch for all the NPCs in my experience to start using Actors; so far, I’ve been using Roblox-Parallel-Worker to get cross-actor communication done. Shared tables especially are going to be great for my use case.

Also excited for the future of Luau, especially with regards to how parallel code is now run more than once per frame! Deferred is truly the future of Roblox development and I’ve never been more happy to put in the effort to make the switch for old projects and write more defensive code in newer projects that’s designed to work with Deferred right away rather than stay relying on Immediate and obscure race conditions. I know that my team is still hoping for not-Instance-based desynchronisation.

One request my team and I did have while using them is that Bone.TransformedCFrame is not read safe. A lot of our NPCs are now mesh deformed and we require that so we can deploy parallel Lua with the bone point solvers in our raycast hitbox implementation. Hoping that we could get that on read safe as it’s critical to not hacking in calculations in parallel for mesh deformed avatar constructs.

39 Likes

Thank you for these updates on parallel luau, however I’d like to ask for a more descriptive tutorial on the api page, i find it a bit confusing to start out with it, maybe some place examples. other than that, amazing stuff!

17 Likes

Incredible work guys! Love all the incredible changes your implementing to improve performance and the overall mobility of roblox.

12 Likes

Along with the potential this can be really useful for server authentication in some cases.

8 Likes

Generating parts, such as terrain generation, is really difficult in parallel due to the limitations of being unable to copy and etc.

12 Likes

This was long overdue. The lack of SharedTables basically rendered the whole feature useless to me in the past; however, through some simple testing, I found that both the read and write overhead is massive (up to 15x) compared to standard tables. Great initial release, but I hope for future performance improvements.

Additionally, it would be nice to have a :WaitForMessage(topic) method.

15 Likes

Only feedback besides the current thread I have for more functions available to parallel luau in engine features, is that task.desynchronize and task.synchronize shouldn’t give a warning within Roblox Studio’s and Roblox Player’s in-game command bar.

In-Game Command Bar:

Studio Command Bar:

I end up testing a lot of code within command bars to make sure that they properly run in Parallel Luau and seeing this warning constantly appear every time the functions are called can be annoying when testing. It should be inferred that if the user runs task.desync/sync within a command bar that the code isn’t located within a script and wouldn’t be possible to be under an actor, as is being used to test parallel code, especially since it even works in parallel fine with no problems. Would like to know if you guys were aware of the command bar “bug” and if I should file a feature/bug thread on this.

I’d like to state that I use Parallel Luau in a lot of experiences I work for and the performance gains on the client-side are pretty great. I have no real current problems with using and developing with Parallel Luau either, so that’s great. While the server-side does see performance gains, due to the low core count it’s not as noticeable as the gains on the client-side, but better performance is better performance, at some point in time in the future I wouldn’t mind servers having more core counts or working better with parallel.

9 Likes

Actual W update, however on the topic of having more api’s available to parallel scripts, would it be possible to mark FilterDescendantsInstances from RaycastParams to safe? Right now only way to get around this is to either cripple performance (due to switching from parallel, serial then back to parallel again) or just mark the RaycastParams at the top of the script before any meaningful execution.

Also, what about module scripts? While pretty much SharedTables are just module scripts but without the instance i don’t think those should be left behind. Would there be any changes for those?

15 Likes

Amazing update! I’ve been waiting not-so-patiently for shared tables.
In terms of more thread-safe APIs, TweenService:GetValue() would be great to have.

11 Likes

How does this work regarding functions and tables, will it be the same as bindables where a table is made immutable and functions cannot be loaded cross-VM

6 Likes

I’m wondering about that as well. One possibility could be to include functions within the SharedTable concept. However, it’s worth noting that the term “SharedTable” might be misleading to some, as it could imply a table that replicates across a network between clients and servers.

5 Likes

Actual W update, however on the topic of having more api’s available to parallel scripts, would it be possible to mark FilterDescendantsInstances from RaycastParams to safe? Right now only way to get around this is to either cripple performance (due to switching from parallel, serial then back to parallel again) or just mark the RaycastParams at the top of the script before any meaningful execution.

Hi @XenoDenissboss1, we found that users generally wanted to add to a filter, so we created a new method to allow this: RaycastParams:AddToFilter

Although we realize that method may not cover all use cases. Does this method help for the use cases you had in mind?

If it doesn’t help, could you provide some more details on the type of use case(s) you would like addressed?

7 Likes

For the Thread-safe APIs, I would love to have:

  • Model:GetBoundingBox
  • Model:GetExtentsSize

If you need to do a calculation using those things from many models, you are unable to do so, like the rendering distance I currently do which I use the origin of the Model by accessing the CFrame returned by GetBoundingBox and then check the distance between the player and that position to parent objects to a specific folder or back to workspace. (Another weird thing we have to do to avoid lag)


Small Issue I Found: SharedTable doesn’t show auto-completion like for a normal table.


Overall great, I barely know anything about parallel execution and the implications it has, but I would really love to have a function that when called runs in parallel kinda like:

task.runParallel(Function) → Yields the current coroutine until it finishes and returns anything I returned from the function.

I truly don’t like having to create an actor and put scripts under it.

13 Likes

Personally ive needed some methods in order to get ray casts to bypass some specific objects (bypassing transparent objects and whatever else). Good example would be one of my own little raycast renderer:


It does support transparent objects along with recursive transparency so i primarily needed the ability to edit the FilterDescendantsInstances straight in parallel as constantly switching around serial and parallel in such performance critical phase will reduce the performance of the whole thing by a ton. I primarily got around this issue by just slightly moving the ray’s start position by like 0.001 studs in their direction to not cause a stack overflow error (since the rays would just keep hitting the same spots indefinitely).
Ive tried using the AddToFilter function and im really not sure if there’s something wrong with my code or if theres something wrong with this function as this isnt the first time ive had problems with various roblox features that just break or act weirdly in parallel. Right now the issue im facing is the fact that it now simply will not display any transparent objects. (It will display a few pixels of the transparent objects for a frame then after it will simply not display anything)

Technically there have been barely any changes and logically there should not be that big of a difference between just casting a ray slightly inside a part to go past its collisions and simply adding said part to a filter so im really not sure since again, ive had issues with other things just in parallel code. (Bindable events would magically transform numbered table entries into strings and setting signal behavior to deferred just causes the whole thing to no longer function)

(Also while the ray cast renderer may not entirely be a thing actually usable for real games, the same code to get rays to bypass transparent objects is currently being used in some other of my projects that are actually designed to be used in a real world scenario.)

9 Likes

@SuperDave6502 Are there any plans to make Terrain:ReadVoxels() and (maybe) Terrain:WriteVoxels() compatible with parallel? Currently they mutex lock, which means they can be used in parallel, but only one thread at a time can use them, ruining the point of using them in parallel :

6 Likes

What is the difference between using the actor messaging feature and using bindable events under the actor other than safety?
Is there a performance benefit, is serialization faster.
Can you send complicated tables with tables as indices, or arrays which contains nil values?

5 Likes

From what testing ive done and ideas ive got in how to use it, i see the situation with bindables and shared tables to be the following:

  • Bindables = 1.25x-2x faster, Less convenient, most likely will use more memory (depends on your setup and workload)
  • SharedTables = 1.25x-2x slower, WAY more convenient, should use less memory (again, depends on setup, but fact is that you can just use a single table shared across like a module while for bindables i see a decent chunk of setups requiring the cloning and sending back and fourth said duplicated data for use)

This is just my observation. Technically maybe you can get away with better performance from SharedTables if you had to fire a lot of bindables back and fourth but thats most likely about it. Could be wrong.

10 Likes

Making GetPivot() safe to call in parallel was definitely a lifesaver. Thanks for the change!

9 Likes