Search code examples
c++inheritancestructlualua-userdata

How do I set up a metatable to inherit from another metatable while at the same time changing the userdata to another type?


This is something I want to do in C++ using the Lua C API.

I'm trying to figure out a good way to make userdata derive from a base userdata object.

I want to be able to do this:

local item = me:GetCurrentItem()
print(item:GetPos())

instead of:

local item = me:GetCurrentItem()
print(item:GetBaseObject():GetPos())

In these examples, me:GetCurrentItem() returns userdata with some functions, but it lacks the base functions which item:GetBaseObject() returns.

I'm binding Lua in the Crysis Wars SDK for learning purposes. The SDK provides an interface to the base entity which is a struct. The IItem struct (me:GetCurrentItem()) is the same. Since these are structs I can't cast them to the base struct or call its base functions. I have to use the IEntity *GetEntity() function.

I've tried chaning the self pointer in __index but it causes the local variable "item" to become "entity" which is kinda obvious, but I want it to revert back after calling the GetPos function, which seems illogical somehow.

Does anyone have a good solution to this issue?


Solution

  • The obvious solution would be defining an item:GetPos() function that does the redirect.

    function Item:GetPos()
      return self:GetBaseObject():GetPos()
    end
    

    Where Item is the item's metatable.

    This is as efficient as making changes on the metatable, and less problematic.

    EDIT: I can help you a bit with the repetitiveness too.

    You can implement the following two functions:

    function delegate(klass, methodName, memberName)
      klass[methodName] = function(self, ...)
        local member = self[memberName]
        if type(member) == 'function' then member = self[memberName](self) end
        return member[methodName](member, ...)
      end
    end
    

    And then use it like this:

    delegate(Item, 'GetPos', 'GetBaseObject')
    

    That single line whould do the same as the Item:GetPos 3-line definition above.

    If you need to repeat this a lot, you can optimize it further with this other function:

    function delegateMany(klass, methodNames, memberName)
      for _,methodName in ipairs(methodNames) do
        delegateMethod(klass, methodName, memberName)
      end
    end
    

    Which should allow you to do:

    deletageMany(Item, {'GetPost', 'SetPos', 'GetColor', 'SetColor'}, 'GetBaseObject')
    

    I haven't tested any of these functions, so beware of bugs. They should work with both "gotten properties" (self:GetSomething() as well as simple accesses self.something). The code assumes that you would always use ':' to invoke the delegated methods, so self is added when needed.