基于lua协程的AI服务实现

以前写过一编博客介绍我们游戏的AI服务器。

基本的结构就是利用windows的fiber,在每个fiber中运行一个lua虚拟机,具体的内容可以产参看

http://blog.csdn.net/sniperhuangwei/article/details/5425471

但这个方案有一个缺点,就是随着项目的推移,AI脚本变得越来越复杂,每个虚拟机占用的内存就变得越来越大。

当一个进程上运行的AI对象数量很大时这个进程就吃掉了非常大的内存。

经过一番考量,我决定不使用windows的fiber来支持用户态线程调度框架,而是改用lua的coroutine.

这样整个进程只需要维护一个lua虚拟机就够了,即使AI脚本变得更复杂所占用的内存也不会太恐怖.

下面是coroutine的调度框架,基本结构跟原来的fiber框架是一样的.

co.lua

coObject = 
{
next_co = nil, --活动队列中的下一个协程对象
co = nil, --协程对象
status, --当前的状态
block = nil, --阻塞结构
timeout,
index = 0,
sc, --归属的调度器
name
}
function coObject:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function coObject:init(name,sc,co)
self.name = name
self.sc = sc
self.co = co
end
function coObject:Signal(ev)
if self.block ~= nil then
if self.block:WakeUp(ev) then
self.sc:Add2Active(self)
end
end
end
blockStruct = {
bs_type,
}
function blockStruct:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function blockStruct:WakeUp(type)
return true
end

timer.lua

timer = {
m_size = 0, --元素的数量
m_data = {} --元素
}
function timer:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function timer:Up(index)
local parent_idx = self:Parent(index)
while parent_idx > 0 do
if self.m_data[index].timeout < self.m_data[parent_idx].timeout then
self:swap(index,parent_idx)
index = parent_idx
parent_idx = self:Parent(index)
else
break
end
end
end
function timer:Down(index)
local l = self:Left(index)
local r = self:Right(index)
local min = index
if l <= self.m_size and self.m_data[l].timeout < self.m_data[index].timeout then
min = l
end
if r <= self.m_size and self.m_data[r].timeout < self.m_data[min].timeout then
min = r
end
if min ~= index then
self:swap(index,min)
self:Down(min)
end
end
function timer:Parent(index)
return index/2
end
function timer:Left(index)
return 2*index
end
function timer:Right(index)
return 2*index + 1
end
function timer:Change(co)
local index = co.index
if index == 0 then
return
end
--尝试往下调整
self:Down(index)
--尝试往上调整
self:Up(index)
end
function timer:Insert(co)
if co.index ~= 0 then
return
end
self.m_size = self.m_size + 1
table.insert(self.m_data,co)
co.index = self.m_size
self:Up(self.m_size)
end
function timer:Min()
if self.m_size == 0 then
return 0
end
return self.m_data[1].timeout
end
function timer:PopMin()
local co = self.m_data[1]
self:swap(1,self.m_size)
self.m_data[self.m_size] = nil
self.m_size = self.m_size - 1
self:Down(1)
co.index = 0
return co
end
function timer:Size()
return self.m_size
end
function timer:swap(idx1,idx2)
local tmp = self.m_data[idx1]
self.m_data[idx1] = self.m_data[idx2]
self.m_data[idx2] = tmp
self.m_data[idx1].index = idx1
self.m_data[idx2].index = idx2
end
function timer:Clear()
while m_size > 0 do
self:PopMin()
end
self.m_size = 0
end

scheduler.lua

scheduler =
{
active_head = nil,--活动列表头
active_tail = nil,--活动列表尾
pending_add = {},--等待添加到活动列表中的coObject
m_timer = nil,
}
function scheduler:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function scheduler:init()
self.m_timer = timer:new()
end
--添加到活动列表中
function scheduler:Add2Active(coObj)
table.insert(self.pending_add,coObj)
end
--尝试唤醒uid
function scheduler:TryWakeup(coObj,ev)
if coObj.block then
coObj:Signal(ev)
end
end
--强制唤醒纤程
function scheduler:ForceWakeup(coObj)
if coObj.status ~= "ACTIVED" then
coObj.sc:Add2Active(coObj)
end
end
--强制唤醒阻塞在type条件上的纤程
function scheduler:ForceWakeup(coObj,type)
if coObj.status ~= "ACTIVED" and coObj.block and coObj.block.bs_type == type then
coObj.sc:Add2Active(coObj)
end
end
--睡眠ms
function scheduler:Sleep(coObj,ms)
if ms > 0 then
coObj.timeout = GetTick() + ms
if coObj.index == 0 then
self.m_timer:Insert(coObj)
else
self.m_timer:Change(coObj)
end
coObj.status = "SLEEP"
end
coroutine.yield(coObj.co)
end
--暂时释放执行权
function scheduler:Yield(coObj)
coObj.status = "YIELD"
coroutine.yield(coObj.co)
end
--主调度循环
function scheduler:Schedule()
--将pending_add中所有coObject添加到活动列表中
for k,v in pairs(self.pending_add) do
v.next_co = nil
if self.active_tail ~= nil then
self.active_tail.next_co = v
self.active_tail = v
else
self.active_head = v
self.active_tail = v
end
end
self.pending_add = {}
--运行所有可运行的coObject对象
local cur = self.active_head
local pre = nil
while cur ~= nil do
coroutine.resume(cur.co,cur)
print("从coro中回来")
local status = cur.status
--当纤程处于以下状态时需要从可运行队列中移除
if status == "DEAD" or status == "SLEEP" or status == "WAIT4EVENT" or status == "YIELD" then
--删除首元素
if cur == self.active_head then
--同时也是尾元素
if cur == self.active_tail then
self.active_head = nil
self.active_tail = nil
else
self.active_head = cur.next_co
end
elseif cur == self.active_tail then
pre.next_co = nil
self.active_tail = pre
else
pre.next_co = cur.next_co
end
local tmp = cur
cur = cur.next_co
tmp.next_co = nil
--如果仅仅是让出处理器,需要重新投入到可运行队列中
if status == "YIELD" then
self:Add2Active(tmp)
end
else
pre = cur
cur = cur.next_co
end
end
--看看有没有timeout的纤程
local now = GetTick()
while self.m_timer:Min() ~=0 and self.m_timer:Min() <= now do
local co = self.m_timer:PopMin()
if co.status == "WAIT4EVENT" or co.status == "SLEEP" then
self:Add2Active(co)
end
end
end

test.lua

function cofun(coObj)
while true do
Show()
print(coObj.name)
coObj.sc:Sleep(coObj,1)
end
end
function test()
local sc = scheduler:new()
sc:init()
local co1 = coObject:new()
local coro1 = coroutine.create(cofun)
co1:init("1",sc,coro1)
local co2 = coObject:new()
local coro2 = coroutine.create(cofun)
co2:init("2",sc,coro2)
local co3 = coObject:new()
local coro3 = coroutine.create(cofun)
co3:init("3",sc,coro3)
local co4 = coObject:new()
local coro4 = coroutine.create(cofun)
co4:init("4",sc,coro4)
sc:Add2Active(co1)
sc:Add2Active(co2)
sc:Add2Active(co3)
sc:Add2Active(co4)
while true do
sc:Schedule()
end
end