Non-Methods in Modules

(I hope I got the terminology correct)

So I’ve been working to improve my code for a while now, and I’m currently trying to write more modular code (smaller functions, and modules)

something I recently coded

local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local sharedModules = ReplicatedStorage.SharedModules
local serverModules = ServerStorage.ServerModules
local ZoneInformation = require(sharedModules.ZoneInformation)
local ZoneManager = require(serverModules.ZoneManager)

local PlayerZoneManager = {}


local function getPlayerInThisZone(player, zoneName)
	return PlayerZoneManager:getZone(zoneName)[player]	
end


local function getPlayersZoneName(player)
	for zoneName, zone in pairs(ZoneManager.zones) do
		local playerInZone = getPlayerInThisZone(player, zoneName)
		if playerInZone then
			return zoneName
		end
	end	
end


local function setPlayerZone(player, zoneName, value)
	local zone = PlayerZoneManager:getZone(zoneName)
	if value then
		zone[player] = true
		ZoneManager:onZonePlayerAdded(player, zoneName)
	else
		zone[player] = nil
		ZoneManager:onZonePlayerRemoved(player, zoneName)
	end
end


function PlayerZoneManager:getZone(zoneName)
	return ZoneManager.zones[zoneName].players
end

function PlayerZoneManager:addPlayerToZone(player, zoneName)
	if not self:getZone(zoneName) then return end
	local currentZoneName = getPlayersZoneName(player)
	if currentZoneName then
		if currentZoneName == zoneName then return end
		self:removePlayerFromZone(player, currentZoneName)
	end
	setPlayerZone(player, zoneName, true)
end


function PlayerZoneManager:removePlayerFromZone(player, zoneName)
	local zoneName = zoneName or getPlayersZoneName(player)
	setPlayerZone(player, zoneName, false)
end

return PlayerZoneManager

local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local sharedModules = ReplicatedStorage.SharedModules
local serverModules = ServerStorage.ServerModules
local ZoneInformation = require(sharedModules.ZoneInformation)


local ZoneManager = {}
ZoneManager.zones = {}

local function init()
	for _, zoneInfo in ipairs(ZoneInformation) do
		ZoneManager.zones[zoneInfo.name] = {players = {}, enemies = {active = false, enemyObjects = {}}}
	end
end

function ZoneManager:onZonePlayerAdded(player, zoneName)
	print(player, "added: " .. zoneName)
end


function ZoneManager:onZonePlayerRemoved(player, zoneName)
	print(player, "removed: " .. zoneName)
end

init()

return ZoneManager

I’m still working on improving it, but is it bad practice to have regular functions in modules in general?

these regular functions are just for the module and not for any other scripts/modules, should I convert them to methods or is it ok to keep them as is?

my reasoning for making them Non-Methods is because they won’t be used anywhere else but I have no idea if thss is the right way to go about it

also before anyone asks why I have two ZoneManagers when I could use one it’s because I’m also making a enemy zone manager and if I combine enemy + player zone managers it would get confusing to read really quickly

1 Like

It’s good practice. If these functions don’t need to be accessed outside of the module, there is no reason to include them as table member functions. That would be both unnecessary, and slightly detrimental to performance (table access is slower than simply calling a local function).

Additionally, in terms of table member functions, they don’t all have to be methods. If the function is not accessing self, i.e. you aren’t writing OOP, then there really isn’t any reason to call it as a method–doing so will incur a (minor) decrease in performance. For example, Instance.new is not a method, but Part:Destroy() is; the latter refers to a specific object, while the former does not. The latter is equivalent to Part.Destroy(Part)

If you’re confused as to what defines a “method”, I recommend reading this – https://www.lua.org/pil/16.html

If you’re interested in performance, here are the results from calling a table member function several different ways for 1x10^7 times each:

  • Calling as a method: 0.57340979576111 seconds
  • Calling without the colon operator: 0.43766403198242 seconds
  • Calling as a local function: 0.33759498596191 seconds

The above does not tend to apply to builtin Lua tables, such as math, for which calling member functions will be just as performant as if they were localised, and also for Roblox instances (the performance of calling a function like SetPrimaryPartCFrame will effectively be equivalent regardless of whether the colon operator is utilised). This is due to recent changes in Roblox’s implementation of Lua.

Code from performance test:
local trials = 1e7

local tbl = {}
function tbl.DoStuff(self)
end

local DoStuff = tbl.DoStuff

local start = tick()
for i = 1, trials do
	tbl:DoStuff()
end
print(tick() - start)

local start = tick()
for i = 1, trials do
	tbl.DoStuff(tbl)
end
print(tick() - start)

local start = tick()
for i = 1, trials do
	DoStuff(tbl)
end
print(tick() - start)
2 Likes

Adding to this, using colon syntax where OOP isn’t used makes the code harder to read because it makes the reader unsure of what is and isn’t an object. The syntax also makes it harder to store and use the functions separate of the module without wrapping.

3 Likes