That appeared to do nothing.
What about if you change
Class.__index = function(self, Index)
return self.BackingTable[Index]
end
to
Class.__index = function(self, Index)
return self.BackingTable[Index] and self.BackingTable[Index] or Class
end
Still nil.
Did a test and removed Class.__index = blah from the .new function and calling either PropaneTank:Destroy() or self:Destroy() would work, so its something going on in with that __index. ā¦Is it overwriting the Tool metatableās method? or something?
@magnalite - Iāve gone ahead and fixed the formatting of your post to work with Discourse I kept the same formatting and content, just made it work with Discourseās formatting which doesnāt support [center] BBCode. Iāve also moved it to the Tutorials section. Thank you for this wonderful tutorial!
@Lilly_S Thanks! Much appreciated
The way .__index works is it is called when an index does not exist. In this case when you create an object you create a new empty table with data about the objects in. It has none of the methods in it. To allow your object to have the appropriate methods called on it a metatable with its .__index pointing to the class table is assigned to the table with the objects data in it. When .__index returns a table lua looks into that table to see if the index exists in that too. So what actually happens when you call a method on an object is this.
Object:DoSomething()
Object[āDoSomethingā] is nil, does its metatable have an .__index set?
It does, .__index returned SomeClass.
Does SomeClass[āDoSomethingā] exist?
It does! call it!
This allows you to assign the methods without duplicating them into every single object you make.
In your case you are making .__index return a value from BackingTable and so it never has a chance to see if the method exists in the Class table.
Iām not sure why my previous suggestion wouldnāt work though
Probably because there was no Class[Index], just Class.
And by doing;
Class.__index = function(self, Index)
return self.BackingTable[Index] or Class[Index]
end
I was able to get the :Destroy() function to work. ^.^ Thanks!
@Lilly_S Thanks for updating and moving the thread!
^-^ Btw I find it helps if I keep my classes in separate module scripts and require them when I need them.
Thanks for this tutorial! I am trying to learn OOP and I have a quick question for anyone who can answer it.
I have an object that has events connected to it (and some values like Power, Agility, etc.)
Is this all that is necessary for a :Delete() function?
function Thing:Delete()
for i,v in pairs(self) do
if tostring(v) == "Connection" then
v:disconnect()
end
end
self = nil
end
Or do I also have to iterate through all the values and set them to nil, like self.Power = nil
, or is just setting self = nil
fine?
This is incredibly helpful! Iāve always been vaguely aware of OOP and have used objects once or twice, but never really understood how it worked or how to properly utilize it. This cleared up a lot of questions!
No, that is sufficient. It will GC (garbage collect) the members when all references are lost to them (and if all references are lost to the object, youāre good to go!)
I like to call setmetatable(self, nil
) on my code too. Itās a good defensive programming practice, as it means dead objects donāt have methods to call.
Youāll notice the snippet you used is going to be a pain to type out. My solution is to use Maid. In my deconstructors Iāll just deconstruct Maid which cleans up my signals. Hereās an example:
function CameraControls:Disable()
if self.Enabled then
self.Enabled = false
self.Maid:DoCleaning()
self.Maid = nil
self.LastMousePosition = nil
UserInputService.MouseBehavior = "Default"
end
end
-snip-
i have found a solution to my question
Would you recommend having multiple Module Scripts for different things?
So thereās gonna be a lot of functions for weapons in my game; same for AI. Would it be worth having two Module scripts. One called Weapon and one called AI? Or should I just use one?
Only advantage I can think of for using 2 is organisation; but I think thatās important ( to me atleast! ).
You should split the code up as much as reasonably possible. Each class (module) should also try to keep to itself as much as possible (ie not directly editing objects from other classes and be as self containing as possible).
This idea is known as āencapsulationā which is one of the āthree pillars of oopā.
For those looking for an easy way to implement replication triggered by setting object properties, here is a sample of a simple way to do this:
Car = {}
function Car.new(position, driver, model)
local newcar = {}
newcar.Speed = 0
local mt = {}
function mt.__index(self, index)
return Car[index] or newcar[index]
end
function mt.__newindex(self, index, value)
self:Replicate(index)
newcar[index] = value
end
local proxy = {}
setmetatable(proxy, mt)
return proxy
end
function Car:Replicate(property)
--RemoteEvent:FireAllClients()
end
function Car:Boost()
self.Speed = self.Speed + 5
end
I know nothing about efficiency, so I couldnāt tell you if this is fast or not, but it seems to do the job.
The key difference with this vs. the method outlined in the OP is that custom metamethods are defined for each object. These metamethods are called whenever someone reads or writes a value in the proxy
table. The table is nominally empty because any reads are caught by __index
and sent to Car
or newcar
and any writes are sent to newcar
. The __index
method is technically backwards from luaās implementation ā by default if the key trying to be access in the table is nil
it falls back to the table. It means you canāt overwrite anything defined in Car
in the instance, but it seems like a questionable practice to do this regardless.
Know this is old, but looking for some advice.
So I have my own object, called machine. Things can transfer resources into a machine. So it makes sense to create a method
machine:Transfer(resource)
However there are many types of machine, and so machine:Transfer() has different functionality depending on the machine type. Similar situations arise with other methods for the machine. What is the best way to handle this?
Currently I have a table of machine types, that looks something like this
table[Id] = {Name = āBlablablaā, TransferFunction = AnotherTable.Machine1TransferFunction}
So the machine object looks like this
function machine:Transfer(resource)
table[self.Id].TransferFunction(self,resource)
end
But this certainly feels wrong.
You should be able to just override Transfer in your extended class, no?
Sorry, but I have no idea what that means. I think Iāve still got a lot of OOP to learn!
assuming machine
is the superclass, you should have an extended class (sub-class) for each type of machine you have, that all inherit from the superclassā¦ and in those sub-classes, you should be able to override the behavior or Transfer
.
As in, if your original class is Machine, and you extend it with CoolMachine, and you want to change the behavior of :Transfer, you should be able to justā¦
function CoolMachine:Transfer(resource)
--code
end
And itāll override the default Machine:Transfer() behavior.
Ah, I get it all now, thanks!
ā¦jeeze how did I go so long without knowing this stuff?