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 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#1: renderFrameId = 100, significantly lagged due to local CPU overheated
- player#2: renderFrameId = 240 - 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)`. 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. 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-isatty v0.0.3 // indirect
github.com/mattn/go-sqlite3 v1.9.0 github.com/mattn/go-sqlite3 v1.9.0
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 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/thoas/go-funk v0.0.0-20180716193722-1060394a7713
github.com/ugorji/go v1.1.1 // indirect github.com/ugorji/go v1.1.1 // indirect
go.uber.org/atomic v1.3.2 // 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/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 h1:5B0uxl2lzNRVkJVg+uGHxWtRt4C0Wjc6kJKo5XYx8xE=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= 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 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= 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= 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/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 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE=
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= 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 h1:knaxjm6QMbUMNvuaSnJZmw0gRX4V/79JVUQiziJGM84=
github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713/go.mod h1:mlR+dHGb+4YgXkf13rkQTuzrneeHANxOm6+ZnEV9HsA= 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= github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w=

View File

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

View File

@ -3,9 +3,9 @@ package models
import ( import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"github.com/ByteArena/box2d"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/solarlune/resolv"
"go.uber.org/zap" "go.uber.org/zap"
"io/ioutil" "io/ioutil"
"math/rand" "math/rand"
@ -41,12 +41,14 @@ const (
) )
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_CONTROLLED_PLAYER = (1 << 1)
COLLISION_CATEGORY_BARRIER = (1 << 2) COLLISION_CATEGORY_BARRIER = (1 << 2)
COLLISION_MASK_FOR_CONTROLLED_PLAYER = (COLLISION_CATEGORY_BARRIER) COLLISION_MASK_FOR_CONTROLLED_PLAYER = (COLLISION_CATEGORY_BARRIER)
COLLISION_MASK_FOR_BARRIER = (COLLISION_CATEGORY_CONTROLLED_PLAYER) COLLISION_MASK_FOR_BARRIER = (COLLISION_CATEGORY_CONTROLLED_PLAYER)
COLLISION_PLAYER_INDEX_PREFIX = (1 << 17)
COLLISION_BARRIER_INDEX_PREFIX = (1 << 16)
) )
var DIRECTION_DECODER = [][]int32{ var DIRECTION_DECODER = [][]int32{
@ -65,7 +67,7 @@ var DIRECTION_DECODER = [][]int32{
{0, -1}, {0, -1},
} }
var DIRECTION_DECODER_INVERSE_LENGTH = []float32{ var DIRECTION_DECODER_INVERSE_LENGTH = []float64{
0.0, 0.0,
1.0, 1.0,
1.0, 1.0,
@ -120,6 +122,8 @@ type Room struct {
Id int32 Id int32
Capacity int Capacity int
Players map[int32]*Player Players map[int32]*Player
PlayersArr []*Player // ordered by joinIndex
CollisionSysMap map[int32]*resolv.Object
/** /**
* The following `PlayerDownsyncSessionDict` is NOT individually put * The following `PlayerDownsyncSessionDict` is NOT individually put
* under `type Player struct` for a reason. * under `type Player struct` for a reason.
@ -143,22 +147,23 @@ type Room struct {
Score float32 Score float32
State int32 State int32
Index int 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 ServerFPS int32
BattleDurationNanos int64 BattleDurationNanos int64
EffectivePlayerCount int32 EffectivePlayerCount int32
DismissalWaitGroup sync.WaitGroup DismissalWaitGroup sync.WaitGroup
Barriers map[int32]*Barrier Barriers map[int32]*Barrier
AccumulatedLocalIdForBullets int32
CollidableWorld *box2d.B2World
AllPlayerInputsBuffer *RingBuffer AllPlayerInputsBuffer *RingBuffer
RenderFrameBuffer *RingBuffer RenderFrameBuffer *RingBuffer
LastAllConfirmedInputFrameId int32 LastAllConfirmedInputFrameId int32
LastAllConfirmedInputFrameIdWithChange int32 LastAllConfirmedInputFrameIdWithChange int32
LastAllConfirmedInputList []uint64 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) InputScaleFrames uint32 // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames)
JoinIndexBooleanArr []bool JoinIndexBooleanArr []bool
RollbackEstimatedDt float64
StageName string StageName string
StageDiscreteW int32 StageDiscreteW int32
@ -170,8 +175,8 @@ type Room struct {
} }
const ( const (
PLAYER_DEFAULT_SPEED = 200 // Hardcoded PLAYER_DEFAULT_SPEED = float64(200) // Hardcoded
ADD_SPEED = 100 // Hardcoded ADD_SPEED = float64(100) // Hardcoded
) )
func (pR *Room) updateScore() { func (pR *Room) updateScore() {
@ -321,11 +326,17 @@ func (pR *Room) ChooseStage() error {
return nil return nil
} }
func (pR *Room) ConvertToInputFrameId(originalFrameId int32, inputDelayFrames int32) int32 { func (pR *Room) ConvertToInputFrameId(renderFrameId int32, inputDelayFrames int32) int32 {
if originalFrameId < inputDelayFrames { // Specifically when "renderFrameId < inputDelayFrames", the result is 0.
return 0 return ((renderFrameId - inputDelayFrames) >> pR.InputScaleFrames)
} }
return ((originalFrameId - 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 { func (pR *Room) EncodeUpsyncCmd(upsyncCmd *pb.InputFrameUpsync) uint64 {
@ -335,19 +346,6 @@ func (pR *Room) EncodeUpsyncCmd(upsyncCmd *pb.InputFrameUpsync) uint64 {
return ret 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 { func (pR *Room) AllPlayerInputsBufferString() string {
s := make([]string, 0) s := make([]string, 0)
s = append(s, fmt.Sprintf("{lastAllConfirmedInputFrameId: %v, lastAllConfirmedInputFrameIdWithChange: %v}", pR.LastAllConfirmedInputFrameId, pR.LastAllConfirmedInputFrameIdWithChange)) 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. // 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) 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() pR.refreshColliders()
/** /**
@ -397,15 +396,19 @@ func (pR *Room) StartBattle() {
Logger.Info("The `battleMainLoop` is started for:", zap.Any("roomId", pR.Id)) Logger.Info("The `battleMainLoop` is started for:", zap.Any("roomId", pR.Id))
for { 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 // The legacy frontend code needs this "kickoffFrame" to remove the "ready to start 3-2-1" panel
kickoffFrame := pb.RoomDownsyncFrame{ kickoffFrame := pb.RoomDownsyncFrame{
Id: pR.Tick, Id: pR.RenderFrameId,
RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_START, RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_START,
Players: toPbPlayers(pR.Players), Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(), SentAt: utils.UnixtimeMilli(),
CountdownNanos: (pR.BattleDurationNanos - totalElapsedNanos), CountdownNanos: (pR.BattleDurationNanos - totalElapsedNanos),
} }
pR.RenderFrameBuffer.Put(&kickoffFrame)
for playerId, player := range pR.Players { for playerId, player := range pR.Players {
if swapped := atomic.CompareAndSwapInt32(&player.BattleState, PlayerBattleStateIns.ACTIVE, PlayerBattleStateIns.ACTIVE); !swapped { if swapped := atomic.CompareAndSwapInt32(&player.BattleState, PlayerBattleStateIns.ACTIVE, PlayerBattleStateIns.ACTIVE); !swapped {
/* /*
@ -418,33 +421,27 @@ func (pR *Room) StartBattle() {
} }
if totalElapsedNanos > pR.BattleDurationNanos { 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() pR.StopBattleForSettlement()
} }
if swapped := atomic.CompareAndSwapInt32(&pR.State, RoomBattleStateIns.IN_BATTLE, RoomBattleStateIns.IN_BATTLE); !swapped { if swapped := atomic.CompareAndSwapInt32(&pR.State, RoomBattleStateIns.IN_BATTLE, RoomBattleStateIns.IN_BATTLE); !swapped {
return 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" // Prefab and buffer backend inputFrameDownsync
if pR.shouldPrefabInputFrameDownsync(pR.RenderFrameId) {
refInputFrameId := int32(999999999) // Hardcoded as a max reference. noDelayInputFrameId := pR.ConvertToInputFrameId(pR.RenderFrameId, 0)
for playerId, _ := range pR.Players { pR.prefabInputFrameDownsync(noDelayInputFrameId)
thatId := atomic.LoadInt32(&(pR.Players[playerId].AckingInputFrameId))
if thatId > refInputFrameId {
continue
}
refInputFrameId = thatId
} }
for pR.CanPopSt(refInputFrameId) { // Force setting all-confirmed of buffered inputFrames periodically
// _ = pR.AllPlayerInputsBuffer.Pop() unconfirmedMask := pR.forceConfirmationIfApplicable()
f := pR.AllPlayerInputsBuffer.Pop().(*pb.InputFrameDownsync)
if pR.inputFrameIdDebuggable(f.InputFrameId) { // Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId"
// Popping of an "inputFrame" would be AFTER its being all being confirmed, because it requires the "inputFrame" to be all acked if 0 <= pR.CurDynamicsRenderFrameId {
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)) nextDynamicsRenderFrameId := pR.ConvertToLastUsedRenderFrameId(pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames)
} pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId)
} }
lastAllConfirmedInputFrameIdWithChange := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameIdWithChange)) lastAllConfirmedInputFrameIdWithChange := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameIdWithChange))
@ -455,11 +452,13 @@ func (pR *Room) StartBattle() {
*/ */
continue continue
} }
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! // [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)) toSendInputFrames := make([]*pb.InputFrameDownsync, 0, pR.AllPlayerInputsBuffer.Cnt)
candidateToSendInputFrameId := anchorInputFrameId + 1 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! // [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) debugSendingInputFrameId := int32(-1)
@ -467,31 +466,57 @@ func (pR *Room) StartBattle() {
for candidateToSendInputFrameId <= lastAllConfirmedInputFrameIdWithChange { for candidateToSendInputFrameId <= lastAllConfirmedInputFrameIdWithChange {
tmp := pR.AllPlayerInputsBuffer.GetByFrameId(candidateToSendInputFrameId) tmp := pR.AllPlayerInputsBuffer.GetByFrameId(candidateToSendInputFrameId)
if nil == tmp { if nil == tmp {
break 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) f := tmp.(*pb.InputFrameDownsync)
if pR.inputFrameIdDebuggable(candidateToSendInputFrameId) { if pR.inputFrameIdDebuggable(candidateToSendInputFrameId) {
debugSendingInputFrameId = 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)) 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) toSendInputFrames = append(toSendInputFrames, f)
candidateToSendInputFrameId++ candidateToSendInputFrameId++
} }
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) { if 0 >= len(toSendInputFrames) {
continue continue
} }
pR.sendSafely(toSendInputFrames, playerId) pR.sendSafely(toSendInputFrames, playerId)
atomic.StoreInt32(&(pR.Players[playerId].LastSentInputFrameId), candidateToSendInputFrameId-1) atomic.StoreInt32(&(pR.Players[playerId].LastSentInputFrameId), candidateToSendInputFrameId-1)
if -1 != debugSendingInputFrameId { 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)) 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.Tick++ for 0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < pR.CurDynamicsRenderFrameId {
_ = pR.RenderFrameBuffer.Pop()
}
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() now := utils.UnixtimeNano()
elapsedInCalculation := now - stCalculation elapsedInCalculation := (now - stCalculation)
totalElapsedNanos = (now - battleMainLoopStartedNanos) totalElapsedNanos = (now - battleMainLoopStartedNanos)
// Logger.Info("Elapsed time statistics:", zap.Any("roomId", pR.Id), zap.Any("elapsedInCalculation", elapsedInCalculation), zap.Any("totalElapsedNanos", totalElapsedNanos)) // Logger.Info("Elapsed time statistics:", zap.Any("roomId", pR.Id), zap.Any("elapsedInCalculation", elapsedInCalculation), zap.Any("totalElapsedNanos", totalElapsedNanos))
time.Sleep(time.Duration(nanosPerFrame - elapsedInCalculation)) time.Sleep(time.Duration(nanosPerFrame - elapsedInCalculation))
@ -515,7 +540,6 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
ackingFrameId := pReq.AckingFrameId ackingFrameId := pReq.AckingFrameId
ackingInputFrameId := pReq.AckingInputFrameId ackingInputFrameId := pReq.AckingInputFrameId
for _, inputFrameUpsync := range inputFrameUpsyncBatch {
if _, existent := pR.Players[playerId]; !existent { if _, existent := pR.Players[playerId]; !existent {
Logger.Warn("upcmd player doesn't exist:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId)) Logger.Warn("upcmd player doesn't exist:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId))
return return
@ -528,6 +552,8 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
if swapped := atomic.CompareAndSwapInt32(&(pR.Players[playerId].AckingInputFrameId), pR.Players[playerId].AckingInputFrameId, ackingInputFrameId); !swapped { 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)) panic(fmt.Sprintf("Failed to update AckingInputFrameId to %v for roomId=%v, playerId=%v", ackingInputFrameId, pR.Id, playerId))
} }
for _, inputFrameUpsync := range inputFrameUpsyncBatch {
clientInputFrameId := inputFrameUpsync.InputFrameId clientInputFrameId := inputFrameUpsync.InputFrameId
if clientInputFrameId < pR.AllPlayerInputsBuffer.StFrameId { 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)) 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,20 +564,9 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
encodedInput := pR.EncodeUpsyncCmd(inputFrameUpsync) encodedInput := pR.EncodeUpsyncCmd(inputFrameUpsync)
if clientInputFrameId >= pR.AllPlayerInputsBuffer.EdFrameId { 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. 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))
for clientInputFrameId >= pR.AllPlayerInputsBuffer.EdFrameId { return
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) tmp2 := pR.AllPlayerInputsBuffer.GetByFrameId(clientInputFrameId)
if nil == tmp2 { if nil == tmp2 {
// This shouldn't happen due to the previous 2 checks // This shouldn't happen due to the previous 2 checks
@ -559,34 +574,38 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
return return
} }
inputFrameDownsync := tmp2.(*pb.InputFrameDownsync) inputFrameDownsync := tmp2.(*pb.InputFrameDownsync)
oldConfirmedList := inputFrameDownsync.ConfirmedList oldConfirmedList := atomic.LoadUint64(&(inputFrameDownsync.ConfirmedList))
if (oldConfirmedList & joinMask) > 0 { 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)) 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 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. // 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 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)) Logger.Warn("Failed input CAS:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("clientInputFrameId", clientInputFrameId))
}
return return
} }
newConfirmedList := (oldConfirmedList | joinMask) newConfirmedList := (oldConfirmedList | joinMask)
if swapped := atomic.CompareAndSwapUint64(&(inputFrameDownsync.ConfirmedList), oldConfirmedList, newConfirmedList); !swapped { if swapped := atomic.CompareAndSwapUint64(&(inputFrameDownsync.ConfirmedList), oldConfirmedList, newConfirmedList); !swapped {
if encodedInput > 0 { // [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)) Logger.Warn("Failed confirm CAS:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("clientInputFrameId", clientInputFrameId))
}
return return
} }
totPlayerCnt := uint32(len(pR.Players)) totPlayerCnt := uint32(len(pR.Players))
allConfirmedMask := uint64((1 << totPlayerCnt) - 1) // TODO: What if a player is disconnected backthen? allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
if allConfirmedMask == newConfirmedList { 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) { 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! 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)) 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! 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 { for i, v := range inputFrameDownsync.InputList {
@ -594,10 +613,7 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
pR.LastAllConfirmedInputList[i] = v pR.LastAllConfirmedInputList[i] = v
} }
if pR.inputFrameIdDebuggable(clientInputFrameId) { 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.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 pR.State = RoomBattleStateIns.STOPPING_BATTLE_FOR_SETTLEMENT
Logger.Info("Stopping the `battleMainLoop` for:", zap.Any("roomId", pR.Id)) Logger.Info("Stopping the `battleMainLoop` for:", zap.Any("roomId", pR.Id))
pR.Tick++ pR.RenderFrameId++
for playerId, _ := range pR.Players { for playerId, _ := range pR.Players {
assembledFrame := pb.RoomDownsyncFrame{ assembledFrame := pb.RoomDownsyncFrame{
Id: pR.Tick, Id: pR.RenderFrameId,
RefFrameId: pR.Tick, // Hardcoded for now. RefFrameId: pR.RenderFrameId, // Hardcoded for now.
Players: toPbPlayers(pR.Players), Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(), SentAt: utils.UnixtimeMilli(),
CountdownNanos: -1, // TODO: Replace this magic constant! CountdownNanos: -1, // TODO: Replace this magic constant!
@ -661,7 +677,7 @@ func (pR *Room) onBattlePrepare(cb BattleStartCbType) {
} }
battleReadyToStartFrame := pb.RoomDownsyncFrame{ battleReadyToStartFrame := pb.RoomDownsyncFrame{
Id: pR.Tick, Id: pR.RenderFrameId,
Players: toPbPlayers(pR.Players), Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(), SentAt: utils.UnixtimeMilli(),
RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_READY_TO_START, 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. // 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.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.PlayerDownsyncSessionDict = make(map[int32]*websocket.Conn)
pR.PlayerSignalToCloseDict = make(map[int32]SignalToCloseConnCbType) pR.PlayerSignalToCloseDict = make(map[int32]SignalToCloseConnCbType)
@ -908,7 +926,7 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
switch pPlayer.BattleState { switch pPlayer.BattleState {
case PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK: case PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK:
playerAckedFrame = pb.RoomDownsyncFrame{ playerAckedFrame = pb.RoomDownsyncFrame{
Id: pR.Tick, Id: pR.RenderFrameId,
Players: toPbPlayers(pR.Players), Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(), SentAt: utils.UnixtimeMilli(),
RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_PLAYER_ADDED_AND_ACKED, 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: case PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK:
playerAckedFrame = pb.RoomDownsyncFrame{ playerAckedFrame = pb.RoomDownsyncFrame{
Id: pR.Tick, Id: pR.RenderFrameId,
Players: toPbPlayers(pR.Players), Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(), SentAt: utils.UnixtimeMilli(),
RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_PLAYER_READDED_AND_ACKED, 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) { switch v := s.(type) {
case pb.WsResp:
resp := s.(pb.WsResp)
pResp = &resp
case pb.RoomDownsyncFrame: case pb.RoomDownsyncFrame:
roomDownsyncFrame := s.(pb.RoomDownsyncFrame) roomDownsyncFrame := s.(pb.RoomDownsyncFrame)
resp = &pb.WsResp{ pResp = &pb.WsResp{
Ret: int32(Constants.RetCode.Ok), Ret: int32(Constants.RetCode.Ok),
EchoedMsgId: int32(0), EchoedMsgId: int32(0),
Act: DOWNSYNC_MSG_ACT_ROOM_FRAME, Act: DOWNSYNC_MSG_ACT_ROOM_FRAME,
@ -976,7 +997,7 @@ func (pR *Room) sendSafely(s interface{}, playerId int32) {
} }
case []*pb.InputFrameDownsync: case []*pb.InputFrameDownsync:
toSendFrames := s.([]*pb.InputFrameDownsync) toSendFrames := s.([]*pb.InputFrameDownsync)
resp = &pb.WsResp{ pResp = &pb.WsResp{
Ret: int32(Constants.RetCode.Ok), Ret: int32(Constants.RetCode.Ok),
EchoedMsgId: int32(0), EchoedMsgId: int32(0),
Act: DOWNSYNC_MSG_ACT_INPUT_BATCH, 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)) 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 { 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)) 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 { func (pR *Room) inputFrameIdDebuggable(inputFrameId int32) bool {
return 0 == (inputFrameId % 10) return 0 == (inputFrameId % 10)
} }
func (pR *Room) refreshColliders() { 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 { for _, player := range pR.Players {
var bdDef box2d.B2BodyDef joinIndex := player.JoinIndex
colliderOffset := box2d.MakeB2Vec2(0, 0) // Matching that of client-side setting. pR.PlayersArr[joinIndex-1] = player
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
} }
} }
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" "container/heap"
"fmt" "fmt"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/solarlune/resolv"
"go.uber.org/zap" "go.uber.org/zap"
. "server/common" . "server/common"
"sync" "sync"
@ -100,27 +101,31 @@ func InitRoomHeapManager() {
pq[i] = &Room{ pq[i] = &Room{
Id: int32(i + 1), Id: int32(i + 1),
Players: make(map[int32]*Player), Players: make(map[int32]*Player),
PlayersArr: make([]*Player, roomCapacity),
CollisionSysMap: make(map[int32]*resolv.Object),
PlayerDownsyncSessionDict: make(map[int32]*websocket.Conn), PlayerDownsyncSessionDict: make(map[int32]*websocket.Conn),
PlayerSignalToCloseDict: make(map[int32]SignalToCloseConnCbType), PlayerSignalToCloseDict: make(map[int32]SignalToCloseConnCbType),
Capacity: roomCapacity, Capacity: roomCapacity,
Score: calRoomScore(0, roomCapacity, currentRoomBattleState), Score: calRoomScore(0, roomCapacity, currentRoomBattleState),
State: currentRoomBattleState, State: currentRoomBattleState,
Index: i, Index: i,
Tick: 0, RenderFrameId: 0,
CurDynamicsRenderFrameId: 0,
EffectivePlayerCount: 0, EffectivePlayerCount: 0,
//BattleDurationNanos: int64(5 * 1000 * 1000 * 1000), //BattleDurationNanos: int64(5 * 1000 * 1000 * 1000),
BattleDurationNanos: int64(30 * 1000 * 1000 * 1000), BattleDurationNanos: int64(30 * 1000 * 1000 * 1000),
ServerFPS: 60, ServerFPS: 60,
Barriers: make(map[int32]*Barrier), Barriers: make(map[int32]*Barrier),
AccumulatedLocalIdForBullets: 0,
AllPlayerInputsBuffer: NewRingBuffer(1024), AllPlayerInputsBuffer: NewRingBuffer(1024),
RenderFrameBuffer: NewRingBuffer(1024), RenderFrameBuffer: NewRingBuffer(1024),
LastAllConfirmedInputFrameId: -1, LastAllConfirmedInputFrameId: -1,
LastAllConfirmedInputFrameIdWithChange: -1, LastAllConfirmedInputFrameIdWithChange: -1,
LastAllConfirmedInputList: make([]uint64, roomCapacity), LastAllConfirmedInputList: make([]uint64, roomCapacity),
InputDelayFrames: 4, InputDelayFrames: 4,
NstDelayFrames: 2,
InputScaleFrames: 2, InputScaleFrames: 2,
JoinIndexBooleanArr: joinIndexBooleanArr, JoinIndexBooleanArr: joinIndexBooleanArr,
RollbackEstimatedDt: float64(1.0) / 60,
} }
roomMap[pq[i].Id] = pq[i] roomMap[pq[i].Id] = pq[i]
pq[i].ChooseStage() pq[i].ChooseStage()

View File

@ -407,7 +407,7 @@ type Player struct {
X float64 `protobuf:"fixed64,2,opt,name=x,proto3" json:"x,omitempty"` X float64 `protobuf:"fixed64,2,opt,name=x,proto3" json:"x,omitempty"`
Y float64 `protobuf:"fixed64,3,opt,name=y,proto3" json:"y,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"` 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"` BattleState int32 `protobuf:"varint,6,opt,name=battleState,proto3" json:"battleState,omitempty"`
LastMoveGmtMillis int32 `protobuf:"varint,7,opt,name=lastMoveGmtMillis,proto3" json:"lastMoveGmtMillis,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"` Score int32 `protobuf:"varint,10,opt,name=score,proto3" json:"score,omitempty"`
@ -475,7 +475,7 @@ func (x *Player) GetDir() *Direction {
return nil return nil
} }
func (x *Player) GetSpeed() int32 { func (x *Player) GetSpeed() float64 {
if x != nil { if x != nil {
return x.Speed return x.Speed
} }
@ -596,6 +596,171 @@ func (x *PlayerMeta) GetJoinIndex() int32 {
return 0 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 { type RoomDownsyncFrame struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -612,7 +777,7 @@ type RoomDownsyncFrame struct {
func (x *RoomDownsyncFrame) Reset() { func (x *RoomDownsyncFrame) Reset() {
*x = RoomDownsyncFrame{} *x = RoomDownsyncFrame{}
if protoimpl.UnsafeEnabled { 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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -625,7 +790,7 @@ func (x *RoomDownsyncFrame) String() string {
func (*RoomDownsyncFrame) ProtoMessage() {} func (*RoomDownsyncFrame) ProtoMessage() {}
func (x *RoomDownsyncFrame) ProtoReflect() protoreflect.Message { 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 { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -638,7 +803,7 @@ func (x *RoomDownsyncFrame) ProtoReflect() protoreflect.Message {
// Deprecated: Use RoomDownsyncFrame.ProtoReflect.Descriptor instead. // Deprecated: Use RoomDownsyncFrame.ProtoReflect.Descriptor instead.
func (*RoomDownsyncFrame) Descriptor() ([]byte, []int) { 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 { func (x *RoomDownsyncFrame) GetId() int32 {
@ -683,171 +848,6 @@ func (x *RoomDownsyncFrame) GetPlayerMetas() map[int32]*PlayerMeta {
return nil 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 { type WsReq struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x56, 0x0a, 0x10, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46,
0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x6e,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
0x72, 0x65, 0x66, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x1e,
0x52, 0x0a, 0x72, 0x65, 0x66, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x49, 0x0a, 0x07, 0x0a, 0x0a, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x18, 0x06, 0x20, 0x01,
0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x28, 0x05, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x22, 0x7c,
0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x0a, 0x12, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e,
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,
0x73, 0x79, 0x6e, 0x63, 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 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, 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, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x70, 0x75,
0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x69, 0x6e, 0x70,
0x63, 0x6f, 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x22, 0x7c, 0x0a, 0x12, 0x49, 0x6e, 0x70, 0x75, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x22, 0x6d, 0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63,
0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x01, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x0f,
0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12,
0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x28, 0x0a, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4c, 0x69, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xd7, 0x03, 0x0a, 0x11, 0x52, 0x6f,
0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12,
0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x0f, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12,
0x65, 0x61, 0x74, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6c, 0x69, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x02, 0x20,
0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12,
0x28, 0x03, 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x49, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
0x61, 0x6d, 0x70, 0x22, 0xca, 0x02, 0x0a, 0x05, 0x57, 0x73, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 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, 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, 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, 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 (*BattleColliderInfo)(nil), // 5: treasurehunterx.BattleColliderInfo
(*Player)(nil), // 6: treasurehunterx.Player (*Player)(nil), // 6: treasurehunterx.Player
(*PlayerMeta)(nil), // 7: treasurehunterx.PlayerMeta (*PlayerMeta)(nil), // 7: treasurehunterx.PlayerMeta
(*RoomDownsyncFrame)(nil), // 8: treasurehunterx.RoomDownsyncFrame (*InputFrameUpsync)(nil), // 8: treasurehunterx.InputFrameUpsync
(*InputFrameUpsync)(nil), // 9: treasurehunterx.InputFrameUpsync (*InputFrameDownsync)(nil), // 9: treasurehunterx.InputFrameDownsync
(*InputFrameDownsync)(nil), // 10: treasurehunterx.InputFrameDownsync (*HeartbeatUpsync)(nil), // 10: treasurehunterx.HeartbeatUpsync
(*HeartbeatUpsync)(nil), // 11: treasurehunterx.HeartbeatUpsync (*RoomDownsyncFrame)(nil), // 11: treasurehunterx.RoomDownsyncFrame
(*WsReq)(nil), // 12: treasurehunterx.WsReq (*WsReq)(nil), // 12: treasurehunterx.WsReq
(*WsResp)(nil), // 13: treasurehunterx.WsResp (*WsResp)(nil), // 13: treasurehunterx.WsResp
nil, // 14: treasurehunterx.BattleColliderInfo.StrToVec2DListMapEntry 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 0, // 6: treasurehunterx.Player.dir:type_name -> treasurehunterx.Direction
16, // 7: treasurehunterx.RoomDownsyncFrame.players:type_name -> treasurehunterx.RoomDownsyncFrame.PlayersEntry 16, // 7: treasurehunterx.RoomDownsyncFrame.players:type_name -> treasurehunterx.RoomDownsyncFrame.PlayersEntry
17, // 8: treasurehunterx.RoomDownsyncFrame.playerMetas:type_name -> treasurehunterx.RoomDownsyncFrame.PlayerMetasEntry 17, // 8: treasurehunterx.RoomDownsyncFrame.playerMetas:type_name -> treasurehunterx.RoomDownsyncFrame.PlayerMetasEntry
9, // 9: treasurehunterx.WsReq.inputFrameUpsyncBatch:type_name -> treasurehunterx.InputFrameUpsync 8, // 9: treasurehunterx.WsReq.inputFrameUpsyncBatch:type_name -> treasurehunterx.InputFrameUpsync
11, // 10: treasurehunterx.WsReq.hb:type_name -> treasurehunterx.HeartbeatUpsync 10, // 10: treasurehunterx.WsReq.hb:type_name -> treasurehunterx.HeartbeatUpsync
8, // 11: treasurehunterx.WsResp.rdf:type_name -> treasurehunterx.RoomDownsyncFrame 11, // 11: treasurehunterx.WsResp.rdf:type_name -> treasurehunterx.RoomDownsyncFrame
10, // 12: treasurehunterx.WsResp.inputFrameDownsyncBatch:type_name -> treasurehunterx.InputFrameDownsync 9, // 12: treasurehunterx.WsResp.inputFrameDownsyncBatch:type_name -> treasurehunterx.InputFrameDownsync
5, // 13: treasurehunterx.WsResp.bciFrame:type_name -> treasurehunterx.BattleColliderInfo 5, // 13: treasurehunterx.WsResp.bciFrame:type_name -> treasurehunterx.BattleColliderInfo
3, // 14: treasurehunterx.BattleColliderInfo.StrToVec2DListMapEntry.value:type_name -> treasurehunterx.Vec2DList 3, // 14: treasurehunterx.BattleColliderInfo.StrToVec2DListMapEntry.value:type_name -> treasurehunterx.Vec2DList
4, // 15: treasurehunterx.BattleColliderInfo.StrToPolygon2DListMapEntry.value:type_name -> treasurehunterx.Polygon2DList 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{} { 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 { switch v := v.(*InputFrameUpsync); i {
case 0: case 0:
return &v.state return &v.state
@ -1413,7 +1401,7 @@ func file_room_downsync_frame_proto_init() {
return nil 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 { switch v := v.(*InputFrameDownsync); i {
case 0: case 0:
return &v.state return &v.state
@ -1425,7 +1413,7 @@ func file_room_downsync_frame_proto_init() {
return nil 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 { switch v := v.(*HeartbeatUpsync); i {
case 0: case 0:
return &v.state return &v.state
@ -1437,6 +1425,18 @@ func file_room_downsync_frame_proto_init() {
return nil 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{} { file_room_downsync_frame_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WsReq); i { switch v := v.(*WsReq); i {
case 0: case 0:

View File

@ -45,7 +45,7 @@ message Player {
double x = 2; double x = 2;
double y = 3; double y = 3;
Direction dir = 4; Direction dir = 4;
int32 speed = 5; double speed = 5;
int32 battleState = 6; int32 battleState = 6;
int32 lastMoveGmtMillis = 7; int32 lastMoveGmtMillis = 7;
int32 score = 10; int32 score = 10;
@ -61,15 +61,6 @@ message PlayerMeta {
int32 joinIndex = 5; 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 { message InputFrameUpsync {
int32 inputFrameId = 1; int32 inputFrameId = 1;
int32 encodedDir = 6; int32 encodedDir = 6;
@ -85,6 +76,15 @@ message HeartbeatUpsync {
int64 clientTimestamp = 1; 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 { message WsReq {
int32 msgId = 1; int32 msgId = 1;
int32 playerId = 2; int32 playerId = 2;

View File

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

View File

@ -1815,7 +1815,7 @@ $root.treasurehunterx = (function() {
if (message.dir != null && Object.hasOwnProperty.call(message, "dir")) if (message.dir != null && Object.hasOwnProperty.call(message, "dir"))
$root.treasurehunterx.Direction.encode(message.dir, writer.uint32(/* id 4, wireType 2 =*/34).fork()).ldelim(); $root.treasurehunterx.Direction.encode(message.dir, writer.uint32(/* id 4, wireType 2 =*/34).fork()).ldelim();
if (message.speed != null && Object.hasOwnProperty.call(message, "speed")) 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")) if (message.battleState != null && Object.hasOwnProperty.call(message, "battleState"))
writer.uint32(/* id 6, wireType 0 =*/48).int32(message.battleState); writer.uint32(/* id 6, wireType 0 =*/48).int32(message.battleState);
if (message.lastMoveGmtMillis != null && Object.hasOwnProperty.call(message, "lastMoveGmtMillis")) if (message.lastMoveGmtMillis != null && Object.hasOwnProperty.call(message, "lastMoveGmtMillis"))
@ -1877,7 +1877,7 @@ $root.treasurehunterx = (function() {
break; break;
} }
case 5: { case 5: {
message.speed = reader.int32(); message.speed = reader.double();
break; break;
} }
case 6: { case 6: {
@ -1950,8 +1950,8 @@ $root.treasurehunterx = (function() {
return "dir." + error; return "dir." + error;
} }
if (message.speed != null && message.hasOwnProperty("speed")) if (message.speed != null && message.hasOwnProperty("speed"))
if (!$util.isInteger(message.speed)) if (typeof message.speed !== "number")
return "speed: integer expected"; return "speed: number expected";
if (message.battleState != null && message.hasOwnProperty("battleState")) if (message.battleState != null && message.hasOwnProperty("battleState"))
if (!$util.isInteger(message.battleState)) if (!$util.isInteger(message.battleState))
return "battleState: integer expected"; return "battleState: integer expected";
@ -1994,7 +1994,7 @@ $root.treasurehunterx = (function() {
message.dir = $root.treasurehunterx.Direction.fromObject(object.dir); message.dir = $root.treasurehunterx.Direction.fromObject(object.dir);
} }
if (object.speed != null) if (object.speed != null)
message.speed = object.speed | 0; message.speed = Number(object.speed);
if (object.battleState != null) if (object.battleState != null)
message.battleState = object.battleState | 0; message.battleState = object.battleState | 0;
if (object.lastMoveGmtMillis != null) if (object.lastMoveGmtMillis != null)
@ -2042,7 +2042,7 @@ $root.treasurehunterx = (function() {
if (message.dir != null && message.hasOwnProperty("dir")) if (message.dir != null && message.hasOwnProperty("dir"))
object.dir = $root.treasurehunterx.Direction.toObject(message.dir, options); object.dir = $root.treasurehunterx.Direction.toObject(message.dir, options);
if (message.speed != null && message.hasOwnProperty("speed")) 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")) if (message.battleState != null && message.hasOwnProperty("battleState"))
object.battleState = message.battleState; object.battleState = message.battleState;
if (message.lastMoveGmtMillis != null && message.hasOwnProperty("lastMoveGmtMillis")) if (message.lastMoveGmtMillis != null && message.hasOwnProperty("lastMoveGmtMillis"))
@ -2381,446 +2381,6 @@ $root.treasurehunterx = (function() {
return PlayerMeta; 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() { treasurehunterx.InputFrameUpsync = (function() {
/** /**
@ -3564,6 +3124,446 @@ $root.treasurehunterx = (function() {
return HeartbeatUpsync; 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() { treasurehunterx.WsReq = (function() {
/** /**