I am creating a procedural generation system and am trying to use actors to optimize performance (there are currently large lag spikes when generating levels)
I currently create 3 “Worker” actors which run generation code:
local function buildCave(jobId, payload)
local sharedResultKey = payload.sharedResultKey
local success, tileGrid, featureGrid, wallFeatureGrid = pcall(CaveBuilder.build, payload.depth, payload.integrity, {
config = mineConfig,
seed = payload.seed,
})
if success then
local gridWidth, gridHeight = getGridSize(tileGrid)
local sharedResult = SharedTable.new()
local sharedTileGrid = toSharedValue(tileGrid)
sharedTileGrid._gridWidth = gridWidth
sharedTileGrid._gridHeight = gridHeight
sharedResult.jobId = jobId
sharedResult.tileGrid = sharedTileGrid
sharedResult.featureGrid = toSharedValue(featureGrid)
sharedResult.wallFeatureGrid = toSharedValue(wallFeatureGrid)
SharedTableRegistry:SetSharedTable(sharedResultKey, sharedResult)
resultEvent:Fire({
jobId = jobId,
success = true,
sharedResultKey = sharedResultKey,
})
else
resultEvent:Fire({
jobId = jobId,
success = false,
error = tostring(tileGrid),
})
end
end
actor:BindToMessageParallel("GenerateFloor", buildCave)
(toSharedValue is basically just a deep recursive table clone)
I clone this script into 3 actors and manage them with a task scheduler and I use BindableEvents to communicate with the scheduler.
The game keeps a stack of 3 layers, generating and removing them as the player descends floors.
When a layer is generated, a server lag spike occurs, similar as if it was generated on the main thread. This really shouldn’t occur for true isolated threads, since the code does not await for worker threads to finish anywhere. The map renderer does clone a lot of instances, but I have it call task.wait() after every 5 tiles. (i % 5 == 0)
That’s when I found out that actors aren’t true isolated threads.
From my experiments with them, actors really only guarantee parallelism as they can still slow down the main serial thread.
Mine generation is only faster when generating multiple floors at once, which isn’t what my generator is built for. It generates large, expensive floors once every few minutes, except for the start of the game when the layer stack is built.
Here’s another test I did on a simple worker/coordinator setup. (This time with the coordinator script in its own actor)
Coordinator
local ServerScriptService = game:GetService("ServerScriptService")
local worker = ServerScriptService.W1
local actor = script:GetActor()
actor:BindToMessage("Done", function()
print("Worker done!")
end)
while true do
task.wait(5)
worker:SendMessage("Work")
end
Worker
local actor = script:GetActor()
local coordinator = game:GetService("ServerScriptService").Coordinator
actor:BindToMessageParallel("Work", function()
local v = 0
for i = 1, 1_000_000_000 do
v += i
end
coordinator:SendMessage("Done")
end)
This also pauses the main server thread until the task is complete.
Is there a way to avoid this issue, having parallel code not also run on/block the serial thread? This feature really wouldn’t be much help for my game otherwise.