parent
623007aca2
commit
b75d8afe62
|
@ -1,6 +1,7 @@
|
||||||
# Compile output
|
# Compile output
|
||||||
/build/
|
/build/
|
||||||
/docs/build
|
/docs/build
|
||||||
|
/docs/.venv
|
||||||
/*.o
|
/*.o
|
||||||
/zh_CN.qm
|
/zh_CN.qm
|
||||||
/fk_ver
|
/fk_ver
|
||||||
|
|
|
@ -19,3 +19,4 @@
|
||||||
ui.rst
|
ui.rst
|
||||||
hegemony.rst
|
hegemony.rst
|
||||||
shendiaochan.rst
|
shendiaochan.rst
|
||||||
|
schedule.rst
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
.. SPDX-License-Identifier: GFDL-1.3-or-later
|
||||||
|
|
||||||
|
关于Fk在同一个Lua中运行多个游戏房间的思考
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
目前Fk的实现中,每个游戏房间是一根线程,这就出现了一个问题:
|
||||||
|
当同时游戏的房间很多的时候,会给RAM带来相当大的负担,毕竟lua_State太多了,
|
||||||
|
每个Room都有一个。 目前比较流行的模式是只需三人就可进行的斗地主,
|
||||||
|
有时候同时运行着30多桌斗地主,直接把内存推向1GB占用,云服务器那才多少点资源。
|
||||||
|
|
||||||
|
如果这么多房间都在同一个Lua运行的话,情况应该会有很大改观。
|
||||||
|
|
||||||
|
多个房间如何调度?
|
||||||
|
------------------
|
||||||
|
|
||||||
|
在当前阶段已经实现了重连和观战机制,这是借助于协程机制实现的。
|
||||||
|
当Lua正在执行delay或者等待用户答复时,Lua会从主协程中切换出来,
|
||||||
|
然后去另一个协程处理诸如旁观、重连等游戏逻辑之外的请求。
|
||||||
|
|
||||||
|
如果把这种空白时间更加充分的利用起来的话,或许就能同时执行多个房间了。
|
||||||
|
|
||||||
|
现在每个游戏房间在Lua中都是一个Room对象的实例,而Room
|
||||||
|
本身是通过一个协程执行着主游戏逻辑。
|
||||||
|
考虑修改一下,使得同一个Lua中能执行多个Room协程。
|
||||||
|
|
||||||
|
暂且考虑开个数组保存所有正在运行中的房间。
|
||||||
|
|
||||||
|
就绪队列
|
||||||
|
--------
|
||||||
|
|
||||||
|
调度器除了维护运行中房间的数组之外,还维护一个就绪队列。
|
||||||
|
|
||||||
|
当房间不处于阻塞状态时,或者已经可以脱离阻塞状态,那么就认为他就绪。
|
||||||
|
|
||||||
|
此外还有一个特殊的协程,他用来处理托管、旁观等请求。
|
||||||
|
当他的这个请求队列为空的时候,也视为他未就绪。
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
所谓阻塞状态目前指的是正在delay或者正在等候答复时。自然而然的,
|
||||||
|
脱离阻塞状态就是delay的时间已经结束或者已收到答复。
|
||||||
|
|
||||||
|
当所有房间全部未就绪时,调度器调用sleep睡一段时间,让出CPU。
|
||||||
|
|
||||||
|
当房间因为delay而延时时,可以知道他恢复就绪的确切等待时间。但如果是等待答复、
|
||||||
|
等待新请求这种依赖玩家操作的协程,其就绪时间就完全不可预测了。
|
||||||
|
正是这种不可预测的等待时间才使得我们调度器只能小睡个几毫秒。
|
||||||
|
|
||||||
|
如何多睡一段时间?
|
||||||
|
------------------
|
||||||
|
|
||||||
|
假设有多个房处于delay中,那么能睡的最长时间就是其中所有delay剩余时间的最小值。
|
||||||
|
但问题在于那些不可预测的就绪时间。如果另一个线程能告诉Lua答复已经就绪的话,
|
||||||
|
那么睡眠问题就好解决的多了。
|
||||||
|
|
||||||
|
实际上,可以借助信号量机制。当调度器开始睡觉时,肯定是调用了cpp函数。
|
||||||
|
这个cpp函数里面先将一个信号量置为0,然后再tryAcquire这个信号量一段时间。
|
||||||
|
当收到答复/收到请求时,把这个信号量+1。这样调度器就知道自己该醒过来啦。
|
||||||
|
|
||||||
|
如何避免房间等待太久?
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
目前的调度思路如下:
|
||||||
|
|
||||||
|
- 若就绪队列为空,那么从所有运行中的房间进行一遍筛选。
|
||||||
|
- 若还为空,再筛选。
|
||||||
|
- 假如第二遍还空,那就睡觉。
|
||||||
|
- 假如就绪队列不空,那么弹出第一个,然后把控制权交给这个协程,等他自己让出。
|
||||||
|
|
||||||
|
在这个过程中,由于有些房间在等待就绪队列变空的过程中满足了就绪的条件,
|
||||||
|
那么再次筛选时,有可能重复的房间又会再次位于就绪队列靠前的位置。
|
||||||
|
这虽然不会导致某个房间永久等待下去的情况,但却可能让一个房间等待太长时间,
|
||||||
|
那我就又要被艾特了。
|
||||||
|
|
||||||
|
一种或许可行的解法是,在再次刷新队列的时候,优先选出上次队列里面没有的房间。
|
||||||
|
不知道这样如何呢?
|
||||||
|
|
||||||
|
怎么把现有的改成这样?
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
CPP代码方面,首先Room已经不再是一个线程了,自然不能再当QThread的子类。
|
||||||
|
另外开个什么类来占据线程吧。
|
||||||
|
|
||||||
|
既然不是QThread的子类的话,那Room::run就得改了。原本是启动线程,现在还是改成
|
||||||
|
pushRequest吧。新请求立刻唤醒调度器,然后调度器切到请求处理协程处理新房间请求,
|
||||||
|
然后就开战!
|
||||||
|
|
||||||
|
既然要pushRequest,那么原先的请求队列也要从Room中移走了,这个得直接归属于线程。
|
||||||
|
当然,lua_State也要从Room撤走。
|
||||||
|
|
||||||
|
Lua代码方面,首先由于Engine成了所有房间共用的变量,必须杜绝所有对Engine
|
||||||
|
内变量的修改。点名cost_data和card.mark,这两个东西得通过__index大法托管给
|
||||||
|
Room挂着。剩下的没啥好说的,当然还有实现调度器。
|
||||||
|
|
||||||
|
协程池
|
||||||
|
------
|
||||||
|
|
||||||
|
虽然题文无关,但是FK确实在运行过程中不断的产生着协程。
|
||||||
|
为了避免因为不断产生新的协程导致的开销,可以使用协程池来管理。
|
||||||
|
|
||||||
|
用完的协程就存在某个数组留待下次使用。当需要启动一个新协程的时候,
|
||||||
|
先从协程池找,没有的话就新建一个。
|
|
@ -4,5 +4,36 @@
|
||||||
inspect = require "inspect"
|
inspect = require "inspect"
|
||||||
dbg = require "debugger"
|
dbg = require "debugger"
|
||||||
|
|
||||||
|
function PrintWhere()
|
||||||
|
local info = debug.getinfo(2)
|
||||||
|
local name = info.name
|
||||||
|
local line = info.currentline
|
||||||
|
local namewhat = info.namewhat
|
||||||
|
local shortsrc = info.short_src
|
||||||
|
if (namewhat == "method") and
|
||||||
|
(shortsrc ~= "[C]") and
|
||||||
|
(not string.find(shortsrc, "/lib")) then
|
||||||
|
print(shortsrc .. ":" .. line .. ": " .. name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--debug.sethook(PrintWhere, "l")
|
||||||
|
|
||||||
|
function Traceback()
|
||||||
|
print(debug.traceback())
|
||||||
|
end
|
||||||
|
|
||||||
|
local msgh = function(err)
|
||||||
|
fk.qCritical(err)
|
||||||
|
print(debug.traceback(nil, 2))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Pcall(f, ...)
|
||||||
|
local ret = { xpcall(f, msgh, ...) }
|
||||||
|
local err = table.remove(ret, 1)
|
||||||
|
if err ~= false then
|
||||||
|
return table.unpack(ret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function p(v) print(inspect(v)) end
|
function p(v) print(inspect(v)) end
|
||||||
function pt(t) for k, v in pairs(t) do print(k, v) end end
|
function pt(t) for k, v in pairs(t) do print(k, v) end end
|
||||||
|
|
|
@ -77,7 +77,6 @@ function Player:initialize()
|
||||||
self.chained = false
|
self.chained = false
|
||||||
self.dying = false
|
self.dying = false
|
||||||
self.dead = false
|
self.dead = false
|
||||||
self.state = ""
|
|
||||||
self.drank = 0
|
self.drank = 0
|
||||||
|
|
||||||
self.player_skills = {}
|
self.player_skills = {}
|
||||||
|
|
|
@ -57,6 +57,20 @@ function Skill:initialize(name, frequency)
|
||||||
self.attached_equip = nil
|
self.attached_equip = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Skill:__index(k)
|
||||||
|
if k == "cost_data" then
|
||||||
|
return Fk:currentRoom().skill_costs[self.name]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Skill:__newindex(k, v)
|
||||||
|
if k == "cost_data" then
|
||||||
|
Fk:currentRoom().skill_costs[self.name] = v
|
||||||
|
else
|
||||||
|
rawset(self, k, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function Skill:__tostring()
|
function Skill:__tostring()
|
||||||
return "<Skill " .. self.name .. ">"
|
return "<Skill " .. self.name .. ">"
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,7 +26,9 @@ Util.lockTable = function(t)
|
||||||
return setmetatable({}, new_mt)
|
return setmetatable({}, new_mt)
|
||||||
end
|
end
|
||||||
|
|
||||||
function printf(fmt, ...) print(string.format(fmt, ...)) end
|
function printf(fmt, ...)
|
||||||
|
print(string.format(fmt, ...))
|
||||||
|
end
|
||||||
|
|
||||||
-- the iterator of QList object
|
-- the iterator of QList object
|
||||||
local qlist_iterator = function(list, n)
|
local qlist_iterator = function(list, n)
|
||||||
|
@ -307,34 +309,6 @@ function string:endsWith(e)
|
||||||
return e == "" or self:sub(-#e) == e
|
return e == "" or self:sub(-#e) == e
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class Sql
|
|
||||||
Sql = {
|
|
||||||
---@param filename string
|
|
||||||
open = function(filename)
|
|
||||||
return fk.OpenDatabase(filename)
|
|
||||||
end,
|
|
||||||
|
|
||||||
---@param db fk.SQLite3
|
|
||||||
close = function(db)
|
|
||||||
fk.CloseDatabase(db)
|
|
||||||
end,
|
|
||||||
|
|
||||||
--- Execute an SQL statement.
|
|
||||||
---@param db fk.SQLite3
|
|
||||||
---@param sql string
|
|
||||||
exec = function(db, sql)
|
|
||||||
fk.ExecSQL(db, sql)
|
|
||||||
end,
|
|
||||||
|
|
||||||
--- Execute a `SELECT` SQL statement.
|
|
||||||
---@param db fk.SQLite3
|
|
||||||
---@param sql string
|
|
||||||
---@return table[] @ Array of Json object, the key is column name and value is row value
|
|
||||||
exec_select = function(db, sql)
|
|
||||||
return json.decode(fk.SelectFromDb(db, sql))
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
FileIO = {
|
FileIO = {
|
||||||
pwd = fk.QmlBackend_pwd,
|
pwd = fk.QmlBackend_pwd,
|
||||||
ls = function(filename)
|
ls = function(filename)
|
||||||
|
|
|
@ -8,42 +8,12 @@ FPlayer = {}
|
||||||
---@return integer id
|
---@return integer id
|
||||||
function FPlayer:getId()end
|
function FPlayer:getId()end
|
||||||
|
|
||||||
---@param id integer
|
|
||||||
function FPlayer:setId(id)end
|
|
||||||
|
|
||||||
---@return string name
|
---@return string name
|
||||||
function FPlayer:getScreenName()end
|
function FPlayer:getScreenName()end
|
||||||
|
|
||||||
---@param name string
|
|
||||||
function FPlayer:setScreenName(name)end
|
|
||||||
|
|
||||||
---@return string avatar
|
---@return string avatar
|
||||||
function FPlayer:getAvatar()end
|
function FPlayer:getAvatar()end
|
||||||
|
|
||||||
---@param avatar string
|
|
||||||
function FPlayer:setAvatar(avatar)end
|
|
||||||
|
|
||||||
---@return string state
|
|
||||||
function FPlayer:getStateString()end
|
|
||||||
|
|
||||||
---@param state string
|
|
||||||
function FPlayer:setStateString(state)end
|
|
||||||
|
|
||||||
---@class fk.ServerPlayer : fk.Player
|
|
||||||
FServerPlayer = {}
|
|
||||||
|
|
||||||
---@return fk.Server
|
|
||||||
function FServerPlayer:getServer()end
|
|
||||||
|
|
||||||
---@return fk.Room
|
|
||||||
function FServerPlayer:getRoom()end
|
|
||||||
|
|
||||||
---@param room fk.Room
|
|
||||||
function FServerPlayer:setRoom(room)end
|
|
||||||
|
|
||||||
---@param msg string
|
|
||||||
function FServerPlayer:speak(msg)end
|
|
||||||
|
|
||||||
--- Send a request to client, and allow client to reply within *timeout* seconds.
|
--- Send a request to client, and allow client to reply within *timeout* seconds.
|
||||||
---
|
---
|
||||||
--- *timeout* must not be negative or **nil**.
|
--- *timeout* must not be negative or **nil**.
|
||||||
|
|
|
@ -1,52 +1 @@
|
||||||
-- SPDX-License-Identifier: GPL-3.0-or-later
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class fk.Server
|
|
||||||
FServer = {}
|
|
||||||
|
|
||||||
---@type fk.Server
|
|
||||||
fk.ServerInstance = {}
|
|
||||||
|
|
||||||
---@class fk.Room
|
|
||||||
--- Room (C++)
|
|
||||||
FRoom = {}
|
|
||||||
|
|
||||||
---@param owner fk.ServerPlayer
|
|
||||||
---@param name string
|
|
||||||
---@param capacity integer
|
|
||||||
function FServer:createRoom(owner,name,capacity)end
|
|
||||||
|
|
||||||
---@param id integer
|
|
||||||
---@return fk.Room
|
|
||||||
function FServer:findRoom(id)end
|
|
||||||
|
|
||||||
---@return fk.Room
|
|
||||||
function FServer:lobby()end
|
|
||||||
|
|
||||||
---@param id integer
|
|
||||||
---@return fk.ServerPlayer
|
|
||||||
function FServer:findPlayer(id)end
|
|
||||||
|
|
||||||
---@return fk.SQLite3
|
|
||||||
function FServer:getDatabase()end
|
|
||||||
|
|
||||||
function FRoom:getServer()end
|
|
||||||
function FRoom:getId()end
|
|
||||||
function FRoom:isLobby()end
|
|
||||||
function FRoom:getName()end
|
|
||||||
function FRoom:setName(name)end
|
|
||||||
function FRoom:getCapacity()end
|
|
||||||
function FRoom:setCapacity(capacity)end
|
|
||||||
function FRoom:isFull()end
|
|
||||||
function FRoom:isAbandoned()end
|
|
||||||
function FRoom:addPlayer(player)end
|
|
||||||
function FRoom:removePlayer(player)end
|
|
||||||
function FRoom:getOwner()end
|
|
||||||
function FRoom:setOwner(owner)end
|
|
||||||
function FRoom:getPlayers()end
|
|
||||||
function FRoom:findPlayer(id)end
|
|
||||||
function FRoom:getTimeout()end
|
|
||||||
function FRoom:isStarted()end
|
|
||||||
function FRoom:doBroadcastNotify(targets,command,jsonData)end
|
|
||||||
function FRoom:gameOver()end
|
|
||||||
|
|
|
@ -1,22 +1 @@
|
||||||
-- SPDX-License-Identifier: GPL-3.0-or-later
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class fk.SQLite3
|
|
||||||
SQLite3 = {}
|
|
||||||
|
|
||||||
---@param filename string
|
|
||||||
---@return fk.SQLite3
|
|
||||||
function fk.OpenDatabase(filename)end
|
|
||||||
|
|
||||||
---@param db fk.SQLite3
|
|
||||||
---@param sql string
|
|
||||||
---@return string jsonData
|
|
||||||
function fk.SelectFromDb(db, sql)end
|
|
||||||
|
|
||||||
---@param db fk.SQLite3
|
|
||||||
---@param sql string
|
|
||||||
function fk.ExecSQL(db, sql)end
|
|
||||||
|
|
||||||
---@param db fk.SQLite3
|
|
||||||
function fk.CloseDatabase(db)end
|
|
||||||
|
|
|
@ -47,16 +47,6 @@ local function discardInit(room, player)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function checkNoHuman(room)
|
|
||||||
for _, p in ipairs(room.players) do
|
|
||||||
-- TODO: trust
|
|
||||||
if p.serverplayer:getStateString() == "online" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
room:gameOver("")
|
|
||||||
end
|
|
||||||
|
|
||||||
GameEvent.functions[GameEvent.DrawInitial] = function(self)
|
GameEvent.functions[GameEvent.DrawInitial] = function(self)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
|
|
||||||
|
@ -96,6 +86,13 @@ GameEvent.functions[GameEvent.DrawInitial] = function(self)
|
||||||
local currentTime = os.time()
|
local currentTime = os.time()
|
||||||
local elapsed = 0
|
local elapsed = 0
|
||||||
|
|
||||||
|
for _, id in ipairs(luck_data.playerList) do
|
||||||
|
local pl = room:getPlayerById(id)
|
||||||
|
if luck_data[id].luckTime > 0 then
|
||||||
|
pl.serverplayer:setThinking(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
elapsed = os.time() - currentTime
|
elapsed = os.time() - currentTime
|
||||||
if remainTime - elapsed <= 0 then
|
if remainTime - elapsed <= 0 then
|
||||||
|
@ -112,14 +109,16 @@ GameEvent.functions[GameEvent.DrawInitial] = function(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, id in ipairs(ldata.playerList) do
|
for _, id in ipairs(ldata.playerList) do
|
||||||
if room:getPlayerById(id)._splayer:getStateString() ~= "online" then
|
local pl = room:getPlayerById(id)
|
||||||
|
if pl._splayer:getState() ~= fk.Player_Online then
|
||||||
ldata[id].luckTime = 0
|
ldata[id].luckTime = 0
|
||||||
|
pl.serverplayer:setThinking(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- room:setTag("LuckCardData", ldata)
|
-- room:setTag("LuckCardData", ldata)
|
||||||
|
|
||||||
checkNoHuman(room)
|
room:checkNoHuman()
|
||||||
|
|
||||||
coroutine.yield("__handleRequest", (remainTime - elapsed) * 1000)
|
coroutine.yield("__handleRequest", (remainTime - elapsed) * 1000)
|
||||||
end
|
end
|
||||||
|
|
|
@ -186,13 +186,10 @@ function GameEvent:clear()
|
||||||
|
|
||||||
logic.game_event_stack:pop()
|
logic.game_event_stack:pop()
|
||||||
|
|
||||||
local err, msg
|
Pcall(self.exit_func, self)
|
||||||
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
|
for _, f in ipairs(self.extra_exit_funcs) do
|
||||||
if type(f) == "function" then
|
if type(f) == "function" then
|
||||||
err, msg = xpcall(f, debug.traceback, self)
|
Pcall(f, self)
|
||||||
if err == false then fk.qCritical(msg) end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -105,6 +105,8 @@ request_handlers["luckcard"] = function(room, id, reqlist)
|
||||||
|
|
||||||
if pdata.luckTime > 0 then
|
if pdata.luckTime > 0 then
|
||||||
p:doNotify("AskForLuckCard", pdata.luckTime)
|
p:doNotify("AskForLuckCard", pdata.luckTime)
|
||||||
|
else
|
||||||
|
p.serverplayer:setThinking(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
room:setTag("LuckCardData", luck_data)
|
room:setTag("LuckCardData", luck_data)
|
||||||
|
@ -132,23 +134,34 @@ request_handlers["changeself"] = function(room, id, reqlist)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
request_handlers["newroom"] = function(s, id)
|
||||||
|
s:registerRoom(id)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 处理异步请求的协程,本身也是个死循环就是了。
|
||||||
|
-- 为了适应调度器,目前又暂且将请求分为“耗时请求”和不耗时请求。
|
||||||
|
-- 耗时请求处理后会立刻挂起。不耗时的请求则会不断处理直到请求队列空后再挂起。
|
||||||
local function requestLoop(self)
|
local function requestLoop(self)
|
||||||
local rest_time = 0
|
|
||||||
while true do
|
while true do
|
||||||
local ret = false
|
local ret = false
|
||||||
local request = self.room:fetchRequest()
|
local request = self.thread:fetchRequest()
|
||||||
if request ~= "" then
|
if request ~= "" then
|
||||||
ret = true
|
ret = true
|
||||||
local reqlist = request:split(",")
|
local reqlist = request:split(",")
|
||||||
local id = tonumber(reqlist[1])
|
local roomId = tonumber(table.remove(reqlist, 1))
|
||||||
local command = reqlist[2]
|
local room = self:getRoom(roomId)
|
||||||
request_handlers[command](self, id, reqlist)
|
|
||||||
elseif rest_time > 10 then
|
if room then
|
||||||
-- let current thread sleep 10ms
|
RoomInstance = room
|
||||||
-- otherwise CPU usage will be 100% (infinite yield <-> resume loop)
|
local id = tonumber(reqlist[1])
|
||||||
fk.QThread_msleep(10)
|
local command = reqlist[2]
|
||||||
|
request_handlers[command](room, id, reqlist)
|
||||||
|
RoomInstance = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not ret then
|
||||||
|
coroutine.yield()
|
||||||
end
|
end
|
||||||
rest_time = coroutine.yield(ret)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
--- 一个房间中只有一个Room实例,保存在RoomInstance全局变量中。
|
--- 一个房间中只有一个Room实例,保存在RoomInstance全局变量中。
|
||||||
---@class Room : Object
|
---@class Room : Object
|
||||||
---@field public room fk.Room @ C++层面的Room类实例,别管他就是了,用不着
|
---@field public room fk.Room @ C++层面的Room类实例,别管他就是了,用不着
|
||||||
|
---@field public id integer @ 房间的id
|
||||||
|
---@field private main_co any @ 本房间的主协程
|
||||||
---@field public players ServerPlayer[] @ 这个房间中所有参战玩家
|
---@field public players ServerPlayer[] @ 这个房间中所有参战玩家
|
||||||
---@field public alive_players ServerPlayer[] @ 所有还活着的玩家
|
---@field public alive_players ServerPlayer[] @ 所有还活着的玩家
|
||||||
---@field public observers fk.ServerPlayer[] @ 旁观者清单,这是c++玩家列表,别乱动
|
---@field public observers fk.ServerPlayer[] @ 旁观者清单,这是c++玩家列表,别乱动
|
||||||
|
@ -24,6 +26,7 @@
|
||||||
---@field public logic GameLogic @ 这个房间使用的游戏逻辑,可能根据游戏模式而变动
|
---@field public logic GameLogic @ 这个房间使用的游戏逻辑,可能根据游戏模式而变动
|
||||||
---@field public request_queue table<userdata, table>
|
---@field public request_queue table<userdata, table>
|
||||||
---@field public request_self table<integer, integer>
|
---@field public request_self table<integer, integer>
|
||||||
|
---@field public skill_costs table<string, any> @ 存放skill.cost_data用
|
||||||
local Room = class("Room")
|
local Room = class("Room")
|
||||||
|
|
||||||
-- load classes used by the game
|
-- load classes used by the game
|
||||||
|
@ -62,46 +65,7 @@ dofile "lua/server/ai/init.lua"
|
||||||
---@param _room fk.Room
|
---@param _room fk.Room
|
||||||
function Room:initialize(_room)
|
function Room:initialize(_room)
|
||||||
self.room = _room
|
self.room = _room
|
||||||
_room.startGame = function(_self)
|
self.id = _room:getId()
|
||||||
Room.initialize(self, _room) -- clear old data
|
|
||||||
self.settings = json.decode(_room:settings())
|
|
||||||
Fk.disabled_packs = self.settings.disabledPack
|
|
||||||
Fk.disabled_generals = self.settings.disabledGenerals
|
|
||||||
local main_co = coroutine.create(function()
|
|
||||||
self:run()
|
|
||||||
end)
|
|
||||||
local request_co = coroutine.create(function()
|
|
||||||
self:requestLoop()
|
|
||||||
end)
|
|
||||||
local ret, err_msg, rest_time = true, true
|
|
||||||
while not self.game_finished do
|
|
||||||
ret, err_msg, rest_time = coroutine.resume(main_co, err_msg)
|
|
||||||
|
|
||||||
-- handle error
|
|
||||||
if ret == false then
|
|
||||||
fk.qCritical(err_msg)
|
|
||||||
print(debug.traceback(main_co))
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
ret, err_msg = coroutine.resume(request_co, rest_time)
|
|
||||||
if ret == false then
|
|
||||||
fk.qCritical(err_msg)
|
|
||||||
print(debug.traceback(request_co))
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If ret == true, then when err_msg is true, that means no request
|
|
||||||
end
|
|
||||||
|
|
||||||
coroutine.close(main_co)
|
|
||||||
coroutine.close(request_co)
|
|
||||||
|
|
||||||
if not self.game_finished then
|
|
||||||
self:doBroadcastNotify("GameOver", "")
|
|
||||||
self.room:gameOver()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self.players = {}
|
self.players = {}
|
||||||
self.alive_players = {}
|
self.alive_players = {}
|
||||||
|
@ -123,8 +87,119 @@ function Room:initialize(_room)
|
||||||
end
|
end
|
||||||
self.request_queue = {}
|
self.request_queue = {}
|
||||||
self.request_self = {}
|
self.request_self = {}
|
||||||
|
self.skill_costs = {}
|
||||||
|
|
||||||
|
self.settings = json.decode(self.room:settings())
|
||||||
|
Fk.disabled_packs = self.settings.disabledPack
|
||||||
|
Fk.disabled_generals = self.settings.disabledGenerals
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- 供调度器使用的函数。能让房间开始运行/从挂起状态恢复。
|
||||||
|
function Room:resume()
|
||||||
|
-- 如果还没运行的话就先创建自己的主协程
|
||||||
|
if not self.main_co then
|
||||||
|
self.main_co = coroutine.create(function()
|
||||||
|
self:run()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local ret, err_msg, rest_time = true, true, nil
|
||||||
|
local main_co = self.main_co
|
||||||
|
|
||||||
|
if self:checkNoHuman() then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not self.game_finished then
|
||||||
|
ret, err_msg, rest_time = coroutine.resume(main_co, err_msg)
|
||||||
|
|
||||||
|
-- handle error
|
||||||
|
if ret == false then
|
||||||
|
fk.qCritical(err_msg)
|
||||||
|
print(debug.traceback(main_co))
|
||||||
|
goto GAME_OVER
|
||||||
|
end
|
||||||
|
|
||||||
|
if rest_time == "over" then
|
||||||
|
goto GAME_OVER
|
||||||
|
end
|
||||||
|
|
||||||
|
return false, rest_time
|
||||||
|
end
|
||||||
|
|
||||||
|
::GAME_OVER::
|
||||||
|
coroutine.close(main_co)
|
||||||
|
self.main_co = nil
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 供调度器使用的函数,用来指示房间是否就绪。
|
||||||
|
-- 如果没有就绪的话,可能会返回第二个值来告诉调度器自己还有多久就绪。
|
||||||
|
function Room:isReady()
|
||||||
|
-- 没有活人了?那就告诉调度器我就绪了,恢复时候就会自己杀掉
|
||||||
|
if self:checkNoHuman(true) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 因为delay函数而延时:判断延时是否已经结束。
|
||||||
|
-- 注意整个delay函数的实现都搬到这来了,delay本身只负责挂起协程了。
|
||||||
|
if self.in_delay then
|
||||||
|
local rest = self.delay_duration - (os.getms() - self.delay_start) / 1000
|
||||||
|
if rest <= 0 then
|
||||||
|
self.in_delay = false
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false, rest
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 剩下的就是因为等待应答而未就绪了
|
||||||
|
-- 检查所有正在等回答的玩家,如果已经过了烧条时间
|
||||||
|
-- 那么就不认为他还需要时间就绪了
|
||||||
|
-- 然后在调度器第二轮刷新的时候就应该能返回自己已就绪
|
||||||
|
local ret = true
|
||||||
|
local rest
|
||||||
|
for _, p in ipairs(self.players) do
|
||||||
|
-- 这里判断的话需要用_splayer了,不然一控多的情况下会导致重复判断
|
||||||
|
if p._splayer:thinking() then
|
||||||
|
ret = false
|
||||||
|
-- 烧条烧光了的话就把thinking设为false
|
||||||
|
rest = p.request_timeout * 1000 - (os.getms() -
|
||||||
|
p.request_start) / 1000
|
||||||
|
|
||||||
|
if rest <= 0 then
|
||||||
|
p._splayer:setThinking(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ret, (rest and rest > 1) and rest or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Room:checkNoHuman(chkOnly)
|
||||||
|
if #self.players == 0 then return end
|
||||||
|
|
||||||
|
for _, p in ipairs(self.players) do
|
||||||
|
-- TODO: trust
|
||||||
|
if p.serverplayer:getState() == fk.Player_Online then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not chkOnly then
|
||||||
|
self:gameOver("")
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function Room:__tostring()
|
||||||
|
return string.format("<Room #%d>", self.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[ 敢删就寄,算了
|
||||||
|
function Room:__gc()
|
||||||
|
self.room:checkAbandoned()
|
||||||
|
end
|
||||||
|
--]]
|
||||||
|
|
||||||
--- 正式在这个房间中开始游戏。
|
--- 正式在这个房间中开始游戏。
|
||||||
---
|
---
|
||||||
--- 当这个函数返回之后,整个Room线程也宣告结束。
|
--- 当这个函数返回之后,整个Room线程也宣告结束。
|
||||||
|
@ -636,6 +711,12 @@ function Room:doRaceRequest(command, players, jsonData)
|
||||||
table.removeOne(players, p)
|
table.removeOne(players, p)
|
||||||
table.insertIfNeed(canceled_players, p)
|
table.insertIfNeed(canceled_players, p)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- 骗过调度器让他以为自己尚未就绪
|
||||||
|
if p.id > 0 then
|
||||||
|
p.request_timeout = remainTime - elapsed
|
||||||
|
p.serverplayer:setThinking(true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if winner then
|
if winner then
|
||||||
self:doBroadcastNotify("CancelRequest", "")
|
self:doBroadcastNotify("CancelRequest", "")
|
||||||
|
@ -657,21 +738,17 @@ function Room:doRaceRequest(command, players, jsonData)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
Room.requestLoop = require "server.request"
|
|
||||||
|
|
||||||
--- 延迟一段时间。
|
--- 延迟一段时间。
|
||||||
---
|
---
|
||||||
--- 这个函数只应该在主协程中使用。
|
--- 这个函数不应该在请求处理协程中使用。
|
||||||
---@param ms integer @ 要延迟的毫秒数
|
---@param ms integer @ 要延迟的毫秒数
|
||||||
function Room:delay(ms)
|
function Room:delay(ms)
|
||||||
local start = os.getms()
|
local start = os.getms()
|
||||||
while true do
|
self.delay_start = start
|
||||||
local rest = ms - (os.getms() - start) / 1000
|
self.delay_duration = ms
|
||||||
if rest <= 0 then
|
self.in_delay = true
|
||||||
break
|
coroutine.yield("__handleRequest", ms)
|
||||||
end
|
|
||||||
coroutine.yield("__handleRequest", rest)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 向多名玩家告知一次移牌行为。
|
--- 向多名玩家告知一次移牌行为。
|
||||||
|
@ -1158,7 +1235,7 @@ function Room:askForGeneral(player, generals, n)
|
||||||
if #generals == n then return n == 1 and generals[1] or generals end
|
if #generals == n then return n == 1 and generals[1] or generals end
|
||||||
local defaultChoice = table.random(generals, n)
|
local defaultChoice = table.random(generals, n)
|
||||||
|
|
||||||
if (player.state == "online") then
|
if (player.serverplayer:getState() == fk.Player_Online) then
|
||||||
local result = self:doRequest(player, command, json.encode{ generals, n })
|
local result = self:doRequest(player, command, json.encode{ generals, n })
|
||||||
local choices
|
local choices
|
||||||
if result == "" then
|
if result == "" then
|
||||||
|
@ -2766,6 +2843,8 @@ end
|
||||||
--- 结束一局游戏。
|
--- 结束一局游戏。
|
||||||
---@param winner string @ 获胜的身份,空字符串表示平局
|
---@param winner string @ 获胜的身份,空字符串表示平局
|
||||||
function Room:gameOver(winner)
|
function Room:gameOver(winner)
|
||||||
|
if not self.game_started then return end
|
||||||
|
|
||||||
self.logic:trigger(fk.GameFinished, nil, winner)
|
self.logic:trigger(fk.GameFinished, nil, winner)
|
||||||
self.game_started = false
|
self.game_started = false
|
||||||
self.game_finished = true
|
self.game_finished = true
|
||||||
|
@ -2794,7 +2873,16 @@ function Room:gameOver(winner)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.room:gameOver()
|
self.room:gameOver()
|
||||||
coroutine.yield("__handleRequest", 0)
|
|
||||||
|
if table.contains(
|
||||||
|
{ "running", "normal" },
|
||||||
|
coroutine.status(self.main_co)
|
||||||
|
) then
|
||||||
|
coroutine.yield("__handleRequest", "over")
|
||||||
|
else
|
||||||
|
coroutine.close(self.main_co)
|
||||||
|
self.main_co = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param card Card
|
---@param card Card
|
||||||
|
@ -2913,6 +3001,4 @@ function Room:updateQuestSkillState(player, skillName, failed)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function CreateRoom(_room)
|
return Room
|
||||||
RoomInstance = Room:new(_room)
|
|
||||||
end
|
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
local Room = require "server.room"
|
||||||
|
|
||||||
|
--[[
|
||||||
|
local verbose = function(...)
|
||||||
|
printf(...)
|
||||||
|
end
|
||||||
|
--]]
|
||||||
|
|
||||||
|
-- 所有当前正在运行的房间(即游戏尚未结束的房间)
|
||||||
|
---@type table<integer, Room>
|
||||||
|
local runningRooms = {}
|
||||||
|
|
||||||
|
-- 所有处于就绪态的房间,以及request协程(如果就绪的话)
|
||||||
|
---@type Room[]
|
||||||
|
local readyRooms = {}
|
||||||
|
|
||||||
|
local requestCo = coroutine.create(function(room)
|
||||||
|
require "server.request"(room)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- 仿照Room接口编写的request协程处理器
|
||||||
|
local requestRoom = setmetatable({
|
||||||
|
|
||||||
|
-- minDelayTime 是当没有任何就绪房间时,可以睡眠的时间。
|
||||||
|
-- 因为这个时间是所有房间预期就绪用时的最小值,故称为minDelayTime。
|
||||||
|
minDelayTime = -1,
|
||||||
|
|
||||||
|
getRoom = function(_, roomId)
|
||||||
|
return runningRooms[roomId]
|
||||||
|
end,
|
||||||
|
|
||||||
|
resume = function(self)
|
||||||
|
local err, msg = coroutine.resume(requestCo, self)
|
||||||
|
if err == false then
|
||||||
|
fk.qCritical(msg)
|
||||||
|
print(debug.traceback(requestCo))
|
||||||
|
end
|
||||||
|
return nil, 0
|
||||||
|
end,
|
||||||
|
|
||||||
|
isReady = function(self)
|
||||||
|
return self.thread:hasRequest()
|
||||||
|
end,
|
||||||
|
|
||||||
|
registerRoom = function(self, id)
|
||||||
|
local cRoom = self.thread:getRoom(id)
|
||||||
|
local room = Room:new(cRoom)
|
||||||
|
runningRooms[room.id] = room
|
||||||
|
end,
|
||||||
|
|
||||||
|
}, {
|
||||||
|
__tostring = function()
|
||||||
|
return "<Request Room>"
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
runningRooms[-1] = requestRoom
|
||||||
|
|
||||||
|
-- 从所有运行中房间中挑出就绪的房间。
|
||||||
|
-- 方法暂时就是最简单的遍历。
|
||||||
|
local function refreshReadyRooms()
|
||||||
|
-- verbose '[+] Refreshing ready queue...'
|
||||||
|
for k, v in pairs(runningRooms) do
|
||||||
|
local ready, rest = v:isReady()
|
||||||
|
if ready then
|
||||||
|
table.insertIfNeed(readyRooms, v)
|
||||||
|
elseif rest and rest >= 0 then
|
||||||
|
local time = requestRoom.minDelayTime
|
||||||
|
time = math.min((time <= 0 and 9999999 or time), rest)
|
||||||
|
requestRoom.minDelayTime = math.ceil(time)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- verbose('[+] now have %d ready rooms...', #readyRooms)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 主循环。只要线程没有被杀掉,就一直循环下去。
|
||||||
|
-- 函数每轮循环会从队列中取一个元素并交给控制权,
|
||||||
|
-- 如果没有,则尝试刷新队列,无法刷新则开始睡眠。
|
||||||
|
local function mainLoop()
|
||||||
|
-- request协程的专用特判变量。因为处理request不应当重置睡眠时长
|
||||||
|
local rest_sleep_time
|
||||||
|
|
||||||
|
while not requestRoom.thread:isTerminated() do
|
||||||
|
local room = table.remove(readyRooms, 1)
|
||||||
|
if room then
|
||||||
|
-- verbose '============= LOOP =============='
|
||||||
|
-- verbose('[*] Switching to %s...', tostring(room))
|
||||||
|
|
||||||
|
RoomInstance = (room ~= requestRoom and room or nil)
|
||||||
|
local over, rest = room:resume()
|
||||||
|
RoomInstance = nil
|
||||||
|
|
||||||
|
if over then
|
||||||
|
-- verbose('[#] %s is finished, removing ...', tostring(room))
|
||||||
|
room.logic = nil
|
||||||
|
runningRooms[room.id] = nil
|
||||||
|
else
|
||||||
|
local time = requestRoom.minDelayTime
|
||||||
|
if room == requestRoom then
|
||||||
|
rest = rest_sleep_time
|
||||||
|
end
|
||||||
|
|
||||||
|
if rest and rest >= 0 then
|
||||||
|
time = math.min((time <= 0 and 9999999 or time), rest)
|
||||||
|
else
|
||||||
|
time = -1
|
||||||
|
end
|
||||||
|
requestRoom.minDelayTime = math.ceil(time)
|
||||||
|
-- verbose("[+] minDelay is %d ms...", requestRoom.minDelayTime)
|
||||||
|
-- verbose('[-] %s successfully yielded, %d ready rooms left...',
|
||||||
|
-- tostring(room), #readyRooms)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
refreshReadyRooms()
|
||||||
|
if #readyRooms == 0 then
|
||||||
|
refreshReadyRooms()
|
||||||
|
if #readyRooms == 0 then
|
||||||
|
local time = requestRoom.minDelayTime
|
||||||
|
-- verbose('[.] Sleeping for %d ms...', time)
|
||||||
|
local cur = os.getms()
|
||||||
|
|
||||||
|
-- 调用RoomThread的trySleep函数开始真正的睡眠。会被wakeUp(c++)唤醒。
|
||||||
|
requestRoom.thread:trySleep(time)
|
||||||
|
|
||||||
|
-- verbose('[!] Waked up after %f ms...', (os.getms() - cur) / 1000)
|
||||||
|
|
||||||
|
if time > 0 then
|
||||||
|
rest_sleep_time = math.floor(time - (os.getms() - cur) / 1000)
|
||||||
|
else
|
||||||
|
rest_sleep_time = -1
|
||||||
|
end
|
||||||
|
|
||||||
|
requestRoom.minDelayTime = -1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- verbose '=========== LOOP END ============'
|
||||||
|
-- verbose '[:)] Goodbye!'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 当Cpp侧的RoomThread运行时,以下这个函数就是这个线程的主函数。
|
||||||
|
-- 而这个函数里面又调用了上面的mainLoop。
|
||||||
|
function InitScheduler(_thread)
|
||||||
|
requestRoom.thread = _thread
|
||||||
|
Pcall(mainLoop)
|
||||||
|
end
|
|
@ -24,7 +24,6 @@ function ServerPlayer:initialize(_self)
|
||||||
self._splayer = _self -- 真正在玩的玩家
|
self._splayer = _self -- 真正在玩的玩家
|
||||||
self._observers = { _self } -- "旁观"中的玩家,然而不包括真正的旁观者
|
self._observers = { _self } -- "旁观"中的玩家,然而不包括真正的旁观者
|
||||||
self.id = _self:getId()
|
self.id = _self:getId()
|
||||||
self.state = _self:getStateString()
|
|
||||||
self.room = nil
|
self.room = nil
|
||||||
|
|
||||||
-- Below are for doBroadcastRequest
|
-- Below are for doBroadcastRequest
|
||||||
|
@ -86,46 +85,36 @@ function ServerPlayer:doRequest(command, jsonData, timeout)
|
||||||
self.serverplayer:doRequest(command, jsonData, timeout)
|
self.serverplayer:doRequest(command, jsonData, timeout)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function checkNoHuman(room)
|
|
||||||
for _, p in ipairs(room.players) do
|
|
||||||
-- TODO: trust
|
|
||||||
if p.serverplayer:getStateString() == "online" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
room:gameOver("")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function _waitForReply(player, timeout)
|
local function _waitForReply(player, timeout)
|
||||||
local result
|
local result
|
||||||
local start = os.getms()
|
local start = os.getms()
|
||||||
local state = player.serverplayer:getStateString()
|
local state = player.serverplayer:getState()
|
||||||
if state ~= "online" then
|
player.request_timeout = timeout
|
||||||
if state ~= "robot" then
|
player.request_start = start
|
||||||
checkNoHuman(player.room)
|
if state ~= fk.Player_Online then
|
||||||
|
if state ~= fk.Player_Robot then
|
||||||
|
player.room:checkNoHuman()
|
||||||
player.room:delay(500)
|
player.room:delay(500)
|
||||||
return "__cancel"
|
return "__cancel"
|
||||||
end
|
end
|
||||||
-- Let AI make reply. First handle request
|
-- Let AI make reply. First handle request
|
||||||
local ret_msg = true
|
-- coroutine.yield("__handleRequest", 0)
|
||||||
while ret_msg do
|
|
||||||
-- when ret_msg is false, that means there is no request in the queue
|
|
||||||
ret_msg = coroutine.yield("__handleRequest", 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
checkNoHuman(player.room)
|
player.room:checkNoHuman()
|
||||||
player.ai:readRequestData()
|
player.ai:readRequestData()
|
||||||
local reply = player.ai:makeReply()
|
local reply = player.ai:makeReply()
|
||||||
return reply
|
return reply
|
||||||
end
|
end
|
||||||
while true do
|
while true do
|
||||||
|
player.serverplayer:setThinking(true)
|
||||||
result = player.serverplayer:waitForReply(0)
|
result = player.serverplayer:waitForReply(0)
|
||||||
if result ~= "__notready" then
|
if result ~= "__notready" then
|
||||||
|
player.serverplayer:setThinking(false)
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
local rest = timeout * 1000 - (os.getms() - start) / 1000
|
local rest = timeout * 1000 - (os.getms() - start) / 1000
|
||||||
if timeout and rest <= 0 then
|
if timeout and rest <= 0 then
|
||||||
|
player.serverplayer:setThinking(false)
|
||||||
return ""
|
return ""
|
||||||
end
|
end
|
||||||
coroutine.yield("__handleRequest", rest)
|
coroutine.yield("__handleRequest", rest)
|
||||||
|
@ -277,7 +266,7 @@ end
|
||||||
|
|
||||||
function ServerPlayer:reconnect()
|
function ServerPlayer:reconnect()
|
||||||
local room = self.room
|
local room = self.room
|
||||||
self.serverplayer:setStateString("online")
|
self.serverplayer:setState(fk.Player_Online)
|
||||||
|
|
||||||
self:doNotify("Setup", json.encode{
|
self:doNotify("Setup", json.encode{
|
||||||
self.id,
|
self.id,
|
||||||
|
|
|
@ -11,6 +11,7 @@ set(freekill_SRCS
|
||||||
"server/server.cpp"
|
"server/server.cpp"
|
||||||
"server/serverplayer.cpp"
|
"server/serverplayer.cpp"
|
||||||
"server/room.cpp"
|
"server/room.cpp"
|
||||||
|
"server/roomthread.cpp"
|
||||||
"ui/qmlbackend.cpp"
|
"ui/qmlbackend.cpp"
|
||||||
"swig/freekill-wrap.cxx"
|
"swig/freekill-wrap.cxx"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "router.h"
|
#include "router.h"
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "client_socket.h"
|
#include "client_socket.h"
|
||||||
|
#include "roomthread.h"
|
||||||
#include <qjsondocument.h>
|
#include <qjsondocument.h>
|
||||||
#ifndef FK_CLIENT_ONLY
|
#ifndef FK_CLIENT_ONLY
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
@ -311,6 +312,11 @@ void Router::handlePacket(const QByteArray &rawPacket) {
|
||||||
else if (type & TYPE_REPLY) {
|
else if (type & TYPE_REPLY) {
|
||||||
QMutexLocker locker(&replyMutex);
|
QMutexLocker locker(&replyMutex);
|
||||||
|
|
||||||
|
ServerPlayer *player = qobject_cast<ServerPlayer *>(parent());
|
||||||
|
player->setThinking(false);
|
||||||
|
// qDebug() << "wake up!";
|
||||||
|
player->getRoom()->getThread()->wakeUp();
|
||||||
|
|
||||||
if (requestId != this->expectedReplyId)
|
if (requestId != this->expectedReplyId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -328,6 +334,7 @@ void Router::handlePacket(const QByteArray &rawPacket) {
|
||||||
extraReplyReadySemaphore->release();
|
extraReplyReadySemaphore->release();
|
||||||
extraReplyReadySemaphore = nullptr;
|
extraReplyReadySemaphore = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
locker.unlock();
|
locker.unlock();
|
||||||
emit replyReady();
|
emit replyReady();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,48 +5,56 @@
|
||||||
#include <qjsonarray.h>
|
#include <qjsonarray.h>
|
||||||
#include <qjsondocument.h>
|
#include <qjsondocument.h>
|
||||||
|
|
||||||
|
#include "roomthread.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "serverplayer.h"
|
#include "serverplayer.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
Room::Room(Server *server) {
|
Room::Room(RoomThread *m_thread) {
|
||||||
setObjectName("Room");
|
auto server = ServerInstance;
|
||||||
id = server->nextRoomId;
|
id = server->nextRoomId;
|
||||||
server->nextRoomId++;
|
server->nextRoomId++;
|
||||||
this->server = server;
|
this->server = server;
|
||||||
setParent(server);
|
this->m_thread = m_thread;
|
||||||
|
if (m_thread) { // In case of lobby
|
||||||
|
m_thread->addRoom(this);
|
||||||
|
}
|
||||||
|
// setParent(server);
|
||||||
|
|
||||||
m_abandoned = false;
|
m_abandoned = false;
|
||||||
owner = nullptr;
|
owner = nullptr;
|
||||||
gameStarted = false;
|
gameStarted = false;
|
||||||
robot_id = -2; // -1 is reserved in UI logic
|
robot_id = -2; // -1 is reserved in UI logic
|
||||||
timeout = 15;
|
timeout = 15;
|
||||||
|
|
||||||
|
m_ready = true;
|
||||||
|
|
||||||
// 如果是普通房间而不是大厅,就初始化Lua,否则置Lua为nullptr
|
// 如果是普通房间而不是大厅,就初始化Lua,否则置Lua为nullptr
|
||||||
L = nullptr;
|
|
||||||
if (!isLobby()) {
|
if (!isLobby()) {
|
||||||
// 如果不是大厅,那么:
|
// 如果不是大厅,那么:
|
||||||
// * 只要房间添加人了,那么从大厅中移掉这个人
|
// * 只要房间添加人了,那么从大厅中移掉这个人
|
||||||
// * 只要有人离开房间,那就把他加到大厅去
|
// * 只要有人离开房间,那就把他加到大厅去
|
||||||
connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer);
|
connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer);
|
||||||
connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer);
|
connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer);
|
||||||
|
|
||||||
L = CreateLuaState();
|
|
||||||
DoLuaScript(L, "lua/freekill.lua");
|
|
||||||
DoLuaScript(L, "lua/server/room.lua");
|
|
||||||
initLua();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Room::~Room() {
|
Room::~Room() {
|
||||||
if (isRunning()) {
|
if (gameStarted) {
|
||||||
wait();
|
gameOver();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_thread) {
|
||||||
|
m_thread->removeRoom(this);
|
||||||
}
|
}
|
||||||
if (L)
|
|
||||||
lua_close(L);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Server *Room::getServer() const { return server; }
|
Server *Room::getServer() const { return server; }
|
||||||
|
|
||||||
|
RoomThread *Room::getThread() const { return m_thread; }
|
||||||
|
|
||||||
|
void Room::setThread(RoomThread *t) { m_thread = t; }
|
||||||
|
|
||||||
int Room::getId() const { return id; }
|
int Room::getId() const { return id; }
|
||||||
|
|
||||||
void Room::setId(int id) { this->id = id; }
|
void Room::setId(int id) { this->id = id; }
|
||||||
|
@ -81,7 +89,20 @@ bool Room::isAbandoned() const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::setAbandoned(bool abandoned) { m_abandoned = abandoned; }
|
// Lua专用,lua room销毁时检查c++的Room是不是也差不多可以销毁了
|
||||||
|
void Room::checkAbandoned() {
|
||||||
|
if (isAbandoned()) {
|
||||||
|
bool tmp = m_abandoned;
|
||||||
|
m_abandoned = true;
|
||||||
|
if (!tmp) {
|
||||||
|
emit abandoned();
|
||||||
|
} else {
|
||||||
|
deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Room::setAbandoned(bool a) { m_abandoned = a; }
|
||||||
|
|
||||||
ServerPlayer *Room::getOwner() const { return owner; }
|
ServerPlayer *Room::getOwner() const { return owner; }
|
||||||
|
|
||||||
|
@ -218,6 +239,8 @@ void Room::removePlayer(ServerPlayer *player) {
|
||||||
// 原先的跑路机器人会在游戏结束后自动销毁掉
|
// 原先的跑路机器人会在游戏结束后自动销毁掉
|
||||||
server->addPlayer(runner);
|
server->addPlayer(runner);
|
||||||
|
|
||||||
|
m_thread->wakeUp();
|
||||||
|
|
||||||
// 发出信号,让大厅添加这个人
|
// 发出信号,让大厅添加这个人
|
||||||
emit playerRemoved(runner);
|
emit playerRemoved(runner);
|
||||||
}
|
}
|
||||||
|
@ -397,7 +420,7 @@ void Room::gameOver() {
|
||||||
// 玩家也不能在这里清除,因为要能返回原来房间继续玩呢
|
// 玩家也不能在这里清除,因为要能返回原来房间继续玩呢
|
||||||
// players.clear();
|
// players.clear();
|
||||||
// owner = nullptr;
|
// owner = nullptr;
|
||||||
clearRequest();
|
// clearRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::manuallyStart() {
|
void Room::manuallyStart() {
|
||||||
|
@ -405,43 +428,11 @@ void Room::manuallyStart() {
|
||||||
foreach (auto p, players) {
|
foreach (auto p, players) {
|
||||||
p->setReady(false);
|
p->setReady(false);
|
||||||
}
|
}
|
||||||
start();
|
gameStarted = true;
|
||||||
|
m_thread->pushRequest(QString("-1,%1,newroom").arg(QString::number(id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Room::fetchRequest() {
|
|
||||||
if (!gameStarted)
|
|
||||||
return "";
|
|
||||||
request_queue_mutex.lock();
|
|
||||||
QString ret = "";
|
|
||||||
if (!request_queue.isEmpty()) {
|
|
||||||
ret = request_queue.dequeue();
|
|
||||||
}
|
|
||||||
request_queue_mutex.unlock();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Room::pushRequest(const QString &req) {
|
void Room::pushRequest(const QString &req) {
|
||||||
if (!gameStarted)
|
m_thread->pushRequest(QString("%1,%2").arg(QString::number(id), req));
|
||||||
return;
|
|
||||||
request_queue_mutex.lock();
|
|
||||||
request_queue.enqueue(req);
|
|
||||||
request_queue_mutex.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Room::clearRequest() {
|
|
||||||
request_queue_mutex.lock();
|
|
||||||
request_queue.clear();
|
|
||||||
request_queue_mutex.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Room::hasRequest() const { return !request_queue.isEmpty(); }
|
|
||||||
|
|
||||||
void Room::run() {
|
|
||||||
gameStarted = true;
|
|
||||||
|
|
||||||
clearRequest();
|
|
||||||
|
|
||||||
// 此处调用了Lua Room:run()函数
|
|
||||||
roomStart();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,19 @@
|
||||||
|
|
||||||
class Server;
|
class Server;
|
||||||
class ServerPlayer;
|
class ServerPlayer;
|
||||||
|
class RoomThread;
|
||||||
|
|
||||||
class Room : public QThread {
|
class Room : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit Room(Server *m_server);
|
explicit Room(RoomThread *m_thread);
|
||||||
~Room();
|
~Room();
|
||||||
|
|
||||||
// Property reader & setter
|
// Property reader & setter
|
||||||
// ==================================={
|
// ==================================={
|
||||||
Server *getServer() const;
|
Server *getServer() const;
|
||||||
|
RoomThread *getThread() const;
|
||||||
|
void setThread(RoomThread *t);
|
||||||
int getId() const;
|
int getId() const;
|
||||||
void setId(int id);
|
void setId(int id);
|
||||||
bool isLobby() const;
|
bool isLobby() const;
|
||||||
|
@ -26,7 +29,8 @@ class Room : public QThread {
|
||||||
const QByteArray getSettings() const;
|
const QByteArray getSettings() const;
|
||||||
void setSettings(QByteArray settings);
|
void setSettings(QByteArray settings);
|
||||||
bool isAbandoned() const;
|
bool isAbandoned() const;
|
||||||
void setAbandoned(bool abandoned); // never use this function
|
void checkAbandoned();
|
||||||
|
void setAbandoned(bool a);
|
||||||
|
|
||||||
ServerPlayer *getOwner() const;
|
ServerPlayer *getOwner() const;
|
||||||
void setOwner(ServerPlayer *owner);
|
void setOwner(ServerPlayer *owner);
|
||||||
|
@ -55,17 +59,8 @@ class Room : public QThread {
|
||||||
void updateWinRate(int id, const QString &general, const QString &mode,
|
void updateWinRate(int id, const QString &general, const QString &mode,
|
||||||
int result);
|
int result);
|
||||||
void gameOver();
|
void gameOver();
|
||||||
|
|
||||||
void initLua();
|
|
||||||
|
|
||||||
void roomStart();
|
|
||||||
void manuallyStart();
|
void manuallyStart();
|
||||||
LuaFunction startGame;
|
|
||||||
|
|
||||||
QString fetchRequest();
|
|
||||||
void pushRequest(const QString &req);
|
void pushRequest(const QString &req);
|
||||||
void clearRequest();
|
|
||||||
bool hasRequest() const;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void abandoned();
|
void abandoned();
|
||||||
|
@ -73,11 +68,9 @@ class Room : public QThread {
|
||||||
void playerAdded(ServerPlayer *player);
|
void playerAdded(ServerPlayer *player);
|
||||||
void playerRemoved(ServerPlayer *player);
|
void playerRemoved(ServerPlayer *player);
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void run();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Server *server;
|
Server *server;
|
||||||
|
RoomThread *m_thread;
|
||||||
int id; // Lobby's id is 0
|
int id; // Lobby's id is 0
|
||||||
QString name; // “阴间大乱斗”
|
QString name; // “阴间大乱斗”
|
||||||
int capacity; // by default is 5, max is 8
|
int capacity; // by default is 5, max is 8
|
||||||
|
@ -90,12 +83,9 @@ class Room : public QThread {
|
||||||
QList<int> runned_players;
|
QList<int> runned_players;
|
||||||
int robot_id;
|
int robot_id;
|
||||||
bool gameStarted;
|
bool gameStarted;
|
||||||
|
bool m_ready;
|
||||||
|
|
||||||
int timeout;
|
int timeout;
|
||||||
|
|
||||||
lua_State *L;
|
|
||||||
QMutex request_queue_mutex;
|
|
||||||
QQueue<QString> request_queue; // json string
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _ROOM_H
|
#endif // _ROOM_H
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "roomthread.h"
|
||||||
|
#include "server.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include <qtpreprocessorsupport.h>
|
||||||
|
|
||||||
|
RoomThread::RoomThread(Server *m_server) {
|
||||||
|
setObjectName("Room");
|
||||||
|
this->m_server = m_server;
|
||||||
|
m_capacity = 100; // TODO: server cfg
|
||||||
|
terminated = false;
|
||||||
|
|
||||||
|
L = CreateLuaState();
|
||||||
|
DoLuaScript(L, "lua/freekill.lua");
|
||||||
|
DoLuaScript(L, "lua/server/scheduler.lua");
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomThread::~RoomThread() {
|
||||||
|
tryTerminate();
|
||||||
|
if (isRunning()) {
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
lua_close(L);
|
||||||
|
// foreach (auto room, room_list) {
|
||||||
|
// room->deleteLater();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
Server *RoomThread::getServer() const {
|
||||||
|
return m_server;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RoomThread::isFull() const {
|
||||||
|
// return room_list.count() >= m_capacity;
|
||||||
|
return m_capacity <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Room *RoomThread::getRoom(int id) const {
|
||||||
|
return m_server->findRoom(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomThread::addRoom(Room *room) {
|
||||||
|
Q_UNUSED(room);
|
||||||
|
m_capacity--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomThread::removeRoom(Room *room) {
|
||||||
|
room->setThread(nullptr);
|
||||||
|
m_capacity++;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString RoomThread::fetchRequest() {
|
||||||
|
// if (!gameStarted)
|
||||||
|
// return "";
|
||||||
|
request_queue_mutex.lock();
|
||||||
|
QString ret = "";
|
||||||
|
if (!request_queue.isEmpty()) {
|
||||||
|
ret = request_queue.dequeue();
|
||||||
|
}
|
||||||
|
request_queue_mutex.unlock();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomThread::pushRequest(const QString &req) {
|
||||||
|
// if (!gameStarted)
|
||||||
|
// return;
|
||||||
|
request_queue_mutex.lock();
|
||||||
|
request_queue.enqueue(req);
|
||||||
|
request_queue_mutex.unlock();
|
||||||
|
wakeUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomThread::clearRequest() {
|
||||||
|
request_queue_mutex.lock();
|
||||||
|
request_queue.clear();
|
||||||
|
request_queue_mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RoomThread::hasRequest() {
|
||||||
|
request_queue_mutex.lock();
|
||||||
|
auto ret = !request_queue.isEmpty();
|
||||||
|
request_queue_mutex.unlock();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomThread::trySleep(int ms) {
|
||||||
|
if (sema_wake.available() > 0) {
|
||||||
|
sema_wake.acquire(sema_wake.available());
|
||||||
|
}
|
||||||
|
|
||||||
|
sema_wake.tryAcquire(1, ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomThread::wakeUp() {
|
||||||
|
sema_wake.release(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomThread::tryTerminate() {
|
||||||
|
terminated = true;
|
||||||
|
wakeUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RoomThread::isTerminated() const {
|
||||||
|
return terminated;
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#ifndef _ROOMTHREAD_H
|
||||||
|
#define _ROOMTHREAD_H
|
||||||
|
|
||||||
|
#include <qsemaphore.h>
|
||||||
|
class Room;
|
||||||
|
class Server;
|
||||||
|
|
||||||
|
class RoomThread : public QThread {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit RoomThread(Server *m_server);
|
||||||
|
~RoomThread();
|
||||||
|
|
||||||
|
Server *getServer() const;
|
||||||
|
bool isFull() const;
|
||||||
|
|
||||||
|
Room *getRoom(int id) const;
|
||||||
|
void addRoom(Room *room);
|
||||||
|
void removeRoom(Room *room);
|
||||||
|
|
||||||
|
QString fetchRequest();
|
||||||
|
void pushRequest(const QString &req);
|
||||||
|
void clearRequest();
|
||||||
|
bool hasRequest();
|
||||||
|
|
||||||
|
void trySleep(int ms);
|
||||||
|
void wakeUp();
|
||||||
|
|
||||||
|
void tryTerminate();
|
||||||
|
bool isTerminated() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Server *m_server;
|
||||||
|
// QList<Room *> room_list;
|
||||||
|
int m_capacity;
|
||||||
|
|
||||||
|
lua_State *L;
|
||||||
|
QMutex request_queue_mutex;
|
||||||
|
QQueue<QString> request_queue; // json string
|
||||||
|
QSemaphore sema_wake;
|
||||||
|
volatile bool terminated;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _ROOMTHREAD_H
|
|
@ -14,6 +14,7 @@
|
||||||
#include "packman.h"
|
#include "packman.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "room.h"
|
#include "room.h"
|
||||||
|
#include "roomthread.h"
|
||||||
#include "router.h"
|
#include "router.h"
|
||||||
#include "server_socket.h"
|
#include "server_socket.h"
|
||||||
#include "serverplayer.h"
|
#include "serverplayer.h"
|
||||||
|
@ -44,6 +45,8 @@ Server::Server(QObject *parent) : QObject(parent) {
|
||||||
connect(lobby(), &Room::playerAdded, this, &Server::updateRoomList);
|
connect(lobby(), &Room::playerAdded, this, &Server::updateRoomList);
|
||||||
connect(lobby(), &Room::playerRemoved, this, &Server::updateRoomList);
|
connect(lobby(), &Room::playerRemoved, this, &Server::updateRoomList);
|
||||||
|
|
||||||
|
threads.append(new RoomThread(this));
|
||||||
|
|
||||||
// 启动心跳包线程
|
// 启动心跳包线程
|
||||||
auto heartbeatThread = QThread::create([=]() {
|
auto heartbeatThread = QThread::create([=]() {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -63,7 +66,7 @@ Server::Server(QObject *parent) : QObject(parent) {
|
||||||
|
|
||||||
foreach (auto p, this->players.values()) {
|
foreach (auto p, this->players.values()) {
|
||||||
if (p->getState() == Player::Online && !p->alive) {
|
if (p->getState() == Player::Online && !p->alive) {
|
||||||
p->kicked();
|
emit p->kicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,11 +80,11 @@ Server::~Server() {
|
||||||
isListening = false;
|
isListening = false;
|
||||||
ServerInstance = nullptr;
|
ServerInstance = nullptr;
|
||||||
m_lobby->deleteLater();
|
m_lobby->deleteLater();
|
||||||
foreach (auto room, idle_rooms) {
|
// foreach (auto room, idle_rooms) {
|
||||||
room->deleteLater();
|
// room->deleteLater();
|
||||||
}
|
// }
|
||||||
foreach (auto room, rooms) {
|
foreach (auto thread, threads) {
|
||||||
room->deleteLater();
|
thread->deleteLater();
|
||||||
}
|
}
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
RSA_free(rsa);
|
RSA_free(rsa);
|
||||||
|
@ -102,14 +105,27 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Room *room;
|
Room *room;
|
||||||
|
RoomThread *thread = nullptr;
|
||||||
|
|
||||||
|
foreach (auto t, threads) {
|
||||||
|
if (!t->isFull()) {
|
||||||
|
thread = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!thread && nextRoomId != 0) {
|
||||||
|
thread = new RoomThread(this);
|
||||||
|
threads.append(thread);
|
||||||
|
}
|
||||||
|
|
||||||
if (!idle_rooms.isEmpty()) {
|
if (!idle_rooms.isEmpty()) {
|
||||||
room = idle_rooms.pop();
|
room = idle_rooms.pop();
|
||||||
room->setId(nextRoomId);
|
room->setId(nextRoomId);
|
||||||
nextRoomId++;
|
nextRoomId++;
|
||||||
room->setAbandoned(false);
|
room->setAbandoned(false);
|
||||||
|
room->setThread(thread);
|
||||||
rooms.insert(room->getId(), room);
|
rooms.insert(room->getId(), room);
|
||||||
} else {
|
} else {
|
||||||
room = new Room(this);
|
room = new Room(thread);
|
||||||
connect(room, &Room::abandoned, this, &Server::onRoomAbandoned);
|
connect(room, &Room::abandoned, this, &Server::onRoomAbandoned);
|
||||||
if (room->isLobby())
|
if (room->isLobby())
|
||||||
m_lobby = room;
|
m_lobby = room;
|
||||||
|
@ -270,12 +286,10 @@ void Server::processRequest(const QByteArray &msg) {
|
||||||
body
|
body
|
||||||
<< (cmp < 0
|
<< (cmp < 0
|
||||||
? QString("[\"server is still on version %%2\",\"%1\"]")
|
? QString("[\"server is still on version %%2\",\"%1\"]")
|
||||||
.arg(FK_VERSION)
|
.arg(FK_VERSION, "1")
|
||||||
.arg("1")
|
|
||||||
: QString(
|
: QString(
|
||||||
"[\"server is using version %%2, please update\",\"%1\"]")
|
"[\"server is using version %%2, please update\",\"%1\"]")
|
||||||
.arg(FK_VERSION)
|
.arg(FK_VERSION, "1"));
|
||||||
.arg("1"));
|
|
||||||
|
|
||||||
client->send(JsonArray2Bytes(body));
|
client->send(JsonArray2Bytes(body));
|
||||||
client->disconnectFromHost();
|
client->disconnectFromHost();
|
||||||
|
@ -464,26 +478,15 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
|
||||||
|
|
||||||
void Server::onRoomAbandoned() {
|
void Server::onRoomAbandoned() {
|
||||||
Room *room = qobject_cast<Room *>(sender());
|
Room *room = qobject_cast<Room *>(sender());
|
||||||
if (room->isRunning()) {
|
|
||||||
room->wait();
|
|
||||||
}
|
|
||||||
room->gameOver();
|
room->gameOver();
|
||||||
rooms.remove(room->getId());
|
rooms.remove(room->getId());
|
||||||
updateRoomList();
|
updateRoomList();
|
||||||
|
// 按理说这时候就可以删除了,但是这里肯定比Lua先检测到。
|
||||||
|
// 倘若在Lua的Room:gameOver时C++的Room被删除了问题就大了。
|
||||||
|
// FIXME: 但是这终归是内存泄漏!以后啥时候再改吧。
|
||||||
// room->deleteLater();
|
// room->deleteLater();
|
||||||
idle_rooms.push(room);
|
idle_rooms.push(room);
|
||||||
// 懒得改了!
|
room->getThread()->removeRoom(room);
|
||||||
// 这里出bug的原因还是在于room的销毁工作没做好
|
|
||||||
// room销毁这块bug很多
|
|
||||||
// if (idle_rooms.length() > 10) {
|
|
||||||
// auto junk = idle_rooms[0];
|
|
||||||
// idle_rooms.removeFirst();
|
|
||||||
// junk->deleteLater();
|
|
||||||
// }
|
|
||||||
#ifdef QT_DEBUG
|
|
||||||
qDebug() << rooms.size() << "running room(s)," << idle_rooms.size()
|
|
||||||
<< "idle room(s).";
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::onUserDisconnected() {
|
void Server::onUserDisconnected() {
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
class ServerSocket;
|
class ServerSocket;
|
||||||
class ClientSocket;
|
class ClientSocket;
|
||||||
class ServerPlayer;
|
class ServerPlayer;
|
||||||
|
class RoomThread;
|
||||||
|
|
||||||
#include "room.h"
|
#include "room.h"
|
||||||
|
|
||||||
|
@ -62,8 +63,9 @@ private:
|
||||||
Room *m_lobby;
|
Room *m_lobby;
|
||||||
QMap<int, Room *> rooms;
|
QMap<int, Room *> rooms;
|
||||||
QStack<Room *> idle_rooms;
|
QStack<Room *> idle_rooms;
|
||||||
|
QList<RoomThread *> threads;
|
||||||
int nextRoomId;
|
int nextRoomId;
|
||||||
friend Room::Room(Server *server);
|
friend Room::Room(RoomThread *m_thread);
|
||||||
QHash<int, ServerPlayer *> players;
|
QHash<int, ServerPlayer *> players;
|
||||||
|
|
||||||
RSA *rsa;
|
RSA *rsa;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "serverplayer.h"
|
#include "serverplayer.h"
|
||||||
#include "client_socket.h"
|
#include "client_socket.h"
|
||||||
#include "room.h"
|
#include "room.h"
|
||||||
|
#include "roomthread.h"
|
||||||
#include "router.h"
|
#include "router.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ ServerPlayer::ServerPlayer(Room *room) {
|
||||||
|
|
||||||
alive = true;
|
alive = true;
|
||||||
m_busy = false;
|
m_busy = false;
|
||||||
|
m_thinking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerPlayer::~ServerPlayer() {
|
ServerPlayer::~ServerPlayer() {
|
||||||
|
@ -108,3 +110,16 @@ void ServerPlayer::kick() {
|
||||||
}
|
}
|
||||||
setSocket(nullptr);
|
setSocket(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ServerPlayer::thinking() {
|
||||||
|
m_thinking_mutex.lock();
|
||||||
|
bool ret = m_thinking;
|
||||||
|
m_thinking_mutex.unlock();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerPlayer::setThinking(bool t) {
|
||||||
|
m_thinking_mutex.lock();
|
||||||
|
m_thinking = t;
|
||||||
|
m_thinking_mutex.unlock();
|
||||||
|
}
|
||||||
|
|
|
@ -40,6 +40,9 @@ public:
|
||||||
|
|
||||||
bool busy() const { return m_busy; }
|
bool busy() const { return m_busy; }
|
||||||
void setBusy(bool busy) { m_busy = busy; }
|
void setBusy(bool busy) { m_busy = busy; }
|
||||||
|
|
||||||
|
bool thinking();
|
||||||
|
void setThinking(bool t);
|
||||||
signals:
|
signals:
|
||||||
void disconnected();
|
void disconnected();
|
||||||
void kicked();
|
void kicked();
|
||||||
|
@ -49,7 +52,9 @@ private:
|
||||||
Router *router;
|
Router *router;
|
||||||
Server *server;
|
Server *server;
|
||||||
Room *room; // Room that player is in, maybe lobby
|
Room *room; // Room that player is in, maybe lobby
|
||||||
bool m_busy;
|
bool m_busy; // (Lua专用) 是否有doRequest没处理完?见于神貂蝉这种一控多的
|
||||||
|
bool m_thinking; // 是否在烧条?
|
||||||
|
QMutex m_thinking_mutex; // 注意setBusy只在Lua使用,所以不需要锁。
|
||||||
|
|
||||||
QString requestCommand;
|
QString requestCommand;
|
||||||
QString requestData;
|
QString requestData;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "serverplayer.h"
|
#include "serverplayer.h"
|
||||||
#include "clientplayer.h"
|
#include "clientplayer.h"
|
||||||
#include "room.h"
|
#include "room.h"
|
||||||
|
#include "roomthread.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "qmlbackend.h"
|
#include "qmlbackend.h"
|
||||||
class ClientPlayer *Self = nullptr;
|
class ClientPlayer *Self = nullptr;
|
||||||
|
@ -17,4 +18,3 @@ class ClientPlayer *Self = nullptr;
|
||||||
%include "qml-nogui.i"
|
%include "qml-nogui.i"
|
||||||
%include "player.i"
|
%include "player.i"
|
||||||
%include "server.i"
|
%include "server.i"
|
||||||
%include "sqlite3.i"
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "serverplayer.h"
|
#include "serverplayer.h"
|
||||||
#include "clientplayer.h"
|
#include "clientplayer.h"
|
||||||
#include "room.h"
|
#include "room.h"
|
||||||
|
#include "roomthread.h"
|
||||||
#include "qmlbackend.h"
|
#include "qmlbackend.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
%}
|
%}
|
||||||
|
@ -17,4 +18,3 @@
|
||||||
%include "player.i"
|
%include "player.i"
|
||||||
%include "client.i"
|
%include "client.i"
|
||||||
%include "server.i"
|
%include "server.i"
|
||||||
%include "sqlite3.i"
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ public:
|
||||||
Invalid,
|
Invalid,
|
||||||
Online,
|
Online,
|
||||||
Trust,
|
Trust,
|
||||||
|
Run,
|
||||||
|
Robot, // only for real robot
|
||||||
Offline
|
Offline
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,12 +23,7 @@ public:
|
||||||
void setAvatar(const QString &avatar);
|
void setAvatar(const QString &avatar);
|
||||||
|
|
||||||
State getState() const;
|
State getState() const;
|
||||||
QString getStateString() const;
|
|
||||||
void setState(State state);
|
void setState(State state);
|
||||||
void setStateString(const QString &state);
|
|
||||||
|
|
||||||
bool isReady() const;
|
|
||||||
void setReady(bool ready);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
%nodefaultctor ClientPlayer;
|
%nodefaultctor ClientPlayer;
|
||||||
|
|
|
@ -1,63 +1,22 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
%nodefaultctor Server;
|
|
||||||
%nodefaultdtor Server;
|
|
||||||
class Server : public QObject {
|
|
||||||
public:
|
|
||||||
Room *lobby() const;
|
|
||||||
void createRoom(ServerPlayer *owner, const QString &name, int capacity);
|
|
||||||
Room *findRoom(int id) const;
|
|
||||||
ServerPlayer *findPlayer(int id) const;
|
|
||||||
|
|
||||||
sqlite3 *getDatabase();
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Server *ServerInstance;
|
|
||||||
|
|
||||||
%nodefaultctor Room;
|
%nodefaultctor Room;
|
||||||
%nodefaultdtor Room;
|
%nodefaultdtor Room;
|
||||||
class Room : public QThread {
|
class Room : public QObject {
|
||||||
public:
|
public:
|
||||||
// Property reader & setter
|
// Property reader & setter
|
||||||
// ==================================={
|
// ==================================={
|
||||||
Server *getServer() const;
|
|
||||||
int getId() const;
|
int getId() const;
|
||||||
bool isLobby() const;
|
|
||||||
QString getName() const;
|
|
||||||
void setName(const QString &name);
|
|
||||||
int getCapacity() const;
|
|
||||||
void setCapacity(int capacity);
|
|
||||||
bool isFull() const;
|
|
||||||
bool isAbandoned() const;
|
|
||||||
|
|
||||||
ServerPlayer *getOwner() const;
|
|
||||||
void setOwner(ServerPlayer *owner);
|
|
||||||
|
|
||||||
void addPlayer(ServerPlayer *player);
|
|
||||||
void addRobot(ServerPlayer *player);
|
|
||||||
void removePlayer(ServerPlayer *player);
|
|
||||||
QList<ServerPlayer *> getPlayers() const;
|
QList<ServerPlayer *> getPlayers() const;
|
||||||
ServerPlayer *findPlayer(int id) const;
|
|
||||||
|
|
||||||
QList<ServerPlayer *> getObservers() const;
|
QList<ServerPlayer *> getObservers() const;
|
||||||
int getTimeout() const;
|
int getTimeout() const;
|
||||||
|
void checkAbandoned();
|
||||||
bool isStarted() const;
|
|
||||||
// ====================================}
|
|
||||||
|
|
||||||
void doBroadcastNotify(
|
|
||||||
const QList<ServerPlayer *> targets,
|
|
||||||
const QString &command,
|
|
||||||
const QString &jsonData
|
|
||||||
);
|
|
||||||
|
|
||||||
void updateWinRate(int id, const QString &general, const QString &mode,
|
void updateWinRate(int id, const QString &general, const QString &mode,
|
||||||
int result);
|
int result);
|
||||||
void gameOver();
|
void gameOver();
|
||||||
|
|
||||||
LuaFunction startGame;
|
|
||||||
QString fetchRequest();
|
|
||||||
bool hasRequest() const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
%extend Room {
|
%extend Room {
|
||||||
|
@ -66,14 +25,28 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
%nodefaultctor RoomThread;
|
||||||
|
%nodefaultdtor RoomThread;
|
||||||
|
class RoomThread : public QThread {
|
||||||
|
public:
|
||||||
|
Room *getRoom(int id);
|
||||||
|
|
||||||
|
QString fetchRequest();
|
||||||
|
void clearRequest();
|
||||||
|
bool hasRequest();
|
||||||
|
|
||||||
|
void trySleep(int ms);
|
||||||
|
bool isTerminated() const;
|
||||||
|
};
|
||||||
|
|
||||||
%{
|
%{
|
||||||
void Room::initLua()
|
void RoomThread::run()
|
||||||
{
|
{
|
||||||
lua_getglobal(L, "debug");
|
lua_getglobal(L, "debug");
|
||||||
lua_getfield(L, -1, "traceback");
|
lua_getfield(L, -1, "traceback");
|
||||||
lua_replace(L, -2);
|
lua_replace(L, -2);
|
||||||
lua_getglobal(L, "CreateRoom");
|
lua_getglobal(L, "InitScheduler");
|
||||||
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0);
|
SWIG_NewPointerObj(L, this, SWIGTYPE_p_RoomThread, 0);
|
||||||
int error = lua_pcall(L, 1, 0, -2);
|
int error = lua_pcall(L, 1, 0, -2);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -81,46 +54,20 @@ void Room::initLua()
|
||||||
qCritical() << error_msg;
|
qCritical() << error_msg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::roomStart() {
|
|
||||||
Q_ASSERT(startGame);
|
|
||||||
|
|
||||||
lua_getglobal(L, "debug");
|
|
||||||
lua_getfield(L, -1, "traceback");
|
|
||||||
lua_replace(L, -2);
|
|
||||||
|
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, startGame);
|
|
||||||
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0);
|
|
||||||
|
|
||||||
int error = lua_pcall(L, 1, 0, -3);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
const char *error_msg = lua_tostring(L, -1);
|
|
||||||
qCritical() << error_msg;
|
|
||||||
lua_pop(L, 2);
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
%}
|
%}
|
||||||
|
|
||||||
%nodefaultctor ServerPlayer;
|
%nodefaultctor ServerPlayer;
|
||||||
%nodefaultdtor ServerPlayer;
|
%nodefaultdtor ServerPlayer;
|
||||||
class ServerPlayer : public Player {
|
class ServerPlayer : public Player {
|
||||||
public:
|
public:
|
||||||
Server *getServer() const;
|
|
||||||
Room *getRoom() const;
|
|
||||||
void setRoom(Room *room);
|
|
||||||
|
|
||||||
void speak(const QString &message);
|
|
||||||
|
|
||||||
void doRequest(const QString &command,
|
void doRequest(const QString &command,
|
||||||
const QString &json_data, int timeout);
|
const QString &json_data, int timeout);
|
||||||
QString waitForReply(int timeout);
|
QString waitForReply(int timeout);
|
||||||
void doNotify(const QString &command, const QString &json_data);
|
void doNotify(const QString &command, const QString &json_data);
|
||||||
|
|
||||||
void prepareForRequest(const QString &command, const QString &data);
|
|
||||||
|
|
||||||
bool busy() const;
|
bool busy() const;
|
||||||
void setBusy(bool busy);
|
void setBusy(bool busy);
|
||||||
|
|
||||||
|
bool thinking();
|
||||||
|
void setThinking(bool t);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
struct sqlite3;
|
|
||||||
|
|
||||||
sqlite3 *OpenDatabase(const QString &filename);
|
|
||||||
QString SelectFromDb(sqlite3 *db, const QString &sql);
|
|
||||||
void ExecSQL(sqlite3 *db, const QString &sql);
|
|
||||||
void CloseDatabase(sqlite3 *db);
|
|
Loading…
Reference in New Issue