vRp.CD2g_test/server/lockstep/lockstep.go

272 lines
8.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package lockstep
import (
"encoding/json"
"sync"
"time"
)
// NewLockstep 创建一个锁步(帧)同步默认实现的组件(Lockstep)进行返回
func NewLockstep[ClientID comparable, Command any](options ...Option[ClientID, Command]) *Lockstep[ClientID, Command] {
lockstep := &Lockstep[ClientID, Command]{
currentFrame: -1,
frameRate: 15,
serialization: func(frame int64, commands []Command) []byte {
frameStruct := struct {
Frame int64 `json:"frame"`
Commands []Command `json:"commands"`
}{frame, commands}
data, _ := json.Marshal(frameStruct)
return data
},
clients: make(map[ClientID]Client[ClientID]),
clientFrame: make(map[ClientID]int64),
frameCache: make(map[int64][]byte),
}
for _, option := range options {
option(lockstep)
}
return lockstep
}
// Lockstep 锁步(帧)同步默认实现
// - 支持最大帧上限 WithFrameLimit
// - 自定逻辑帧频率默认为每秒15帧(帧/66ms) WithFrameRate
// - 自定帧序列化方式 WithSerialization
// - 从特定帧开始追帧
// - 兼容各种基于TCP/UDP/Unix的网络类型可通过客户端实现其他网络类型同步
type Lockstep[ClientID comparable, Command any] struct {
running bool // 运行状态
runningLock sync.RWMutex // 运行状态锁
initFrame int64 // 初始帧
frameRate int64 // 帧率每秒N帧
frameLimit int64 // 帧上限
serialization func(frame int64, commands []Command) []byte // 序列化函数
clients map[ClientID]Client[ClientID] // 接受广播的客户端
clientFrame map[ClientID]int64 // 客户端当前帧
clientLock sync.RWMutex // 客户端锁
currentFrame int64 // 当前主要帧
currentCommands []Command // 当前帧指令
currentFrameLock sync.RWMutex // 当前主要帧锁
frameCache map[int64][]byte // 帧序列化缓存
frameCacheLock sync.RWMutex // 帧序列化缓存锁
ticker *time.Ticker // 定时器
lockstepStoppedEventHandles []StoppedEventHandle[ClientID, Command]
}
// JoinClient 将客户端加入到广播队列中,通常在开始广播前使用
// - 如果客户端在开始广播后加入,将丢失之前的帧数据,如要从特定帧开始追帧请使用 JoinClientWithFrame
func (slf *Lockstep[ClientID, Command]) JoinClient(client Client[ClientID]) {
slf.clientLock.Lock()
defer slf.clientLock.Unlock()
slf.clients[client.GetID()] = client
}
// JoinClientWithFrame 加入客户端到广播队列中,并从特定帧开始追帧
// - 可用于重连及状态同步、帧同步混用的情况
// - 混用:服务端记录指令时同时做一次状态计算,新客户端加入时直接同步当前状态,之后从特定帧开始广播
func (slf *Lockstep[ClientID, Command]) JoinClientWithFrame(client Client[ClientID], frameIndex int64) {
slf.currentFrameLock.RLock()
if frameIndex > slf.currentFrame {
frameIndex = slf.currentFrame
}
slf.currentFrameLock.RUnlock()
if frameIndex < slf.initFrame {
frameIndex = slf.initFrame
}
slf.clientLock.Lock()
slf.clients[client.GetID()] = client
slf.clientFrame[client.GetID()] = frameIndex
slf.clientLock.Unlock()
}
// GetClientCount 获取客户端数量
func (slf *Lockstep[ClientID, Command]) GetClientCount() int {
slf.clientLock.RLock()
defer slf.clientLock.RUnlock()
return len(slf.clients)
}
// DropCache 丢弃特定帧的缓存,当 handler 返回 true 时将丢弃缓存
func (slf *Lockstep[ClientID, Command]) DropCache(handler func(frame int64) bool) {
slf.frameCacheLock.Lock()
defer slf.frameCacheLock.Unlock()
for frame := range slf.frameCache {
if handler(frame) {
delete(slf.frameCache, frame)
}
}
}
// LeaveClient 将客户端从广播队列中移除
func (slf *Lockstep[ClientID, Command]) LeaveClient(clientId ClientID) {
slf.clientLock.Lock()
defer slf.clientLock.Unlock()
delete(slf.clients, clientId)
delete(slf.clientFrame, clientId)
}
// StartBroadcast 开始广播
// - 在开始广播后将持续按照设定的帧率进行帧数推进,并在每一帧推进时向客户端进行同步,需提前将客户端加入广播队列 JoinClient
// - 广播过程中使用 AddCommand 将该帧数据追加到当前帧中
func (slf *Lockstep[ClientID, Command]) StartBroadcast() {
slf.runningLock.Lock()
if slf.running {
slf.runningLock.Unlock()
return
}
slf.running = true
if slf.ticker == nil {
slf.ticker = time.NewTicker(time.Second / time.Duration(slf.frameRate))
}
slf.runningLock.Unlock()
slf.currentFrame = slf.initFrame
go func(ls *Lockstep[ClientID, Command]) {
for {
ls.runningLock.RLock()
if ls.ticker == nil || !ls.running {
ls.runningLock.RUnlock()
break
}
_, ok := <-ls.ticker.C
ls.runningLock.RUnlock()
if !ok {
break
}
go func(ls *Lockstep[ClientID, Command]) {
ls.currentFrameLock.RLock()
if ls.frameLimit > 0 && ls.currentFrame >= ls.frameLimit {
ls.currentFrameLock.RUnlock()
ls.StopBroadcast()
return
}
ls.currentFrameLock.RUnlock()
ls.currentFrameLock.Lock()
ls.currentFrame++
currentFrame := ls.currentFrame
currentCommands := ls.currentCommands
ls.currentCommands = make([]Command, 0, len(currentCommands))
ls.currentFrameLock.Unlock()
ls.clientLock.RLock()
defer ls.clientLock.RUnlock()
ls.frameCacheLock.Lock()
defer ls.frameCacheLock.Unlock()
for clientId, client := range ls.clients {
var i = ls.clientFrame[clientId]
if i < ls.initFrame {
i = ls.initFrame
}
for ; i < currentFrame; i++ {
cache, exist := ls.frameCache[i]
if !exist {
cache = ls.serialization(i, currentCommands)
ls.frameCache[i] = cache
}
client.Write(cache)
}
ls.clientFrame[clientId] = currentFrame
}
}(ls)
}
}(slf)
}
// StopBroadcast 停止广播
func (slf *Lockstep[ClientID, Command]) StopBroadcast() {
slf.runningLock.Lock()
if !slf.running {
slf.runningLock.Unlock()
return
}
slf.running = false
if slf.ticker != nil {
slf.ticker.Stop()
}
slf.ticker = nil
slf.runningLock.Unlock()
slf.OnLockstepStoppedEvent()
slf.currentFrameLock.Lock()
defer slf.currentFrameLock.Unlock()
slf.frameCacheLock.Lock()
defer slf.frameCacheLock.Unlock()
slf.clientLock.Lock()
defer slf.clientLock.Unlock()
slf.frameCache = make(map[int64][]byte)
slf.currentCommands = make([]Command, 0)
slf.currentFrame = -1
slf.clientFrame = make(map[ClientID]int64)
}
// IsRunning 是否正在广播
func (slf *Lockstep[ClientID, Command]) IsRunning() bool {
slf.runningLock.RLock()
defer slf.runningLock.RUnlock()
return slf.running
}
// AddCommand 添加命令到当前帧
func (slf *Lockstep[ClientID, Command]) AddCommand(command Command) {
slf.currentFrameLock.Lock()
defer slf.currentFrameLock.Unlock()
slf.currentCommands = append(slf.currentCommands, command)
}
// AddCommands 添加命令到当前帧
func (slf *Lockstep[ClientID, Command]) AddCommands(commands []Command) {
slf.currentFrameLock.Lock()
defer slf.currentFrameLock.Unlock()
slf.currentCommands = append(slf.currentCommands, commands...)
}
// GetCurrentFrame 获取当前帧
func (slf *Lockstep[ClientID, Command]) GetCurrentFrame() int64 {
slf.currentFrameLock.RLock()
defer slf.currentFrameLock.RUnlock()
return slf.currentFrame
}
// GetClientCurrentFrame 获取客户端当前帧
func (slf *Lockstep[ClientID, Command]) GetClientCurrentFrame(clientId ClientID) int64 {
slf.clientLock.RLock()
defer slf.clientLock.RUnlock()
return slf.clientFrame[clientId]
}
// GetFrameLimit 获取帧上限
// - 未设置时将返回0
func (slf *Lockstep[ClientID, Command]) GetFrameLimit() int64 {
return slf.frameLimit
}
// GetCurrentCommands 获取当前帧还未结束时的所有指令
func (slf *Lockstep[ClientID, Command]) GetCurrentCommands() []Command {
slf.currentFrameLock.RLock()
defer slf.currentFrameLock.RUnlock()
return slf.currentCommands
}
// RegLockstepStoppedEvent 当广播停止时将触发被注册的事件处理函数
func (slf *Lockstep[ClientID, Command]) RegLockstepStoppedEvent(handle StoppedEventHandle[ClientID, Command]) {
slf.lockstepStoppedEventHandles = append(slf.lockstepStoppedEventHandles, handle)
}
func (slf *Lockstep[ClientID, Command]) OnLockstepStoppedEvent() {
for _, handle := range slf.lockstepStoppedEventHandles {
handle(slf)
}
}