What does "return setmetatable({}, metatable)" exactly do?

Heya There!!

I’m rewriting an entity system for my game using composition. Though, as I’m watching a video on how to add composition, he uses return setmetatable({}, metatable). I’m wondering how that works and how I can use that to make OOP classes or whatnot.

metatables can add metamethods to tables
roblox docs explain it better here

for OOP the most common approach is doing something like this:

local Class = {}
Class.__index = Class -- the index metamethod makes it so if you try to index a member of a table that isn't inside the table, it either searches a table (what this does) or does a function

function Class.new(x, y)
    local self = {}
    
    self.x = x
    self.y = y

    return setmetatable(self, Class) -- sets metatable to Class so that it has the __index metamethod
end

function Class:method() -- this syntax makes it so it automatically passes `self` as the first arg, nothing else
    print(self.x, self.y)
end
2 Likes

Alrighty then, but how would I go by using such to make an entity system? (You can provide me a simple version I can go off with)

local Entity = {}
Entity.__index = Entity

function Entity.new(stats)
    local self = {}

    self.speed = stats.speed
    self.damage = stats.damage
    self.hp = stats.hp

    self.state = "idle"

    return setmetatable(self, Entity)
end

function Entity:takeDamage(damage)
    self.hp -= damage
end

-- you can add more methods here like update cycles if that's what you want your entity to have

return Entity

of course this is a very oversimplified and generalized example

then you could do

local Entity = require(path.to.entity.module)

local zombie = Entity.new({speed = 1, damage = 1, hp = 100})
zombie:takeDamage(1)
print(zombie.hp) --> 99 (hopefully)
also i just realized you specifically meant composition in the original post so here's how composition works i guess

composition is when you have different reusable “components” for different parts of code

for example: a Car could have an Engine (separate component/object) and could have Wheels (another separate object). the car object itself wouldn’t be the one directly handling engine rpm or wheel rotation. the respective components would be doing that work

here’s a small example i guess:

-- Wheels

local Wheels = {}
Wheels.__index = Wheels

function Wheels.new()
    local self = {}
    -- some properties here
    return setmetatable(self, Wheels)
end

-- Engine

local Engine = {}
Engine.__index = Engine

function Engine.new()
    local self = {}
    -- some other properties here
    return setmetatable(self, Engine)
end

-- Car

local Wheels = require(path.to.wheels.component)
local Engine = require(path.to.engine.component)

local Car = {}
Car.__index = Car

function Car.new()
    local self = {}

    self.wheels = Wheels.new() -- new wheels component
    self.engine = Engine.new() -- new engine component

    return setmetatable(self, Car)
end

this is useful because it makes it really easy to reuse components for different things, like you could probably reuse wheels component for a Bike class, etc.

also the difference between composition and inheritance (the other example with entities i provided) is that composition is a “has-a” (a Car has an Engine) relationship whereas inheritance is a “is-a” relationship (a Truck is a Car)

if anything here is wrong then feel free to correct me

return setmetatable({}, metatable) is the most efficient memory-wise approach one can take, ~0.046KB (newproxy(true) is at least ~0.062KB, unless newproxy(meta) gets supported, then it would be the cheapest at ~0.015KB). By the referenced table being empty, it forces the __index and __newindex methods to be called upon. Typically the methods, or tables, point to another table that stores the necessary values to be accessed or modified.

This approach setmetatable({}, ...), or using newproxy(true) is necessary when you want to support access modifiers, or other neat features.

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