Refactoring backend for periodical force confirmation.

This commit is contained in:
genxium 2022-09-29 12:21:04 +08:00
parent 14fb8e94b2
commit 266335b7c6
11 changed files with 1083 additions and 1033 deletions

View File

@ -1,3 +1,4 @@
# Potential avalanche from local lag
Under the current "input delay" algorithm, the lag of a single player would cause all the other players to receive outdated commands, e.g. when at a certain moment
- player#1: renderFrameId = 100, significantly lagged due to local CPU overheated
- player#2: renderFrameId = 240
@ -9,3 +10,34 @@ players #2, #3 #4 would receive "outdated(in their subjective feelings) but all-
In a "no-server & p2p" setup, I couldn't think of a proper way to cope with such edge case. Solely on the frontend we could only mitigate the impact to players #2, #3, #4, e.g. a potential lag due to "large range of frame-chasing" is proactively avoided in `<proj-root>/frontend/assets/scripts/Map.js, function update(dt)`.
However in a "server as authority" setup, the server could force confirming an inputFrame without player#1's upsync, and notify player#1 to apply a "roomDownsyncFrame" as well as drop all its outdated local inputFrames.
# Start up frames
renderFrameId | generatedInputFrameId | toApplyInputFrameId
-------------------|----------------------------|----------------------
0, 1, 2, 3 | 0, _EMP_, _EMP_, _EMP_ | 0
4, 5, 6, 7 | 1, _EMP_, _EMP_, _EMP_ | 0
8, 9, 10, 11 | 2, _EMP_, _EMP_, _EMP_ | 1
12, 13, 14, 15 | 3, _EMP_, _EMP_, _EMP_ | 2
It should be reasonable to assume that inputFrameId=0 is always of all-empty content, because human has no chance of clicking at the very first render frame.
# Alignment of the current setup
The following setup is chosen deliberately for some "%4" number coincidence.
- NstDelayFrames = 2
- InputDelayFrames = 4
- InputScaleFrames = 2
If "InputDelayFrames" is changed, the impact would be as follows, kindly note that "372%4 == 0".
### pR.InputDelayFrames = 4
toApplyInputFrameId | renderFrameId
--------------------------|-----------------------------------------
92 | _EMP_, _EMP_, _EMP_, _EMP_, 372, 373, 374, 375
91 | 368, 369, 370, 371
### pR.InputDelayFrames = 5
toApplyInputFrameId | renderFrameId
--------------------------|-----------------------------------------
92 | _EMP_, _EMP_, _EMP_, _EMP_, 373, 374, 375
91 | _EMP_, 369, 370, 371, 372
90 | 368

View File

@ -26,6 +26,7 @@ require (
github.com/mattn/go-isatty v0.0.3 // indirect
github.com/mattn/go-sqlite3 v1.9.0
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
github.com/solarlune/resolv v0.5.1
github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713
github.com/ugorji/go v1.1.1 // indirect
go.uber.org/atomic v1.3.2 // indirect

View File

@ -45,6 +45,8 @@ github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0 h1:5B0uxl2lzNRVkJVg+uGHxWtRt4C0Wjc6kJKo5XYx8xE=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
github.com/kvartborg/vector v0.0.0-20200419093813-2cba0cabb4f0 h1:v8lWpj5957KtDMKu+xQtlu6G3ZoZR6Tn9bsfZCRG5Xw=
github.com/kvartborg/vector v0.0.0-20200419093813-2cba0cabb4f0/go.mod h1:GAX7tMJqXx9fB1BrsTWPOXy6IBRX+J461BffVPAdpwo=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
@ -61,6 +63,8 @@ github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0C
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE=
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/solarlune/resolv v0.5.1 h1:Ul6PAs/zaxiMUOEYz1Z6VeUj5k3CDcWMvSh+kivybDY=
github.com/solarlune/resolv v0.5.1/go.mod h1:HjM2f/0NoVjVdZsi26GtugX5aFbA62COEFEXkOhveRw=
github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713 h1:knaxjm6QMbUMNvuaSnJZmw0gRX4V/79JVUQiziJGM84=
github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713/go.mod h1:mlR+dHGb+4YgXkf13rkQTuzrneeHANxOm6+ZnEV9HsA=
github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w=

View File

@ -37,7 +37,7 @@ type Player struct {
X float64 `json:"x,omitempty"`
Y float64 `json:"y,omitempty"`
Dir *Direction `json:"dir,omitempty"`
Speed int32 `json:"speed,omitempty"`
Speed float64 `json:"speed,omitempty"`
BattleState int32 `json:"battleState,omitempty"`
LastMoveGmtMillis int32 `json:"lastMoveGmtMillis,omitempty"`
Score int32 `json:"score,omitempty"`

View File

@ -3,9 +3,9 @@ package models
import (
"encoding/xml"
"fmt"
"github.com/ByteArena/box2d"
"github.com/golang/protobuf/proto"
"github.com/gorilla/websocket"
"github.com/solarlune/resolv"
"go.uber.org/zap"
"io/ioutil"
"math/rand"
@ -41,12 +41,14 @@ const (
)
const (
// You can equivalently use the `GroupIndex` approach, but the more complicated and general purpose approach is used deliberately here. Reference http://www.aurelienribon.com/post/2011-07-box2d-tutorial-collision-filtering.
COLLISION_CATEGORY_CONTROLLED_PLAYER = (1 << 1)
COLLISION_CATEGORY_BARRIER = (1 << 2)
COLLISION_MASK_FOR_CONTROLLED_PLAYER = (COLLISION_CATEGORY_BARRIER)
COLLISION_MASK_FOR_BARRIER = (COLLISION_CATEGORY_CONTROLLED_PLAYER)
COLLISION_PLAYER_INDEX_PREFIX = (1 << 17)
COLLISION_BARRIER_INDEX_PREFIX = (1 << 16)
)
var DIRECTION_DECODER = [][]int32{
@ -65,7 +67,7 @@ var DIRECTION_DECODER = [][]int32{
{0, -1},
}
var DIRECTION_DECODER_INVERSE_LENGTH = []float32{
var DIRECTION_DECODER_INVERSE_LENGTH = []float64{
0.0,
1.0,
1.0,
@ -117,9 +119,11 @@ func calRoomScore(inRoomPlayerCount int32, roomPlayerCnt int, currentRoomBattleS
}
type Room struct {
Id int32
Capacity int
Players map[int32]*Player
Id int32
Capacity int
Players map[int32]*Player
PlayersArr []*Player // ordered by joinIndex
CollisionSysMap map[int32]*resolv.Object
/**
* The following `PlayerDownsyncSessionDict` is NOT individually put
* under `type Player struct` for a reason.
@ -143,22 +147,23 @@ type Room struct {
Score float32
State int32
Index int
Tick int32
RenderFrameId int32
CurDynamicsRenderFrameId int32 // [WARNING] The dynamics of backend is ALWAYS MOVING FORWARD BY ALL-CONFIRMED INPUTFRAMES (either by upsync or forced), i.e. no rollback
ServerFPS int32
BattleDurationNanos int64
EffectivePlayerCount int32
DismissalWaitGroup sync.WaitGroup
Barriers map[int32]*Barrier
AccumulatedLocalIdForBullets int32
CollidableWorld *box2d.B2World
AllPlayerInputsBuffer *RingBuffer
RenderFrameBuffer *RingBuffer
LastAllConfirmedInputFrameId int32
LastAllConfirmedInputFrameIdWithChange int32
LastAllConfirmedInputList []uint64
InputDelayFrames int32
InputDelayFrames int32 // in the count of render frames
NstDelayFrames int32 // network-single-trip delay in the count of render frames, proposed to be (InputDelayFrames >> 1) because we expect a round-trip delay to be exactly "InputDelayFrames"
InputScaleFrames uint32 // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames)
JoinIndexBooleanArr []bool
RollbackEstimatedDt float64
StageName string
StageDiscreteW int32
@ -170,8 +175,8 @@ type Room struct {
}
const (
PLAYER_DEFAULT_SPEED = 200 // Hardcoded
ADD_SPEED = 100 // Hardcoded
PLAYER_DEFAULT_SPEED = float64(200) // Hardcoded
ADD_SPEED = float64(100) // Hardcoded
)
func (pR *Room) updateScore() {
@ -310,9 +315,9 @@ func (pR *Room) ChooseStage() error {
Logger.Info("ChooseStage printing polygon2D for barrierPolygon2DList", zap.Any("barrierLocalIdInBattle", barrierLocalIdInBattle), zap.Any("polygon2D.Anchor", polygon2D.Anchor), zap.Any("polygon2D.Points", polygon2D.Points))
*/
pR.Barriers[barrierLocalIdInBattle] = &Barrier{
X: polygon2D.Anchor.X,
Y: polygon2D.Anchor.Y,
Boundary: polygon2D,
X: polygon2D.Anchor.X,
Y: polygon2D.Anchor.Y,
Boundary: polygon2D,
}
barrierLocalIdInBattle++
@ -321,11 +326,17 @@ func (pR *Room) ChooseStage() error {
return nil
}
func (pR *Room) ConvertToInputFrameId(originalFrameId int32, inputDelayFrames int32) int32 {
if originalFrameId < inputDelayFrames {
return 0
}
return ((originalFrameId - inputDelayFrames) >> pR.InputScaleFrames)
func (pR *Room) ConvertToInputFrameId(renderFrameId int32, inputDelayFrames int32) int32 {
// Specifically when "renderFrameId < inputDelayFrames", the result is 0.
return ((renderFrameId - inputDelayFrames) >> pR.InputScaleFrames)
}
func (pR *Room) ConvertToFirstUsedRenderFrameId(inputFrameId int32, inputDelayFrames int32) int32 {
return ((inputFrameId << pR.InputScaleFrames) + inputDelayFrames)
}
func (pR *Room) ConvertToLastUsedRenderFrameId(inputFrameId int32, inputDelayFrames int32) int32 {
return ((inputFrameId << pR.InputScaleFrames) + inputDelayFrames + (1 << pR.InputScaleFrames)-1)
}
func (pR *Room) EncodeUpsyncCmd(upsyncCmd *pb.InputFrameUpsync) uint64 {
@ -335,19 +346,6 @@ func (pR *Room) EncodeUpsyncCmd(upsyncCmd *pb.InputFrameUpsync) uint64 {
return ret
}
func (pR *Room) CanPopSt(refLowerInputFrameId int32) bool {
rb := pR.AllPlayerInputsBuffer
if rb.Cnt <= 0 {
return false
}
if rb.StFrameId <= refLowerInputFrameId {
// already delayed too much
return true
}
return false
}
func (pR *Room) AllPlayerInputsBufferString() string {
s := make([]string, 0)
s = append(s, fmt.Sprintf("{lastAllConfirmedInputFrameId: %v, lastAllConfirmedInputFrameIdWithChange: %v}", pR.LastAllConfirmedInputFrameId, pR.LastAllConfirmedInputFrameIdWithChange))
@ -374,9 +372,10 @@ func (pR *Room) StartBattle() {
// Always instantiates a new channel and let the old one die out due to not being retained by any root reference.
nanosPerFrame := 1000000000 / int64(pR.ServerFPS)
pR.Tick = 0
pR.RenderFrameId = 0
pR.CurDynamicsRenderFrameId = 0
// Refresh "Colliders" for server-side contact listening of B2World.
// Refresh "Colliders"
pR.refreshColliders()
/**
@ -397,15 +396,19 @@ func (pR *Room) StartBattle() {
Logger.Info("The `battleMainLoop` is started for:", zap.Any("roomId", pR.Id))
for {
if 0 == pR.Tick {
stCalculation := utils.UnixtimeNano()
if 0 == pR.RenderFrameId {
// The legacy frontend code needs this "kickoffFrame" to remove the "ready to start 3-2-1" panel
kickoffFrame := pb.RoomDownsyncFrame{
Id: pR.Tick,
Id: pR.RenderFrameId,
RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_START,
Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(),
CountdownNanos: (pR.BattleDurationNanos - totalElapsedNanos),
}
pR.RenderFrameBuffer.Put(&kickoffFrame)
for playerId, player := range pR.Players {
if swapped := atomic.CompareAndSwapInt32(&player.BattleState, PlayerBattleStateIns.ACTIVE, PlayerBattleStateIns.ACTIVE); !swapped {
/*
@ -418,80 +421,102 @@ func (pR *Room) StartBattle() {
}
if totalElapsedNanos > pR.BattleDurationNanos {
Logger.Info(fmt.Sprintf("The `battleMainLoop` is stopped:\n%v", pR.AllPlayerInputsBufferString()))
Logger.Info(fmt.Sprintf("The `battleMainLoop` for roomId=%v is stopped:\n%v", pR.Id, pR.AllPlayerInputsBufferString()))
pR.StopBattleForSettlement()
}
if swapped := atomic.CompareAndSwapInt32(&pR.State, RoomBattleStateIns.IN_BATTLE, RoomBattleStateIns.IN_BATTLE); !swapped {
return
}
stCalculation := utils.UnixtimeNano()
// TODO: Force confirm some non-all-confirmed but outdated input frames, derive server-sider RenderFrameBuffer elements from them, and send to respective players with "RoomDownsyncFrame.refFrameId=MAGIC_ROOM_DOWNSYNC_FRAME_ID_PLAYER_READDED_AND_ACKED"
refInputFrameId := int32(999999999) // Hardcoded as a max reference.
for playerId, _ := range pR.Players {
thatId := atomic.LoadInt32(&(pR.Players[playerId].AckingInputFrameId))
if thatId > refInputFrameId {
continue
}
refInputFrameId = thatId
// Prefab and buffer backend inputFrameDownsync
if pR.shouldPrefabInputFrameDownsync(pR.RenderFrameId) {
noDelayInputFrameId := pR.ConvertToInputFrameId(pR.RenderFrameId, 0)
pR.prefabInputFrameDownsync(noDelayInputFrameId)
}
for pR.CanPopSt(refInputFrameId) {
// _ = pR.AllPlayerInputsBuffer.Pop()
f := pR.AllPlayerInputsBuffer.Pop().(*pb.InputFrameDownsync)
if pR.inputFrameIdDebuggable(f.InputFrameId) {
// Popping of an "inputFrame" would be AFTER its being all being confirmed, because it requires the "inputFrame" to be all acked
Logger.Info("inputFrame lifecycle#5[popped]:", zap.Any("roomId", pR.Id), zap.Any("refInputFrameId", refInputFrameId), zap.Any("inputFrameId", f.InputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId))
}
}
// Force setting all-confirmed of buffered inputFrames periodically
unconfirmedMask := pR.forceConfirmationIfApplicable()
lastAllConfirmedInputFrameIdWithChange := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameIdWithChange))
for playerId, player := range pR.Players {
if swapped := atomic.CompareAndSwapInt32(&player.BattleState, PlayerBattleStateIns.ACTIVE, PlayerBattleStateIns.ACTIVE); !swapped {
/*
[WARNING] DON'T send anything into "DedicatedForwardingChanForPlayer" if the player is disconnected, because it could jam the channel and cause significant delay upon "battle recovery for reconnected player".
*/
continue
}
// Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId"
if 0 <= pR.CurDynamicsRenderFrameId {
nextDynamicsRenderFrameId := pR.ConvertToLastUsedRenderFrameId(pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames)
pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId)
}
toSendInputFrames := make([]*pb.InputFrameDownsync, 0, pR.AllPlayerInputsBuffer.Cnt)
// [WARNING] Websocket is TCP-based, thus no need to re-send a previously sent inputFrame to a same player!
anchorInputFrameId := atomic.LoadInt32(&(pR.Players[playerId].LastSentInputFrameId))
candidateToSendInputFrameId := anchorInputFrameId + 1
lastAllConfirmedInputFrameIdWithChange := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameIdWithChange))
for playerId, player := range pR.Players {
if swapped := atomic.CompareAndSwapInt32(&player.BattleState, PlayerBattleStateIns.ACTIVE, PlayerBattleStateIns.ACTIVE); !swapped {
/*
[WARNING] DON'T send anything into "DedicatedForwardingChanForPlayer" if the player is disconnected, because it could jam the channel and cause significant delay upon "battle recovery for reconnected player".
*/
continue
}
// [WARNING] Websocket is TCP-based, thus no need to re-send a previously sent inputFrame to a same player!
toSendInputFrames := make([]*pb.InputFrameDownsync, 0, pR.AllPlayerInputsBuffer.Cnt)
candidateToSendInputFrameId := atomic.LoadInt32(&(pR.Players[playerId].LastSentInputFrameId)) + 1
if candidateToSendInputFrameId < pR.AllPlayerInputsBuffer.StFrameId {
Logger.Warn("LastSentInputFrameId already popped:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("lastSentInputFrameId", candidateToSendInputFrameId-1), zap.Any("playerAckingInputFrameId", player.AckingInputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString()))
candidateToSendInputFrameId = pR.AllPlayerInputsBuffer.StFrameId
}
// [WARNING] EDGE CASE HERE: Upon initialization, all of "lastAllConfirmedInputFrameId", "lastAllConfirmedInputFrameIdWithChange" and "anchorInputFrameId" are "-1", thus "candidateToSendInputFrameId" starts with "0", however "inputFrameId: 0" might not have been all confirmed!
debugSendingInputFrameId := int32(-1)
// [WARNING] EDGE CASE HERE: Upon initialization, all of "lastAllConfirmedInputFrameId", "lastAllConfirmedInputFrameIdWithChange" and "anchorInputFrameId" are "-1", thus "candidateToSendInputFrameId" starts with "0", however "inputFrameId: 0" might not have been all confirmed!
debugSendingInputFrameId := int32(-1)
for candidateToSendInputFrameId <= lastAllConfirmedInputFrameIdWithChange {
tmp := pR.AllPlayerInputsBuffer.GetByFrameId(candidateToSendInputFrameId)
if nil == tmp {
break
}
f := tmp.(*pb.InputFrameDownsync)
if pR.inputFrameIdDebuggable(candidateToSendInputFrameId) {
debugSendingInputFrameId = candidateToSendInputFrameId
Logger.Info("inputFrame lifecycle#3[sending]:", zap.Any("roomId", pR.Id), zap.Any("refInputFrameId", refInputFrameId), zap.Any("playerId", playerId), zap.Any("playerAnchorInputFrameId", anchorInputFrameId), zap.Any("playerAckingInputFrameId", player.AckingInputFrameId), zap.Any("inputFrameId", candidateToSendInputFrameId), zap.Any("inputFrameId-doublecheck", f.InputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId), zap.Any("ConfirmedList", f.ConfirmedList))
}
toSendInputFrames = append(toSendInputFrames, f)
candidateToSendInputFrameId++
}
for candidateToSendInputFrameId <= lastAllConfirmedInputFrameIdWithChange {
tmp := pR.AllPlayerInputsBuffer.GetByFrameId(candidateToSendInputFrameId)
if nil == tmp {
panic(fmt.Sprintf("Required inputFrameId=%v for roomId=%v, playerId=%v doesn't exist! AllPlayerInputsBuffer=%v", candidateToSendInputFrameId, pR.Id, playerId, pR.AllPlayerInputsBufferString()))
}
f := tmp.(*pb.InputFrameDownsync)
if pR.inputFrameIdDebuggable(candidateToSendInputFrameId) {
debugSendingInputFrameId = candidateToSendInputFrameId
Logger.Info("inputFrame lifecycle#3[sending]:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("playerAckingInputFrameId", player.AckingInputFrameId), zap.Any("inputFrameId", candidateToSendInputFrameId), zap.Any("inputFrameId-doublecheck", f.InputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString()), zap.Any("ConfirmedList", f.ConfirmedList))
}
toSendInputFrames = append(toSendInputFrames, f)
candidateToSendInputFrameId++
}
if 0 >= len(toSendInputFrames) {
continue
}
indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1)
var joinMask uint64 = (1 << indiceInJoinIndexBooleanArr)
if 0 < (unconfirmedMask & joinMask) {
refRenderFrame := pR.RenderFrameBuffer.GetByFrameId(pR.CurDynamicsRenderFrameId).(*pb.RoomDownsyncFrame)
resp := pb.WsResp {
Ret: int32(Constants.RetCode.Ok),
EchoedMsgId: int32(0),
Act: DOWNSYNC_MSG_ACT_ROOM_FRAME,
InputFrameDownsyncBatch: toSendInputFrames,
Rdf: refRenderFrame,
}
pR.sendSafely(resp, playerId)
} else {
if 0 >= len(toSendInputFrames) {
continue
}
pR.sendSafely(toSendInputFrames, playerId)
atomic.StoreInt32(&(pR.Players[playerId].LastSentInputFrameId), candidateToSendInputFrameId-1)
if -1 != debugSendingInputFrameId {
Logger.Info("inputFrame lifecycle#4[sent]:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("playerAckingInputFrameId", player.AckingInputFrameId), zap.Any("inputFrameId", debugSendingInputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString()))
}
}
}
pR.sendSafely(toSendInputFrames, playerId)
atomic.StoreInt32(&(pR.Players[playerId].LastSentInputFrameId), candidateToSendInputFrameId-1)
if -1 != debugSendingInputFrameId {
Logger.Info("inputFrame lifecycle#4[sent]:", zap.Any("roomId", pR.Id), zap.Any("refInputFrameId", refInputFrameId), zap.Any("playerId", playerId), zap.Any("playerAckingInputFrameId", player.AckingInputFrameId), zap.Any("inputFrameId", debugSendingInputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId))
}
}
for 0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < pR.CurDynamicsRenderFrameId {
_ = pR.RenderFrameBuffer.Pop()
}
pR.Tick++
toApplyInputFrameId := pR.ConvertToInputFrameId(pR.CurDynamicsRenderFrameId, pR.InputDelayFrames)
for 0 < pR.AllPlayerInputsBuffer.Cnt && pR.AllPlayerInputsBuffer.StFrameId < toApplyInputFrameId {
f := pR.AllPlayerInputsBuffer.Pop().(*pb.InputFrameDownsync)
if pR.inputFrameIdDebuggable(f.InputFrameId) {
// Popping of an "inputFrame" would be AFTER its being all being confirmed, because it requires the "inputFrame" to be all acked
Logger.Info("inputFrame lifecycle#5[popped]:", zap.Any("roomId", pR.Id), zap.Any("inputFrameId", f.InputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId))
}
}
pR.RenderFrameId++
now := utils.UnixtimeNano()
elapsedInCalculation := now - stCalculation
elapsedInCalculation := (now - stCalculation)
totalElapsedNanos = (now - battleMainLoopStartedNanos)
// Logger.Info("Elapsed time statistics:", zap.Any("roomId", pR.Id), zap.Any("elapsedInCalculation", elapsedInCalculation), zap.Any("totalElapsedNanos", totalElapsedNanos))
time.Sleep(time.Duration(nanosPerFrame - elapsedInCalculation))
@ -515,19 +540,20 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
ackingFrameId := pReq.AckingFrameId
ackingInputFrameId := pReq.AckingInputFrameId
if _, existent := pR.Players[playerId]; !existent {
Logger.Warn("upcmd player doesn't exist:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId))
return
}
if swapped := atomic.CompareAndSwapInt32(&(pR.Players[playerId].AckingFrameId), pR.Players[playerId].AckingFrameId, ackingFrameId); !swapped {
panic(fmt.Sprintf("Failed to update AckingFrameId to %v for roomId=%v, playerId=%v", ackingFrameId, pR.Id, playerId))
}
if swapped := atomic.CompareAndSwapInt32(&(pR.Players[playerId].AckingInputFrameId), pR.Players[playerId].AckingInputFrameId, ackingInputFrameId); !swapped {
panic(fmt.Sprintf("Failed to update AckingInputFrameId to %v for roomId=%v, playerId=%v", ackingInputFrameId, pR.Id, playerId))
}
for _, inputFrameUpsync := range inputFrameUpsyncBatch {
if _, existent := pR.Players[playerId]; !existent {
Logger.Warn("upcmd player doesn't exist:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId))
return
}
if swapped := atomic.CompareAndSwapInt32(&(pR.Players[playerId].AckingFrameId), pR.Players[playerId].AckingFrameId, ackingFrameId); !swapped {
panic(fmt.Sprintf("Failed to update AckingFrameId to %v for roomId=%v, playerId=%v", ackingFrameId, pR.Id, playerId))
}
if swapped := atomic.CompareAndSwapInt32(&(pR.Players[playerId].AckingInputFrameId), pR.Players[playerId].AckingInputFrameId, ackingInputFrameId); !swapped {
panic(fmt.Sprintf("Failed to update AckingInputFrameId to %v for roomId=%v, playerId=%v", ackingInputFrameId, pR.Id, playerId))
}
clientInputFrameId := inputFrameUpsync.InputFrameId
if clientInputFrameId < pR.AllPlayerInputsBuffer.StFrameId {
Logger.Warn("Obsolete inputFrameUpsync:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("clientInputFrameId", clientInputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId))
@ -538,66 +564,56 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
encodedInput := pR.EncodeUpsyncCmd(inputFrameUpsync)
if clientInputFrameId >= pR.AllPlayerInputsBuffer.EdFrameId {
// The outer-if branching is for reducing an extra get-and-set operation, which is now placed in the else-branch.
for clientInputFrameId >= pR.AllPlayerInputsBuffer.EdFrameId {
newInputList := make([]uint64, len(pR.Players))
newInputList[indiceInJoinIndexBooleanArr] = encodedInput
pR.AllPlayerInputsBuffer.Put(&pb.InputFrameDownsync{
InputFrameId: pR.AllPlayerInputsBuffer.EdFrameId,
InputList: newInputList,
ConfirmedList: joinMask, // by now only the current player has confirmed this input frame
})
if pR.inputFrameIdDebuggable(clientInputFrameId) {
Logger.Info("inputFrame lifecycle#1[inserted]", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("inputFrameId", clientInputFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId))
}
}
} else {
tmp2 := pR.AllPlayerInputsBuffer.GetByFrameId(clientInputFrameId)
if nil == tmp2 {
// This shouldn't happen due to the previous 2 checks
Logger.Warn("Mysterious error getting an input frame:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("clientInputFrameId", clientInputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId))
return
}
inputFrameDownsync := tmp2.(*pb.InputFrameDownsync)
oldConfirmedList := inputFrameDownsync.ConfirmedList
if (oldConfirmedList & joinMask) > 0 {
Logger.Warn("Cmd already confirmed but getting set attempt:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("clientInputFrameId", clientInputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId))
return
}
// In Golang 1.12, there's no "compare-and-swap primitive" on a custom struct (or it's pointer, unless it's an unsafe pointer https://pkg.go.dev/sync/atomic@go1.12#CompareAndSwapPointer). Although CAS on custom struct is possible in Golang 1.19 https://pkg.go.dev/sync/atomic@go1.19.1#Value.CompareAndSwap, using a single word is still faster whenever possible.
if swapped := atomic.CompareAndSwapUint64(&inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr], uint64(0), encodedInput); !swapped {
if encodedInput > 0 {
Logger.Warn("Failed input CAS:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("clientInputFrameId", clientInputFrameId))
}
return
}
newConfirmedList := (oldConfirmedList | joinMask)
if swapped := atomic.CompareAndSwapUint64(&(inputFrameDownsync.ConfirmedList), oldConfirmedList, newConfirmedList); !swapped {
if encodedInput > 0 {
Logger.Warn("Failed confirm CAS:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("clientInputFrameId", clientInputFrameId))
}
return
}
totPlayerCnt := uint32(len(pR.Players))
allConfirmedMask := uint64((1 << totPlayerCnt) - 1) // TODO: What if a player is disconnected backthen?
if allConfirmedMask == newConfirmedList {
if false == pR.equalInputLists(inputFrameDownsync.InputList, pR.LastAllConfirmedInputList) {
atomic.StoreInt32(&(pR.LastAllConfirmedInputFrameIdWithChange), clientInputFrameId) // [WARNING] Different from the CAS in "battleMainLoop", it's safe to just update "pR.LastAllConfirmedInputFrameIdWithChange" here, because only monotonic increment is possible here!
Logger.Info("Key inputFrame change", zap.Any("roomId", pR.Id), zap.Any("inputFrameId", clientInputFrameId), zap.Any("lastInputFrameId", pR.LastAllConfirmedInputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId), zap.Any("newInputList", inputFrameDownsync.InputList), zap.Any("lastInputList", pR.LastAllConfirmedInputList))
}
atomic.StoreInt32(&(pR.LastAllConfirmedInputFrameId), clientInputFrameId) // [WARNING] It's IMPORTANT that "pR.LastAllConfirmedInputFrameId" is NOT NECESSARILY CONSECUTIVE, i.e. if one of the players disconnects and reconnects within a considerable amount of frame delays!
for i, v := range inputFrameDownsync.InputList {
// To avoid potential misuse of pointers
pR.LastAllConfirmedInputList[i] = v
}
if pR.inputFrameIdDebuggable(clientInputFrameId) {
Logger.Info("inputFrame lifecycle#2[allconfirmed]", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("inputFrameId", clientInputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId))
}
}
Logger.Warn("inputFrame too advanced! is the player cheating?", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("inputFrameId", clientInputFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId))
return
}
tmp2 := pR.AllPlayerInputsBuffer.GetByFrameId(clientInputFrameId)
if nil == tmp2 {
// This shouldn't happen due to the previous 2 checks
Logger.Warn("Mysterious error getting an input frame:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("clientInputFrameId", clientInputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId))
return
}
inputFrameDownsync := tmp2.(*pb.InputFrameDownsync)
oldConfirmedList := atomic.LoadUint64(&(inputFrameDownsync.ConfirmedList))
if (oldConfirmedList & joinMask) > 0 {
Logger.Warn("Cmd already confirmed but getting set attempt, omitting this upsync cmd:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("clientInputFrameId", clientInputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId))
return
}
// In Golang 1.12, there's no "compare-and-swap primitive" on a custom struct (or it's pointer, unless it's an unsafe pointer https://pkg.go.dev/sync/atomic@go1.12#CompareAndSwapPointer). Although CAS on custom struct is possible in Golang 1.19 https://pkg.go.dev/sync/atomic@go1.19.1#Value.CompareAndSwap, using a single word is still faster whenever possible.
if swapped := atomic.CompareAndSwapUint64(&inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr], uint64(0), encodedInput); !swapped {
Logger.Warn("Failed input CAS:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("clientInputFrameId", clientInputFrameId))
return
}
newConfirmedList := (oldConfirmedList | joinMask)
if swapped := atomic.CompareAndSwapUint64(&(inputFrameDownsync.ConfirmedList), oldConfirmedList, newConfirmedList); !swapped {
// [WARNING] Upon this error, the actual input has already been updated, which is an expected result if it caused by the force confirmation from "battleMainLoop".
Logger.Warn("Failed confirm CAS:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("clientInputFrameId", clientInputFrameId))
return
}
totPlayerCnt := uint32(len(pR.Players))
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
if allConfirmedMask == newConfirmedList {
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, playerId)
}
}
}
func (pR *Room) onInputFrameDownsyncAllConfirmed(inputFrameDownsync *pb.InputFrameDownsync, playerId int32) {
clientInputFrameId := inputFrameDownsync.InputFrameId
if false == pR.equalInputLists(inputFrameDownsync.InputList, pR.LastAllConfirmedInputList) {
atomic.StoreInt32(&(pR.LastAllConfirmedInputFrameIdWithChange), clientInputFrameId) // [WARNING] Different from the CAS in "battleMainLoop", it's safe to just update "pR.LastAllConfirmedInputFrameIdWithChange" here, because only monotonic increment is possible here!
Logger.Info("Key inputFrame change", zap.Any("roomId", pR.Id), zap.Any("inputFrameId", clientInputFrameId), zap.Any("lastInputFrameId", pR.LastAllConfirmedInputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString()), zap.Any("newInputList", inputFrameDownsync.InputList), zap.Any("lastInputList", pR.LastAllConfirmedInputList))
}
atomic.StoreInt32(&(pR.LastAllConfirmedInputFrameId), clientInputFrameId) // [WARNING] It's IMPORTANT that "pR.LastAllConfirmedInputFrameId" is NOT NECESSARILY CONSECUTIVE, i.e. if one of the players disconnects and reconnects within a considerable amount of frame delays!
for i, v := range inputFrameDownsync.InputList {
// To avoid potential misuse of pointers
pR.LastAllConfirmedInputList[i] = v
}
if pR.inputFrameIdDebuggable(clientInputFrameId) {
Logger.Info("inputFrame lifecycle#2[allconfirmed]", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("inputFrameId", clientInputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString()))
}
}
@ -619,11 +635,11 @@ func (pR *Room) StopBattleForSettlement() {
}
pR.State = RoomBattleStateIns.STOPPING_BATTLE_FOR_SETTLEMENT
Logger.Info("Stopping the `battleMainLoop` for:", zap.Any("roomId", pR.Id))
pR.Tick++
pR.RenderFrameId++
for playerId, _ := range pR.Players {
assembledFrame := pb.RoomDownsyncFrame{
Id: pR.Tick,
RefFrameId: pR.Tick, // Hardcoded for now.
Id: pR.RenderFrameId,
RefFrameId: pR.RenderFrameId, // Hardcoded for now.
Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(),
CountdownNanos: -1, // TODO: Replace this magic constant!
@ -661,7 +677,7 @@ func (pR *Room) onBattlePrepare(cb BattleStartCbType) {
}
battleReadyToStartFrame := pb.RoomDownsyncFrame{
Id: pR.Tick,
Id: pR.RenderFrameId,
Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(),
RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_READY_TO_START,
@ -733,6 +749,8 @@ func (pR *Room) onDismissed() {
// Always instantiates new HeapRAM blocks and let the old blocks die out due to not being retained by any root reference.
pR.Players = make(map[int32]*Player)
pR.PlayersArr = make([]*Player, pR.Capacity)
pR.CollisionSysMap = make(map[int32]*resolv.Object)
pR.PlayerDownsyncSessionDict = make(map[int32]*websocket.Conn)
pR.PlayerSignalToCloseDict = make(map[int32]SignalToCloseConnCbType)
@ -908,7 +926,7 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
switch pPlayer.BattleState {
case PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK:
playerAckedFrame = pb.RoomDownsyncFrame{
Id: pR.Tick,
Id: pR.RenderFrameId,
Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(),
RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_PLAYER_ADDED_AND_ACKED,
@ -916,7 +934,7 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
}
case PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK:
playerAckedFrame = pb.RoomDownsyncFrame{
Id: pR.Tick,
Id: pR.RenderFrameId,
Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(),
RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_PLAYER_READDED_AND_ACKED,
@ -963,12 +981,15 @@ func (pR *Room) sendSafely(s interface{}, playerId int32) {
}
}()
var resp *pb.WsResp = nil
var pResp *pb.WsResp = nil
switch v := s.(type) {
case pb.WsResp:
resp := s.(pb.WsResp)
pResp = &resp
case pb.RoomDownsyncFrame:
roomDownsyncFrame := s.(pb.RoomDownsyncFrame)
resp = &pb.WsResp{
pResp = &pb.WsResp{
Ret: int32(Constants.RetCode.Ok),
EchoedMsgId: int32(0),
Act: DOWNSYNC_MSG_ACT_ROOM_FRAME,
@ -976,7 +997,7 @@ func (pR *Room) sendSafely(s interface{}, playerId int32) {
}
case []*pb.InputFrameDownsync:
toSendFrames := s.([]*pb.InputFrameDownsync)
resp = &pb.WsResp{
pResp = &pb.WsResp{
Ret: int32(Constants.RetCode.Ok),
EchoedMsgId: int32(0),
Act: DOWNSYNC_MSG_ACT_INPUT_BATCH,
@ -986,7 +1007,7 @@ func (pR *Room) sendSafely(s interface{}, playerId int32) {
panic(fmt.Sprintf("Unknown downsync message type, roomId=%v, playerId=%v, roomState=%v, v=%v", pR.Id, playerId, v))
}
theBytes, marshalErr := proto.Marshal(resp)
theBytes, marshalErr := proto.Marshal(pResp)
if nil != marshalErr {
panic(fmt.Sprintf("Error marshaling downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount))
}
@ -996,156 +1017,142 @@ func (pR *Room) sendSafely(s interface{}, playerId int32) {
}
}
func (pR *Room) shouldPrefabInputFrameDownsync(renderFrameId int32) bool {
return ((renderFrameId & ((1 << pR.InputScaleFrames) - 1)) == 0)
}
func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *pb.InputFrameDownsync {
/*
Kindly note that on backend the prefab is much simpler than its frontend counterpart, because frontend will upsync its latest command immediately if there's any change w.r.t. its own prev cmd, thus if no upsync received from a frontend,
- EITHER it's due to local lag and bad network,
- OR there's no change w.r.t. to its prev cmd.
*/
var currInputFrameDownsync *pb.InputFrameDownsync = nil
if 0 == inputFrameId && 0 == pR.AllPlayerInputsBuffer.Cnt {
currInputFrameDownsync = &pb.InputFrameDownsync{
InputFrameId: 0,
InputList: make([]uint64, pR.Capacity),
ConfirmedList: uint64(0),
}
} else {
tmp := pR.AllPlayerInputsBuffer.GetByFrameId(inputFrameId - 1)
if nil == tmp {
panic(fmt.Sprintf("Error prefabbing inputFrameDownsync: roomId=%v, AllPlayerInputsBuffer=%v", pR.Id, pR.AllPlayerInputsBufferString()))
}
prevInputFrameDownsync := tmp.(*pb.InputFrameDownsync)
currInputList := prevInputFrameDownsync.InputList // Would be a clone of the values
currInputFrameDownsync = &pb.InputFrameDownsync{
InputFrameId: inputFrameId,
InputList: currInputList,
ConfirmedList: uint64(0),
}
}
pR.AllPlayerInputsBuffer.Put(currInputFrameDownsync)
return currInputFrameDownsync
}
func (pR *Room) forceConfirmationIfApplicable() uint64 {
// Force confirmation of non-all-confirmed inputFrame EXACTLY ONE AT A TIME, returns the non-confirmed mask of players, e.g. in a 4-player-battle returning 1001 means that players with JoinIndex=1 and JoinIndex=4 are non-confirmed for inputFrameId2
renderFrameId1 := (pR.RenderFrameId - pR.NstDelayFrames) // the renderFrameId which should've been rendered on frontend
if 0 > renderFrameId1 || !pR.shouldPrefabInputFrameDownsync(renderFrameId1) {
/*
The backend "shouldPrefabInputFrameDownsync" shares the same rule as frontend "shouldGenerateInputFrameUpsync".
*/
return 0
}
inputFrameId2 := pR.ConvertToInputFrameId(renderFrameId1, 0) // The inputFrame to force confirmation (if necessary)
tmp := pR.AllPlayerInputsBuffer.GetByFrameId(inputFrameId2)
if nil == tmp {
panic(fmt.Sprintf("inputFrameId2=%v doesn't exist for roomId=%v, this is abnormal because the server should prefab inputFrameDownsync in a most advanced pace, check the prefab logic! AllPlayerInputsBuffer=%v", inputFrameId2, pR.Id, pR.AllPlayerInputsBufferString()))
}
inputFrame2 := tmp.(*pb.InputFrameDownsync)
totPlayerCnt := uint32(pR.Capacity)
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
if swapped := atomic.CompareAndSwapUint64(&(inputFrame2.ConfirmedList), allConfirmedMask, allConfirmedMask); swapped {
Logger.Info(fmt.Sprintf("inputFrameId2=%v is already all-confirmed for roomId=%v, no need to force confirmation of it", inputFrameId2, pR.Id))
return 0
}
// Force confirmation of "inputFrame2"
oldConfirmedList := atomic.LoadUint64(&(inputFrame2.ConfirmedList))
atomic.StoreUint64(&(inputFrame2.ConfirmedList), allConfirmedMask)
pR.onInputFrameDownsyncAllConfirmed(inputFrame2, -1)
return (oldConfirmedList^allConfirmedMask)
}
func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRenderFrameId int32) {
if fromRenderFrameId >= toRenderFrameId {
return
}
totPlayerCnt := uint32(pR.Capacity)
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
for collisionSysRenderFrameId := fromRenderFrameId; collisionSysRenderFrameId < toRenderFrameId; collisionSysRenderFrameId++ {
delayedInputFrameId := pR.ConvertToInputFrameId(collisionSysRenderFrameId, pR.InputDelayFrames)
if 0 <= delayedInputFrameId {
tmp := pR.AllPlayerInputsBuffer.GetByFrameId(delayedInputFrameId)
if nil == tmp {
panic(fmt.Sprintf("delayedInputFrameId=%v doesn't exist for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! AllPlayerInputsBuffer=%v", delayedInputFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, collisionSysRenderFrameId, pR.AllPlayerInputsBufferString()))
}
delayedInputFrame := tmp.(pb.InputFrameDownsync)
if swapped := atomic.CompareAndSwapUint64(&(delayedInputFrame.ConfirmedList), allConfirmedMask, allConfirmedMask); !swapped {
panic(fmt.Sprintf("delayedInputFrameId=%v is not yet all-confirmed for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! AllPlayerInputsBuffer=%v", delayedInputFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, collisionSysRenderFrameId, pR.AllPlayerInputsBufferString()))
}
inputList := delayedInputFrame.InputList
// Ordered by joinIndex to guarantee determinism
for _, player := range pR.PlayersArr {
joinIndex := player.JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := pR.CollisionSysMap[collisionPlayerIndex]
encodedInput := inputList[joinIndex-1]
decodedInput := DIRECTION_DECODER[encodedInput]
decodedInputSpeedFactor := DIRECTION_DECODER_INVERSE_LENGTH[encodedInput]
baseChange := player.Speed * pR.RollbackEstimatedDt * decodedInputSpeedFactor
dx := baseChange * float64(decodedInput[0])
dy := baseChange * float64(decodedInput[1])
if collision := playerCollider.Check(dx, dy, "Barrier"); collision != nil {
changeWithCollision := collision.ContactWithObject(collision.Objects[0])
dx = changeWithCollision.X()
dy = changeWithCollision.Y()
}
playerCollider.X += dx
playerCollider.Y += dy
// Update in "collision space"
playerCollider.Update()
player.Dir.Dx = decodedInput[0]
player.Dir.Dy = decodedInput[1]
player.X = playerCollider.X
player.Y = playerCollider.Y
}
}
newRenderFrame := pb.RoomDownsyncFrame{
Id: collisionSysRenderFrameId+1,
RefFrameId: collisionSysRenderFrameId,
Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(),
CountdownNanos: (pR.BattleDurationNanos - int64(collisionSysRenderFrameId)*int64(pR.RollbackEstimatedDt*1000000000)),
}
pR.RenderFrameBuffer.Put(&newRenderFrame)
pR.CurDynamicsRenderFrameId++
}
}
func (pR *Room) inputFrameIdDebuggable(inputFrameId int32) bool {
return 0 == (inputFrameId % 10)
}
func (pR *Room) refreshColliders() {
gravity := box2d.MakeB2Vec2(0.0, 0.0)
world := box2d.MakeB2World(gravity)
world.SetContactFilter(&box2d.B2ContactFilter{})
pR.CollidableWorld = &world
Logger.Info("Begins players collider processing:", zap.Any("roomId", pR.Id))
for _, player := range pR.Players {
var bdDef box2d.B2BodyDef
colliderOffset := box2d.MakeB2Vec2(0, 0) // Matching that of client-side setting.
bdDef = box2d.MakeB2BodyDef()
bdDef.Type = box2d.B2BodyType.B2_dynamicBody
bdDef.Position.Set(player.X+colliderOffset.X, player.Y+colliderOffset.Y)
b2Body := pR.CollidableWorld.CreateBody(&bdDef)
b2CircleShape := box2d.MakeB2CircleShape()
b2CircleShape.M_radius = 32 // Matching that of client-side setting.
fd := box2d.MakeB2FixtureDef()
fd.Shape = &b2CircleShape
fd.Filter.CategoryBits = COLLISION_CATEGORY_CONTROLLED_PLAYER
fd.Filter.MaskBits = COLLISION_MASK_FOR_CONTROLLED_PLAYER
fd.Density = 0.0
b2Body.CreateFixtureFromDef(&fd)
player.CollidableBody = b2Body
b2Body.SetUserData(player)
}
Logger.Info("Ends players collider processing:", zap.Any("roomId", pR.Id))
Logger.Info("Begins barriers collider processing:", zap.Any("roomId", pR.Id))
for _, barrier := range pR.Barriers {
var bdDef box2d.B2BodyDef
bdDef.Type = box2d.B2BodyType.B2_dynamicBody
bdDef = box2d.MakeB2BodyDef()
bdDef.Position.Set(barrier.Boundary.Anchor.X, barrier.Boundary.Anchor.Y)
b2Body := pR.CollidableWorld.CreateBody(&bdDef)
pointsCount := len(barrier.Boundary.Points)
b2Vertices := make([]box2d.B2Vec2, pointsCount)
for vIndex, v2 := range barrier.Boundary.Points {
b2Vertices[vIndex] = v2.ToB2Vec2()
}
b2PolygonShape := box2d.MakeB2PolygonShape()
b2PolygonShape.Set(b2Vertices, pointsCount)
fd := box2d.MakeB2FixtureDef()
fd.Shape = &b2PolygonShape
fd.Filter.CategoryBits = COLLISION_CATEGORY_BARRIER
fd.Filter.MaskBits = COLLISION_MASK_FOR_BARRIER
fd.Density = 0.0
b2Body.CreateFixtureFromDef(&fd)
barrier.CollidableBody = b2Body
b2Body.SetUserData(barrier)
}
Logger.Info("Ends barriers collider processing:", zap.Any("roomId", pR.Id))
listener := RoomBattleContactListener{
name: "DelayNoMore",
room: pR,
}
/*
* Setting a "ContactListener" for "pR.CollidableWorld"
* will only trigger corresponding callbacks in the
* SAME GOROUTINE of "pR.CollidableWorld.Step(...)" according
* to "https://github.com/ByteArena/box2d/blob/master/DynamicsB2World.go" and
* "https://github.com/ByteArena/box2d/blob/master/DynamicsB2Contact.go".
*
* The invocation-chain involves "Step -> SolveTOI -> B2ContactUpdate -> [BeginContact, EndContact, PreSolve]".
*/
pR.CollidableWorld.SetContactListener(listener)
}
type RoomBattleContactListener struct {
name string
room *Room
}
// Implementing the GolangBox2d contact listeners [begins].
/**
* Note that the execution of these listeners is within the SAME GOROUTINE as that of "`battleMainLoop` in the same room".
* See the comments in `Room.refreshColliders()` for details.
*/
func (l RoomBattleContactListener) BeginContact(contact box2d.B2ContactInterface) {
var pBarrier *Barrier
var pPlayer *Player
switch v := contact.GetNodeA().Other.GetUserData().(type) {
case *Barrier:
pBarrier = v
case *Player:
pPlayer = v
default:
//
}
switch v := contact.GetNodeB().Other.GetUserData().(type) {
case *Barrier:
pBarrier = v
case *Player:
pPlayer = v
default:
}
if pBarrier != nil && pPlayer != nil {
Logger.Info("player begins collision with barrier:", zap.Any("barrier", pBarrier), zap.Any("player", pPlayer))
// TODO: Push back player
joinIndex := player.JoinIndex
pR.PlayersArr[joinIndex-1] = player
}
}
func (l RoomBattleContactListener) EndContact(contact box2d.B2ContactInterface) {
var pBarrier *Barrier
var pPlayer *Player
switch v := contact.GetNodeA().Other.GetUserData().(type) {
case *Barrier:
pBarrier = v
case *Player:
pPlayer = v
default:
}
switch v := contact.GetNodeB().Other.GetUserData().(type) {
case *Barrier:
pBarrier = v
case *Player:
pPlayer = v
default:
}
if pBarrier != nil && pPlayer != nil {
Logger.Info("player ends collision with barrier:", zap.Any("barrier", pBarrier), zap.Any("player", pPlayer))
}
}
func (l RoomBattleContactListener) PreSolve(contact box2d.B2ContactInterface, oldManifold box2d.B2Manifold) {
//fmt.Printf("I am PreSolve %s\n", l.name);
}
func (l RoomBattleContactListener) PostSolve(contact box2d.B2ContactInterface, impulse *box2d.B2ContactImpulse) {
//fmt.Printf("PostSolve %s\n", l.name);
}
// Implementing the GolangBox2d contact listeners [ends].

View File

@ -4,6 +4,7 @@ import (
"container/heap"
"fmt"
"github.com/gorilla/websocket"
"github.com/solarlune/resolv"
"go.uber.org/zap"
. "server/common"
"sync"
@ -100,27 +101,31 @@ func InitRoomHeapManager() {
pq[i] = &Room{
Id: int32(i + 1),
Players: make(map[int32]*Player),
PlayersArr: make([]*Player, roomCapacity),
CollisionSysMap: make(map[int32]*resolv.Object),
PlayerDownsyncSessionDict: make(map[int32]*websocket.Conn),
PlayerSignalToCloseDict: make(map[int32]SignalToCloseConnCbType),
Capacity: roomCapacity,
Score: calRoomScore(0, roomCapacity, currentRoomBattleState),
State: currentRoomBattleState,
Index: i,
Tick: 0,
RenderFrameId: 0,
CurDynamicsRenderFrameId: 0,
EffectivePlayerCount: 0,
//BattleDurationNanos: int64(5 * 1000 * 1000 * 1000),
BattleDurationNanos: int64(30 * 1000 * 1000 * 1000),
ServerFPS: 60,
Barriers: make(map[int32]*Barrier),
AccumulatedLocalIdForBullets: 0,
AllPlayerInputsBuffer: NewRingBuffer(1024),
RenderFrameBuffer: NewRingBuffer(1024),
RenderFrameBuffer: NewRingBuffer(1024),
LastAllConfirmedInputFrameId: -1,
LastAllConfirmedInputFrameIdWithChange: -1,
LastAllConfirmedInputList: make([]uint64, roomCapacity),
InputDelayFrames: 4,
NstDelayFrames: 2,
InputScaleFrames: 2,
JoinIndexBooleanArr: joinIndexBooleanArr,
RollbackEstimatedDt: float64(1.0) / 60,
}
roomMap[pq[i].Id] = pq[i]
pq[i].ChooseStage()

View File

@ -19,7 +19,7 @@ import (
const (
LOW_SCORE_TREASURE_TYPE = 1
HIGH_SCORE_TREASURE_TYPE = 2
SPEED_SHOES_TYPE = 3
SPEED_SHOES_TYPE = 3
LOW_SCORE_TREASURE_SCORE = 100
HIGH_SCORE_TREASURE_SCORE = 200

View File

@ -407,7 +407,7 @@ type Player struct {
X float64 `protobuf:"fixed64,2,opt,name=x,proto3" json:"x,omitempty"`
Y float64 `protobuf:"fixed64,3,opt,name=y,proto3" json:"y,omitempty"`
Dir *Direction `protobuf:"bytes,4,opt,name=dir,proto3" json:"dir,omitempty"`
Speed int32 `protobuf:"varint,5,opt,name=speed,proto3" json:"speed,omitempty"`
Speed float64 `protobuf:"fixed64,5,opt,name=speed,proto3" json:"speed,omitempty"`
BattleState int32 `protobuf:"varint,6,opt,name=battleState,proto3" json:"battleState,omitempty"`
LastMoveGmtMillis int32 `protobuf:"varint,7,opt,name=lastMoveGmtMillis,proto3" json:"lastMoveGmtMillis,omitempty"`
Score int32 `protobuf:"varint,10,opt,name=score,proto3" json:"score,omitempty"`
@ -475,7 +475,7 @@ func (x *Player) GetDir() *Direction {
return nil
}
func (x *Player) GetSpeed() int32 {
func (x *Player) GetSpeed() float64 {
if x != nil {
return x.Speed
}
@ -596,6 +596,171 @@ func (x *PlayerMeta) GetJoinIndex() int32 {
return 0
}
type InputFrameUpsync struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
InputFrameId int32 `protobuf:"varint,1,opt,name=inputFrameId,proto3" json:"inputFrameId,omitempty"`
EncodedDir int32 `protobuf:"varint,6,opt,name=encodedDir,proto3" json:"encodedDir,omitempty"`
}
func (x *InputFrameUpsync) Reset() {
*x = InputFrameUpsync{}
if protoimpl.UnsafeEnabled {
mi := &file_room_downsync_frame_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InputFrameUpsync) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InputFrameUpsync) ProtoMessage() {}
func (x *InputFrameUpsync) ProtoReflect() protoreflect.Message {
mi := &file_room_downsync_frame_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InputFrameUpsync.ProtoReflect.Descriptor instead.
func (*InputFrameUpsync) Descriptor() ([]byte, []int) {
return file_room_downsync_frame_proto_rawDescGZIP(), []int{8}
}
func (x *InputFrameUpsync) GetInputFrameId() int32 {
if x != nil {
return x.InputFrameId
}
return 0
}
func (x *InputFrameUpsync) GetEncodedDir() int32 {
if x != nil {
return x.EncodedDir
}
return 0
}
type InputFrameDownsync struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
InputFrameId int32 `protobuf:"varint,1,opt,name=inputFrameId,proto3" json:"inputFrameId,omitempty"`
InputList []uint64 `protobuf:"varint,2,rep,packed,name=inputList,proto3" json:"inputList,omitempty"` // Indexed by "joinIndex", we try to compress the "single player input" into 1 word (64-bit for 64-bit Golang runtime) because atomic compare-and-swap only works on 1 word. Although CAS on custom struct is possible in Golang 1.19 https://pkg.go.dev/sync/atomic@go1.19.1#Value.CompareAndSwap, using a single word is still faster whenever possible.
ConfirmedList uint64 `protobuf:"varint,3,opt,name=confirmedList,proto3" json:"confirmedList,omitempty"` // Indexed by "joinIndex", same compression concern as above
}
func (x *InputFrameDownsync) Reset() {
*x = InputFrameDownsync{}
if protoimpl.UnsafeEnabled {
mi := &file_room_downsync_frame_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InputFrameDownsync) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InputFrameDownsync) ProtoMessage() {}
func (x *InputFrameDownsync) ProtoReflect() protoreflect.Message {
mi := &file_room_downsync_frame_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InputFrameDownsync.ProtoReflect.Descriptor instead.
func (*InputFrameDownsync) Descriptor() ([]byte, []int) {
return file_room_downsync_frame_proto_rawDescGZIP(), []int{9}
}
func (x *InputFrameDownsync) GetInputFrameId() int32 {
if x != nil {
return x.InputFrameId
}
return 0
}
func (x *InputFrameDownsync) GetInputList() []uint64 {
if x != nil {
return x.InputList
}
return nil
}
func (x *InputFrameDownsync) GetConfirmedList() uint64 {
if x != nil {
return x.ConfirmedList
}
return 0
}
type HeartbeatUpsync struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ClientTimestamp int64 `protobuf:"varint,1,opt,name=clientTimestamp,proto3" json:"clientTimestamp,omitempty"`
}
func (x *HeartbeatUpsync) Reset() {
*x = HeartbeatUpsync{}
if protoimpl.UnsafeEnabled {
mi := &file_room_downsync_frame_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HeartbeatUpsync) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HeartbeatUpsync) ProtoMessage() {}
func (x *HeartbeatUpsync) ProtoReflect() protoreflect.Message {
mi := &file_room_downsync_frame_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HeartbeatUpsync.ProtoReflect.Descriptor instead.
func (*HeartbeatUpsync) Descriptor() ([]byte, []int) {
return file_room_downsync_frame_proto_rawDescGZIP(), []int{10}
}
func (x *HeartbeatUpsync) GetClientTimestamp() int64 {
if x != nil {
return x.ClientTimestamp
}
return 0
}
type RoomDownsyncFrame struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -612,7 +777,7 @@ type RoomDownsyncFrame struct {
func (x *RoomDownsyncFrame) Reset() {
*x = RoomDownsyncFrame{}
if protoimpl.UnsafeEnabled {
mi := &file_room_downsync_frame_proto_msgTypes[8]
mi := &file_room_downsync_frame_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -625,7 +790,7 @@ func (x *RoomDownsyncFrame) String() string {
func (*RoomDownsyncFrame) ProtoMessage() {}
func (x *RoomDownsyncFrame) ProtoReflect() protoreflect.Message {
mi := &file_room_downsync_frame_proto_msgTypes[8]
mi := &file_room_downsync_frame_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -638,7 +803,7 @@ func (x *RoomDownsyncFrame) ProtoReflect() protoreflect.Message {
// Deprecated: Use RoomDownsyncFrame.ProtoReflect.Descriptor instead.
func (*RoomDownsyncFrame) Descriptor() ([]byte, []int) {
return file_room_downsync_frame_proto_rawDescGZIP(), []int{8}
return file_room_downsync_frame_proto_rawDescGZIP(), []int{11}
}
func (x *RoomDownsyncFrame) GetId() int32 {
@ -683,171 +848,6 @@ func (x *RoomDownsyncFrame) GetPlayerMetas() map[int32]*PlayerMeta {
return nil
}
type InputFrameUpsync struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
InputFrameId int32 `protobuf:"varint,1,opt,name=inputFrameId,proto3" json:"inputFrameId,omitempty"`
EncodedDir int32 `protobuf:"varint,6,opt,name=encodedDir,proto3" json:"encodedDir,omitempty"`
}
func (x *InputFrameUpsync) Reset() {
*x = InputFrameUpsync{}
if protoimpl.UnsafeEnabled {
mi := &file_room_downsync_frame_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InputFrameUpsync) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InputFrameUpsync) ProtoMessage() {}
func (x *InputFrameUpsync) ProtoReflect() protoreflect.Message {
mi := &file_room_downsync_frame_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InputFrameUpsync.ProtoReflect.Descriptor instead.
func (*InputFrameUpsync) Descriptor() ([]byte, []int) {
return file_room_downsync_frame_proto_rawDescGZIP(), []int{9}
}
func (x *InputFrameUpsync) GetInputFrameId() int32 {
if x != nil {
return x.InputFrameId
}
return 0
}
func (x *InputFrameUpsync) GetEncodedDir() int32 {
if x != nil {
return x.EncodedDir
}
return 0
}
type InputFrameDownsync struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
InputFrameId int32 `protobuf:"varint,1,opt,name=inputFrameId,proto3" json:"inputFrameId,omitempty"`
InputList []uint64 `protobuf:"varint,2,rep,packed,name=inputList,proto3" json:"inputList,omitempty"` // Indexed by "joinIndex", we try to compress the "single player input" into 1 word (64-bit for 64-bit Golang runtime) because atomic compare-and-swap only works on 1 word. Although CAS on custom struct is possible in Golang 1.19 https://pkg.go.dev/sync/atomic@go1.19.1#Value.CompareAndSwap, using a single word is still faster whenever possible.
ConfirmedList uint64 `protobuf:"varint,3,opt,name=confirmedList,proto3" json:"confirmedList,omitempty"` // Indexed by "joinIndex", same compression concern as above
}
func (x *InputFrameDownsync) Reset() {
*x = InputFrameDownsync{}
if protoimpl.UnsafeEnabled {
mi := &file_room_downsync_frame_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InputFrameDownsync) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InputFrameDownsync) ProtoMessage() {}
func (x *InputFrameDownsync) ProtoReflect() protoreflect.Message {
mi := &file_room_downsync_frame_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InputFrameDownsync.ProtoReflect.Descriptor instead.
func (*InputFrameDownsync) Descriptor() ([]byte, []int) {
return file_room_downsync_frame_proto_rawDescGZIP(), []int{10}
}
func (x *InputFrameDownsync) GetInputFrameId() int32 {
if x != nil {
return x.InputFrameId
}
return 0
}
func (x *InputFrameDownsync) GetInputList() []uint64 {
if x != nil {
return x.InputList
}
return nil
}
func (x *InputFrameDownsync) GetConfirmedList() uint64 {
if x != nil {
return x.ConfirmedList
}
return 0
}
type HeartbeatUpsync struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ClientTimestamp int64 `protobuf:"varint,1,opt,name=clientTimestamp,proto3" json:"clientTimestamp,omitempty"`
}
func (x *HeartbeatUpsync) Reset() {
*x = HeartbeatUpsync{}
if protoimpl.UnsafeEnabled {
mi := &file_room_downsync_frame_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HeartbeatUpsync) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HeartbeatUpsync) ProtoMessage() {}
func (x *HeartbeatUpsync) ProtoReflect() protoreflect.Message {
mi := &file_room_downsync_frame_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HeartbeatUpsync.ProtoReflect.Descriptor instead.
func (*HeartbeatUpsync) Descriptor() ([]byte, []int) {
return file_room_downsync_frame_proto_rawDescGZIP(), []int{11}
}
func (x *HeartbeatUpsync) GetClientTimestamp() int64 {
if x != nil {
return x.ClientTimestamp
}
return 0
}
type WsReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -1119,7 +1119,7 @@ var file_room_downsync_frame_proto_rawDesc = []byte{
0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72,
0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x03, 0x64, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64,
0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x12, 0x20, 0x0a,
0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x12, 0x20, 0x0a,
0x0b, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01,
0x28, 0x05, 0x52, 0x0b, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
0x2c, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x65, 0x47, 0x6d, 0x74, 0x4d, 0x69,
@ -1138,54 +1138,54 @@ var file_room_downsync_frame_proto_rawDesc = []byte{
0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x6a, 0x6f, 0x69, 0x6e,
0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6a, 0x6f, 0x69,
0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xd7, 0x03, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x44,
0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x0a,
0x72, 0x65, 0x66, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
0x52, 0x0a, 0x72, 0x65, 0x66, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x49, 0x0a, 0x07,
0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e,
0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e,
0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d,
0x65, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07,
0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x74, 0x41,
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x74, 0x41, 0x74, 0x12,
0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x4e, 0x61, 0x6e, 0x6f,
0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x64, 0x6f,
0x77, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x12, 0x55, 0x0a, 0x0b, 0x70, 0x6c, 0x61, 0x79, 0x65,
0x72, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x74,
0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x52,
0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65,
0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x52, 0x0b, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x1a, 0x53,
0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79,
0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x17, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72,
0x78, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
0x02, 0x38, 0x01, 0x1a, 0x5b, 0x0a, 0x10, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74,
0x61, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73,
0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65,
0x72, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
0x22, 0x56, 0x0a, 0x10, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70,
0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x56, 0x0a, 0x10, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46,
0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x1e,
0x0a, 0x0a, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x18, 0x06, 0x20, 0x01,
0x28, 0x05, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x22, 0x7c,
0x0a, 0x12, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e,
0x73, 0x79, 0x6e, 0x63, 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61,
0x6d, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x6e, 0x63, 0x6f,
0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x65, 0x6e,
0x63, 0x6f, 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x22, 0x7c, 0x0a, 0x12, 0x49, 0x6e, 0x70, 0x75,
0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x22,
0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65,
0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x18,
0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74,
0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4c, 0x69, 0x73,
0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d,
0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x0f, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62,
0x65, 0x61, 0x74, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6c, 0x69,
0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01,
0x28, 0x03, 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x22, 0xca, 0x02, 0x0a, 0x05, 0x57, 0x73, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a,
0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x0f,
0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12,
0x28, 0x0a, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xd7, 0x03, 0x0a, 0x11, 0x52, 0x6f,
0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12,
0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x02, 0x20,
0x01, 0x28, 0x05, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12,
0x49, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x2f, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65,
0x72, 0x78, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46,
0x72, 0x61, 0x6d, 0x65, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x52, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65,
0x6e, 0x74, 0x41, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x74,
0x41, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x4e,
0x61, 0x6e, 0x6f, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x64, 0x6f, 0x77, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x12, 0x55, 0x0a, 0x0b, 0x70, 0x6c,
0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x33, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72,
0x78, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72,
0x61, 0x6d, 0x65, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61,
0x73, 0x1a, 0x53, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03,
0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e,
0x74, 0x65, 0x72, 0x78, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5b, 0x0a, 0x10, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72,
0x4d, 0x65, 0x74, 0x61, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72,
0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x50, 0x6c,
0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
0x02, 0x38, 0x01, 0x22, 0xca, 0x02, 0x0a, 0x05, 0x57, 0x73, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a,
0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6d, 0x73,
0x67, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12,
@ -1250,10 +1250,10 @@ var file_room_downsync_frame_proto_goTypes = []interface{}{
(*BattleColliderInfo)(nil), // 5: treasurehunterx.BattleColliderInfo
(*Player)(nil), // 6: treasurehunterx.Player
(*PlayerMeta)(nil), // 7: treasurehunterx.PlayerMeta
(*RoomDownsyncFrame)(nil), // 8: treasurehunterx.RoomDownsyncFrame
(*InputFrameUpsync)(nil), // 9: treasurehunterx.InputFrameUpsync
(*InputFrameDownsync)(nil), // 10: treasurehunterx.InputFrameDownsync
(*HeartbeatUpsync)(nil), // 11: treasurehunterx.HeartbeatUpsync
(*InputFrameUpsync)(nil), // 8: treasurehunterx.InputFrameUpsync
(*InputFrameDownsync)(nil), // 9: treasurehunterx.InputFrameDownsync
(*HeartbeatUpsync)(nil), // 10: treasurehunterx.HeartbeatUpsync
(*RoomDownsyncFrame)(nil), // 11: treasurehunterx.RoomDownsyncFrame
(*WsReq)(nil), // 12: treasurehunterx.WsReq
(*WsResp)(nil), // 13: treasurehunterx.WsResp
nil, // 14: treasurehunterx.BattleColliderInfo.StrToVec2DListMapEntry
@ -1271,10 +1271,10 @@ var file_room_downsync_frame_proto_depIdxs = []int32{
0, // 6: treasurehunterx.Player.dir:type_name -> treasurehunterx.Direction
16, // 7: treasurehunterx.RoomDownsyncFrame.players:type_name -> treasurehunterx.RoomDownsyncFrame.PlayersEntry
17, // 8: treasurehunterx.RoomDownsyncFrame.playerMetas:type_name -> treasurehunterx.RoomDownsyncFrame.PlayerMetasEntry
9, // 9: treasurehunterx.WsReq.inputFrameUpsyncBatch:type_name -> treasurehunterx.InputFrameUpsync
11, // 10: treasurehunterx.WsReq.hb:type_name -> treasurehunterx.HeartbeatUpsync
8, // 11: treasurehunterx.WsResp.rdf:type_name -> treasurehunterx.RoomDownsyncFrame
10, // 12: treasurehunterx.WsResp.inputFrameDownsyncBatch:type_name -> treasurehunterx.InputFrameDownsync
8, // 9: treasurehunterx.WsReq.inputFrameUpsyncBatch:type_name -> treasurehunterx.InputFrameUpsync
10, // 10: treasurehunterx.WsReq.hb:type_name -> treasurehunterx.HeartbeatUpsync
11, // 11: treasurehunterx.WsResp.rdf:type_name -> treasurehunterx.RoomDownsyncFrame
9, // 12: treasurehunterx.WsResp.inputFrameDownsyncBatch:type_name -> treasurehunterx.InputFrameDownsync
5, // 13: treasurehunterx.WsResp.bciFrame:type_name -> treasurehunterx.BattleColliderInfo
3, // 14: treasurehunterx.BattleColliderInfo.StrToVec2DListMapEntry.value:type_name -> treasurehunterx.Vec2DList
4, // 15: treasurehunterx.BattleColliderInfo.StrToPolygon2DListMapEntry.value:type_name -> treasurehunterx.Polygon2DList
@ -1390,18 +1390,6 @@ func file_room_downsync_frame_proto_init() {
}
}
file_room_downsync_frame_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RoomDownsyncFrame); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_room_downsync_frame_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InputFrameUpsync); i {
case 0:
return &v.state
@ -1413,7 +1401,7 @@ func file_room_downsync_frame_proto_init() {
return nil
}
}
file_room_downsync_frame_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
file_room_downsync_frame_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InputFrameDownsync); i {
case 0:
return &v.state
@ -1425,7 +1413,7 @@ func file_room_downsync_frame_proto_init() {
return nil
}
}
file_room_downsync_frame_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
file_room_downsync_frame_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HeartbeatUpsync); i {
case 0:
return &v.state
@ -1437,6 +1425,18 @@ func file_room_downsync_frame_proto_init() {
return nil
}
}
file_room_downsync_frame_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RoomDownsyncFrame); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_room_downsync_frame_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WsReq); i {
case 0:

View File

@ -45,7 +45,7 @@ message Player {
double x = 2;
double y = 3;
Direction dir = 4;
int32 speed = 5;
double speed = 5;
int32 battleState = 6;
int32 lastMoveGmtMillis = 7;
int32 score = 10;
@ -61,15 +61,6 @@ message PlayerMeta {
int32 joinIndex = 5;
}
message RoomDownsyncFrame {
int32 id = 1;
int32 refFrameId = 2;
map<int32, Player> players = 3;
int64 sentAt = 4;
int64 countdownNanos = 5;
map<int32, PlayerMeta> playerMetas = 6;
}
message InputFrameUpsync {
int32 inputFrameId = 1;
int32 encodedDir = 6;
@ -85,6 +76,15 @@ message HeartbeatUpsync {
int64 clientTimestamp = 1;
}
message RoomDownsyncFrame {
int32 id = 1;
int32 refFrameId = 2;
map<int32, Player> players = 3;
int64 sentAt = 4;
int64 countdownNanos = 5;
map<int32, PlayerMeta> playerMetas = 6;
}
message WsReq {
int32 msgId = 1;
int32 playerId = 2;

View File

@ -182,11 +182,11 @@ cc.Class({
return ((renderFrameId - inputDelayFrames) >> this.inputScaleFrames);
},
_convertToRenderFrameId(inputFrameId, inputDelayFrames) {
_convertToFirstUsedRenderFrameId(inputFrameId, inputDelayFrames) {
return ((inputFrameId << this.inputScaleFrames) + inputDelayFrames);
},
_shouldGenerateInputFrameUpsync(renderFrameId) {
shouldGenerateInputFrameUpsync(renderFrameId) {
return ((renderFrameId & ((1 << this.inputScaleFrames)-1)) == 0);
},
@ -608,7 +608,7 @@ cc.Class({
if (null != firstPredictedYetIncorrectInputFrameId) {
const inputFrameId1 = firstPredictedYetIncorrectInputFrameId;
const renderFrameId1 = self._convertToRenderFrameId(inputFrameId1, self.inputDelayFrames); // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId"
const renderFrameId1 = self._convertToFirstUsedRenderFrameId(inputFrameId1, self.inputDelayFrames); // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId"
if (renderFrameId1 < self.renderFrameId) {
/*
A typical case is as follows.
@ -787,7 +787,7 @@ cc.Class({
try {
let prevSelfInput = null, currSelfInput = null;
const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here
if (self._shouldGenerateInputFrameUpsync(self.renderFrameId)) {
if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) {
const prevAndCurrInputs = self._generateInputFrameUpsync(noDelayInputFrameId);
prevSelfInput = prevAndCurrInputs[0];
currSelfInput = prevAndCurrInputs[1];
@ -937,7 +937,7 @@ cc.Class({
_createRoomDownsyncFrameLocally(renderFrameId, collisionSys, collisionSysMap) {
const self = this;
const prevRenderFrameId = renderFrameId-1;
const inputFrameForPrevRenderFrame = (
const inputFrameAppliedOnPrevRenderFrame = (
0 > prevRenderFrameId
?
null
@ -948,11 +948,11 @@ cc.Class({
// TODO: Find a better way to assign speeds instead of using "speedRefRenderFrameId".
const speedRefRenderFrameId = prevRenderFrameId;
const speedRefRenderFrame = (
0 > prevRenderFrameId
0 > speedRefRenderFrameId
?
null
:
self.recentRenderCache.getByFrameId(prevRenderFrameId)
self.recentRenderCache.getByFrameId(speedRefRenderFrameId)
);
const rdf = {
@ -968,13 +968,13 @@ cc.Class({
id: playerRichInfo.id,
x: playerCollider.x,
y: playerCollider.y,
dir: self.ctrl.decodeDirection(null == inputFrameForPrevRenderFrame ? 0 : inputFrameForPrevRenderFrame.inputList[joinIndex-1]),
dir: self.ctrl.decodeDirection(null == inputFrameAppliedOnPrevRenderFrame ? 0 : inputFrameAppliedOnPrevRenderFrame.inputList[joinIndex-1]),
speed: (null == speedRefRenderFrame ? playerRichInfo.speed : speedRefRenderFrame.players[playerRichInfo.id].speed),
joinIndex: joinIndex
};
});
if (
null != inputFrameForPrevRenderFrame && self._allConfirmed(inputFrameForPrevRenderFrame.confirmedList)
null != inputFrameAppliedOnPrevRenderFrame && self._allConfirmed(inputFrameAppliedOnPrevRenderFrame.confirmedList)
&&
self.lastAllConfirmedRenderFrameId >= prevRenderFrameId
&&
@ -1033,6 +1033,8 @@ cc.Class({
playerCollider.y = player.y;
});
// [WARNING] Traverse in the order of joinIndices to guarantee determinism.
/*
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd".
*/
@ -1040,8 +1042,8 @@ cc.Class({
const renderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"
const j = self._convertToInputFrameId(i, self.inputDelayFrames);
const inputList = self.getCachedInputFrameDownsyncWithPrediction(j).inputList;
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
const joinIndex = playerRichInfo.joinIndex;
for (let j in self.playerRichInfoArr) {
const joinIndex = parseInt(j) + 1;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const player = renderFrame.players[playerId];
@ -1055,12 +1057,11 @@ cc.Class({
console.log("playerId=", playerId, "@renderFrameId=", i, ", delayedInputFrameId=", j, ", baseChange=", baseChange, ": x=", playerCollider.x, ", y=", playerCollider.y);
}
*/
});
}
collisionSys.update();
const result = collisionSys.createResult(); // Can I reuse a "self.latestCollisionSysResult" object throughout the whole battle?
// [WARNING] Traverse in the order of joinIndices to guarantee determinism.
for (let i in self.playerRichInfoArr) {
const joinIndex = parseInt(i) + 1;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;

View File

@ -1815,7 +1815,7 @@ $root.treasurehunterx = (function() {
if (message.dir != null && Object.hasOwnProperty.call(message, "dir"))
$root.treasurehunterx.Direction.encode(message.dir, writer.uint32(/* id 4, wireType 2 =*/34).fork()).ldelim();
if (message.speed != null && Object.hasOwnProperty.call(message, "speed"))
writer.uint32(/* id 5, wireType 0 =*/40).int32(message.speed);
writer.uint32(/* id 5, wireType 1 =*/41).double(message.speed);
if (message.battleState != null && Object.hasOwnProperty.call(message, "battleState"))
writer.uint32(/* id 6, wireType 0 =*/48).int32(message.battleState);
if (message.lastMoveGmtMillis != null && Object.hasOwnProperty.call(message, "lastMoveGmtMillis"))
@ -1877,7 +1877,7 @@ $root.treasurehunterx = (function() {
break;
}
case 5: {
message.speed = reader.int32();
message.speed = reader.double();
break;
}
case 6: {
@ -1950,8 +1950,8 @@ $root.treasurehunterx = (function() {
return "dir." + error;
}
if (message.speed != null && message.hasOwnProperty("speed"))
if (!$util.isInteger(message.speed))
return "speed: integer expected";
if (typeof message.speed !== "number")
return "speed: number expected";
if (message.battleState != null && message.hasOwnProperty("battleState"))
if (!$util.isInteger(message.battleState))
return "battleState: integer expected";
@ -1994,7 +1994,7 @@ $root.treasurehunterx = (function() {
message.dir = $root.treasurehunterx.Direction.fromObject(object.dir);
}
if (object.speed != null)
message.speed = object.speed | 0;
message.speed = Number(object.speed);
if (object.battleState != null)
message.battleState = object.battleState | 0;
if (object.lastMoveGmtMillis != null)
@ -2042,7 +2042,7 @@ $root.treasurehunterx = (function() {
if (message.dir != null && message.hasOwnProperty("dir"))
object.dir = $root.treasurehunterx.Direction.toObject(message.dir, options);
if (message.speed != null && message.hasOwnProperty("speed"))
object.speed = message.speed;
object.speed = options.json && !isFinite(message.speed) ? String(message.speed) : message.speed;
if (message.battleState != null && message.hasOwnProperty("battleState"))
object.battleState = message.battleState;
if (message.lastMoveGmtMillis != null && message.hasOwnProperty("lastMoveGmtMillis"))
@ -2381,446 +2381,6 @@ $root.treasurehunterx = (function() {
return PlayerMeta;
})();
treasurehunterx.RoomDownsyncFrame = (function() {
/**
* Properties of a RoomDownsyncFrame.
* @memberof treasurehunterx
* @interface IRoomDownsyncFrame
* @property {number|null} [id] RoomDownsyncFrame id
* @property {number|null} [refFrameId] RoomDownsyncFrame refFrameId
* @property {Object.<string,treasurehunterx.Player>|null} [players] RoomDownsyncFrame players
* @property {number|Long|null} [sentAt] RoomDownsyncFrame sentAt
* @property {number|Long|null} [countdownNanos] RoomDownsyncFrame countdownNanos
* @property {Object.<string,treasurehunterx.PlayerMeta>|null} [playerMetas] RoomDownsyncFrame playerMetas
*/
/**
* Constructs a new RoomDownsyncFrame.
* @memberof treasurehunterx
* @classdesc Represents a RoomDownsyncFrame.
* @implements IRoomDownsyncFrame
* @constructor
* @param {treasurehunterx.IRoomDownsyncFrame=} [properties] Properties to set
*/
function RoomDownsyncFrame(properties) {
this.players = {};
this.playerMetas = {};
if (properties)
for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
if (properties[keys[i]] != null)
this[keys[i]] = properties[keys[i]];
}
/**
* RoomDownsyncFrame id.
* @member {number} id
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.id = 0;
/**
* RoomDownsyncFrame refFrameId.
* @member {number} refFrameId
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.refFrameId = 0;
/**
* RoomDownsyncFrame players.
* @member {Object.<string,treasurehunterx.Player>} players
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.players = $util.emptyObject;
/**
* RoomDownsyncFrame sentAt.
* @member {number|Long} sentAt
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.sentAt = $util.Long ? $util.Long.fromBits(0,0,false) : 0;
/**
* RoomDownsyncFrame countdownNanos.
* @member {number|Long} countdownNanos
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.countdownNanos = $util.Long ? $util.Long.fromBits(0,0,false) : 0;
/**
* RoomDownsyncFrame playerMetas.
* @member {Object.<string,treasurehunterx.PlayerMeta>} playerMetas
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.playerMetas = $util.emptyObject;
/**
* Creates a new RoomDownsyncFrame instance using the specified properties.
* @function create
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {treasurehunterx.IRoomDownsyncFrame=} [properties] Properties to set
* @returns {treasurehunterx.RoomDownsyncFrame} RoomDownsyncFrame instance
*/
RoomDownsyncFrame.create = function create(properties) {
return new RoomDownsyncFrame(properties);
};
/**
* Encodes the specified RoomDownsyncFrame message. Does not implicitly {@link treasurehunterx.RoomDownsyncFrame.verify|verify} messages.
* @function encode
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {treasurehunterx.RoomDownsyncFrame} message RoomDownsyncFrame message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
RoomDownsyncFrame.encode = function encode(message, writer) {
if (!writer)
writer = $Writer.create();
if (message.id != null && Object.hasOwnProperty.call(message, "id"))
writer.uint32(/* id 1, wireType 0 =*/8).int32(message.id);
if (message.refFrameId != null && Object.hasOwnProperty.call(message, "refFrameId"))
writer.uint32(/* id 2, wireType 0 =*/16).int32(message.refFrameId);
if (message.players != null && Object.hasOwnProperty.call(message, "players"))
for (var keys = Object.keys(message.players), i = 0; i < keys.length; ++i) {
writer.uint32(/* id 3, wireType 2 =*/26).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]);
$root.treasurehunterx.Player.encode(message.players[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim();
}
if (message.sentAt != null && Object.hasOwnProperty.call(message, "sentAt"))
writer.uint32(/* id 4, wireType 0 =*/32).int64(message.sentAt);
if (message.countdownNanos != null && Object.hasOwnProperty.call(message, "countdownNanos"))
writer.uint32(/* id 5, wireType 0 =*/40).int64(message.countdownNanos);
if (message.playerMetas != null && Object.hasOwnProperty.call(message, "playerMetas"))
for (var keys = Object.keys(message.playerMetas), i = 0; i < keys.length; ++i) {
writer.uint32(/* id 6, wireType 2 =*/50).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]);
$root.treasurehunterx.PlayerMeta.encode(message.playerMetas[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim();
}
return writer;
};
/**
* Encodes the specified RoomDownsyncFrame message, length delimited. Does not implicitly {@link treasurehunterx.RoomDownsyncFrame.verify|verify} messages.
* @function encodeDelimited
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {treasurehunterx.RoomDownsyncFrame} message RoomDownsyncFrame message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
RoomDownsyncFrame.encodeDelimited = function encodeDelimited(message, writer) {
return this.encode(message, writer).ldelim();
};
/**
* Decodes a RoomDownsyncFrame message from the specified reader or buffer.
* @function decode
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @param {number} [length] Message length if known beforehand
* @returns {treasurehunterx.RoomDownsyncFrame} RoomDownsyncFrame
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
RoomDownsyncFrame.decode = function decode(reader, length) {
if (!(reader instanceof $Reader))
reader = $Reader.create(reader);
var end = length === undefined ? reader.len : reader.pos + length, message = new $root.treasurehunterx.RoomDownsyncFrame(), key, value;
while (reader.pos < end) {
var tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
message.id = reader.int32();
break;
}
case 2: {
message.refFrameId = reader.int32();
break;
}
case 3: {
if (message.players === $util.emptyObject)
message.players = {};
var end2 = reader.uint32() + reader.pos;
key = 0;
value = null;
while (reader.pos < end2) {
var tag2 = reader.uint32();
switch (tag2 >>> 3) {
case 1:
key = reader.int32();
break;
case 2:
value = $root.treasurehunterx.Player.decode(reader, reader.uint32());
break;
default:
reader.skipType(tag2 & 7);
break;
}
}
message.players[key] = value;
break;
}
case 4: {
message.sentAt = reader.int64();
break;
}
case 5: {
message.countdownNanos = reader.int64();
break;
}
case 6: {
if (message.playerMetas === $util.emptyObject)
message.playerMetas = {};
var end2 = reader.uint32() + reader.pos;
key = 0;
value = null;
while (reader.pos < end2) {
var tag2 = reader.uint32();
switch (tag2 >>> 3) {
case 1:
key = reader.int32();
break;
case 2:
value = $root.treasurehunterx.PlayerMeta.decode(reader, reader.uint32());
break;
default:
reader.skipType(tag2 & 7);
break;
}
}
message.playerMetas[key] = value;
break;
}
default:
reader.skipType(tag & 7);
break;
}
}
return message;
};
/**
* Decodes a RoomDownsyncFrame message from the specified reader or buffer, length delimited.
* @function decodeDelimited
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @returns {treasurehunterx.RoomDownsyncFrame} RoomDownsyncFrame
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
RoomDownsyncFrame.decodeDelimited = function decodeDelimited(reader) {
if (!(reader instanceof $Reader))
reader = new $Reader(reader);
return this.decode(reader, reader.uint32());
};
/**
* Verifies a RoomDownsyncFrame message.
* @function verify
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {Object.<string,*>} message Plain object to verify
* @returns {string|null} `null` if valid, otherwise the reason why it is not
*/
RoomDownsyncFrame.verify = function verify(message) {
if (typeof message !== "object" || message === null)
return "object expected";
if (message.id != null && message.hasOwnProperty("id"))
if (!$util.isInteger(message.id))
return "id: integer expected";
if (message.refFrameId != null && message.hasOwnProperty("refFrameId"))
if (!$util.isInteger(message.refFrameId))
return "refFrameId: integer expected";
if (message.players != null && message.hasOwnProperty("players")) {
if (!$util.isObject(message.players))
return "players: object expected";
var key = Object.keys(message.players);
for (var i = 0; i < key.length; ++i) {
if (!$util.key32Re.test(key[i]))
return "players: integer key{k:int32} expected";
{
var error = $root.treasurehunterx.Player.verify(message.players[key[i]]);
if (error)
return "players." + error;
}
}
}
if (message.sentAt != null && message.hasOwnProperty("sentAt"))
if (!$util.isInteger(message.sentAt) && !(message.sentAt && $util.isInteger(message.sentAt.low) && $util.isInteger(message.sentAt.high)))
return "sentAt: integer|Long expected";
if (message.countdownNanos != null && message.hasOwnProperty("countdownNanos"))
if (!$util.isInteger(message.countdownNanos) && !(message.countdownNanos && $util.isInteger(message.countdownNanos.low) && $util.isInteger(message.countdownNanos.high)))
return "countdownNanos: integer|Long expected";
if (message.playerMetas != null && message.hasOwnProperty("playerMetas")) {
if (!$util.isObject(message.playerMetas))
return "playerMetas: object expected";
var key = Object.keys(message.playerMetas);
for (var i = 0; i < key.length; ++i) {
if (!$util.key32Re.test(key[i]))
return "playerMetas: integer key{k:int32} expected";
{
var error = $root.treasurehunterx.PlayerMeta.verify(message.playerMetas[key[i]]);
if (error)
return "playerMetas." + error;
}
}
}
return null;
};
/**
* Creates a RoomDownsyncFrame message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {Object.<string,*>} object Plain object
* @returns {treasurehunterx.RoomDownsyncFrame} RoomDownsyncFrame
*/
RoomDownsyncFrame.fromObject = function fromObject(object) {
if (object instanceof $root.treasurehunterx.RoomDownsyncFrame)
return object;
var message = new $root.treasurehunterx.RoomDownsyncFrame();
if (object.id != null)
message.id = object.id | 0;
if (object.refFrameId != null)
message.refFrameId = object.refFrameId | 0;
if (object.players) {
if (typeof object.players !== "object")
throw TypeError(".treasurehunterx.RoomDownsyncFrame.players: object expected");
message.players = {};
for (var keys = Object.keys(object.players), i = 0; i < keys.length; ++i) {
if (typeof object.players[keys[i]] !== "object")
throw TypeError(".treasurehunterx.RoomDownsyncFrame.players: object expected");
message.players[keys[i]] = $root.treasurehunterx.Player.fromObject(object.players[keys[i]]);
}
}
if (object.sentAt != null)
if ($util.Long)
(message.sentAt = $util.Long.fromValue(object.sentAt)).unsigned = false;
else if (typeof object.sentAt === "string")
message.sentAt = parseInt(object.sentAt, 10);
else if (typeof object.sentAt === "number")
message.sentAt = object.sentAt;
else if (typeof object.sentAt === "object")
message.sentAt = new $util.LongBits(object.sentAt.low >>> 0, object.sentAt.high >>> 0).toNumber();
if (object.countdownNanos != null)
if ($util.Long)
(message.countdownNanos = $util.Long.fromValue(object.countdownNanos)).unsigned = false;
else if (typeof object.countdownNanos === "string")
message.countdownNanos = parseInt(object.countdownNanos, 10);
else if (typeof object.countdownNanos === "number")
message.countdownNanos = object.countdownNanos;
else if (typeof object.countdownNanos === "object")
message.countdownNanos = new $util.LongBits(object.countdownNanos.low >>> 0, object.countdownNanos.high >>> 0).toNumber();
if (object.playerMetas) {
if (typeof object.playerMetas !== "object")
throw TypeError(".treasurehunterx.RoomDownsyncFrame.playerMetas: object expected");
message.playerMetas = {};
for (var keys = Object.keys(object.playerMetas), i = 0; i < keys.length; ++i) {
if (typeof object.playerMetas[keys[i]] !== "object")
throw TypeError(".treasurehunterx.RoomDownsyncFrame.playerMetas: object expected");
message.playerMetas[keys[i]] = $root.treasurehunterx.PlayerMeta.fromObject(object.playerMetas[keys[i]]);
}
}
return message;
};
/**
* Creates a plain object from a RoomDownsyncFrame message. Also converts values to other types if specified.
* @function toObject
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {treasurehunterx.RoomDownsyncFrame} message RoomDownsyncFrame
* @param {$protobuf.IConversionOptions} [options] Conversion options
* @returns {Object.<string,*>} Plain object
*/
RoomDownsyncFrame.toObject = function toObject(message, options) {
if (!options)
options = {};
var object = {};
if (options.objects || options.defaults) {
object.players = {};
object.playerMetas = {};
}
if (options.defaults) {
object.id = 0;
object.refFrameId = 0;
if ($util.Long) {
var long = new $util.Long(0, 0, false);
object.sentAt = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
} else
object.sentAt = options.longs === String ? "0" : 0;
if ($util.Long) {
var long = new $util.Long(0, 0, false);
object.countdownNanos = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
} else
object.countdownNanos = options.longs === String ? "0" : 0;
}
if (message.id != null && message.hasOwnProperty("id"))
object.id = message.id;
if (message.refFrameId != null && message.hasOwnProperty("refFrameId"))
object.refFrameId = message.refFrameId;
var keys2;
if (message.players && (keys2 = Object.keys(message.players)).length) {
object.players = {};
for (var j = 0; j < keys2.length; ++j)
object.players[keys2[j]] = $root.treasurehunterx.Player.toObject(message.players[keys2[j]], options);
}
if (message.sentAt != null && message.hasOwnProperty("sentAt"))
if (typeof message.sentAt === "number")
object.sentAt = options.longs === String ? String(message.sentAt) : message.sentAt;
else
object.sentAt = options.longs === String ? $util.Long.prototype.toString.call(message.sentAt) : options.longs === Number ? new $util.LongBits(message.sentAt.low >>> 0, message.sentAt.high >>> 0).toNumber() : message.sentAt;
if (message.countdownNanos != null && message.hasOwnProperty("countdownNanos"))
if (typeof message.countdownNanos === "number")
object.countdownNanos = options.longs === String ? String(message.countdownNanos) : message.countdownNanos;
else
object.countdownNanos = options.longs === String ? $util.Long.prototype.toString.call(message.countdownNanos) : options.longs === Number ? new $util.LongBits(message.countdownNanos.low >>> 0, message.countdownNanos.high >>> 0).toNumber() : message.countdownNanos;
if (message.playerMetas && (keys2 = Object.keys(message.playerMetas)).length) {
object.playerMetas = {};
for (var j = 0; j < keys2.length; ++j)
object.playerMetas[keys2[j]] = $root.treasurehunterx.PlayerMeta.toObject(message.playerMetas[keys2[j]], options);
}
return object;
};
/**
* Converts this RoomDownsyncFrame to JSON.
* @function toJSON
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
* @returns {Object.<string,*>} JSON object
*/
RoomDownsyncFrame.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
/**
* Gets the default type url for RoomDownsyncFrame
* @function getTypeUrl
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns {string} The default type url
*/
RoomDownsyncFrame.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
if (typeUrlPrefix === undefined) {
typeUrlPrefix = "type.googleapis.com";
}
return typeUrlPrefix + "/treasurehunterx.RoomDownsyncFrame";
};
return RoomDownsyncFrame;
})();
treasurehunterx.InputFrameUpsync = (function() {
/**
@ -3564,6 +3124,446 @@ $root.treasurehunterx = (function() {
return HeartbeatUpsync;
})();
treasurehunterx.RoomDownsyncFrame = (function() {
/**
* Properties of a RoomDownsyncFrame.
* @memberof treasurehunterx
* @interface IRoomDownsyncFrame
* @property {number|null} [id] RoomDownsyncFrame id
* @property {number|null} [refFrameId] RoomDownsyncFrame refFrameId
* @property {Object.<string,treasurehunterx.Player>|null} [players] RoomDownsyncFrame players
* @property {number|Long|null} [sentAt] RoomDownsyncFrame sentAt
* @property {number|Long|null} [countdownNanos] RoomDownsyncFrame countdownNanos
* @property {Object.<string,treasurehunterx.PlayerMeta>|null} [playerMetas] RoomDownsyncFrame playerMetas
*/
/**
* Constructs a new RoomDownsyncFrame.
* @memberof treasurehunterx
* @classdesc Represents a RoomDownsyncFrame.
* @implements IRoomDownsyncFrame
* @constructor
* @param {treasurehunterx.IRoomDownsyncFrame=} [properties] Properties to set
*/
function RoomDownsyncFrame(properties) {
this.players = {};
this.playerMetas = {};
if (properties)
for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
if (properties[keys[i]] != null)
this[keys[i]] = properties[keys[i]];
}
/**
* RoomDownsyncFrame id.
* @member {number} id
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.id = 0;
/**
* RoomDownsyncFrame refFrameId.
* @member {number} refFrameId
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.refFrameId = 0;
/**
* RoomDownsyncFrame players.
* @member {Object.<string,treasurehunterx.Player>} players
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.players = $util.emptyObject;
/**
* RoomDownsyncFrame sentAt.
* @member {number|Long} sentAt
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.sentAt = $util.Long ? $util.Long.fromBits(0,0,false) : 0;
/**
* RoomDownsyncFrame countdownNanos.
* @member {number|Long} countdownNanos
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.countdownNanos = $util.Long ? $util.Long.fromBits(0,0,false) : 0;
/**
* RoomDownsyncFrame playerMetas.
* @member {Object.<string,treasurehunterx.PlayerMeta>} playerMetas
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.playerMetas = $util.emptyObject;
/**
* Creates a new RoomDownsyncFrame instance using the specified properties.
* @function create
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {treasurehunterx.IRoomDownsyncFrame=} [properties] Properties to set
* @returns {treasurehunterx.RoomDownsyncFrame} RoomDownsyncFrame instance
*/
RoomDownsyncFrame.create = function create(properties) {
return new RoomDownsyncFrame(properties);
};
/**
* Encodes the specified RoomDownsyncFrame message. Does not implicitly {@link treasurehunterx.RoomDownsyncFrame.verify|verify} messages.
* @function encode
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {treasurehunterx.RoomDownsyncFrame} message RoomDownsyncFrame message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
RoomDownsyncFrame.encode = function encode(message, writer) {
if (!writer)
writer = $Writer.create();
if (message.id != null && Object.hasOwnProperty.call(message, "id"))
writer.uint32(/* id 1, wireType 0 =*/8).int32(message.id);
if (message.refFrameId != null && Object.hasOwnProperty.call(message, "refFrameId"))
writer.uint32(/* id 2, wireType 0 =*/16).int32(message.refFrameId);
if (message.players != null && Object.hasOwnProperty.call(message, "players"))
for (var keys = Object.keys(message.players), i = 0; i < keys.length; ++i) {
writer.uint32(/* id 3, wireType 2 =*/26).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]);
$root.treasurehunterx.Player.encode(message.players[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim();
}
if (message.sentAt != null && Object.hasOwnProperty.call(message, "sentAt"))
writer.uint32(/* id 4, wireType 0 =*/32).int64(message.sentAt);
if (message.countdownNanos != null && Object.hasOwnProperty.call(message, "countdownNanos"))
writer.uint32(/* id 5, wireType 0 =*/40).int64(message.countdownNanos);
if (message.playerMetas != null && Object.hasOwnProperty.call(message, "playerMetas"))
for (var keys = Object.keys(message.playerMetas), i = 0; i < keys.length; ++i) {
writer.uint32(/* id 6, wireType 2 =*/50).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]);
$root.treasurehunterx.PlayerMeta.encode(message.playerMetas[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim();
}
return writer;
};
/**
* Encodes the specified RoomDownsyncFrame message, length delimited. Does not implicitly {@link treasurehunterx.RoomDownsyncFrame.verify|verify} messages.
* @function encodeDelimited
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {treasurehunterx.RoomDownsyncFrame} message RoomDownsyncFrame message or plain object to encode
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
RoomDownsyncFrame.encodeDelimited = function encodeDelimited(message, writer) {
return this.encode(message, writer).ldelim();
};
/**
* Decodes a RoomDownsyncFrame message from the specified reader or buffer.
* @function decode
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @param {number} [length] Message length if known beforehand
* @returns {treasurehunterx.RoomDownsyncFrame} RoomDownsyncFrame
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
RoomDownsyncFrame.decode = function decode(reader, length) {
if (!(reader instanceof $Reader))
reader = $Reader.create(reader);
var end = length === undefined ? reader.len : reader.pos + length, message = new $root.treasurehunterx.RoomDownsyncFrame(), key, value;
while (reader.pos < end) {
var tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
message.id = reader.int32();
break;
}
case 2: {
message.refFrameId = reader.int32();
break;
}
case 3: {
if (message.players === $util.emptyObject)
message.players = {};
var end2 = reader.uint32() + reader.pos;
key = 0;
value = null;
while (reader.pos < end2) {
var tag2 = reader.uint32();
switch (tag2 >>> 3) {
case 1:
key = reader.int32();
break;
case 2:
value = $root.treasurehunterx.Player.decode(reader, reader.uint32());
break;
default:
reader.skipType(tag2 & 7);
break;
}
}
message.players[key] = value;
break;
}
case 4: {
message.sentAt = reader.int64();
break;
}
case 5: {
message.countdownNanos = reader.int64();
break;
}
case 6: {
if (message.playerMetas === $util.emptyObject)
message.playerMetas = {};
var end2 = reader.uint32() + reader.pos;
key = 0;
value = null;
while (reader.pos < end2) {
var tag2 = reader.uint32();
switch (tag2 >>> 3) {
case 1:
key = reader.int32();
break;
case 2:
value = $root.treasurehunterx.PlayerMeta.decode(reader, reader.uint32());
break;
default:
reader.skipType(tag2 & 7);
break;
}
}
message.playerMetas[key] = value;
break;
}
default:
reader.skipType(tag & 7);
break;
}
}
return message;
};
/**
* Decodes a RoomDownsyncFrame message from the specified reader or buffer, length delimited.
* @function decodeDelimited
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
* @returns {treasurehunterx.RoomDownsyncFrame} RoomDownsyncFrame
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
RoomDownsyncFrame.decodeDelimited = function decodeDelimited(reader) {
if (!(reader instanceof $Reader))
reader = new $Reader(reader);
return this.decode(reader, reader.uint32());
};
/**
* Verifies a RoomDownsyncFrame message.
* @function verify
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {Object.<string,*>} message Plain object to verify
* @returns {string|null} `null` if valid, otherwise the reason why it is not
*/
RoomDownsyncFrame.verify = function verify(message) {
if (typeof message !== "object" || message === null)
return "object expected";
if (message.id != null && message.hasOwnProperty("id"))
if (!$util.isInteger(message.id))
return "id: integer expected";
if (message.refFrameId != null && message.hasOwnProperty("refFrameId"))
if (!$util.isInteger(message.refFrameId))
return "refFrameId: integer expected";
if (message.players != null && message.hasOwnProperty("players")) {
if (!$util.isObject(message.players))
return "players: object expected";
var key = Object.keys(message.players);
for (var i = 0; i < key.length; ++i) {
if (!$util.key32Re.test(key[i]))
return "players: integer key{k:int32} expected";
{
var error = $root.treasurehunterx.Player.verify(message.players[key[i]]);
if (error)
return "players." + error;
}
}
}
if (message.sentAt != null && message.hasOwnProperty("sentAt"))
if (!$util.isInteger(message.sentAt) && !(message.sentAt && $util.isInteger(message.sentAt.low) && $util.isInteger(message.sentAt.high)))
return "sentAt: integer|Long expected";
if (message.countdownNanos != null && message.hasOwnProperty("countdownNanos"))
if (!$util.isInteger(message.countdownNanos) && !(message.countdownNanos && $util.isInteger(message.countdownNanos.low) && $util.isInteger(message.countdownNanos.high)))
return "countdownNanos: integer|Long expected";
if (message.playerMetas != null && message.hasOwnProperty("playerMetas")) {
if (!$util.isObject(message.playerMetas))
return "playerMetas: object expected";
var key = Object.keys(message.playerMetas);
for (var i = 0; i < key.length; ++i) {
if (!$util.key32Re.test(key[i]))
return "playerMetas: integer key{k:int32} expected";
{
var error = $root.treasurehunterx.PlayerMeta.verify(message.playerMetas[key[i]]);
if (error)
return "playerMetas." + error;
}
}
}
return null;
};
/**
* Creates a RoomDownsyncFrame message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {Object.<string,*>} object Plain object
* @returns {treasurehunterx.RoomDownsyncFrame} RoomDownsyncFrame
*/
RoomDownsyncFrame.fromObject = function fromObject(object) {
if (object instanceof $root.treasurehunterx.RoomDownsyncFrame)
return object;
var message = new $root.treasurehunterx.RoomDownsyncFrame();
if (object.id != null)
message.id = object.id | 0;
if (object.refFrameId != null)
message.refFrameId = object.refFrameId | 0;
if (object.players) {
if (typeof object.players !== "object")
throw TypeError(".treasurehunterx.RoomDownsyncFrame.players: object expected");
message.players = {};
for (var keys = Object.keys(object.players), i = 0; i < keys.length; ++i) {
if (typeof object.players[keys[i]] !== "object")
throw TypeError(".treasurehunterx.RoomDownsyncFrame.players: object expected");
message.players[keys[i]] = $root.treasurehunterx.Player.fromObject(object.players[keys[i]]);
}
}
if (object.sentAt != null)
if ($util.Long)
(message.sentAt = $util.Long.fromValue(object.sentAt)).unsigned = false;
else if (typeof object.sentAt === "string")
message.sentAt = parseInt(object.sentAt, 10);
else if (typeof object.sentAt === "number")
message.sentAt = object.sentAt;
else if (typeof object.sentAt === "object")
message.sentAt = new $util.LongBits(object.sentAt.low >>> 0, object.sentAt.high >>> 0).toNumber();
if (object.countdownNanos != null)
if ($util.Long)
(message.countdownNanos = $util.Long.fromValue(object.countdownNanos)).unsigned = false;
else if (typeof object.countdownNanos === "string")
message.countdownNanos = parseInt(object.countdownNanos, 10);
else if (typeof object.countdownNanos === "number")
message.countdownNanos = object.countdownNanos;
else if (typeof object.countdownNanos === "object")
message.countdownNanos = new $util.LongBits(object.countdownNanos.low >>> 0, object.countdownNanos.high >>> 0).toNumber();
if (object.playerMetas) {
if (typeof object.playerMetas !== "object")
throw TypeError(".treasurehunterx.RoomDownsyncFrame.playerMetas: object expected");
message.playerMetas = {};
for (var keys = Object.keys(object.playerMetas), i = 0; i < keys.length; ++i) {
if (typeof object.playerMetas[keys[i]] !== "object")
throw TypeError(".treasurehunterx.RoomDownsyncFrame.playerMetas: object expected");
message.playerMetas[keys[i]] = $root.treasurehunterx.PlayerMeta.fromObject(object.playerMetas[keys[i]]);
}
}
return message;
};
/**
* Creates a plain object from a RoomDownsyncFrame message. Also converts values to other types if specified.
* @function toObject
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {treasurehunterx.RoomDownsyncFrame} message RoomDownsyncFrame
* @param {$protobuf.IConversionOptions} [options] Conversion options
* @returns {Object.<string,*>} Plain object
*/
RoomDownsyncFrame.toObject = function toObject(message, options) {
if (!options)
options = {};
var object = {};
if (options.objects || options.defaults) {
object.players = {};
object.playerMetas = {};
}
if (options.defaults) {
object.id = 0;
object.refFrameId = 0;
if ($util.Long) {
var long = new $util.Long(0, 0, false);
object.sentAt = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
} else
object.sentAt = options.longs === String ? "0" : 0;
if ($util.Long) {
var long = new $util.Long(0, 0, false);
object.countdownNanos = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
} else
object.countdownNanos = options.longs === String ? "0" : 0;
}
if (message.id != null && message.hasOwnProperty("id"))
object.id = message.id;
if (message.refFrameId != null && message.hasOwnProperty("refFrameId"))
object.refFrameId = message.refFrameId;
var keys2;
if (message.players && (keys2 = Object.keys(message.players)).length) {
object.players = {};
for (var j = 0; j < keys2.length; ++j)
object.players[keys2[j]] = $root.treasurehunterx.Player.toObject(message.players[keys2[j]], options);
}
if (message.sentAt != null && message.hasOwnProperty("sentAt"))
if (typeof message.sentAt === "number")
object.sentAt = options.longs === String ? String(message.sentAt) : message.sentAt;
else
object.sentAt = options.longs === String ? $util.Long.prototype.toString.call(message.sentAt) : options.longs === Number ? new $util.LongBits(message.sentAt.low >>> 0, message.sentAt.high >>> 0).toNumber() : message.sentAt;
if (message.countdownNanos != null && message.hasOwnProperty("countdownNanos"))
if (typeof message.countdownNanos === "number")
object.countdownNanos = options.longs === String ? String(message.countdownNanos) : message.countdownNanos;
else
object.countdownNanos = options.longs === String ? $util.Long.prototype.toString.call(message.countdownNanos) : options.longs === Number ? new $util.LongBits(message.countdownNanos.low >>> 0, message.countdownNanos.high >>> 0).toNumber() : message.countdownNanos;
if (message.playerMetas && (keys2 = Object.keys(message.playerMetas)).length) {
object.playerMetas = {};
for (var j = 0; j < keys2.length; ++j)
object.playerMetas[keys2[j]] = $root.treasurehunterx.PlayerMeta.toObject(message.playerMetas[keys2[j]], options);
}
return object;
};
/**
* Converts this RoomDownsyncFrame to JSON.
* @function toJSON
* @memberof treasurehunterx.RoomDownsyncFrame
* @instance
* @returns {Object.<string,*>} JSON object
*/
RoomDownsyncFrame.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
/**
* Gets the default type url for RoomDownsyncFrame
* @function getTypeUrl
* @memberof treasurehunterx.RoomDownsyncFrame
* @static
* @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns {string} The default type url
*/
RoomDownsyncFrame.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
if (typeUrlPrefix === undefined) {
typeUrlPrefix = "type.googleapis.com";
}
return typeUrlPrefix + "/treasurehunterx.RoomDownsyncFrame";
};
return RoomDownsyncFrame;
})();
treasurehunterx.WsReq = (function() {
/**