refactor: 移除 component 包,lockstep 迁移至 server/lockstep
This commit is contained in:
parent
ffd8d047f9
commit
1b8d041ae0
|
@ -11,8 +11,6 @@ Minotaur 是一个基于Golang 1.20 编写的服务端开发支持库,其中
|
||||||
```mermaid
|
```mermaid
|
||||||
mindmap
|
mindmap
|
||||||
root((Minotaur))
|
root((Minotaur))
|
||||||
/component 通用组件接口定义
|
|
||||||
/components 通用组件内置实现
|
|
||||||
/configuration 配置管理功能
|
/configuration 配置管理功能
|
||||||
/game 游戏通用功能接口定义
|
/game 游戏通用功能接口定义
|
||||||
/builtin 游戏通用功能内置实现
|
/builtin 游戏通用功能内置实现
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
// Package components 通用组件的内置实现
|
|
||||||
// - lockstep.go 帧同步组件
|
|
||||||
// - lockstep_options.go 帧同步组件可选项
|
|
||||||
package components
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
// Package component 定义了通用组件的接口
|
|
||||||
// - lockstep.go:帧同步组件接口定义
|
|
||||||
// - lockstep_client.go:帧同步组件客户端接口定义
|
|
||||||
package component
|
|
|
@ -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])
|
|
||||||
)
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
package components
|
package lockstep
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/kercylan98/minotaur/component"
|
"github.com/kercylan98/minotaur/component"
|
||||||
|
"github.com/kercylan98/minotaur/server"
|
||||||
"github.com/kercylan98/minotaur/utils/concurrent"
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
"github.com/kercylan98/minotaur/utils/timer"
|
"github.com/kercylan98/minotaur/utils/timer"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -11,19 +12,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewLockstep 创建一个锁步(帧)同步默认实现的组件(Lockstep)进行返回
|
// 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]{
|
lockstep := &Lockstep[ClientID, Command]{
|
||||||
clients: concurrent.NewBalanceMap[ClientID, component.LockstepClient[ClientID]](),
|
clients: concurrent.NewBalanceMap[ClientID, Client[ClientID]](),
|
||||||
frames: concurrent.NewBalanceMap[int, []Command](),
|
frames: concurrent.NewBalanceMap[int, []Command](),
|
||||||
ticker: timer.GetTicker(10),
|
ticker: timer.GetTicker(10),
|
||||||
frameRate: 15,
|
frameRate: 15,
|
||||||
serialization: func(frame int, commands []Command) []byte {
|
serialization: func(frame int, commands []Command) server.Packet {
|
||||||
frameStruct := struct {
|
frameStruct := struct {
|
||||||
Frame int `json:"frame"`
|
Frame int `json:"frame"`
|
||||||
Commands []Command `json:"commands"`
|
Commands []Command `json:"commands"`
|
||||||
}{frame, commands}
|
}{frame, commands}
|
||||||
data, _ := json.Marshal(frameStruct)
|
data, _ := json.Marshal(frameStruct)
|
||||||
return data
|
return server.NewPacket(data)
|
||||||
},
|
},
|
||||||
clientCurrentFrame: concurrent.NewBalanceMap[ClientID, int](),
|
clientCurrentFrame: concurrent.NewBalanceMap[ClientID, int](),
|
||||||
}
|
}
|
||||||
|
@ -34,36 +35,36 @@ func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[Cli
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lockstep 锁步(帧)同步默认实现
|
// Lockstep 锁步(帧)同步默认实现
|
||||||
// - 支持最大帧上限 WithLockstepFrameLimit
|
// - 支持最大帧上限 WithFrameLimit
|
||||||
// - 自定逻辑帧频率,默认为每秒15帧(帧/66ms) WithLockstepFrameRate
|
// - 自定逻辑帧频率,默认为每秒15帧(帧/66ms) WithFrameRate
|
||||||
// - 自定帧序列化方式 WithLockstepSerialization
|
// - 自定帧序列化方式 WithSerialization
|
||||||
// - 从特定帧开始追帧
|
// - 从特定帧开始追帧
|
||||||
// - 兼容各种基于TCP/UDP/Unix的网络类型,可通过客户端实现其他网络类型同步
|
// - 兼容各种基于TCP/UDP/Unix的网络类型,可通过客户端实现其他网络类型同步
|
||||||
type Lockstep[ClientID comparable, Command any] struct {
|
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] // 所有帧指令
|
frames *concurrent.BalanceMap[int, []Command] // 所有帧指令
|
||||||
ticker *timer.Ticker // 定时器
|
ticker *timer.Ticker // 定时器
|
||||||
frameMutex sync.Mutex // 帧锁
|
frameMutex sync.Mutex // 帧锁
|
||||||
currentFrame int // 当前帧
|
currentFrame int // 当前帧
|
||||||
clientCurrentFrame *concurrent.BalanceMap[ClientID, int] // 客户端当前帧数
|
clientCurrentFrame *concurrent.BalanceMap[ClientID, int] // 客户端当前帧数
|
||||||
running atomic.Bool
|
running atomic.Bool
|
||||||
|
|
||||||
frameRate int // 帧率(每秒N帧)
|
frameRate int // 帧率(每秒N帧)
|
||||||
frameLimit int // 帧上限
|
frameLimit int // 帧上限
|
||||||
serialization func(frame int, commands []Command) []byte // 序列化函数
|
serialization func(frame int, commands []Command) server.Packet // 序列化函数
|
||||||
|
|
||||||
lockstepStoppedEventHandles []component.LockstepStoppedEventHandle[ClientID, Command]
|
lockstepStoppedEventHandles []component.LockstepStoppedEventHandle[ClientID, Command]
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinClient 加入客户端到广播队列中
|
// 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)
|
slf.clients.Set(client.GetID(), client)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinClientWithFrame 加入客户端到广播队列中,并从特定帧开始追帧
|
// 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)
|
slf.clients.Set(client.GetID(), client)
|
||||||
if frameIndex > slf.currentFrame {
|
if frameIndex > slf.currentFrame {
|
||||||
frameIndex = slf.currentFrame
|
frameIndex = slf.currentFrame
|
||||||
|
@ -99,7 +100,7 @@ func (slf *Lockstep[ClientID, Command]) StartBroadcast() {
|
||||||
for clientId, client := range slf.clients.Map() {
|
for clientId, client := range slf.clients.Map() {
|
||||||
var i = slf.clientCurrentFrame.Get(clientId)
|
var i = slf.clientCurrentFrame.Get(clientId)
|
||||||
for ; i < currentFrame; i++ {
|
for ; i < currentFrame; i++ {
|
||||||
client.Send(slf.serialization(i, frames[i]))
|
client.Write(slf.serialization(i, frames[i]))
|
||||||
}
|
}
|
||||||
slf.clientCurrentFrame.Set(clientId, i)
|
slf.clientCurrentFrame.Set(clientId, i)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue