修小bug,分离事件栈 (#303)

This commit is contained in:
notify 2024-01-10 22:51:29 +08:00 committed by GitHub
parent 1005863b1e
commit 51a10ebcf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 205 additions and 185 deletions

View File

@ -505,7 +505,9 @@ end
function Stack:pop()
if self.p == 0 then return nil end
self.p = self.p - 1
return self.t[self.p + 1]
local ret = self.t[self.p + 1]
self.t[self.p + 1] = nil
return ret
end

View File

@ -5,6 +5,8 @@
-- 某类事件对应的结束事件其id刚好就是那个事件的相反数
-- GameEvent.EventFinish = -1
GameEvent.Game = 0
GameEvent.ChangeHp = 1
GameEvent.Damage = 2
GameEvent.LoseHp = 3
@ -44,10 +46,10 @@ dofile "lua/server/events/pindian.lua"
-- 20 = CardEffect
GameEvent.ChangeProperty = 21
dofile "lua/server/events/misc.lua"
-- TODO: fix this
GameEvent.BreakEvent = 999
-- 新的clear函数专用
GameEvent.ClearEvent = 9999
dofile "lua/server/events/misc.lua"
for _, l in ipairs(Fk._custom_events) do
local name, p, m, c, e = l.name, l.p, l.m, l.c, l.e
@ -58,6 +60,8 @@ for _, l in ipairs(Fk._custom_events) do
end
local eventTranslations = {
[GameEvent.Game] = "GameEvent.Game",
[GameEvent.ChangeHp] = "GameEvent.ChangeHp",
[GameEvent.Damage] = "GameEvent.Damage",
[GameEvent.LoseHp] = "GameEvent.LoseHp",
@ -80,7 +84,7 @@ local eventTranslations = {
[GameEvent.ChangeProperty] = "GameEvent.ChangeProperty",
[GameEvent.BreakEvent] = "GameEvent.BreakEvent",
[GameEvent.ClearEvent] = "GameEvent.ClearEvent",
}
function GameEvent.static:translate(id)

View File

@ -1,5 +1,9 @@
-- SPDX-License-Identifier: GPL-3.0-or-later
GameEvent.functions[GameEvent.Game] = function(self)
self.room.logic:run()
end
GameEvent.functions[GameEvent.ChangeProperty] = function(self)
local data = table.unpack(self.data)
local room = self.room
@ -120,3 +124,25 @@ GameEvent.functions[GameEvent.ChangeProperty] = function(self)
logic:trigger(fk.AfterPropertyChange, player, data)
end
GameEvent.functions[GameEvent.ClearEvent] = function(self)
local event = self.data[1]
local logic = self.room.logic
event:clear_func()
for _, f in ipairs(event.extra_clear_funcs) do
if type(f) == "function" then f(event) end
end
-- cleaner顺利执行完了出栈吧
local end_id = logic.current_event_id + 1
if event.id ~= end_id - 1 then
logic.all_game_events[end_id] = event.event
logic.current_event_id = end_id
event.end_id = end_id
else
event.end_id = event.id
end
logic.game_event_stack:pop()
logic.cleaner_stack:pop()
end

View File

@ -13,9 +13,9 @@
---@field public extra_clear_funcs fun(self:GameEvent)[] @ 事件结束时执行的自定义函数列表
---@field public exit_func fun(self: GameEvent) @ 事件结束后执行的函数
---@field public extra_exit_funcs fun(self:GameEvent)[] @ 事件结束后执行的自定义函数
---@field public exec_ret boolean? @ exec函数的返回值可能不存在
---@field public interrupted boolean @ 事件是否是因为被中断而结束的,可能是防止事件或者被杀
---@field public killed boolean @ 事件因为终止一切结算而被中断(所谓的“被杀”)
---@field public revived boolean @ 事件被killed但因为在cleaner中发生而被复活
local GameEvent = class("GameEvent")
---@type (fun(self: GameEvent): bool)[]
@ -163,190 +163,28 @@ function GameEvent:searchEvents(eventType, n, func, endEvent)
return ret
end
function GameEvent:clear()
local clear_co = coroutine.create(function()
self:clear_func()
for _, f in ipairs(self.extra_clear_funcs) do
if type(f) == "function" then f(self) end
end
end)
function GameEvent:exec()
local room = self.room
local logic = room.logic
self.parent = logic:getCurrentEvent()
local zhuran_jmp, zhuran_msg -- SB老朱然
if self:prepare_func() then return true end
while true do
local err, yield_result, extra_yield_result = coroutine.resume(clear_co)
logic:pushEvent(self)
if err == false then
-- handle error, then break
if not string.find(yield_result, "__manuallyBreak") then
fk.qCritical(yield_result .. "\n" .. debug.traceback(clear_co))
end
coroutine.close(clear_co)
break
end
local co = coroutine.create(self.main_func)
self._co = co
if yield_result == "__handleRequest" then
-- yield to requestLoop
coroutine.yield(yield_result, extra_yield_result)
coroutine.yield(self, "__newEvent")
elseif type(yield_result) == "table" and yield_result.class
and yield_result:isInstanceOf(GameEvent) and self ~= yield_result then
-- 不是谁TM还在cleaner里面玩老朱然啊
-- 总之cleaner不能断
-- 倒是没必要手动resume新一轮while true会自动resume只要把返回值
-- 传回去就行
-- 一般来说都是由cleaner中的trigger引起
-- 以胆守合击为例就是trigger -> SkillEffect事件 -> UseCard事件 -> 胆守
-- 此时胆守的话最后从SkillEffect事件的exec内部yield出来
-- 当前协程就应该正在执行room:useSkill函数resume会去只会让那个函数返回
if zhuran_jmp == nil or zhuran_jmp.id > yield_result.id then
zhuran_jmp = yield_result
zhuran_msg = extra_yield_result
end
-- 自己本来应该被杀的但是因为自己正在执行self:clear()而逃过一劫啊
-- 还是得标记一下被杀才行,顺便因为实际上没死所以标记被复活
self.killed = true
self.revived = true
-- 什么都不做等下轮while自己resume
else
coroutine.close(clear_co)
break
end
end
-- cleaner顺利执行完了出栈吧
local logic = RoomInstance.logic
local end_id = logic.current_event_id + 1
if self.id ~= end_id - 1 then
logic.all_game_events[end_id] = self.event
logic.current_event_id = end_id
self.end_id = end_id
else
self.end_id = self.id
end
logic.game_event_stack:pop()
-- 好了确保cleaner走完了此时中断就会进入下层事件的正常中断处理
if zhuran_jmp then
coroutine.close(self._co)
coroutine.yield(zhuran_jmp, zhuran_msg)
-- 此时仍可能出现在插结在其他事件的clear函数中
-- 但就算被交付回去了也能安然返回而不是继续while
-- 但愿如此吧
end
-- 保险而已其实如果被杀的话应该已经在前面的yield终止了
-- 但担心cleaner嵌套三国杀是这样的还是补一刀
if self.killed then return end
-- 恭喜没被杀掉,我们来执行一些事件结束之后的结算吧
Pcall(self.exit_func, self)
for _, f in ipairs(self.extra_exit_funcs) do
if type(f) == "function" then
Pcall(f, self)
end
end
end
local function breakEvent(self, extra_yield_result)
local cancelEvent = GameEvent:new(GameEvent.BreakEvent, self)
cancelEvent.toId = self.id
local notcanceled = cancelEvent:exec()
local ret, extra_ret = false, nil
if not notcanceled then
self.interrupted = true
self:clear()
ret = true
extra_ret = extra_yield_result
end
return ret, extra_ret
end
function GameEvent:exec()
local room = self.room
local logic = room.logic
local ret = false -- false or nil means this event is running normally
local extra_ret
self.parent = logic:getCurrentEvent()
if self:prepare_func() then return true end
logic.game_event_stack:push(self)
logic.current_event_id = logic.current_event_id + 1
self.id = logic.current_event_id
logic.all_game_events[self.id] = self
logic.event_recorder[self.event] = logic.event_recorder[self.event] or {}
table.insert(logic.event_recorder[self.event], self)
local co = coroutine.create(self.main_func)
self._co = co
while true do
local err, yield_result, extra_yield_result = coroutine.resume(co)
if err == false then
-- handle error, then break
if not string.find(yield_result, "__manuallyBreak") then
fk.qCritical(yield_result .. "\n" .. debug.traceback(co))
end
self.interrupted = true
self:clear()
ret = true
coroutine.close(co)
break
end
if yield_result == "__handleRequest" then
-- yield to requestLoop
coroutine.yield(yield_result, extra_yield_result)
elseif type(yield_result) == "table" and yield_result.class
and yield_result:isInstanceOf(GameEvent) then
if self ~= yield_result then
-- yield to corresponding GameEvent, first pop self from stack
self.interrupted = true
self.killed = true -- 老朱然!你不得好死
self:clear()
-- logic.game_event_stack:pop(self)
coroutine.close(co)
-- then, call yield
coroutine.yield(yield_result, extra_yield_result)
-- 如果是在cleaner/exit里面发生此类中断的话是会被cleaner原地返回的
-- 此时正常执行程序流就变成继续while循环了这是不行的
break
elseif extra_yield_result == "__breakEvent" then
if breakEvent(self) then
coroutine.close(co)
break
end
end
elseif yield_result == "__breakEvent" then
-- try to break this event
if breakEvent(self) then
coroutine.close(co)
break
end
else
-- normally exit, simply break the loop
self:clear()
extra_ret = yield_result
coroutine.close(co)
break
end
end
return ret, extra_ret
return self.interrupted, self.exec_ret
end
function GameEvent:shutdown()

View File

@ -7,6 +7,7 @@
---@field public refresh_skill_table table<Event, TriggerSkill[]>
---@field public skills string[]
---@field public game_event_stack Stack
---@field public cleaner_stack Stack
---@field public role_table string[][]
---@field public all_game_events GameEvent[]
---@field public event_recorder table<integer, GameEvent>
@ -20,6 +21,7 @@ function GameLogic:initialize(room)
self.refresh_skill_table = {}
self.skills = {} -- skillName[]
self.game_event_stack = Stack:new()
self.cleaner_stack = Stack:new()
self.all_game_events = {}
self.event_recorder = {}
self.current_event_id = 0
@ -389,9 +391,7 @@ function GameLogic:trigger(event, target, data, refresh_only)
skill_names = table.map(table.filter(skills, filter_func), Util.NameMapper)
broken = broken or (event == fk.AskForPeaches
and room:getPlayerById(data.who).hp > 0) or cur_event.revived
-- ^^^^^^^^^^^^^^^^^
-- 如果事件复活了那么其实说明事件已经死过了赶紧break掉
and room:getPlayerById(data.who).hp > 0) or cur_event.killed
if broken then break end
end
@ -408,6 +408,138 @@ function GameLogic:trigger(event, target, data, refresh_only)
return broken
end
-- 此为启动事件管理器并启动第一个事件的初始函数
function GameLogic:start()
local root_event = GameEvent:new(GameEvent.Game)
self:pushEvent(root_event)
-- 此时的协程room.main_co
-- 事件管理器协程同时也是Game事件
-- 当新事件想要exec时就切回此处由这里负责调度协程
-- 一个事件结束后也切回此处然后resume
local co = coroutine.create(root_event.main_func)
root_event._co = co
local jump_to -- shutdown函数用
while true do
-- 对于cleaner和正常事件处理更后面来的
local ne = self:getCurrentEvent()
local ce = self:getCurrentCleaner()
local e = ce and (ce.id >= ne.id and ce or ne) or ne
-- 如果正在jump的话判断是否需要继续clean否则正常继续
if e == ne and jump_to ~= nil then
e.interrupted = true
e.killed = e ~= jump_to
self:clearEvent(e)
coroutine.close(e._co)
if e == jump_to then jump_to = nil end -- shutdown结束了
e = self:getCurrentCleaner()
end
-- ret, evt解释
-- * true, nil: 中止
-- * false, nil: 正常结束
-- * true, GameEvent: 中止直到某event
-- * false, GameEvent: 未结束插入新event
-- 若jump_to不为nil表示正在中断至某某事件
local ret, evt = self:resumeEvent(e)
if evt == nil then
e.interrupted = ret
self:clearEvent(e)
coroutine.close(e._co)
elseif ret == true then
-- 跳到越早发生的事件越好
if not jump_to then
jump_to = evt
else
jump_to = jump_to.id < evt.id and jump_to or evt
end
end
end
end
---@param event GameEvent
function GameLogic:pushEvent(event)
self.game_event_stack:push(event)
self.current_event_id = self.current_event_id + 1
event.id = self.current_event_id
self.all_game_events[event.id] = event
self.event_recorder[event.event] = self.event_recorder[event.event] or {}
table.insert(self.event_recorder[event.event], event)
end
-- 一般来说从GameEvent:exec切回start再被start调用
-- 作用是启动新事件 都是结构差不多的函数
---@param event GameEvent
---@return boolean, GameEvent?
function GameLogic:resumeEvent(event, ...)
local ret, evt
local co = event._co
while true do
local err, yield_result, extra_yield_result = coroutine.resume(co, ...)
if err == false then
-- handle error, then break
if not string.find(yield_result, "__manuallyBreak") then
fk.qCritical(yield_result .. "\n" .. debug.traceback(co))
end
ret = true
break
end
if yield_result == "__handleRequest" then
-- yield to requestLoop
coroutine.yield(yield_result, extra_yield_result)
elseif type(yield_result) == "table" and yield_result.class
and yield_result:isInstanceOf(GameEvent) then
if extra_yield_result == "__newEvent" then
ret, evt = false, yield_result
break
elseif extra_yield_result == "__breakEvent" then
ret, evt = true, yield_result
if event.event ~= GameEvent.ClearEvent then break end
end
elseif yield_result == "__breakEvent" then
ret = true
if event.event ~= GameEvent.ClearEvent then break end
else
ret = false
event.exec_ret = yield_result
break
end
end
return ret, evt
end
---@return GameEvent
function GameLogic:getCurrentCleaner()
return self.cleaner_stack.t[self.cleaner_stack.p]
end
-- 事件中的清理。
-- cleaner单独开协程运行exitFunc须转到上个事件的协程内执行
-- 注意插入新event
---@param event GameEvent
function GameLogic:clearEvent(event)
if event.event == GameEvent.ClearEvent then return end
local ce = GameEvent(GameEvent.ClearEvent, event)
ce.id = self.current_event_id
local co = coroutine.create(ce.main_func)
ce._co = co
self.cleaner_stack:push(ce)
end
---@return GameEvent
function GameLogic:getCurrentEvent()
return self.game_event_stack.t[self.game_event_stack.p]

View File

@ -254,9 +254,11 @@ function Room:run()
end
local mode = Fk.game_modes[self.settings.gameMode]
self.logic = (mode.logic and mode.logic() or GameLogic):new(self)
if mode.rule then self.logic:addTriggerSkill(mode.rule) end
self.logic:run()
local logic = (mode.logic and mode.logic() or GameLogic):new(self)
self.logic = logic
if mode.rule then logic:addTriggerSkill(mode.rule) end
-- GameEvent(GameEvent.Game):exec()
logic:start()
end
------------------------------------------------------------------------

View File

@ -93,6 +93,12 @@ local function mainLoop()
if over then
-- verbose('[#] %s is finished, removing ...', tostring(room))
for _, e in ipairs(room.logic.game_event_stack.t) do
coroutine.close(e._co)
end
for _, e in ipairs(room.logic.cleaner_stack.t) do
coroutine.close(e._co)
end
room.logic = nil
runningRooms[room.id] = nil
else

View File

@ -321,6 +321,16 @@ function ServerPlayer:reconnect()
local room = self.room
self.serverplayer:setState(fk.Player_Online)
self:doNotify("Setup", json.encode{
self.id,
self._splayer:getScreenName(),
self._splayer:getAvatar(),
})
self:doNotify("AddTotalGameTime", json.encode {
self.id,
self._splayer:getTotalGameTime(),
})
self:doNotify("EnterLobby", "")
self:doNotify("EnterRoom", json.encode{
#room.players, room.timeout, room.settings,

View File

@ -46,7 +46,7 @@ public:
static int GetMicroSecond(lua_State *L) {
struct timeval tv;
gettimeofday(&tv, nullptr);
long long microsecond = tv.tv_sec * 1000000 + tv.tv_usec;
long long microsecond = (long long)tv.tv_sec * 1000000 + tv.tv_usec;
lua_pushnumber(L, microsecond);
return 1;
}