2018年6月27日星期三

tolua移除协程的各种坑

一开始发现tolua没有停止协程的功能,而lua协程也不支持从外部停止,于是就想自己封装一层。最开始写了一个最简单的stop接口:
function coroutine.stop(co)
   print("stopCo1", co, debug.traceback())
   local timer = comap[co]
   print("stopCo2", timer)
   if timer ~= nil then
      comap[co] = nil
      timer:Stop()
      print(timer, timer.running)
   end
end
看上去似乎没有任何问题,通过对定时器的暂停,这样子协程就不会跑了。
对了,comap是我自己加的缓存协程和定时器的字典。
但后来发现一个问题,就是协程突然不执行了。
经过各种排查,发现是复用引起的。在coroutine.step调用结束之后,定时器被Stop了,而且被回收到了对象池,
但是compa[co]并没有清空,导致定时器被别人使用的时候,突然来了一个stop把那个其实不属于自己的定时器停止了。
于是通过修改coroutine.step函数修复了这个bug:
local action = function()
   local flag, msg = resume(co, unpack(args))
   timer.func = nil
   table.insert(pool, timer)

   comap[co] = nil

   timer:Stop()
   if not flag then                                                         
      error(debug.traceback(co, msg))
      return 
   end       
end
后来又出现了一个bug,协程又他妈不执行了。经过仔细的排查,发现是这样的一个流程导致的:
local action = function()
   local flag, msg = resume(co, unpack(args)) 
    执行resume里面又调用了coroutine.step,导致comap[co]里面的timer其实已经
    不是上一个协程的timer
    如果你继续执行comap[co],就会导致那个正在执行的timer反而被清空。
  timer.func = nil
   table.insert(pool, timer)

      comap[co] = nil

   timer:Stop()
   if not flag then                                                         
      error(debug.traceback(co, msg))
      return 
   end       
end
于是增加了一个判断:
local action = function()
   local flag, msg = resume(co, unpack(args))
   timer.func = nil
   table.insert(pool, timer)
   if timer == comap[co] then
      comap[co] = nil
   end
   timer:Stop()
   if not flag then                                                         
      error(debug.traceback(co, msg))
      return 
   end       
end
(PS:这里请假了tolua作者蒙哥,其实是我的时序出了问题,要在resume之前清除字典索引,应该这么写:
local action = function()
comap[co] = nil
timer.func = nil
timer:Stop()

 local flagmsg = resume(counpack(args)) table.insert(pooltimer) if not flag then error(debug.traceback(comsg)) return end end
本来以为这样已经完美,可新问题出现了,协程无法被停止了,经过仔细的排查,发现是这样的。
function FrameTimer:Start()    
   if not self.handle then
      self.handle = CoUpdateBeat:CreateListener(self.Update, self)
   end
   if self.running then
      print("runing twice", self)
   else
      print(self, debug.traceback())
   end

   CoUpdateBeat:AddListener(self.handle)
   print("lock1", CoUpdateBeat.lock)
   self.running = true
end
协程的定时器启动的时候,CoUpdateBeat正在执行,tolua是这么处理的:
function _event:AddListener(handle)
   assert(handle)
   if self.lock then
      table.insert(self.opList, function() self.list:pushnode(handle) end)      
   else
      self.list:pushnode(handle)
   end    
end

对于正在执行的情况下,把将要加入队列的handle放到opList中,在当前队列执行完毕后,去执行opList操作,把handle放入到队列中。
如下:
_event.__call = function(self, ...)       
   local _list = self.list    
   self.lock = true
   local ilist = ilist
   for i, f in ilist(_list) do
      self.current = i
      local flag, msg = f(...)
      
      if not flag then
         if self.keepSafe then                       
            _list:remove(i)
         end
         self.lock = false     
         error(msg)          
      end
   end    

   local opList = self.opList
   self.opList = {}
   self.lock = false     

   for _, op in ipairs(opList) do                         
      op()
   end
end
但我使用的时候出现了一种情况,导致无法停止协程。
首先,我启动了一个协程,定时器被AddListener,此时刚好UpdateBeat正在被执行,于是lock为true,这个操作被放到opList中,而并没有真正的加入到队列中。直到执行了op()之后,才被放入到队列中。所以目前的状况是,协程在队列中,但还没有被执行。
然后,下一个Update来临,协程对列正在被执行,但刚好还没执行到我启动的那个协程,就在这时,我调用了stop函数,想要停止这个协程,由于协程队列在执行,所以我的这个remove请求会被放到opList中,所以只有等到协程执行完毕之后,我的remove才会被执行。于是这个协程其实没有被停止。
这其实就是我们平时经常碰到的问题,c#遍历一个字典的时候,你是无法移除里面的东西的,你只能设置一个标志。于是我也打算这么解决这个问题。如果tiemr的标志running为false,那么即时协程执行到了,就直接return.
function FrameTimer:Update()    
   if Time.frameCount >= self.count then
      print(self, self.running)
      if not self.running then
         return
      end
      self.func()    
      
      if self.loop > 0 then
         self.loop = self.loop - 1
      end
      
      if self.loop == 0 then
         self:Stop()
      else
         self.count = Time.frameCount + self.duration
      end
   end
end

希望以后不会再有问题了

lua 面向对象的实现问题

一开始打算用最简单的形式,就是如下:
function AIExecuteQueue:New()
    local o = {}
    setmetatable(o, self)
    self.__index = self
    o:init()
    return o
end
一开始没啥问题,但后来逐渐发现问题。
首先我希望要有一个基类Base
Base:New里面绑定了一些事件。
AIExecuteQueue继承自事件
我一开始这么写:
AIExecuteQueue = Base:New()
然后调用AIExecuteQueue:New()
由于o的元表是AIExecuteQueue, 而AIExecuteQueue的元表是Base,看上去还不错,但实际并非如此。
还没有调用AIExecuteQueue:New的时候,Base:New已经被调用了,也就是说事件已经被绑定了,这和我们的预期完全不同。
后来看了云风的实现,发现是这样:
local _class={}
 
function class(super)
 local class_type={}
 class_type.ctor=false
 class_type.super=super
 class_type.new=function(...) 
   local obj={}
   do
    local create
    create = function(c,...)
     if c.super then
      create(c.super,...)
     end
     if c.ctor then
      c.ctor(obj,...)
     end
    end
 
    create(class_type,...)
   end
   setmetatable(obj,{ __index=_class[class_type] })
   return obj
  end
 local vtbl={}
 _class[class_type]=vtbl
 
 setmetatable(class_type,{__newindex=
  function(t,k,v)
   vtbl[k]=v
  end
 })
 
 if super then
  setmetatable(vtbl,{__index=
   function(t,k)
    local ret=_class[super][k]
    vtbl[k]=ret
    return ret
   end
  })
 end
 
 return class_type
end
非常有意思的方法,new本身其实就是构造了一个空table, 这个空table,访问这个table就是访问_class里面的实际table.
那这样怎么继承呢,如果指定了super,那么在访问实际表的时候,首先访问vtbl,如果vtbl里面没有,那么就会访问vtbl的元表,也就是super里面的key,同时还会把这个值拷贝到vtbl中,加快访问速度。
这样反过来看我们的一开始的设计问题,在定义New的时候,生成空tabel a, 让a的元表指向AIExecuteQueue. 然后由于AIExecuteQueue继承自base 我们需要让AIExecuteQueue的元表指向base
function AIExecuteQueue:New()
    local o = {}
    setmetatable(o, self)
    self.__index = self
    
setmetatable(AIEcecuteQueue, Base)
AIExecuteQueue.__index = Base
return oend

最后 我们试想一下怎么重写一个init函数 里面调用base.init

self.super.init(self,xxx)
虽然蛋疼 但暂时也没有更好的办法了

最近发现一个新的问题,self.super不支持递归调用,就是C继承B,继承A,那么c.usper.init()并不会调用A.init()
于是修改如下:
---
--- Created by yxriyin.
--- DateTime: 2017/12/28 16:05
---

local _class={}

function CallBaseFunc() end

function Class(super, o)
    local class_type={}
    class_type.ctor=false
    class_type.super=super
    class_type.New=function(...)
        local obj={}
        setmetatable(obj,{ __index=_class[class_type] })
        do
            local create
            create = function(c,...)
                if c.super then
                    create(c.super,...)
                end
                if c.ctor then
                    c.ctor(obj,...)
                end
            end

            create(class_type,...)
        end

        return obj
    end
    local vtbl= o or {}
    vtbl.super = super
    _class[class_type]=vtbl

    setmetatable(class_type,{__newindex=
    function(t,k,v)
        vtbl[k]=v
    end,
        __index = vtbl,
    })

    if super then
        setmetatable(vtbl,{__index=
        function(t,k)
            local ret=_class[super][k]
            vtbl[k]=ret
            return ret
        end
        })

        function class_type.callBase(self, f, ...)
            if not self.virtualList then
                self.virtualList = {}
            end
            local base = self.super

            if self.virtualList[f] then
                base = self.virtualList[f]
            else
                local base1 = self
                local base2 = base
                while base1 and base2 do
                    if base1[f] == base2[f] then
                        base = base.super
                        base2 = base2.super
                        base1 = base1.super
                    else
                        break
                    end
                end
            end


            self.virtualList[f] = base.super
            local result = base[f](self, ...)
            self.virtualList[f] = nil
            return result
        end
    end



    return class_type
end
这里模拟了一个最简单的虚表的实现
这样子就是self:callBase("method")来实现调用子类的函数,而且支持递归调用