Isolating parallel computations from the main thread

The Problem

As a Roblox developer, it is currently difficult to run complex compute jobs without impacting the main thread. Actors only address a part of this, allowing parallelism but not complete isolation. This is currently intended behavior, as outlined in the best practices of the parallel lua docs. Parallel computations currently run in their own execution phase alongside other parallel code.

However, there are still cases where developers would need to run long computations periodically. For example, I have a level-based procedural generation system which keeps a stack of 3 levels in the world, generating or deleting them as the player progresses through the run. I attempted to use actors to hopefully improve performance, but I only noticed improvements for batch-generations (such as at the start of the game, when the level stack initializes). As the game is fundamentally linear, where players progress through each level one-by-one, only one generation job would be active at once during gameplay.

My generator fundamentally generates levels in serial. If I were to modify it to run generation jobs in parallel, it would be difficult to enforce specific constraints, such as ensuring rooms do not overlap with other rooms. A better option for my game would be to offload level generations to a separate thread.


Code example

Let’s say we have a simple worker actor which would, in practice, generate level grids.

local actor = script:GetActor()
local coordinator = game:GetService("ServerScriptService").Coordinator

actor:BindToMessageParallel("Work", function()
	--Simulate generation
    local v = 0
	for i = 1, 1_000_000_000 do
		v += i
	end
	coordinator:SendMessage("Done")
end)

We then periodically send messages from a different actor telling the worker to work.

local actor = script:GetActor()
local worker = game:GetService("ServerScriptService").W1

actor:BindToMessage("Done", function()
	print("Worker done!")
end)

while true do
	task.wait(5)
	worker:SendMessage("Work")	
end

Currently, the serial thread freezes while the actor is doing work in parallel. Because we only have one worker actor, parallelization is not helpful in this case. However, if we had multiple worker actors and the coordinator sends multiple jobs at once and splits them to each actor, performance would be improved (intended actor use-case).


Proposed solution

Add a new instance: WorkerActor. This would be similar to the regular Actor instance except that it cannot run serial code. Jobs sent to that actor would always run on it’s own CPU thread and would be entirely isolated from the main execution loop. Scripts inside the actor would have task.synchronize() and task.desynchronize() disabled due to isolation from the execution loop. The actor:BindToMessage() event would also be disabled, forcing the use of actor:BindToMessageParallel().

If Roblox is able to address this issue, it would improve my development experience because it allows us to offload expensive computation jobs from the serial thread, improving game performance for cases such as intermittent level-based procedural generation.

7 Likes