服务端 - 多房间与游戏流程

6. 服务端 - 多房间与游戏流程#

从这里开始Lua代码就要发挥相当重要的作用了,首先来看单线程环境下的并发实现。

6.1. 房间调度#

每个房间对应一个Lua协程。对于房间数量很多的情况,需要为一定数量的房间各启用一个线程,每一个线程由RoomThread类管理。

在房间调度模块,关注的对象只有RoomThread内部的工作流程。而调度本身是通过事件循环(Qt提供)进行的,每个房间在工作很短一段时间后必须主动让出线程控制权,在Lua 的角度体现为让出协程,在C++的角度则是函数返回。该模块主要的作用范围是C++代码, 主要有三个类发挥着作用。

@startuml
!theme plain
class RoomThread {
  -Room[] rooms
  +void addRoom(Room *room)
  +void removeRoom(Room *room)
  +void pushRequest(string req)
  +void delay(int roomId, int ms)
  +void wakeUp(int roomId)
}
class Room {
  -RoomThread *thread 
  -QTimer request_timer = nullptr
  +void setRequestTimer(int ms)
  +void destroyRequestTimer()
  +void delay(int ms)
}
class Scheduler {
  -RoomThread *thread 
  -lua_State *L
  +void handleRequest(string req)
  +void doDelay(int roomId, int ms)
  +void resumeRoom(int roomId)
}

note top of Room
  图中标出的三个方法只由Lua调用
end note

Scheduler -* RoomThread
RoomThread *- Room

@enduml

@startuml
title 信号pushRequest的发起与处理
!include uml/s-logic-schedule-sig.iuml

Rou -> R : notify
activate R
R -> S : pushRequest
S -> L : handleRequest
deactivate R
@enduml

@startuml
title 信号delay的发起与处理
!include uml/s-logic-schedule-sig.iuml

L -> R : delay
activate L
R -> S : delay
S <-- S : doDelay
S -> L : resumeRoom
deactivate L
@enduml

@startuml
title 信号wakeUp的发起与处理
!include uml/s-logic-schedule-sig.iuml

Rou -> R : reply/Room::abandoned
activate R
R -> S : wakeUp
S -> L : resumeRoom
deactivate R
@enduml

setRequestTimer在发起请求之后调用,用来创建一个计时器,当时间达到出牌时间时唤醒 房间。destroyRequestTimer用于清理本次request创建的计时器。其工作原理如图所示, 注意图中省略了一部分信息传递流程。

@startuml
!theme plain
title 一对一request中request timer的原理
participant Lua
participant Room
actor "玩家" as P

Lua -> P : doRequest
Lua -> Room : setRequestTimer
create control Timer
Room -> Timer ** : new

alt 成功情况
  P -> Lua : reply
else 掉线或者干脆烧条的情况
  P ->x Lua : 未能发送数据
  Timer -> Room : 时间到
  Room -> Lua : wakeUp
  Lua -> Lua : 放弃等待
end

Lua -> Room : destroyRequestTimer
Room -> Timer !! : delete

@enduml

6.2. 请求与答复#

此处指的是类似服务器询问玩家是否发动某某技能,玩家选择如何作出答复的过程。 该概念需要Lua和C++代码的紧密配合才能正常运转,因此从C++和Lua两方进行说明。 先明确一下需要实现的功能:

  1. 能向玩家发送请求包,能检测玩家是否已经做出回复

  2. 在玩家出现网络状态波动时,能及时进行响应

  3. 当玩家消耗了所有用时都不答复时,能正确按默认条件处理

  4. 支持一名玩家同时控制多个角色

从这里开始会涉及Lua的类了,在Lua类和C++类共存的图中,Lua类标记为浅蓝色。 在这三个相关类中,Lua侧的Request类无疑发挥最重要的作用,一般的使用方法是构造, 填充data和默认回复,再调用ask方法进行询问与等待。

@startuml
!theme plain
hide empty methods
class Router {
  -int requestId
  -int requestTimeout
  -int expectReplyId
  -string m_reply
  -QMutex replyMutex
  +void doRequest(command, jsonData, timeout)
  +string waitForReply(timeout)
  +void handlePacket(raw)
}

class ServerPlayer {
  -Router *router
  -bool m_thinking
  +void thinking()
  +void setThinking(thinking)
}

class Request #aliceblue {
  +Room room
  +ServerPlayer[] players
  +int n
  +bool? accept_cancel
  +int timeout
  +string command
  +map<int, any> data
  +map<int, any> default_reply
  +map<int, any> result
  -map<CPlayer, boolean> send_success
  -map<CPlayer, int[]> pending_requests
  +void setData(player, data)
  +void setDefaultReply(player, data)
  +void ask()
  +ServerPlayer[] getWinners()
  -void _sendPacket(player)
  -void _checkReply(player)
  -void finish()
}

Router -l[#hide]- ServerPlayer
Router -r[#hide]- Request

@enduml

6.3. 游戏逻辑#

此处完全由Lua实现,这里主要说明事件机制的实现方案,及其运行与中断的机制。