How does parallel/multi-threaded Lua work when using module scripts with metatables?

I’ve been looking into using parallel Lua to optimize NPCs for a project, but there isn’t a whole lot of clear information about the workings of parallel Lua and module scripts.

I use metatables to implement object oriented programming like so:

local entity = {}
entity.__index = entity

function entity:new(o)
 local o = o or {}
 setmetatable(o, entity)

 return o
end

function entity:moveTo(target)
 -- Pathfinding logic or something
end

function entity:findNearesttarget()
 -- Chase logic
end

return entity

Now, since doing a :GetAllChildren() every single time I want to look for the nearest target is slow, I have another modulescript somewhere that keeps track of all players/entities that are added that have humanoids.
Players and NPCs are added to a target list when spawned and removed when dead or despawned.

To optimize my NPCs so I can have 100s of them, I want to do the target seeking and pathfinding logic multi-threaded.
However I’m not sure what exactly happens if a module script is require()'d on another thread.

Will this duplicate all the target adding/removing from a target list for every thread?
And what happens to that list of available humanoids/targets if the thread ends or is synchronized again?
Will every actor have their own personal copy of everything?

All NPCs will likely be actors with a script in them that only has 3 lines of code since all logic is inside modulescripts and uses metatables to keep stuff object-oriented and easily reusable.

I cannot find a lot of detailed information on multi-threading.
I myself have a 16-core / 32-threads CPU and I want to multi-thread my systems over as many cores as there are available to minimize the lag.

My AI logic might get really complex since I plan on giving all AI per-weapon logic (they can pick-up and drop different weapons and spawn with different strategies so they aren’t as dumb and can challenge the player in multiple ways).

I already know how to implement most of it but I want to multi-thread it right away so I don’t end up having to redesign my whole AI system later.

I would greatly appreciate any help or tips, especially knowledge on how this whole parallel Lua thing works.
I’m not unfamiliar with multi-threading but not really used to it either.
I was going to experiment around a little but not entirely sure where to start due a lack of information.

I at least hope I can get the most heavy/intensive AI functions to run parallel since it might get really complex and 100 humanoids with the complex AI I want to implement might lag if all of it were single-threaded.

1 Like

The required module will be ran in the thread it is used, so if it’s ran in a parallel thread, it has to follow the same rules a parallel thread follows, like not being able to call un-safe methods.

Whatever is in the original module script isn’t changed by other active threads.
Ex:

local module = pathToModule
local requiredModule = require(module)
--// module is not the same as requiredModule because they are different and don't 
--// have the same reference

Required module and the original module are different in terms of reference and data. You most likely need to require the module for it to track the players/entities. Every time something is added in the module’s array of targets, tell the current NPC parallel actors to update their array of targets using a “Universal” BindableEvent. Though you probably have to lower down the use of actors by not having each individual NPC be an actor to prevent Roblox from creating too many threads when the event is fired.

Every actor requires to have their own personal copy, they technically do. To communicate data between the actors and server is by using attributes and BindableEvents (for arrays).

What I do in my game handling NPCs is by having an actor for each group of 25-50 NPCs, I have to use BindableEvents to send back any processed information to the main Server script since I can’t use my custom signal module.

2 Likes

Now I do wonder, theoretically if I have let’s say… 4 CPU cores but 40 actors. Would every 10 actors run on one CPU core and all share the same copy of a module script’s contents (meaning there are only 4 copies of said module script) or would ALL 40 actors have their own unique module script copy (40 copies of a module script’s contents)?

And how would I scale this system to people that have 64 CPU cores for example?
I feel like that by having 25 NPCs per thread/actor simply wouldn’t utilize all CPU cores.

I mentioned having a CPU that has 16 cores / 32 threads.
I could in theory have 320 NPCs, simulating 10 NPCs per thread.

But I don’t think Roblox provides an API or easy way to detect how many cores a player has which makes things difficult.
I could make a “CPU core count” setting for it, but most players probably wouldn’t know what the setting is for.
Automatic core detection would be most ideal.

2 Likes

Oh I should’ve mentioned, it’s a solo/singleplayer-oriented project,
so in case you’re wondering “Why does he need to know how many threads a player’s PC has?” it is because most action happens locally.
(Yes I know it’s not exploiter proof but I got my reasons.)

To be honest that’s a cool idea, knowing how many cores a player has to fully utilize the usage of them, but I do think Roblox automatically handles the cpu core - actor situation.
Oh and I just realized, after Parallel LUAU beta, you can only require a module if the script requiring it is in serial mode.

1 Like

Yea, it is a bit confusing, I do not know for what reason that is.
If every Luau VM or thread has their own personal copy of module scripts, why should it be serial?

Or perhaps they changed it and module scripts are no longer are copied to every thread?
I just know too little about parallel Luau to really experiment or use it.

I know how multi-threading works outside of Roblox in other programming languages.
But Roblox does everything automatically under the hood so I’m left to figure it all out to get optimal performance rather than being able to do things myself.

1 Like