I have found a solution to some of this. You need to find a way to parallel your threads with your MAIN thread. So nothing is running after the main thread. Use your micro debugger to ensure nothing like this is occurring
Notice how the heartbeat stage is processing work, then after that the runParallel area starts. What you want is for the runParallel to be paired alongside it. So my recommendation is to have multiple instances of your script in different actors which control only a qaudrant of the work, therefore its split eqaully and all runs at the same time
It’s also important that you limit interaction with the main thread, therefore periodically batch everything and send it via a BindableEvent to the main thread (still in the actor, just not running in desync) and update whatever. This way its truly parallel
You do not want it to look like this.
This is not parallel with the main thread even though it looks like it. The main thread is still having to process work in the heartbeat area (offscreen) then the multi-threading begins, which is NOT GOOD because its still waiting on the main thread to start it.
I found out that those Parallel modules are not very smart to use; use a simple one like this. Currently I’m using this for my mass projectile solver, which is doing around 30k projectiles realtime
-- Inspired by : https://github.com/Bue-von-hon/Sagitta/blob/main/src/Dispatcher/init.lua
local RS = game:GetService("RunService")
local isServer = RS:IsServer()
export type TPool = typeof(setmetatable({}, {})) & {
_container : Folder?,
_init : boolean,
Threads : {Actor}?,
init : () -> (),
new : (number, ModuleScript, (...any) -> (...any)) -> (TPool),
Dispatch : (TPool, {[any] : any}) -> ()
}
--ProjectileThreadPool
local PThreadPool = {} :: TPool
PThreadPool._container = nil
PThreadPool._init = false
function PThreadPool.init() : TPool
if PThreadPool._init then warn "Already called init..."; return PThreadPool end
PThreadPool._container = Instance.new "Folder"
PThreadPool._container.Name = "Threads"
PThreadPool._container.Parent = isServer and game.ServerScriptService or game.ReplicatedFirst
return PThreadPool
end
function PThreadPool.new(threads: number, module: ModuleScript, callback: (...any) -> (...any)) : TPool
local Actors = {}
for i=1, threads do
local Template = script.Actor:Clone()
local Context = (isServer and Template.Script or Template.LocalScript)
local _ = (isServer and Template.LocalScript or Template.Script):Destroy()
Template.Name = "Thread" .. i
Template.Output.Event:Connect(callback)
Template:SetAttribute("task", 0)
Context.Parent = Template
Template.Parent = PThreadPool._container
Context.Enabled = true
Actors[i] = Template
task.delay(0, function()
Template:SendMessage("start", module)
end)
end
RS.PostSimulation:Wait()
return setmetatable({Threads = Actors}, {__index = PThreadPool})
end
function PThreadPool:Dispatch(info: {[any] : any})
table.sort(self.Threads, function(a, b)
return a:GetAttribute("task") < b:GetAttribute("task")
end)
local Thread = self.Threads[1] :: Actor
Thread:SendMessage("dispatch", info)
end
return PThreadPool
Your sorting logic is also very important, you’ll want to spread the load evenly. This one uses a task based one,