Using OOP for game, need help with a few things

Hello there,
My game relies on OOP to be fully functional. That being said, I had a few questions about OOP.


QUESTION 1 - METATABLES OR FUNCTION CONTROL FLOW?

TL;DR:

Is it better to use metatables over casually inserting functions into the object table?

Unfold me for details & examples

I wanted to know which technique was better than the other:

Technique 1 - Metatables

local Class = {}
Class.__index = Class

function Class.new()
  return setmetatable({}, {__index = Class})
end

function Class:DoSomething()
  --code
end

(inheritance):

local Subclass = setmetatable({}, {__index = Class})
Subclass.__index = Subclass

function Subclass.new()
  return setmetatable({}, {__index = Class})
end

function Subclass:DoSomething2()
  --code
end

function Subclass:DoSomething()
  Class.DoSometing(self)
  --code
end

Technique 2 - Function control flow

local Class = {}

--In case of concrete class
function Class.new()
  local new = {}
  new.DoSomething = Class.DoSomething

  return new
end

--In case of abstract class
function Class.inherits(new)
  new.DoSometing = DoSomething
end

--Static
function Class.DoSomething(self)
  --code
end

(inheritance):

local Subclass = {}

--In case of concrete
function Subclass.new()
  local new = {} --Or if concrete superclass, then initializes as Class.new()
  Class.inherits() --In case of abstract superclass only

  new.DoSomething2 = DoSomething2
  new.DoSomething = DoSomething

  return new
end

function DoSomething2(self)
  --code
end

function DoSomething(self)
  Class.DoSomething(self)
  --code
end

Any difference in performance? One uses metatables, and the other controls which function enters the table.


QUESTION 2 - WRAP THE CUSTOMIZED OBJECT ON A ROBLOX INSTANCE

Ok so, it’s farily simple. My classes are wrapping already-existing instances. I wanted to know how to actually BIND the table to the instance. The current setup I have is that I store objects in a table in this way:

type Object = {
  Instance: Instance
}

type StoreTable = {
  [Instance]: Object
}

(The Instance field of the object is equal the Instance key in the storage table. I bind events so that whenever the instance is destroyed, memory is freed obviously.)


QUESTION 3 - WOULD YOU REPLICATE YOUR OBJECT MOBULES TO CLIENTS

I am honestly just afraid that people try to steal my code, it would be so much better to actually replicate everything to make animations, … What do you think about that?


Thank you for all this, please answer EACH question (even with “idk”), much love,
@Varonex_0

  1. Using a metatable is probably more effective and easier than re-assigning the function because when Lua doesn’t find the function it just goes “oh no!” and searches the metatable instead. This means by using a metatable, instead of having two references to the memory location of the function, you only have one reference. It also makes your code look better.

  2. What do you mean “actually BIND the table to the instance”? Do you mean like setting it as a property of the instance? It will instantly get removed if you try to do that. I think your current setup of instance-object storage is the best method.

  3. It depends on your situation. Does the client need the modules? Do you have any way of sending a shorter module with all the necessary info? Is it appropriate for RemoteFunctions, where you can regulate half way with a callback?

The first question I have, is what sort of game must be object orientated like you claim. This seems like a design failure if it’s the case.

The question as to what method would be proper as you outline, “metatables vs functional control”, it would be metatables, that is assuming you’re looking for an object orientated structure that closely matches other languages. Furthermore, using metatables will always trump defining functions in an object structure when it comes to memory usage.

If you’re looking to “bind” a table to a ROBLOX Instance, then you can manipulate the index, so it points to either the instance or the class.

local class = {}
local instance = ...

local object = setmetatable({}, {
   __index = function(self, index)
      if class[index] then return class[index] end
      return instance[index] -- Will error if property not indexable/exist
   end
})

But I stress, the code snippet above isn’t a very good habit to get in too. Defining the object within the class as a property/pointer to your instance is the best approach.

If there is one thing you take away from this, please let it be this!
Lua is not a language in which is meant to support classes, metatables are not classes, they are hacky ways to replicate class behavior. You should really be sticking to functional programming, for both the eligibility of your code base. I’m curious as to why you think you must use object orientated. Take this from someone who has made some of the most complicated and feature packed replication frameworks you’ll ever see. It was not worth it.

Hello, thanks for your answer !

  1. I was generally worried about the lookup time. With the inheritance setup I have seen online, I would say the VM would have to lookup in metatables of metatables. That way, if you have 6 classes that linearily inherit from each other, you’d have a first lookup, then a 2nd one, 3rd, 4th, 5th, 6th, and maybe a 7th if the method doesn’t exist (which I sometimes use to check if a setter exists before invoking it). I did not understand the blurred part. Do you mean I can do setmetatable(k, v), with k, v being two regular tables (v does not point to a table at its __index key)?

  2. It would be lovely to actually have a way to embed the object table to the instance it is intended to work with. I find it painful to reference everything in a 3rd party table, as I generally get the instance itself (contextwise) than the custom object. That means I also have to lookup each time, which is certainly not in O(1) time complexity.

  3. I guess it could? I sacrificed a few things because of the non-replication, and I also allowed myself some others (instance serialization that I would like to not replicate). I guess I could bake something a bit weird to actually solve the problem, but it would most likely be some sort of trick to make things work.

Hello, thanks for the answer !

My project revolves around creation. It is a virtual environment that allows users to create and share their own creation. It will eventually feature complex elements that will allow them to make contraptions and design their own little games. That being said, I decided to use OOP as the programming paradigm, as most game engines use it (like ROBLOX), and it tends to work really well.

  1. Sure, metatables are more readable over time. I am also looking for a solution that is efficient during runtime.

  2. My explanation may have been wrong. I have already considered what you are providing, and it is just a hacky way to access properties in my opinion. Wrapping this in a pcall would be actually safer too.

What I am looking for is a technique to reference the table in the instance. That being said, I have never seen such people during my development time. It would be a really great thing to implement in my opinion.

yeah thats my bad. Edited out of main message. Annoyed at the person who told me that >:(.


you can set a collective __index to stop this if you need.

local function updateMetatable<Obj, Class>(Object: Obj, NewClass: Class): Obj & Class
    local current = getmetatable(Object)
    local newMeta = {Object, NewClass}

    setmetatable(Object, {__index = function(self, index)
        for _, metatable in next, newMeta, nil do
            if metatable[index] then
                return metatable[index]
            end
        end
    end})
end
  1. sorry if i still don’t quite understand, but you want to reference from the instance your own behaviour? An incredibly inefficient way (not to mention a painful structure to look at and implement) that might work is having an ObjectValue pointing to a module, or a module itself, as a child of the instance you can then require for your methods. A third party table might be your best option.

  2. Limit the information you give to the client to only what is necessary. A quick trick to stop less experienced exploiters from accessing it is by returning different things based on a RunService:IsClient() check, but more experienced exploiters could just get around that.

Wow, such a quicky answer thank you !

  1. It’s fine :smile:, and I see what you’ve done here, maybe it would be a more consistent way of doing it, I’ll try this out thank you :pray:t2:

  2. I have done some Unity programming if you want, I just find it depressing to not being able to add customized methods. Attributes are nice, and they reflect this extention idea, even though they completely screwed up while making these because you still have to stick with ObjectValues… And yes, I’d rather keep my current setup than having n modules for n instances.

  3. I think I will stick to this too. I may adjust everything later on. I thought of the use of context, it still is a problem because it indeed does not prevent exploiters to read through all the code and figure things out. That’s what I absolutely want to avoid if you want.

1 Like