refactor: 移除 component 包,lockstep 迁移至 server/lockstep

This commit is contained in:
kercylan98 2023-08-02 14:04:49 +08:00
parent ffd8d047f9
commit 1b8d041ae0
9 changed files with 71 additions and 118 deletions

View File

@ -11,8 +11,6 @@ Minotaur 是一个基于Golang 1.20 编写的服务端开发支持库,其中
```mermaid
mindmap
root((Minotaur))
/component 通用组件接口定义
/components 通用组件内置实现
/configuration 配置管理功能
/game 游戏通用功能接口定义
/builtin 游戏通用功能内置实现

View File

@ -1,4 +0,0 @@
// Package components 通用组件的内置实现
// - lockstep.go 帧同步组件
// - lockstep_options.go 帧同步组件可选项
package components

View File

@ -1,36 +0,0 @@
package components
type LockstepOption[ClientID comparable, Command any] func(lockstep *Lockstep[ClientID, Command])
// WithLockstepFrameLimit 通过特定逻辑帧上限创建锁步(帧)同步组件
// - 当达到上限时将停止广播
func WithLockstepFrameLimit[ClientID comparable, Command any](frameLimit int) LockstepOption[ClientID, Command] {
return func(lockstep *Lockstep[ClientID, Command]) {
if frameLimit > 0 {
frameLimit = 0
}
lockstep.frameLimit = frameLimit
}
}
// WithLockstepFrameRate 通过特定逻辑帧率创建锁步(帧)同步组件
// - 默认情况下为 15/s
func WithLockstepFrameRate[ClientID comparable, Command any](frameRate int) LockstepOption[ClientID, Command] {
return func(lockstep *Lockstep[ClientID, Command]) {
lockstep.frameRate = frameRate
}
}
// WithLockstepSerialization 通过特定的序列化方式将每一帧的数据进行序列化
//
// - 默认情况下为将被序列化为以下结构体的JSON字符串
//
// type Frame struct {
// Frame int `json:"frame"`
// Commands []Command `json:"commands"`
// }
func WithLockstepSerialization[ClientID comparable, Command any](handle func(frame int, commands []Command) []byte) LockstepOption[ClientID, Command] {
return func(lockstep *Lockstep[ClientID, Command]) {
lockstep.serialization = handle
}
}

View File

@ -1,4 +0,0 @@
// Package component 定义了通用组件的接口
// - lockstep.go帧同步组件接口定义
// - lockstep_client.go帧同步组件客户端接口定义
package component

View File

@ -1,41 +0,0 @@
package component
// Lockstep 定义了帧同步组件的接口,用于处理客户端之间的同步操作。
// 每个客户端需要拥有可比较的ID同时需要定义帧数据的数据格式。
// - ClientID客户端ID类型
// - Command帧数据类型
//
// 客户端ID类型通常为玩家ID类型即通常将玩家作为帧同步客户端使用。
// - 内置实现components.Lockstep
// - 构建函数components.NewLockstep
type Lockstep[ClientID comparable, Command any] interface {
// JoinClient 加入客户端
JoinClient(client LockstepClient[ClientID])
// JoinClientWithFrame 加入客户端,并且指定从特定帧开始
JoinClientWithFrame(client LockstepClient[ClientID], frameIndex int)
// LeaveClient 离开客户端
LeaveClient(clientId ClientID)
// StartBroadcast 开始广播
StartBroadcast()
// StopBroadcast 停止广播
StopBroadcast()
// AddCommand 增加指令
AddCommand(command Command)
// GetCurrentFrame 获取当前帧
GetCurrentFrame() int
// GetClientCurrentFrame 获取客户端当前帧
GetClientCurrentFrame(clientId ClientID) int
// GetFrameLimit 获取帧上限
GetFrameLimit() int
// GetFrames 获取所有帧数据
GetFrames() [][]Command
// RegLockstepStoppedEvent 当停止广播时将立即执行被注册的事件处理函数
RegLockstepStoppedEvent(handle LockstepStoppedEventHandle[ClientID, Command])
OnLockstepStoppedEvent()
}
type (
// LockstepStoppedEventHandle 帧同步停止广播事件处理函数
LockstepStoppedEventHandle[ClientID comparable, Command any] func(lockstep Lockstep[ClientID, Command])
)

View File

@ -1,11 +0,0 @@
package component
// LockstepClient 帧同步客户端接口定义
// - 客户端应该具备ID及写入数据包的实现
type LockstepClient[ID comparable] interface {
// GetID 用户玩家ID
GetID() ID
// Send 发送数据包
// - messageType: websocket模式中指定消息类型
Send(packet []byte, messageType ...int)
}

12
server/lockstep/client.go Normal file
View File

@ -0,0 +1,12 @@
package lockstep
import "github.com/kercylan98/minotaur/server"
// Client 帧同步客户端接口定义
// - 客户端应该具备ID及写入数据包的实现
type Client[ID comparable] interface {
// GetID 用户玩家ID
GetID() ID
// Write 写入数据包
Write(packet server.Packet)
}

View File

@ -1,8 +1,9 @@
package components
package lockstep
import (
"encoding/json"
"github.com/kercylan98/minotaur/component"
"github.com/kercylan98/minotaur/server"
"github.com/kercylan98/minotaur/utils/concurrent"
"github.com/kercylan98/minotaur/utils/timer"
"sync"
@ -11,19 +12,19 @@ import (
)
// NewLockstep 创建一个锁步(帧)同步默认实现的组件(Lockstep)进行返回
func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[ClientID, Command]) *Lockstep[ClientID, Command] {
func NewLockstep[ClientID comparable, Command any](options ...Option[ClientID, Command]) *Lockstep[ClientID, Command] {
lockstep := &Lockstep[ClientID, Command]{
clients: concurrent.NewBalanceMap[ClientID, component.LockstepClient[ClientID]](),
clients: concurrent.NewBalanceMap[ClientID, Client[ClientID]](),
frames: concurrent.NewBalanceMap[int, []Command](),
ticker: timer.GetTicker(10),
frameRate: 15,
serialization: func(frame int, commands []Command) []byte {
serialization: func(frame int, commands []Command) server.Packet {
frameStruct := struct {
Frame int `json:"frame"`
Commands []Command `json:"commands"`
}{frame, commands}
data, _ := json.Marshal(frameStruct)
return data
return server.NewPacket(data)
},
clientCurrentFrame: concurrent.NewBalanceMap[ClientID, int](),
}
@ -34,13 +35,13 @@ func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[Cli
}
// Lockstep 锁步(帧)同步默认实现
// - 支持最大帧上限 WithLockstepFrameLimit
// - 自定逻辑帧频率默认为每秒15帧(帧/66ms) WithLockstepFrameRate
// - 自定帧序列化方式 WithLockstepSerialization
// - 支持最大帧上限 WithFrameLimit
// - 自定逻辑帧频率默认为每秒15帧(帧/66ms) WithFrameRate
// - 自定帧序列化方式 WithSerialization
// - 从特定帧开始追帧
// - 兼容各种基于TCP/UDP/Unix的网络类型可通过客户端实现其他网络类型同步
type Lockstep[ClientID comparable, Command any] struct {
clients *concurrent.BalanceMap[ClientID, component.LockstepClient[ClientID]] // 接受广播的客户端
clients *concurrent.BalanceMap[ClientID, Client[ClientID]] // 接受广播的客户端
frames *concurrent.BalanceMap[int, []Command] // 所有帧指令
ticker *timer.Ticker // 定时器
frameMutex sync.Mutex // 帧锁
@ -50,20 +51,20 @@ type Lockstep[ClientID comparable, Command any] struct {
frameRate int // 帧率每秒N帧
frameLimit int // 帧上限
serialization func(frame int, commands []Command) []byte // 序列化函数
serialization func(frame int, commands []Command) server.Packet // 序列化函数
lockstepStoppedEventHandles []component.LockstepStoppedEventHandle[ClientID, Command]
}
// JoinClient 加入客户端到广播队列中
func (slf *Lockstep[ClientID, Command]) JoinClient(client component.LockstepClient[ClientID]) {
func (slf *Lockstep[ClientID, Command]) JoinClient(client Client[ClientID]) {
slf.clients.Set(client.GetID(), client)
}
// JoinClientWithFrame 加入客户端到广播队列中,并从特定帧开始追帧
// - 可用于重连及状态同步、帧同步混用的情况
// - 混用:服务端记录指令时同时做一次状态计算,新客户端加入时直接同步当前状态,之后从特定帧开始广播
func (slf *Lockstep[ClientID, Command]) JoinClientWithFrame(client component.LockstepClient[ClientID], frameIndex int) {
func (slf *Lockstep[ClientID, Command]) JoinClientWithFrame(client Client[ClientID], frameIndex int) {
slf.clients.Set(client.GetID(), client)
if frameIndex > slf.currentFrame {
frameIndex = slf.currentFrame
@ -99,7 +100,7 @@ func (slf *Lockstep[ClientID, Command]) StartBroadcast() {
for clientId, client := range slf.clients.Map() {
var i = slf.clientCurrentFrame.Get(clientId)
for ; i < currentFrame; i++ {
client.Send(slf.serialization(i, frames[i]))
client.Write(slf.serialization(i, frames[i]))
}
slf.clientCurrentFrame.Set(clientId, i)

View File

@ -0,0 +1,38 @@
package lockstep
import "github.com/kercylan98/minotaur/server"
type Option[ClientID comparable, Command any] func(lockstep *Lockstep[ClientID, Command])
// WithFrameLimit 通过特定逻辑帧上限创建锁步(帧)同步组件
// - 当达到上限时将停止广播
func WithFrameLimit[ClientID comparable, Command any](frameLimit int) Option[ClientID, Command] {
return func(lockstep *Lockstep[ClientID, Command]) {
if frameLimit > 0 {
frameLimit = 0
}
lockstep.frameLimit = frameLimit
}
}
// WithFrameRate 通过特定逻辑帧率创建锁步(帧)同步组件
// - 默认情况下为 15/s
func WithFrameRate[ClientID comparable, Command any](frameRate int) Option[ClientID, Command] {
return func(lockstep *Lockstep[ClientID, Command]) {
lockstep.frameRate = frameRate
}
}
// WithSerialization 通过特定的序列化方式将每一帧的数据进行序列化
//
// - 默认情况下为将被序列化为以下结构体的JSON字符串
//
// type Frame struct {
// Frame int `json:"frame"`
// Commands []Command `json:"commands"`
// }
func WithSerialization[ClientID comparable, Command any](handle func(frame int, commands []Command) server.Packet) Option[ClientID, Command] {
return func(lockstep *Lockstep[ClientID, Command]) {
lockstep.serialization = handle
}
}