How do you use a metatable within a ModuleScript?

I wish to use ModueScripts in my game to facilitate status effects, without creating new Value instances. My ideal system would insert new tick()s into the table whenever the status effects are applied in-game. In my testing place, I can’t seem to get a metatable inside a ModuleScript (not the main ModuleScript itself) to fire it’s metamethods.

Here is the testing ModuleScript in question:

ModuleScript
debug.setmemorycategory(script.Name)

local module = {}

function newIndex(tbl, i, v)
	print("LOL")
	coroutine.wrap(function()
		task.wait(3)
		tbl[i] = nil
		print("destroyed"..tostring(v))
	end)
end
function Index(tbl, i)
	print("LMAO")
	return "BRUH"
end

module.CoolTable = {
	
}

local metatable = {
	__newindex = newIndex(),
	__index = Index()
}

setmetatable(module.CoolTable, metatable)

return module

In my testing, I use the following ServerScript to facilitate the creation of new entries in the table:

ServerScript
local module = require(game.ReplicatedStorage.ModuleScript)

while true do
	task.wait(1)
	table.insert(module.CoolTable, os.clock())
	print("did i add something?")
end

For some reason, this is what the console looks like:

image

What am I doing wrong in using the metatable? Thanks in advance!

According to the console those metamethods were invoked. Hence, “LOL” and “LMAO” were output.

1 Like

You’re calling newIndex and Index methods instead of assigning them as metamethods in your metatable which is why it only prints once.

Remove () next to them to assign them properly:

local metatable = {
	__newindex = newIndex,
	__index = Index
}

For some reason, doing this doesn’t seem to work :frowning: :

image

I also tried doing this, and ended up with a similar output:

--module
local metatable = {
	__newindex = function(tbl, i, v)
		print("LOL")
		coroutine.wrap(function()
			task.wait(3)
			tbl[i] = nil
			print("destroyed"..tostring(v))
		end)()
	end,
	
	__index = function(tbl, i)
		print("LMAO")
		return "BRUH"
	end,
}

My attempt to use a function in the module to replace the regular table.insert (in the hope that it would invoke the metamethods) also didn’t end up working:

--module:
function module.tableinsert(tbl, value)
	table.insert(tbl, value)
end

--ServerScript:
module.tableinsert(module.CoolTable, os.clock())

Any ideas on what to try next?

I think you are over complicating things by using meta tables:

type statusInfo = {start :number, stop :number}

local module = {
	Statuses = {} ::{statusInfo};
}

--sends name, startTime, stopTime
module.StatusEnded = Instance.new("BindableEvent",script)
module.StatusEnded.Event:Connect(function(...) --you can connect to this event outside of this script too!
	print("Status: StatusEnded:", ...)
end)

function module:SetStatus(status :string, secs :number)
	print("Status: SetStatus:", status, secs)
	
	local statusInfo :statusInfo = {start = elapsedTime(), stop = elapsedTime() + secs}
	self.Statuses[status] = statusInfo
	
	task.delay(secs,function()
		self.Statuses[status] = nil
		module.StatusEnded:Fire(status,statusInfo.start, statusInfo.stop)
	end)
end

wait(3)
module:SetStatus("test",3)
1 Like

I would recommend you read this it goes really in depth on OOP and module scripts and has a section on using meta tables

1 Like

The solution here works perfectly for the situation I’ve described, but I still wonder why the metatable in the original version of the module does not work. If I can’t figure out why the modulescript’s metatable doesn’t want to work, I’ll probably end up using this workflow to handle the status effects.

I’ve read this article several times before making this post, it doesn’t seem to help solve my problem :frowning:

Thanks for the help anyways!

If I end up using this system, I will need to create unique identifiers for each time that the status effect is added to the table right? (To prevent task.delay from stopping the effect even if the same effect was applied many times after the initial effect)

Example
function module:SetStatus(status :string, secs :number)
	print("Status: SetStatus:", status, secs)
	local theelapsed = elapsedTime()
	local statusInfo :statusInfo = {start = theelapsed, stop = theelapsed + secs}
	self.Statuses[status..tostring(theelapsed)] = statusInfo
	
	task.delay(secs,function()
		self.Statuses[status..tostring(theelapsed)] = nil
		module.StatusEnded:Fire(status,statusInfo.start, statusInfo.stop)
	end)
end

Thanks for the help :slight_smile:

I’ve actually found a way to get around this, by instead creating more tables inside module.Statuses (named after the status effects in-game). This removes the need for the concatenation of the elapsed time with the status effect’s name. Combined with functions that find entries in these individual tables, I can prevent the status effect from disappearing when it is applied multiple times.