diff --git a/README.md b/README.md index 7d6fa25..4bf01c9 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,6 @@ Minotaur 是一个基于Golang 1.20 编写的服务端开发支持库,其中 ```mermaid mindmap root((Minotaur)) - /component 通用组件接口定义 - /components 通用组件内置实现 /configuration 配置管理功能 /game 游戏通用功能接口定义 /builtin 游戏通用功能内置实现 diff --git a/component/components/doc.go b/component/components/doc.go deleted file mode 100644 index 84e7ef6..0000000 --- a/component/components/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Package components 通用组件的内置实现 -// - lockstep.go 帧同步组件 -// - lockstep_options.go 帧同步组件可选项 -package components diff --git a/component/components/lockstep_options.go b/component/components/lockstep_options.go deleted file mode 100644 index 129adc8..0000000 --- a/component/components/lockstep_options.go +++ /dev/null @@ -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 - } -} diff --git a/component/doc.go b/component/doc.go deleted file mode 100644 index e73bc0a..0000000 --- a/component/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Package component 定义了通用组件的接口 -// - lockstep.go:帧同步组件接口定义 -// - lockstep_client.go:帧同步组件客户端接口定义 -package component diff --git a/component/lockstep.go b/component/lockstep.go deleted file mode 100644 index f43af88..0000000 --- a/component/lockstep.go +++ /dev/null @@ -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]) -) diff --git a/component/lockstep_client.go b/component/lockstep_client.go deleted file mode 100644 index 7dbe702..0000000 --- a/component/lockstep_client.go +++ /dev/null @@ -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) -} diff --git a/server/lockstep/client.go b/server/lockstep/client.go new file mode 100644 index 0000000..1780764 --- /dev/null +++ b/server/lockstep/client.go @@ -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) +} diff --git a/component/components/lockstep.go b/server/lockstep/lockstep.go similarity index 80% rename from component/components/lockstep.go rename to server/lockstep/lockstep.go index 8b40d01..4d1a3fb 100644 --- a/component/components/lockstep.go +++ b/server/lockstep/lockstep.go @@ -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,36 +35,36 @@ 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]] // 接受广播的客户端 - frames *concurrent.BalanceMap[int, []Command] // 所有帧指令 - ticker *timer.Ticker // 定时器 - frameMutex sync.Mutex // 帧锁 - currentFrame int // 当前帧 - clientCurrentFrame *concurrent.BalanceMap[ClientID, int] // 客户端当前帧数 + clients *concurrent.BalanceMap[ClientID, Client[ClientID]] // 接受广播的客户端 + frames *concurrent.BalanceMap[int, []Command] // 所有帧指令 + ticker *timer.Ticker // 定时器 + frameMutex sync.Mutex // 帧锁 + currentFrame int // 当前帧 + clientCurrentFrame *concurrent.BalanceMap[ClientID, int] // 客户端当前帧数 running atomic.Bool - frameRate int // 帧率(每秒N帧) - frameLimit int // 帧上限 - serialization func(frame int, commands []Command) []byte // 序列化函数 + frameRate int // 帧率(每秒N帧) + frameLimit int // 帧上限 + 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) diff --git a/server/lockstep/lockstep_options.go b/server/lockstep/lockstep_options.go new file mode 100644 index 0000000..4be3379 --- /dev/null +++ b/server/lockstep/lockstep_options.go @@ -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 + } +}