Log In  


Hey,
Out of curiosity, I wanted to implement Aspect Oriented Programming into metatables used as objects.
I'm having trouble making the __index meta-method to work correctly.

My goal is whenever the functions update or draw are executed, I also execute the functions called pre_update, pre_draw and post_update and post_draw if they exist in the same object.

I can't make the code below to work. What am I doing wrong?

function aop_mt(tbl) 
  tbl = tbl or {}
  tbl.__index = function(tbl,key)
    local ogfunc = rawget(tbl, key)
    if (one_of(key, "update", "draw") and is_fun(ogfunc)) then
      local prefunc = rawget(tbl,"pre_"..key)
      local postfunc = rawget(tbl,"post_"..key)
      return function(self,...)
        if(prefunc) prefunc(self,...)
        local ret = ogfunc(self,...)
        if(postfunc) postfunc(self,...)
        return ret
      end
    end
    return ogfunc
  end
  return tbl
end

--creating an object using aop_mt
object = {
  items = {
    {name="potion", qty=100, price=120, sell=100},
    {name="medicine", qty=100, price=100, sell=80},
    {name="revive", qty=10, price=300, sell=180},
    {name="attack+", qty=10, price=400, sell=250},
  },

  --when I call this function, I also want 'pre_update' to run before, and 'post_update' to run after.
  update=function(self) -- update logic end,

  pre_update=function(self) -- pre-update logic end,
  post_update=function(self) -- post-update logic end,
}
setmetatable(shop,aop_mt())

--utils used above for reference
function is_fun(f) return (f!=nil and type(f)=="function") end

function one_of(value, ...)
  if(select("#",...)==0) return false
  for arg in all({...})do if(value==arg) return true end
  return false
end


Okay so there are a couple obvious problems:

You're calling setmetatable on shop but there's no shop object in your code. I assume object should be called shop or vice versa?

Also your update, pre_update and post_update functions all have their terminating end commented out.

The problem you're having with __index is that its purpose is to find a value to return when there isn't one defined on the object itself. In other words, shop:update() will only trigger a call to __index if shop.update == nil. Since shop.update isn't nil, there's no call to __index.

What you can do instead is use __newindex to replace the user defined update function with the one you want when it's defined.

Here's a sketch of a possible solution:

function aop()
   return setmetatable(
      {},
      {
         __newindex=function(self, key, value)
            -- do a similar thing for draw
            if key == 'update' then
               local function _real_update(obj)
                  if obj.pre_update then
                     obj:pre_update()
                  end
                  local ret_val = value(obj) -- value is the user defined update function
                  if obj.post_update then
                     obj:post_update()
                  end
                  return ret_val
               end
               rawset(self, key, _real_update)
               return  
            end

            -- anything other than update or draw just set it as is.
            rawset(self, key, value)
         end
      }
   )
end

shop = aop()
function shop:update()
   print('update')
end

function shop:pre_update()
   print('pre_update')
end

function shop:post_update()
   print('post_update')
end

shop:update()

The above prints

pre_update
update
post_update

as intended.



[Please log in to post a comment]