How would tables in a meta table access other tables in a meta table...?

I mainly use OOP for the majority of my systems, though I am a bit stumped on something. I’m currently making an entity system for a project of mine and I wanna be able to handle everything in a single entity, which OOP has made easy for me. But I have found a problem.

	self._handlers = {
	Actions = require(script.Handlers:FindFirstChild("Actions")).new(self),
	States = require(script.Handlers:FindFirstChild("States")).new(self),
	Attributes = require(script.Handlers:FindFirstChild("Attributes")).new(self),
	Weapon = require(script.Handlers:FindFirstChild("Weapon")).new(self),
	}

Actions would need access States, but even when I pass the self table through, it says that States doesn’t exist.

I’ve tried to set the other tables metatable to the metatable of the table it’s in…?

i dunno im so lost on this metatable topic dawgie

How would I go about connecting the handlers to the parent table so the other handlers can access them too.

What you’re showing should work. What’s inside Action’s .new()?

1 Like
  • Pass the parent entity (which contains your self._handlers) to each handler upon creation.
  • Within handlers, access other handlers via the parent, e.g., self.parent._handlers["States"].
1 Like
local ActionHandler = {}
ActionHandler.__index = ActionHandler

type MoveDataType = {
	Context : Context,
	MoveType : MoveType,
	Moveset : string,
	Function: Function,
}
function ActionHandler.new(mainData: {})
	local self = setmetatable({}, ActionHandler)
	
	self.Actions = {}
	self.Connections = {}
	
	self.Character = mainData.Character
	self._Signal = mainData.Signal
	
	print(mainData)
	return self
end

function ActionHandler:Add(Name, ActionInfo: MoveDataType, Time : number, Data: {}, CheckInfo: {}, Info: {})
	
	assert(ActionInfo, "ActionHandler: No MoveInfo supplied on this Move.")
	
	self.Actions[Name] = {
		["Type"] = ActionInfo.Context,
		["Style"] = ActionInfo.MoveType,
		["Moveset"] = ActionInfo.MoveSet,
		["Function"] = ActionInfo.Function,
		
		["Timer"] = nil,
		["Time"] = Time,
		
		["CheckInfo"] = CheckInfo,
		
		["Info"] = Info,
		
		["Cancel"] = Data.Cancel,
		
		["Run"] = Data.Run,
	
		["Connection"] = nil
	}
	
	self.Actions[Name].Cancel = Data.Cancel
	self.Actions[Name].Run = Data.Run
	
	self:Run(Name)
end

function ActionHandler:Run(Name)	
	if not self.Actions[Name] then
		warn("Action doesn't exist, add it first.")
		return;
	end
	
	if self.Actions[Name].Connection then
		warn("Action already began running.")
		return;
	end
	
	if self.Actions[Name] then
		self.Actions[Name].Run(self.Character, self)
	end
	
	if self.Actions[Name].Time == nil or self.Actions[Name].Time == 0 then
		return;
	end
	
	self.Actions[Name].Timer = TimerModule.new(Name, self.Time, function()
		warn("Timer time is official over.")
		
		self:Cancel(Name)
	end)
	
	local selectedAction = self:GetAction(Name)
	
	selectedAction.Connection = RunService.Heartbeat:Connect(function()
		
	end)
end

function ActionHandler:GetAction(Name)
	if self.Actions[Name] then
		return self.Actions[Name]
	end
end

function ActionHandler:Cancel(Name, stunValue)
	if self.Actions[Name] then
		self.Actions[Name].Cancel(self.Character, self)
		self.Actions[Name].Connection:Disconnect()
	end
end

function ActionHandler:RunAllActions()
	for i,v in pairs(self.Actions) do
		self:Run(i)
	end
end

function ActionHandler:DestroyAction(Name)
	if self.Actions[Name] then
		self:Cancel(Name)
		self.Actions[Name] = nil
	end
end

function ActionHandler:StopAllActions()
	for i,v in pairs(self.Actions) do
		self:Cancel(i)
	end
end

function ActionHandler:DestroyAllActions()
	for i,v in pairs(self.Actions) do
		self:DestroyAction(i)
	end
end

function ActionHandler:CustomActionHandler(s: "Cancel" | "Destroy", Type: "Context" | "MoveType" | "MoveSet" | "Function" | {}, Value)
	if typeof(s) == "string" then
	
	for i,v in pairs(self.Actions) do
		if s == "Cancel" then
			if v[Type] == Value then
				self:Cancel(i)
			end
		elseif s == "Destroy" then
			if v[Type] == Value then
				self:DestroyAction(i)
			end
		end
	end
	
	else
		
	for _,T in pairs(Type) do
		for ind,val in pairs(self.Actions) do
			if s == "Cancel" then
				if val[T] == Value then
					self:Cancel(ind)
				end
			elseif s == "Destroy" then
				if val[T] == Value then
					self:DestroyAction(ind)
				end
			end
		end
	end	
	end
end

return ActionHandler

it only gives me what was stated above it, for example:

	local self = setmetatable(framework, {})

	self.Signal = GoodSignal.new()
	self.Character = Character
	
	self._handlers = {
	Actions = require(script.Handlers:FindFirstChild("Actions")).new(self),
	States = require(script.Handlers:FindFirstChild("States")).new(self),
	Attributes = require(script.Handlers:FindFirstChild("Attributes")).new(self),
	Weapon = require(script.Handlers:FindFirstChild("Weapon")).new(self),
	}

if I pass self through to Actions it’ll only give me signal and character, not states, attributes, or weapon

That means you should still be able to handle accessing self._handlers.Actions?

I was able to do something similar in my own character controller module which looked like this

-Base class
function PhysicsCharacterController:GetComponent(nameOfComponentModule)
    return self._MovementComponents[nameOfComponentModule]
end

function Running.new(data : PhysicsCharacterController)
    local self = setmetatable({}, Running)

    local model = data._Model

function PhysicsCharacterController:AddComponent(componentModuleOrString : ModuleScript?)
    local componentModule : ModuleScript
    if typeof(componentModuleOrString) == "string" then
        componentModule = script:FindFirstChild(componentModuleOrString, true)
        assert(componentModule, "Component module cannot be found! Please check Components folder")
    end
    if typeof(componentModuleOrString) == "Instance" then
        componentModule = componentModuleOrString
    end

    local componentInitializer = require(componentModule)

    local component = componentInitializer.new(self)
    component.Name = componentModule.Name
    component.ShouldUpdate = true

    self._MovementComponents[componentModule.Name] = component

end

1 Like

You’re trying to access elements from a table while the elements are being created. This is essentially what you’re doing:

-- Both will print an empty table
local function A(Input)
	print("A", Input)
	return 1
end

local function B(Input)
	print("B", Input)
	return 2
end

local Table = {}

Table.Stuff = {
	MyA = A(Table),
	MyB = B(Table)
}

You will need to add your elements manually in the order they depend on each other for them to work.

self._handlers = {}
-- States comes first
self._handlers.States = require(script.Handlers:FindFirstChild("States")).new(self)
-- Actions needs States
self._handlers.Actions = require(script.Handlers:FindFirstChild("Actions")).new(self)
-- etc.
1 Like

Not to look selfish and not insult goodsignal but its kinda outdated now.
If you are interested you could use NormalSignal made by me instead, which is a fork of another signal module (don’t look this rabbit hole or you will regret)

No disrespect to GoodSignal once again it gave good inspiration and were first to start signal modules.

So to clear some confusion as to how metatable OOP (the one you are using) works:

You have a table module that contains a bunch of values (often function but don’t have to be only them specifically) and it has __index (which is a metatable) Metatables | Documentation - Roblox Creator Hub set to itself
For now it doesn’t mean anything to us really:

Now we have a function .new in this module (table) and when we call it, it creates this table,assigns metatable “module” to it and returns that.

setmetatable() ALSO returns the same exact table we passed in first argument so we are not getting any “new” table or something :sweat_smile:

What heppens behind the scenes?
When we index value in our newly created class and it is not here then it looks up for its metatable and finds value in metatable with the same key
In your example lets say you index:

local module = {}
module.__index=module

function module.new()
return setmetatable({},module)
end
function module:Run()


end

local hi = module.new()

hi:Run()
-- checks does hi has value "Run"?
-- nope, hi doesn't have value "Run":
-- searching value "Run" in hi's metatable (module)!
-- Found!
-- returns module.Run

Tip:

if you are not using any methods from class inside the constructor would be more optimized
As since table without metatable .index doesn’t have overhead for indexing anything and it does gain metatable in the end of constructor only as you may see!

function module.new()
local self = {}

return setmetatable(self,module)
end

Also function module.new is also kinda stuipid because you can instead do:

local function NewClass()
return setmetatable({},module)
end



local hi = NewClass()

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.