Side-note: Majority of the functions in your tutorial have a syntax error:
The parenthesis after end
are not supposed to be there because they don’t close anything
Side-note: Majority of the functions in your tutorial have a syntax error:
The parenthesis after end
are not supposed to be there because they don’t close anything
I understand your method is more efficient, but I’d say for most basic games like roleplaying and fps games it should suffice. My method’s advantage is that it’s really easy to set up and is beginner-friendly. But your method is definitely better for large and complex projects, and mine is probably not the best in that case.
Thanks for your tutorial it is good.
Agreeable. Just make sure to add a disclaimer about high memory on large projects.
It’s a way to pack an unknown amount of arguments into one parameter. You can get all of them in a table by doing local values = {...}
Example:
local function printArgs(...)
local words = {...}
for _, word in ipairs(words) do
print(word)
end
end
printArgs('one', 'two', 'three')
Isn’t this teaching bad habits? If you get used to doing it in an inefficient way it could be hard to re-adjust when you move on to larger projects.
It’s still better than not using any OOP method at all. Even though it’s slower than the other OOP method, it’s not noticeable for most use cases.
I still don’t understand the idea of using an inferior method simply because its slightly easier to understand. Creating a function inside an object wastes memory, you shouldn’t just throw it under the rug because “it’s not noticeable for most use cases”. Imagine hundreds of objects being created all with their own function that is identical to each others, it would definitely be noticeable.
I updated the post with a disclaimer about my method not being the fastest.
I’m just wondering, is anyone able to make my OOP method as optimized as @ffrostfall 's method while keeping it as simple as my method? I’d really appreciate it!!
Instead of doing this:
local Person = {}
function Person(name, age)
local self = {}
self.name = name
self.age = age
self.iq = 5
self.IncreaseIQ = function(amount)
self.iq += amount
end)
return self
end
return Person
Just do this:
local Person = {}
Person.__index = Person
function Person.new(name, age)
local self = setmetatable({}, Person)
self.name = name
self.age = age
self.iq = 5
return self
end
function Person:IncreaseIQ(amount)
self.iq += amount
end
return Person
That seems like a pretty good method. Can you explain what exactly you are doing on the second line? Thanks. Person.__index = Person
Person is a metatable of self, when an index isn’t found in self, the metamethod __index is invoked, if this is a function, it will be called with the arguments self and key, if it’s a table, it will be searched through instead for the index (a key)
local meta = {}
meta.__index = meta
local object = setmetatable({}, meta)
-- or
local meta = {}
local object = setmetatable({}, {__index = meta})
local meta = {}
local object = setmetatable({}, meta)
meta.__index = function(self, key)
print(self, "WAS INDEXED WITH THE KEY", key)
return math.random() -- will returns some pseudo random number
end
print(object.some_key)
A metatable is simply a table that “describes the behavior” of another table.
Useful resources:
https://www.lua.org/pil/13.html
https://developer.roblox.com/en-us/articles/Metatables
https://devforum.roblox.com/t/an-in-depth-look-at-oop-roblox-lua/1049827
I wrote multiple articles on OOP, not just this one. I suggest going through them in order, or you will be confused. I also advice not to use the OOP paradigm for everything, just the core game systems that logically should use objects. In the case of everything else, a modular design patter is more efficient.
I agree; OOP tends to be overused, sometimes a simple function will suffice or a different paradigm such as a data oriented design paradigm would be better.
This isn’t the case, Luau has introduced an optimization on which it ‘caches’ functions if they are the same, a function can look the same and not be the same, so it’s important to understand how that works.
If you take his implementation and get two objects and compare their functions together, it will, infact, return true.
Your ‘test code’ somewhat breaks this optimization, my guess is that it’s because the functions are declared inside a table construct, and not after a table is created.
For example, in this case, it does return true.
local function Class()
local self = {
_number = 0
}
function self:Add(num)
self._number += num
end
return self
end
print(
Class().Add == Class().Add
)
Something to note is that using this method does make the creating of the object a lot slower, and it does use some more memory, which is the memory needed to be allocated for the extra entries for these functions in the table, however, there are some benefits, and one of those is type satefy. It just works better with types, metatable OOP breaks with metatables from what I’ve experienced, and this method did not.
The tutorial isn’t that great, like, the example is ehh, using table.index = function()
is really ugly. Also, they could’ve declared the properties (not the methods) on the table construct it self, etc, but this method in its principle is fine. AFAIK, there’s not really big performance advantages to metatable OOP, at least since well, 2021. (I have not been active basically since then) Most optimizations have to do with indexing, and I believe it’s actually faster for Luau to find the function index this way, not sure.
Is there another way to create a function instead of doing table.index = function()
as you mentioned on your last paragraph? Thanks!
I’m aware, I just didn’t go into that because that wasn’t the point of the post. Functionally, that’s what the __index metamethod does, and that’s how oop works.
It was test code to show how it worked, it wasn’t meant to be used at all. Just to show how it worked.
Yeah! That would be:
function table.index(...)
return ...
end
It definitely didn’t sound like it, the entire post was directed at the fact those functions would be separate instances which isn’t the case;
I’m aware and it didn’t work as test code, it didn’t demonstrate how that would actually behave when someone were to use this method properly.
Oh yeah and another thing, the code shown by the OP actually does not work with function caching because the reference to self is the one created by the function so when the function is created, it’s actually different each time because it references a variable that only exists in the context of the object function being created.