Inheriting in a pseudo-class-based structure

In my game I’m trying to create a series of systems based around a pseudo-class-based structure – metatables with .__index and whatnot to create a system that acts like it’s class-based. I don’t think it’s too uncommon?
Regardless, at the moment I have the following structure. Everything but ServerEntities is a modulescript:
ServerEntities (regular script that tracks entities and passes remote/bindable events to them)

BaseEntity (handles health and general tracking)
StorageEntity (can store items and move them between itself and other inventories)
ProductionEntity (can make items out of ingredients inside itself)

The basis of the pseudo-class system is generally fine and working. What I’m having trouble with is inheritance. I cannot seem to get ProductionEntity to properly inherit from StorageEntity.
This is the stuff in ProductionEntity that deals with inheritance:

local BaseProducer = {}
BaseProducer.__index = BaseProducer
local StorageEntity = require(script.Parent)

function BaseProducer:New(model, ID, ...)

local BlankTable = StorageEntity:New(model, ID, ...)
local NewProducer = setmetatable(BlankTable, BaseProducer)
	
NewProducer.__index = function(t, key)
return NewProducer[key]
end

StorageEntity & BaseEntity are set up in the exact same way. Eventually the function returns NewProducer after doing some Producer-specific things that work fine. But the function cannot call any methods from StorageEntity. It retains values set to the table in the :New() function of StorageEntity, but attempts to call methods, like this one in StorageEntity:
function BaseStore:AddListener(player) table.insert(self.Listeners, player) end

…just end up with that function not existing. I call the functions of entities with this code in ServerEntities (so I don’t have to hardcode in each Remote/Bindable Event):

SpecificEntity[entityFunction](SpecificEntity, ...)

SpecificEntity is the entity object, which is certain. entityFunction is the function I want to call, like AddListener, SpecificEntity is provided for the self variable to work, and … is self explanatory.

I don’t suppose anyone has any ideas as to why I can’t get this working? I’ve tried tinkering with the code a lot and asking around my coder friends/acquaintances, but I just can’t seem to get it to function right.

2 Likes

One simple fix for this would be to just clone the keys from the StorageEntity to the ProductionEntity. It seems like you wouldn’t want to use new since new sort of initializes (by the looks of it). You could simply require the StorageEntity and then clone the keys over to the ProductionEntity. It may not be as elegant I guess? I think it would work better in any case though.

1 Like

You’re missing just one line of code - you need to assign the metatable of your subclass to the class it is inhereted from;

setmetatable(BaseProducer, StorageEntity)

You would do this after the line where you require the StorageEntity class (i.e. line 4)

Also, is there any reason for the ‘__index’ function at the end of your class constructor? (this bit)

NewProducer.__index = function(t, key)
    return NewProducer[key]
end

As your NewProducter array isn’t itself a metatable, this isn’t going to do anything (other than act like any other callable function)

There’s a couple of good guides on OOP both here on the dev forums and on the wiki if you want some reference material

2 Likes

That worked! Thanks so much.
And honestly, I’m winging it a bit here with the class-based structure. I generally like to think of myself as a “not-completely-incompetent” scripter, but with this a lot of it is frankensteined from the wiki and help from other coders, so some stuff is probably irrelevant or not needed. I’ll take a look at that index function to see if it’s really not needed.

I also missed the devforum guide, so I’ll definitely be giving that a read; thanks again!

2 Likes

No problem. If you mark that reply as the solution it’ll let others know your problem has been solved :slight_smile:

The setmetatable function returns the first argument that is passed into it. In your case, when you call…

local NewProducer = setmetatable(BlankTable, BaseProducer)

…you’re assigning NewProducer to BlankTable (as this is the first argument). This means you have your table stored under two variables.

I’ve simplified your code down to the main functionality so you can compare;

local StorageEntity = {}
StorageEntity.__index = StorageEntity

function StorageEntity:InheretedPrint()
	print("hello inhereted world!")
end

function StorageEntity:New(model, ID, ...)
	
	local BlankTable = {}
	setmetatable(BlankTable, StorageEntity)
	
	return BlankTable
end

---------------------------------------------------

local BaseProducer = {}
BaseProducer.__index = BaseProducer

setmetatable(BaseProducer, StorageEntity)

function BaseProducer:Print()
	print("hello world!")
end

function BaseProducer:New(model, ID, ...)
	
	local BlankTable = StorageEntity:New(model, ID, ...)
	setmetatable(BlankTable, BaseProducer)
	
	return BlankTable
end

---------------------------------------------------

baseObject = BaseProducer:New()
baseObject:Print()
baseObject:InheretedPrint()
1 Like