Custom Functions/Events

That’s because you’re looking to add onto the existing functions of workspace which you can’t do natively. If you just set the __index metamethod to workspace, you’ll encounter unexpected behaviours or you will be using memory inefficiently.

If __index is just workspace, you will do:
1- Look into the object for that index which should only have properties.
2- Look in the workspace for a member with that index.
Then it ends there.

This isn’t what you want because what happens if we want to add a function? We could define functions per-object but that is not an efficient use of memory because if you have 100 objects, you also would have 100 function objects, when you could have 1 single function object for all 100 objects.

So, bearing that in mind, you want to go:
1- Look in the object for the member (property)
2- Member doesn’t exist in object so we want to look in the methods table
3- Method doesn’t exist in the methods table so we want to look in the instance for the member.
For that reason we want to use a function that goes through all of the members of each involved object, instead of just through two objects.

Remember, in this case, a table’s metatable can be the same for any number of objects.

local t = {}

setmetatable({}, t)

When you do that, the table’s metatable is actually a reference to t, not just a clone of it.

1 Like

So if I were to do that:


local Items = Workspace:GetDescendants()

function Items:GetSpecifiedClass(class) 
    local toSend = {}
    for _,item in pairs(self) do
        if item:IsA(class) then 
            table.insert(toSend,item)
        end
    end

    return table.concat(toSend,",")
end

Items:GetSpecifiedClass("Model")

This is supposed to work , but why when I try to make a custom event (to escape the class checker etc), it requires using metatables

Like,
If I had a custom function that will return me only parts with the given class param, it would have been more efficient than this one

From what I understood so far is that metatables can help us when we want to access something that doesnt exist in a table

In this case you would just be getting a table of items from workspace and adding a function to that table. Using an instance wrapper is less efficient than doing what you’re doing but it increases readability and helps with debugging and whatnot.

Usually that is the case, yes. They do also allow for arithmetics to be performed on tables, calling tables and more. Metatables can also be better for memory since it reduces the number of objects (functions) that exist per-object. But if you’re just looking to create a library of utility functions, a wrapper probably isn’t what you want. You can just make a library of functions and that will be more efficient than wrapping an object.

1 Like

Oh , thanks again man. Appreciate your patience and time!

I’d prob mess with it a bit and update here if needed. Appreicate :+1:

1 Like

Hi again,
So I now have this,
And my goal is to make an event that’d change the properties in that table( must be real properties), what should be changed in order to make this event work properly?

local list = {
    Health;
    WalkSpeed;
}
function list:GiveSpeed(Target:Model, Speed:number) 
   Target.Humanoid.WalkSpeed = Speed
end


list:GiveSpeed(Player1,25)

Tho, it doesnt seem like self/ metatables are used here, and this code works. Weird

From this code you can see it might be easy to make custom events / functions, but is that really good practice?

Do you mean a function that does this? I don’t think an event to change properties would be useful here.

You can use self there like this:

function list:GiveSpeed(Target:Model, Speed:number) 
    Target.Humanoid.WalkSpeed = Speed
    self.WalkSpeed = Speed
end

In this case, though, I think going the OOP-route might be more useful for memory and organization. Let me know.

1 Like

Yeah something like that. Tho,

But isnt self supposed to represent the humanoid of the model?

No, self would be defined as list in this case.

Remember,

list:GiveSpeed(Player1, 25)

is the same as

list.GiveSpeed(list, Player1, 25)
1 Like

Oh, but how would it give the player the speed then?

Also, is it true that if I had this:

local list = {}

local function list:GiveSpeed(....)
      --....
end

Is that true to say the function is inside that table?
Because if not, __index could be useful

You’d probably use Target in this case, you’d redefine target to be a player instead of a model.

function list:GiveSpeed(Target:Player, Speed:number)
    local character = target.Character or target.CharacterAdded:Wait()
    character:WaitForChild('Humanoid').WalkSpeed = Speed
end

Yes, it’s in that table. You can use metatables here though, it would be useful if you’re working with many tables (eg. one table per player) but I’m not sure if you’re doing that.

local metamethods = {}
metamethods.__index = metamethods
function metamethods:GiveSpeed()
    -- ...
end

function metamethods.new()
    local obj = {}
    -- properties/events inside of obj here
    return setmetatable(obj, metamethods)
end

return metamethods

You can’t define local functions inside of a table though, you need to define it as function table:func(), can’t do local function table:func(), you’ll get an error.

local module = require(path.to.module)
local newObj = module.new()
newObj:GiveSpeed() -- in this case, `self` would be newObj
1 Like

Yeah, those custom- functions I understand,

But what makes those different from using metatables

Because I heard some people say there is a huge difference

And this makes it look like it’s easier to make “custom” functions

Oh.
I did that because I am used to use local variables because they are better than global ones

Thats exactly where I am trying to figure out what is that.

What properties do I put inside obj?
“WalkSpeed”? “Health”? “.Changed”?

Metatables are often more helpful when it comes to object oriented programming or doing arithmetics on tables (like adding/subtracting…) because it allows multiple objects of the same class to exist while only referencing a single function which is useful when it comes to being conservative with memory, really it all comes back to memory.

Metatables are also useful for proxy tables, object wrappers (which I mentioned previously), locking properties among many others.

There’s a big difference between a library and an object. In this post, you’re creating a library to manipulate an object. Libraries and objects both are useful in their own ways. Libraries may contain objects but often they’re done for utility and utility only. Libraries are useful when it comes to having complex functions that are related in some way. For example, string and math are both libraries but they both do not contain any object creation. I guess, looking at it, libraries usually work with constant values. A library will have many values inside of them that never change. For example, math.floor is a function obviously, and this value will never change. Likewise, math.pi is a constant number that exists so you don’t have to type out 3.1415926… every time you need to do something that relates to a circle.

I guess the biggest takeaway is that libraries contain objects and values that never change. They’re also useful so you don’t have to rewrite the same function over and over which again is unwise for memory reasons.

The difference between a library and an object is that an object will have variable properties, and usually objects represent something. For example, a part is an object. Every part has its own individual properties, every part is standalone, this goes with any object.

The reason metatables are useful for OOP is for memory reasons (which I keep reiterating on, sorry lmao). Every class should have its own set of functions, however these functions are not per-object, they are a single constant table that exists between every object. 100 objects, 1 table of functions.

For example, if we attempt to compare two instances’ “destroy” functions, they will be the same, despite the two instances not being equal to each other. Think of the class functions to be like a library.

local p1 = Instance.new('Part') 
local p2 = Instance.new('Part') 
print(p1 == p2) -- false, they are two different objects
print(p1.Destroy == p2.Destroy) -- true, although these are two different objects, they share the same "Destroy" function

That’s up to you. If you want to set these properties, you can do something like:

function object.new()
    local newObj = {}
    newObj.WalkSpeed = 16
    newObj.Health = 100
    newObj.Changed = signal.new() -- I'd recommend using a custom signal object since bindable events are inefficient but it's up to you
    return setmetatable(newObj, object)
end

However, it isn’t possible to detect any changes inside the table currently, you’d have to use a proxy table which is another concept that also relies on metatables but yeah.

1 Like

Makes sense, no doubt.
So those functions belong to library since they are constant and do the same thing among different objects.(destroy works for both gui and baseparts for example).

On phone right now so it is a bit difficult to quote what needs to be quoted,but -

About the last, the properties:

  1. What is “signal.new()”?
    2)So once I have that and the function GiveSpeed, I now can use it to give speed( when requiring the module).

local metamethods = {}
metamethods.__index = metamethods
function metamethods:GiveSpeed(target:Model)
    target.Humanoid.WalkSpeed = self.WalkSpeed
end

function metamethods.new()
    local obj = {}
    obj.WalkSpeed = 16
    obj.Health= 100
    -- properties/events inside of obj here
    return setmetatable(obj, metamethods)
end

return metamethods


And on a diff script:

local module = require(blah blah..)

game.Players.PlayerAdded:Connect(function(Player)
    if Player.Name = "Valkyrop" then
        local char = Player.Character or Player.CharacterAdded:Wait()
        if char then
           module:GiveSpeed(char)
        end
    end
end)

Also did I do it correct here:


function metamethods:GiveSpeed(target:Model)
    target.Humanoid.WalkSpeed = self.WalkSpeed
end


Sort of, yeah, they exist to conserve memory. They aren’t really libraries though, they’re usually referred to as class functions, class methods, class members etc…, the class being the thing that your object represents.

signal would be a custom signal object/“event”. Roblox’s signal is called “RBXScriptSignal”, it represents every single event in every type of instance. A signal is also an object. For example workspace.DescendantAdded is a signal.

It could also be a BindableEvent’s “Event” signal which is what most people that aren’t using a custom signal class use.

So in your example, an object isn’t really useful since you’re passing the target model anyway. OOP and metatables aren’t really useful here. You’re probably looking to create a owner/permissions/character module (library) that contains all of the relevant character utility functions.

So,

local permissionModule = {}

function permissionModule:GiveSpeed(target: Model, speed: Number)
    target:WaitForChild('Humanoid').WalkSpeed = speed
end

return permissionModule

Then you could call the function:

local module = require(path.to.module)

players.PlayerAdded:Connect(function(player)
    if player.Name == "Valkyrop" then
        local character = player.Character or player.CharacterAdded:Wait()
        -- you don't have to check if character exists here because we're yielding until it does exist (prev. line)
        module:GiveSpeed(char, 25)
    end
end)

Oh if you’re looking to do that then yes, since self would be equal to module. Just ensure there’s some number inside of module whose index is WalkSpeed.

local module = {}
module.WalkSpeed = 25
function module:GiveSpeed(target: Model)
    target.Humanoid.WalkSpeed = self.WalkSpeed
end
1 Like

Oh, but I thought that the setmetatable thing below connected and made it so that WalkSpeed is inside our table here, hence self is metamethods table and WalkSpeed exists there

local metamethods = {}
metamethods.__index = metamethods
function metamethods:GiveSpeed(target:Model)
    target.Humanoid.WalkSpeed = self.WalkSpeed
end

function metamethods.new()
    local obj = {}
    obj.WalkSpeed = 16
    obj.Health= 100
    -- properties/events inside of obj here
    return setmetatable(obj, metamethods)
end

return metamethods

In that case, self would be equal to the new object, yeah. But you would have to go through your constructor to call the function.

local module = require()

players.PlayerAdded:Connnect(function(player)
    module.new():GiveSpeed(player.Character or player.CharacterAdded:Wait())
end)
1 Like