Metatables: Most efficient way to gain values from separate metatables

Hi

Essentially just what is in the title, I have two separate metatables in different ModuleScripts and I want to get the values of certain variables from one in the other. The best way I can think of to do this is this:

local MetatableA = {}
MetatableA.__index = MetatableA 

local MetatableB = require(script.Parent)

function MetatableA.new()
	local self = setmetatable({}, MetatableB)
end

I ideally want to pass values from A to B in real time - i.e. a renderstepped value in MetatableB would be instantly available in MetatableA. I don’t think this method would do that, just copy the initial values from MetatableB to MetatableA and leave the two to act independantly. I don’t know if what I want is possible at all. If not I can put it all in one script, I just thought this might be a bit easier for organising things.

Any help is appreciated, I’m still learning how to (properly) use metatables so I may have missed something
Thanks

1 Like

If you want to get values of certain variables from one in the other then you should be using require() since you said these are ModuleScripts

Good point, I forgot to put this on that code (wrote this pretty quickly to post on the forum). I’ve edited this now.

Not sure if this is exactly what you want, but:

local MetatableA = {}
MetatableA.__index = MetatableA 

local MetatableB = require(script.Parent)

local function givemeta(recipient, giver)
	--give MetatableA MetatableB
	setmetatable(recipient, giver)
end
givemeta(MetatableA, MetatableB)

I don’t think you can use a .new() construction operator in functions.
Attempted to reference from Metatables, but the information was a bit confusing.

Ok, interesting. I’ll try that and see if it works. Also yeah, I agree that the Metatables page is confusing, main reason I’m asking here to see if anyone knows.

Although you can definitely use .new() in functions (roblox uses it in their own ControlModule) although it might be a metatable-only thing. I don’t know.

-- Module Script A
local ModA = {}

return ModA

-- Module Script B
local ModB = {}
local ModuleA = require(script.Parent)

setmetatable(ModB, {
	__newindex = function(self, index, value)
		rawset(self, index, value)
		ModuleA[index] = value
	end
})

return ModB

Is this what you mean?

Couldn’t you do this by making __index a function?

local metatableA = {}

local otherModules = {} -- your table of other modules

function metatableA:__index(key)
    for i,v in ipairs(otherModules) do
        local valueHere = rawget(v, key)
        if valueHere ~= nil then -- explicitly check that it doesn't equal nil as it will evaluate to false if valueHere is falsy
            return valueHere
        end
    end
end

function metatableA.new()
    local self = {}
    return setmetatable(self, metatableA)
end

-- now for example, if otherModules is like this:
otherModules = {
    { test = true },
    { cool = false }
}
local newMetatableA = metatableA.new()
local isCool = newMetatableA.cool 
print(isCool) --> false
3 Likes

That looks about right, probably, I’m not sure. I’ll try both this and @7z99’s solutions and see which works best… I have been busy today so haven’t had an opportunity to try yet but I’ll send something when I have (hopefully tomorrow)

2 Likes

It’s been 7 days (wow) since I posted this, sorry, but I think this is very close to what I want but not quite. Pretty sure this would pass values from A to B on server startup, but not constantly (which is what I want).

I’ll explain in what I’m actually doing rather than using ModuleA/ModuleB cause it’s more complex than that and I am doing things wrong still.

Structure of scripts (all are independent metatables):

  • PlayerModule
    • ControlModule
      • Movement
      • Crouching
      • Sprinting
    • KeybindModule
      • Keyboard
      • Gamepad

I’ve modified Roblox’s ControlScripts so if you know how those work it’ll make things easier to understand, but essentially I have a module called KeybindModule (acts as BaseCharacterController) and then a variety of different input modules (Keyboard, Gamepad, etc) which depending on various interactions with keys will make a value - example here is moveVector as this is what I’m trying to do first

Move Vector (its not all here, just the important parts)
function Keyboard:UpdateMovement(inputState)
	if inputState == Enum.UserInputState.Cancel then
		self.moveVector = ZERO_VECTOR_3
	else
		self.moveVector = Vector3.new(self.leftValue + self.rightValue, 0, self.fwdValue + self.backValue)
	end
end

function Keyboard:BindContextActions()
	
	--// Movement //--
	local handleMoveForward = function(actionName, inputState, inputObject)
		self.fwdValue = (inputState == Enum.UserInputState.Begin) and -1 or 0
		self:UpdateMovement(inputState)
		return Enum.ContextActionResult.Pass
	end

	local handleMoveBackward = function(actionName, inputState, inputObject)
		self.backValue = (inputState == Enum.UserInputState.Begin) and 1 or 0
		self:UpdateMovement(inputState)
		return Enum.ContextActionResult.Pass
	end

	local handleMoveLeft = function(actionName, inputState, inputObject)
		self.leftValue = (inputState == Enum.UserInputState.Begin) and -1 or 0
		self:UpdateMovement(inputState)
		return Enum.ContextActionResult.Pass
	end

	local handleMoveRight = function(actionName, inputState, inputObject)
		self.rightValue = (inputState == Enum.UserInputState.Begin) and 1 or 0
		self:UpdateMovement(inputState)
		return Enum.ContextActionResult.Pass
	end

The ControlModule is there to switch between input modules depending on what is being used by the player, then do some stuff to actually make the character move through the values communicated to it in the input modules.

What I want to do is pass these values, in real time every frame, to different modules which handle different actions. Why, you may ask? It’s fairly simple: it makes things much easier to understand for me and without it I’d have a script which is about 2000 lines long, which makes everything very hard to find.

Once again, I accept this may not be possible and is not essential for me to make what I want but god would it make things much easier for me to bugfix and actually understand what I’ve done in the future when everything inevitably breaks.

Thanks for your help so far, sorry if you don’t get it, I’m not very good at explaining stuff.

1 Like

So just to clarify, you’re essentially looking to get keybinds from KeybindModule in ControlModule or similar?

Could you do something like having like a “global” __index function or something so whenever any of the modules are indexed with a nonexistent index, it checks points to the topmost module for this index? For example if you do self.KeybindModule.Keyboard from the sprinting module it would point to KeybindModule.Keyboard. Thinking you can either do a third module to contain only the metamethods, or you can use module injection but it’s generally going to achieve the same thing:

PlayerModule:

local playerModule = {}
function playerModule:__index(key) -- need to use an index function here because it would repeatedly point back to playerModule instead of the table being indexed which is probably not what you want
    return self[key]
end
-- could and probably should use a loop here
playerModule.ActiveControlModule = require(script:WaitForChild('ControlModule'))
playerModule.ActiveKeybindModule = require(script:WaitForChild('KeybindModule'))
playerModule.ActiveControlModule._parent = playerModule
playerModule.ActiveControlModule:Init()
playerModule.ActiveKeybindModule._parent = playerModule
playerModule.ActiveKeybindModule:Init()

return setmetatable(playerModule, playerModule)

Inside the Keybind/Control modules:

local module = {}

for i,v in ipairs(script:GetChildren()) do
    local requiredModule = require(v)
    module[v.Name] = requiredModule
    requiredModule._parent = module
    requiredModule:Init()
end

function module:Init()
    setmetatable(module, self._parent)
end

return module

Then finally in each of the lowest level module

local module = {}

function module:Init()
    setmetatable(module, self._parent._parent)
end

return module

This should allow you to have a sort of hierarchy system with a topmost “global” for lack of a better word, which you can access with self.ActiveKeybindModule/self.ActiveControlModule. You can also access the “parent” module with self._parent. You need an initalize function though, as without it _parent will be nil at the time of the first require.

1 Like

Not quite.
KeybindModule is just a template with a load of variables that all input modules use (input modules being keyboard, or controller, or touchscreen controls). Input modules then vary this - yes it may seem stupid but it’s literally what Roblox does, just with keybind customisation instead of fixed keybinds.

ControlModule, by default, serves two purposes: determining which controller is being used and therefore which keybinds to listen out for, and actually performing the actions once the correct keys are pressed.

As an example, lets say I’m using a keyboard and I press space, which is bound to the jump action. This is interpreted in the Keyboard metatable and a variable called isJumping changes to true. This is picked up by ControlModule (which checks to see if the player requests to jump on the Active Controller every frame) and then tells the player’s Humanoid to jump.

What I want to do is let the ControlModule determine what type of controller is being used, and then when a request to do an action is sent to ControlModule, instead of acting on it itself, it gets passed on to a different metatable in a different script.

I think what you have suggested probably would work anyway however I’m going to try tomorrow as it’s getting late for me. Thanks very much though!

2 Likes