Cannot access metatable through BindableFunction

I’m having an issue with being able to access methods through an OOP object through bindable functions. Here’s an example of how my system is setup and where it goes wrong:

First, the module that creates the OOP object:

local Class = {}
Class.__index = Class

	function Class:Print()
		print("Print")
	end

	function Class.new()
		local obj = {}
		setmetatable(obj, Class)
		obj.Example = "Example"
		return obj
	end

return Class

Next, the script that requires the module, and initiates any new objects as requested by BindableFunction invokes:

local Class = require(script.Parent.Class)

function BindableFunction.OnInvoke()
	local obj = Class.new()
	print(obj.Example) -- works as expected, prints "Example"
	obj:Print() -- works as expected, prints "Print"
	return obj
end 

and lastly, where I run into issue - another Script that invokes the BindableFunction, but has 0 access to the metatable/:Print() method of the object returned… -

local obj = BindableFunction:Invoke()
print(obj.Example) -- works as expected, prints "Example"
obj:Print() -- this will error "attempt to call method 'Print' (a nil value)

Can somebody please point out what I’m doing wrong? How come I can access the :Print method through the originating Script and not after it’s returned through a BindableFunction?

Thank you! :angry:

1 Like

BindableFunctions can’t pass back any sort of self-referential table, such as typical Lua OOP instances. You can get around this by changing your BindableFunction to another ModuleScript, as those have no similar restriction.

If you’re going to try to do it with remotes…Well, unfortunately there is no real way to handle that besides passing the properties down as a table and then rebinding those properties at the other end.

9 Likes

How would I go about changing the BindableFunction to utilize a ModuleScript instead? the Class script itself can only be required once, as it is the main controller for the system it is used for. (This is a Tick system, which contains multiple tables inside of the class module, which contain tables of each of the new objects, with functions to fire with specific data, etc…) Which is why I resorted to BindableFunctions in the first place.

For example.

local ServerTick = {}
	ServerTick.__index = ServerTick
	ServerTick.assigned_functions = {
		half_second = {},
		second = {},
		half_minute = {},
		minute = {},
		half_hour = {},
		hour = {}
	}

	function ServerTick:Disconnect()
		if self then
			-- remove self from assigned_functions
			for i=1, #ServerTick.assigned_functions[self.Unit] do -- loop thru the assigned functions to find self
				if ServerTick.assigned_functions[self.Unit][i] == self then
					table.remove(ServerTick.assigned_functions[self.Unit], i) -- remove connection from table.
				end
			end
			self = nil
		end
		return nil
	end

	function ServerTick.Connect(function_to_fire, on_unit, on_value, ...) -- ... are any arguments that need to be passed.
		if function_to_fire then
			local new_connection = {}
			setmetatable(new_connection, ServerTick)
			new_connection.Function = function_to_fire
			new_connection.Unit = on_unit or "second"
			new_connection.Value = on_value or 5 
			--new_connection.Test = ServerTick.Test -- I was able to get around this original issue by actually putting a reference to the metatable's method's that I needed to access via BindableFunctions inside of the new object itself, but I wanted to avoid that.
			
			table.insert(ServerTick.assigned_functions[on_unit], new_connection)
			return new_connection -- return the reference to the new connection, needed to later disconnect to the server tick
		end
	end

So, to preserve access to the ServerTick.assigned_functions table, I can only require the module once… Right? Or am I laying this out all wrong?

require only runs the code in a ModuleScript once per thread. That is, the server will only run a module script once and each client will only run a module script once.

What that means is that even if on the server you require this ServerTick ModuleScript five times, it will only declare ServerTick.assigned_functions once.

After that, each subsequent require just returns a cached value from the first time the ModuleScript was required.

2 Likes

Oh. I have been understanding ModuleScripts and require() wrong this whole time then -

I was under the impression that when using require(), you basically got a instantiated copy of that module’s table, and therefore anything you wrote to that module would not replicate over to any other scripts that are also requiring that script… I think I initially declared this upon testing, but I tested with a ModuleScript in ReplicatedStorage, requiring once from client and once from server, which would explain why I came to this understanding.

So… I CAN modify a modulescript’s data in one script and read it in another, and not have to worry about BindableFunctions at all?! JOY :joy:

Correct - only so long as the changes are read from the same thread (i.e. same server or client) as the changes were made in.

3 Likes

Thank you :slightly_smiling_face:

Another thing you can do is, instead of passing the table through the BindableFunction, you can pass a function that when called returns the table. This way you can pass the table with its reference to the metatable.

2 Likes