Sharing values between metatables

Hi

I’ve made a post about this before but I think it’s easier to make a new one to not confuse things further.

I have two metatables, for simplicity we’ll call them A and B. There are variables in A which I want to use in B. How would I get said variables from A into B so that they update every single frame (using RenderStepped, preferably). I am aware of rawget/rawset but I dont know if that works every frame - please clarify!

To complicate the matter, B must be required from A to hopefully avoid repeat scripts - I plan to add more metatables which need values from A (I would just require A in B and then pull variables out that way, but I can’t sadly because of cyclic dependency)

Thanks

1 Like

I believe it would be simpler to do something like:

local a = {
    x = 0,
    y = 0,
}
local b = {
    z = 0
}
setmetatable(b, {
    __index = a
})
print(b.x) --// prints 0

Make two separate tables A and B, then connect them using a metatable whose __index metamethod is just the table A. B would be able to index values inside A, but A would not be able to index values inside B. When a value in A is updated, it should be updated in B as well via the created metatable.

That would work in some situations, but once again the problem of cyclic dependency…

These two metatables are in separate modulescripts (for organization purposes). A is the “instigator” - it requires B so that B can run in the first place. B needs to get values from A, but can’t set A as it’s __index since it can’t require A or the two modules would require each other, i.e. cyclic dependency which luau hates.

It might be possible to have B set A as it’s __index in the ModuleScript for A while all of the code for B runs in the ModuleScript for B. I don’t know.

Then just remove the cyclic dependency. There should be a way to do the same thing without having to require A on module B or B on module A. In general, cyclic dependencies are bad in OOP and should be avoided.

Not sure if this is what your looking for, but it could be done like this?

-- Module A
local A = {}
A.__index = A

-- Constructor
function A.new(modules: Table)
   local self = setmetatable({}, A)
   self._Modules = modules
   self.Name = "Module A"
   return self
end

function A:HelloFromB()
   print("B said Hello")
end

return A
-- Module B
local B = {}
B.__index = B

-- Constructor
function B.new(modules: Table)
   local self = setmetatable({}, B)
   self._Modules = modules
   self._Modules["A"]:HelloFromB()
   print(self._Modules["A"].Name) -- Should print "Module A"
   return self
end

return B

Now that you have the modules script you would have one main script like so:

-- Main script

local Modules = {}
for _, module in pairs(Modules.Location:GetChildren()) do
   if module and module:IsA("Module") and not Modules[module.Name] then
      Modules[module.Name] = require(module).new(Modules)
   end
end

Of course, you could make your own module system similar to the main script but add more methods such as :GetModule(), and what that would do is return the module from the modules table. Of course, since this isn’t needed but can be done to keep things clean, Hope this helps!

If you set __index to a function, you can use rawget(tbl, key) on the other metatable to avoid cyclicity. that’s a fun word

Would a third ModuleScript that requires both ModuleScripts suffice?

Yes I will probably have to do that, unfortunately. I didn’t want to for organization reasons, but that seems to be the only solution.