以前寫過一編部落格介紹我們遊戲的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