97 lines
3.3 KiB
Go
97 lines
3.3 KiB
Go
package components
|
||
|
||
import (
|
||
"encoding/json"
|
||
"github.com/kercylan98/minotaur/component"
|
||
"github.com/kercylan98/minotaur/utils/synchronization"
|
||
"github.com/kercylan98/minotaur/utils/timer"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[ClientID, Command]) *Lockstep[ClientID, Command] {
|
||
lockstep := &Lockstep[ClientID, Command]{
|
||
clients: synchronization.NewMap[ClientID, component.LockstepClient[ClientID]](),
|
||
frames: synchronization.NewMap[int, []Command](),
|
||
ticker: timer.GetTicker(10),
|
||
frameRate: 15,
|
||
serialization: func(frame int, commands []Command) []byte {
|
||
frameStruct := struct {
|
||
Frame int `json:"frame"`
|
||
Commands []Command `json:"commands"`
|
||
}{frame, commands}
|
||
data, _ := json.Marshal(frameStruct)
|
||
return data
|
||
},
|
||
clientCurrentFrame: synchronization.NewMap[ClientID, int](),
|
||
}
|
||
for _, option := range options {
|
||
option(lockstep)
|
||
}
|
||
return lockstep
|
||
}
|
||
|
||
type Lockstep[ClientID comparable, Command any] struct {
|
||
clients *synchronization.Map[ClientID, component.LockstepClient[ClientID]] // 接受广播的客户端
|
||
frames *synchronization.Map[int, []Command] // 所有帧指令
|
||
ticker *timer.Ticker // 定时器
|
||
frameMutex sync.Mutex // 帧锁
|
||
currentFrame int // 当前帧
|
||
clientCurrentFrame *synchronization.Map[ClientID, int] // 客户端当前帧数
|
||
|
||
frameRate int // 帧率(每秒N帧)
|
||
serialization func(frame int, commands []Command) []byte // 序列化函数
|
||
}
|
||
|
||
func (slf *Lockstep[ClientID, Command]) JoinClient(client component.LockstepClient[ClientID]) {
|
||
slf.clients.Set(client.GetID(), client)
|
||
}
|
||
|
||
func (slf *Lockstep[ClientID, Command]) JoinClientWithFrame(client component.LockstepClient[ClientID], frameIndex int) {
|
||
slf.clients.Set(client.GetID(), client)
|
||
if frameIndex > slf.currentFrame {
|
||
frameIndex = slf.currentFrame
|
||
}
|
||
slf.clientCurrentFrame.Set(client.GetID(), frameIndex)
|
||
}
|
||
|
||
func (slf *Lockstep[ClientID, Command]) LeaveClient(clientId ClientID) {
|
||
slf.clients.Delete(clientId)
|
||
slf.clientCurrentFrame.Delete(clientId)
|
||
}
|
||
|
||
func (slf *Lockstep[ClientID, Command]) StartBroadcast() {
|
||
slf.ticker.Loop("lockstep", timer.Instantly, time.Second/time.Duration(slf.frameRate), timer.Forever, func() {
|
||
|
||
slf.frameMutex.Lock()
|
||
currentFrame := slf.currentFrame
|
||
slf.currentFrame++
|
||
slf.frameMutex.Unlock()
|
||
|
||
frames := slf.frames.Map()
|
||
for clientId, client := range slf.clients.Map() {
|
||
var i = slf.clientCurrentFrame.Get(clientId)
|
||
for ; i < currentFrame; i++ {
|
||
client.Send(slf.serialization(i, frames[i]))
|
||
}
|
||
slf.clientCurrentFrame.Set(clientId, i)
|
||
|
||
}
|
||
})
|
||
}
|
||
|
||
func (slf *Lockstep[ClientID, Command]) Stop() {
|
||
slf.ticker.StopTimer("lockstep")
|
||
slf.frameMutex.Lock()
|
||
slf.currentFrame = 0
|
||
slf.clientCurrentFrame.Clear()
|
||
slf.frames.Clear()
|
||
slf.frameMutex.Unlock()
|
||
}
|
||
|
||
func (slf *Lockstep[ClientID, Command]) AddCommand(command Command) {
|
||
slf.frames.AtomGetSet(slf.currentFrame, func(value []Command, exist bool) (newValue []Command, isSet bool) {
|
||
return append(value, command), true
|
||
})
|
||
}
|