Jump to content

addEventHandler with methods


Bonsai

Recommended Posts

Hey Peeps,

can I somehow use a method as the function argument for event handlers?

I tried something like this but it doesn't work:

This is an OOP example from Google:

  
local MyClass = {} 
MyClass.__index = MyClass 
  
setmetatable(MyClass, { 
  __call = function (cls, ...) 
    return cls.new(...) 
  end, 
}) 
  
function MyClass.new(init) 
  local self = setmetatable({}, MyClass) 
  self.value = init 
  return self 
end 
  
-- the : syntax here causes a "self" arg to be implicitly added before any other args 
function MyClass:set_value(newval) 
  self.value = newval 
end 
  
function MyClass:get_value() 
  return self.value 
end 
  
local instance = MyClass(5) 
-- do stuff with instance... 
  

So imagine one of these methods would handle drawing stuff on the screen.

Then I would need to add an event handler that calls it.

  
function MyClass.new(init) 
  local self = setmetatable({}, MyClass) 
  self.value = init 
  addEventHandler("onClientRender", root, self:draw, true, "low-99") 
  return self 
end 
  

At least thats what I thought. But it outputs this error in log:

"..function arguments expected near.."

Does anyone know how I can make it work?

I need each object to have its own handler.

Bonsai

Link to comment
  • Discord Moderators

As you may already know, self:draw(...) is syntactic sugar for self.draw(self, ...).

But self:draw is a expression to call a function, not to refer to a function itself. So self:draw(...) is, in simple terms, like func(...), but self:draw alone does not refer to func because the function arguments are part of its syntax.

Therefore, to refer to a function in a method, you have to use self.draw.

Link to comment

Thank you, now it doesn't show that error for "addEventHandler" anymore.

But I can't use "self" in the render function like this.

Every object has different things to draw, and I need to access them using self.

Do you know how to solve that?

Bonsai

Link to comment
  • Discord Moderators

It is necessary to rethink your code then, as event handlers do not allow to pass some variable to the handler function and onClientRender can't trigger for a specific element.

This is the code I came across with. It uses a table to save all the instances of the class, so then the onClientRender event handler can iterate over it and do things. Plus it should be more CPU efficient than adding a handler for every instance. Comments in the code explain what the code does and where.

-- Class MT 
local MyClass = {} 
MyClass.__index = MyClass 
MyClass.instances = {} 
  
-- Call the constructor when calling the MyClass table itself 
setmetatable(MyClass, { 
  __call = function (cls, ...) 
    return cls.new(...) 
  end, 
}) 
  
function MyClass.new(init) 
    -- Create new table and let it access our class functions and vars 
    local self = setmetatable({}, MyClass) 
    -- Let the object have a value (it is not saved in the class itself) 
    self.value = init 
    -- Add it into the instances table 
    MyClass.instances[self] = true 
    return self 
end 
  
function MyClass:set_value(newval) 
    -- Delete old entry 
    MyClass.instances[self] = nil 
    -- Set our new value 
    self.value = newval 
    -- Update value in table 
    -- You should do something like this for every value change, so onClientRender gets updated data 
    -- Alternatively, I think that you can use metatables and let them handle this for you, but I think it is too complicated 
    MyClass.instances[self] = true 
end 
  
function MyClass:get_value() 
    return self.value 
end 
  
function MyClass:delete() 
    -- Delete from instance table 
    MyClass.instances[self] = nil 
    -- Delete ourselves 
    self = nil 
end 
  
function drawMyClassInsts() 
    for inst in pairs(MyClass.instances) do 
        -- Whatever you want to do here. To get value: 
        -- value = inst.value 
    end 
end 
addEventHandler("onClientRender", root, drawMyClassInsts, true, "low-99") 

Link to comment

Yep, thats what I wanted to avoid :(

But seems there is no other way then.

Thanks for your detailed answers.

Btw, if I create an instance somewhere like this:

local object = MyClass.new(init);

And I have a destroy method that does something like self = nil inside the class.

Will "object" be nil too or do I have to destroy it in a different way?

Bonsai

Link to comment
  • Discord Moderators

The object variable will still be a table after that, but an unuseful one in this case (it doesn't get rendered), and you should avoid doing things with it. The optimal thing to do is nil'ing it after calling the destructor.

But sadly, it is not possible to delete the object in its own destructor in pure Lua. You can read more about it in this Stack Overflow question.

Link to comment

Having to nil it manually isn't that bad after all.

I changed it like that now and it works perfectly so far.

Now i will change all my other classes to do it the same way.

Having only one render function and a loop inside instead of mulitple handlers seems to be a better solution now indeed.

Thanks,

Bonsai

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...