lua 协程 | 协程实现消息机制(事件队列轮询处理机制)

来源:互联网 发布:2016新开淘宝运营教程 编辑:程序博客网 时间:2024/05/29 17:32

1 协程基础知识

Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。


协程有三种状态:挂起,运行,停止。创建后是挂起状态,即不自动运行。status函数可以查看当前状态。协程可通过yield函数将一段正在运行的代码挂起。

lua的resume-yield可以互相交换数据。如果没有对应的yield,传递给resume的额外参数将作为参数传递给协程主函数:

co = coroutine.create(function (a, b, c)     print("co", a, b, c)end)coroutine.resume(co, 1, 2, 3)运行结果: co 1 2 3
如果没有错误的话,resume将返回true和yield的参数,弱出现错误,返回false与相关错误信息:
co = coroutine.create(function (a, b)     coroutine.yield(a+b, a-b)end)print(coroutine.resume(co, 3, 8))运行结果: true 11 5
同样地,yield也将返回由对应的resume传递而来的参数:

co = coroutine.create (function ()print("begin coroutine")print("co", coroutine.yield())end)coroutine.resume(co) -- 遇到coroutine.yield()挂起coroutine.resume(co, 4, 5) -- 完成第二个print过程运行结果: begin coroutineco 4 5
协程主函数返回值将作为与之对应的resume的返回值(第一个参数是true):

co = coroutine.create(function (a)return 6, 7, coroutine.yield(a-1)end)print(coroutine.resume(co,5)) -- 输出true与coroutine.yield()运行参数print(coroutine.resume(co,5)) -- 输出协程主函数返回值运行结果: true4true 6 7 5

下面用协同程序实现一个典型的生产者-消费者模式。

function receive(prod)local status,value = coroutine.resume(prod) return valueendfunction send( x )coroutine.yield(x) -- 挂起,返回当前协同程序 供resume调用endfunction produce() -- 生产者return coroutine.create(function ()while true do -- 该循环保证了生产者被无限驱动local x = io.read()send(x)endend)endfunction consume(prod) -- 消费者while true dolocal x = receive(prod) -- 消费者驱动生产者的执行print(x)endendfunction filter( prod ) -- 用过滤器处理生产者数据return coroutine.create(function ()while true dolocal x = receive(prod) -- 驱动生产者生成数据x = doSomeProcess(x)send(x)endend)endconsume(filter(produce()))

我们尝试用协程来实现一些算法和应用级的效果。

1)用生产者-消费者模式实现迭代器,该迭代器能输出一个数组的全排列。

function permgen( a,n ) -- 生产者if n <= 0 thencoroutine.yield(a)elsefor i=1,n doa[i],a[n] = a[n],a[i]permgen(a,n-1)a[i],a[n] = a[n],a[i]endendendfunction perm( a ) -- 迭代器 消费者return coroutine.wrap(function ()permgen(a,#a)end)--[[-- 与上面代码效果相同local prod = coroutine.create(function ()permgen(a,#a)end)return function ()local code,rt = coroutine.resume(prod)return rtend]]endlocal a = {1,2,3}for v in perm(a) dolocal st = ""for i,v in ipairs(v) dost = st .. v .. "   "endprint(st)end

2)用协程实现一个多文件下载分发器

有多个文件需要下载,如果采用顺序排队方式,那么大量时间将浪费在 请求下载-下载完成反馈之间的等待过程中。我们用协程解决这一效率问题:请求下载后,如果超时则yield挂起当前协程,并有分发器调用其他协程。

download = function (host,file)local c = socket.connect(host,80)c:send("GET " .. file .. "HTTP/1.0\r\n") -- 发送请求while true dolocal s,status = receive(c) -- 接收if status == "closed" then-- 当前协程的下载过程已完成breakendendendreceive = function (connection)local s,status = connection:receive()if status == "timeout" thencoroutine.yield(connection)end return s,statusenddispatch = function ()while true do if #loadLst <= 0 then-- 所有下载器均完成 结束分发breakendfor i,v in ipairs(loadLst) dolocal status,res = coroutine.resume(v)if not res thentable.remove(loadLst,i) -- 当前下载器完成breakendendendend


2 协同实现消息机制(不同场景等待式消息处理)

游戏中,有时在处理消息时,希望一条一条消息独立处理(独立堆栈),且希望每条消息在不同场景内等待式逐步进行(如一个场景消息处理完,挂起,经过100ms再进行当前消息下一场景的处理),协程能够完成这一过程。下面提供一种实现方案。

local msgLst = {}   -- 存储local curMsgCor = nil -- 当前消息对应的协程function insertPerMsg( msg )  table.insert(msgLst,msg)  scheduleScript(processMsg)  -- -- 定时器中循环执行函数endfunction processMsg( )    if #msgLst <= 0 then        unScheduleScript(processPerMsgCor)    else        if not curMsgCor then            local curMsg = table.remove(msgLst,1)            curMsgCor = coroutine.create(function () processPerMsgCor(curMsg) end) -- 创建coroutine        end        if curMsgCor then            local state,errMsg = coroutine.resume(curMsgCor) -- 重启coroutine            local status = coroutine.status(curMsgCor)  -- 查看coroutine状态: dead,suspend,running            -- 启动失败            if not state then                curMsgCor = nil                local debugInfo = debug.traceback(curMsgCor)                print(debugInfo)            end            -- coroutine消亡            if status == "dead" then                curMsgCor = nil            end        end    endendfunction processPerMsgCor( curMsg )    processForTalk()    coroutine.yield() --  挂起coroutine    processForSpecialRoom()    coroutine.yield()    processForOtherWay()end

3 事件队列轮询处理机制

游戏场景经常需要一次执行一系列事件,每个事件的完成均需要一定时间。如需要在奔跑至指定区域后释放技能或攻击某一对象。可通过事件队列方式完成这一过程。
local Event = class("Event")Event.State = {None = 1,Doing = 2,Done = 3,}function Event:ctor()self.state = self.State.Noneendfunction Event:isNotStart()return self.state == self.State.Noneendfunction Event:setDoing()self.state = self.State.Doingendfunction Event:setFunc( func,... )self.func = funcself.funcParams = {...}endfunction Event:doFunc()self.func(self,unpack(self.funcParams))end-- Func完成后调用function Event:setDone()self.state = self.State.Doneendfunction Event:isDone()return self.state == self.State.Doneendfunction Test:ctor( )-- 创建事件队列self.eventQueue = Queue:create()-- 定时器轮询时间事件队列local schedule = cc.Director:getInstance():getScheduler()self.scheduleId = schedule:scheduleScriptFunc(handler(self,self.runEventQueue),1,false)-- 添加几个事件local deltaTime = cc.DelayTime:create(5)local event = Event:create()-- func中的参数event在执行doFunc时传入local func = function (event)local f1 = function()-- 5秒后输出print("print this after 5 sec")event:setDone()endself:runAction(cc.Sequence:create(deltaTime,cc.CallFunc:create(f1)))endevent:setFunc(func)self:addEvent(event)event = Event:create()func = function (event)local f1 = function()-- 10秒后输出print("print this after 10 sec")event:setDone()endself:runAction(cc.Sequence:create(deltaTime,cc.CallFunc:create(f1)))endevent:setFunc(func)self:addEvent(event)event = Event:create()func = function (event)local f1 = function()-- 15秒后输出print("print this after 15 sec")event:setDone()endself:runAction(cc.Sequence:create(deltaTime,cc.CallFunc:create(f1)))endevent:setFunc(func)self:addEvent(event)endfunction Test:addEvent(event)self.eventQueue:push_back(event)endfunction Test:runEventQueue()if self.eventQueue:size() <= 0 thenreturnend-- 两种写法--[[ 第一种写法:完成当前事件后等待计时器执行下一个事件,事件之间存在时间间隙local curEvent = self.eventQueue:front()if curEvent and not curEvent:isDoing() thenif curEvent:isNotStart() then-- 执行事件curEvent:setDoing()curEvent:doFunc()elseif curEvent:isDone() then-- 事件已完成,删除并转至下个事件self.eventQueue:pop_front()curEvent = nilif self.eventQueue:size() > 0 thencurEvent = self.eventQueue:front()endendend]]-- 第二种写法:当前时间完成即刻执行下一时间while curEvent and not curEvent:isDoing() doif curEvent:isNotStart() then-- 执行事件curEvent:setDoing()curEvent:doFunc()elseif curEvent:isDone() then-- 事件已完成,删除并转至下个事件self.eventQueue:pop_front()curEvent = nilif self.eventQueue:size() > 0 thencurEvent = self.eventQueue:front()endendendend


0 0
原创粉丝点击