Hi, I’m currently building a library to use across most of the scripts in my games. I’m wondering if returning a table containing all the functions would cause a memory leak.
If it causes a memory leak, I could separate them into modules and optionally load them so that it won’t pass unnecessary functions.
I also thought of a way to optionally load them using the metamethod __index, but I’m not sure whether the metamethod itself and its functions would be passed as well, which could occupy even more memory.
I personally prefer the metamethod way if it doesn’t occupy more memory, as I can manage the code in a single view.
I’m estimating 5-10 functions for this library.
I don’t have much knowledge about memory allocations. Thanks!
The other main job of require is to avoid loading the same file twice. For that purpose, it keeps a table with the names of all loaded files. If a required file is already in the table, require simply returns.
local m = require(game.ReplicatedStorage.ModuleScript)
print(string.format("module %*", m))
local m = require(game.ReplicatedStorage.ModuleScript)
print(string.format("module %*", m))
prints the same number.
Speaking about module I’d recommend leaving metatables alone and use something like this:
--!strict
local function new(initialValue: number)
-- private members
local value = initialValue
-- private methods
local function incement()
value += 1
end
return {
-- public methods
updateAndPrint = function()
incement()
print("value", value)
end,
}
end
return {
new = new;
}
example usage:
local m = require(game.ReplicatedStorage.ModuleScript)
local i = m.new(10)
i.updateAndPrint()
i.updateAndPrint()
I’m thinking that some functions kept in the table may not be used in some games.
In this case, can keeping functions in separate modules and then optionally requiring them make a meaningful difference? I’m wondering about the memory usage between separate module instances and functions defined in the table.
I remember somewhere I saw it is posssible for tables to use memory for every instance of this table, even if it appears same. I forgot what actions can lead to this tho, but theoretically it is possible for same module script to return different tables, allocating memory for each of them.
export type ObjectType = {
first: any,
public_method: (self: ObjectType) -> (),
}
local function public_method(self: ObjectType)
end
--// simply don't export this and it stays private
local function private_method(self: ObjectType)
end
local function new(first: any): ObjectType
local object = {
first = first or "undefined",
public_method = public_method,
}
return object
end
the public method is global, not created seperately for every OOP instance
To be completely correct I guess your sample should be:
type A = {
value: number;
updateAndPrint: (A) -> ();
}
local function incement(self: A)
self.value += 1
end
local function updateAndPrint(self: A)
incement(self)
print("value", self.value)
end
local function newA(initialValue: number): A
return {
value = initialValue;
updateAndPrint = updateAndPrint;
}
end
Yeah, I have been there, and I currently prefer closures more because:
To access value from private methods it must be in the table. So it is not private any more:
It’s possible to see it by printing the table. And modify it. To modify upvalues debug library is required (which is hard to do unintentionally).
methods located outside of new’s scope are not private inside the same file. For example, If I add B and create it’s new in the same file, it can access A’s private increment, which is not always intended.
With closures it’s possible to define as many constructors of different types in the same file without polluting “namespace” scope.
If you need to change prototype of a member function: type definition must and implementation be changed.
In case of closures only implementation, so it’s sort of less typing.
Speaking about less typing closures does not require typing “self.” all around.
also check text size of functionally the same code