FreeKill/lua/server/gameevent.lua
notify 713bbca17a
Recorder (#178)
提供了一个简单的事件记录器机制和一个功能简单的查询函数。
在GameEvent的clear环节中,先执行默认的clear函数,再执行用户自订的clear函数。
2023-06-09 01:10:16 +08:00

292 lines
8.2 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- SPDX-License-Identifier: GPL-3.0-or-later
---@class GameEvent: Object
---@field public id integer @ 事件的id随着时间推移自动增加并分配给新事件
---@field public end_id integer @ 事件的对应结束id如果整个事件中未插入事件那么end_id就是自己的id
---@field public room Room @ room实例
---@field public event integer @ 该事件对应的EventType
---@field public data any @ 事件的附加数据,视类型而定
---@field public parent GameEvent @ 事件的父事件(栈中的上一层事件)
---@field public main_func fun(self: GameEvent) @ 事件的主函数
---@field public clear_func fun(self: GameEvent) @ 事件结束时执行的函数
---@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 interrupted boolean @ 事件是否是因为被强行中断而结束的
local GameEvent = class("GameEvent")
---@type fun(self: GameEvent)[]
GameEvent.functions = {}
---@type fun(self: GameEvent)[]
GameEvent.cleaners = {}
---@type fun(self: GameEvent)[]
GameEvent.exit_funcs = {}
local function wrapCoFunc(f, ...)
if not f then return nil end
local args = {...}
return function() return f(table.unpack(args)) end
end
local dummyFunc = Util.DummyFunc
function GameEvent:initialize(event, ...)
self.id = -1
self.end_id = -1
self.room = RoomInstance
self.event = event
self.data = { ... }
self.main_func = wrapCoFunc(GameEvent.functions[event], self) or dummyFunc
self.clear_func = GameEvent.cleaners[event] or dummyFunc
self.extra_clear_funcs = Util.DummyTable
self.exit_func = GameEvent.exit_funcs[event] or dummyFunc
self.extra_exit_funcs = Util.DummyTable
self.interrupted = false
end
function GameEvent:__tostring()
return string.format("<%s #%d>", GameEvent:translate(self.event), self.id)
end
function GameEvent:addCleaner(f)
if self.extra_clear_funcs == Util.DummyTable then
self.extra_clear_funcs = {}
end
table.insert(self.extra_clear_funcs, f)
end
function GameEvent:addExitFunc(f)
if self.extra_exit_funcs == Util.DummyTable then
self.extra_exit_funcs = {}
end
table.insert(self.extra_exit_funcs, f)
end
function GameEvent:findParent(eventType, includeSelf)
if includeSelf and self.event == eventType then return self end
local e = self.parent
repeat
if e.event == eventType then return e end
e = e.parent
until not e
return nil
end
-- 找n个id介于from和to之间的事件。
local function bin_search(events, from, to, n, func)
local left = 1
local right = #events
local mid
local ret = {}
if from < events[1].id then
mid = 1
elseif from > events[right].id then
return ret
else
while true do
if left > right then return ret end
mid = (left + right) // 2
local id = events[mid].id
local id_left = mid == 1 and -math.huge or events[mid - 1].id
if from < id then
if from >= id_left then
break
end
right = mid - 1
else
left = mid + 1
end
end
end
for i = mid, #events do
local v = events[i]
if v.id < to and func(v) then
table.insert(ret, v)
end
if #ret >= n then break end
end
return ret
end
-- 从某个区间中找出类型符合且符合func函数检测的至多n个事件。
---@param eventType integer @ 要查找的事件类型
---@param n integer @ 最多找多少个
---@param func fun(e: GameEvent): boolean @ 过滤用的函数
---@param endEvent GameEvent|nil @ 区间终止点,默认为本事件结束
---@return GameEvent[] @ 找到的符合条件的所有事件最多n个但不保证有n个
function GameEvent:searchEvents(eventType, n, func, endEvent)
local logic = self.room.logic
local events = logic.event_recorder[eventType]
local from = self.id
local to = endEvent and endEvent.id or self.end_id
if to == -1 then to = #logic.all_game_events end
n = n or 1
func = func or function() return true end
local ret
if #events < 6 then
ret = {}
for _, v in ipairs(events) do
if v.id > from and v.id < to and func(v) then
table.insert(ret, v)
end
if #ret >= n then break end
end
else
ret = bin_search(events, from, to, n, func)
end
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)
while true do
local err, yield_result, extra_yield_result = coroutine.resume(clear_co)
if err == false then
-- handle error, then break
if not string.find(yield_result, "__manuallyBreak") then
fk.qCritical(yield_result)
print(debug.traceback(co))
end
coroutine.close(clear_co)
break
end
if yield_result == "__handleRequest" then
-- yield to requestLoop
coroutine.yield(yield_result, extra_yield_result)
else
coroutine.close(clear_co)
break
end
end
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()
local err, msg
err, msg = xpcall(self.exit_func, debug.traceback, self)
if err == false then fk.qCritical(msg) end
for _, f in ipairs(self.extra_exit_funcs) do
if type(f) == "function" then
err, msg = xpcall(f, debug.traceback, self)
if err == false then fk.qCritical(msg) end
end
end
end
local function breakEvent(self, extra_yield_result)
local cancelEvent = GameEvent:new(GameEvent.BreakEvent, self)
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()
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)
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)
print(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:clear()
-- logic.game_event_stack:pop(self)
coroutine.close(co)
-- then, call yield
coroutine.yield(yield_result, extra_yield_result)
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
end
function GameEvent:shutdown()
-- yield to self and break
coroutine.yield(self, "__breakEvent")
end
return GameEvent