Minor updates.

This commit is contained in:
genxium 2022-10-03 00:22:05 +08:00
parent 54d6e52498
commit 9d9bea21ef
5 changed files with 377 additions and 315 deletions

View File

@ -26,10 +26,10 @@ const (
UPSYNC_MSG_ACT_PLAYER_CMD = int32(2) UPSYNC_MSG_ACT_PLAYER_CMD = int32(2)
UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK = int32(3) UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK = int32(3)
DOWNSYNC_MSG_ACT_HB_REQ = int32(1) DOWNSYNC_MSG_ACT_HB_REQ = int32(1)
DOWNSYNC_MSG_ACT_INPUT_BATCH = int32(2) DOWNSYNC_MSG_ACT_INPUT_BATCH = int32(2)
DOWNSYNC_MSG_ACT_ROOM_FRAME = int32(3) DOWNSYNC_MSG_ACT_BATTLE_STOPPED = int32(3)
DOWNSYNC_MSG_ACT_FORCED_RESYNC = int32(4) DOWNSYNC_MSG_ACT_FORCED_RESYNC = int32(4)
DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = int32(-1) DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = int32(-1)
DOWNSYNC_MSG_ACT_BATTLE_START = int32(0) DOWNSYNC_MSG_ACT_BATTLE_START = int32(0)
@ -334,6 +334,10 @@ func (pR *Room) ConvertToInputFrameId(renderFrameId int32, inputDelayFrames int3
return ((renderFrameId - inputDelayFrames) >> pR.InputScaleFrames) return ((renderFrameId - inputDelayFrames) >> pR.InputScaleFrames)
} }
func (pR *Room) ConvertToGeneratingRenderFrameId(inputFrameId int32) int32 {
return (inputFrameId << pR.InputScaleFrames)
}
func (pR *Room) ConvertToFirstUsedRenderFrameId(inputFrameId int32, inputDelayFrames int32) int32 { func (pR *Room) ConvertToFirstUsedRenderFrameId(inputFrameId int32, inputDelayFrames int32) int32 {
return ((inputFrameId << pR.InputScaleFrames) + inputDelayFrames) return ((inputFrameId << pR.InputScaleFrames) + inputDelayFrames)
} }
@ -349,6 +353,10 @@ func (pR *Room) EncodeUpsyncCmd(upsyncCmd *pb.InputFrameUpsync) uint64 {
return ret return ret
} }
func (pR *Room) RenderFrameBufferString() string {
return fmt.Sprintf("{renderFrameId: %d, stRenderFrameId: %d, edRenderFrameId: %d, lastAllConfirmedRenderFrameId: %d}", pR.RenderFrameId, pR.RenderFrameBuffer.StFrameId, pR.RenderFrameBuffer.EdFrameId, pR.CurDynamicsRenderFrameId)
}
func (pR *Room) AllPlayerInputsBufferString(allDetails bool) string { func (pR *Room) AllPlayerInputsBufferString(allDetails bool) string {
if allDetails { if allDetails {
// Appending of the array of strings can be very SLOW due to on-demand heap allocation! Use this printing with caution. // Appending of the array of strings can be very SLOW due to on-demand heap allocation! Use this printing with caution.
@ -366,7 +374,7 @@ func (pR *Room) AllPlayerInputsBufferString(allDetails bool) string {
s = append(s, fmt.Sprintf("{inputFrameId: %v, inputList: %v, confirmedList: %v}", f.InputFrameId, f.InputList, f.ConfirmedList)) s = append(s, fmt.Sprintf("{inputFrameId: %v, inputList: %v, confirmedList: %v}", f.InputFrameId, f.InputList, f.ConfirmedList))
} }
return strings.Join(s, "\n") return strings.Join(s, "; ")
} else { } else {
return fmt.Sprintf("{renderFrameId: %d, stInputFrameId: %d, edInputFrameId: %d, lastAllConfirmedInputFrameIdWithChange: %d, lastAllConfirmedInputFrameId: %d}", pR.RenderFrameId, pR.AllPlayerInputsBuffer.StFrameId, pR.AllPlayerInputsBuffer.EdFrameId, pR.LastAllConfirmedInputFrameIdWithChange, pR.LastAllConfirmedInputFrameId) return fmt.Sprintf("{renderFrameId: %d, stInputFrameId: %d, edInputFrameId: %d, lastAllConfirmedInputFrameIdWithChange: %d, lastAllConfirmedInputFrameId: %d}", pR.RenderFrameId, pR.AllPlayerInputsBuffer.StFrameId, pR.AllPlayerInputsBuffer.EdFrameId, pR.LastAllConfirmedInputFrameIdWithChange, pR.LastAllConfirmedInputFrameId)
} }
@ -443,7 +451,7 @@ func (pR *Room) StartBattle() {
dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt
} }
lastAllConfirmedInputFrameIdWithChange := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameIdWithChange)) upperToSendInputFrameId := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameId))
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 {
// [WARNING] DON'T send anything if the player is disconnected, because it could jam the channel and cause significant delay upon "battle recovery for reconnected player". // [WARNING] DON'T send anything if the player is disconnected, because it could jam the channel and cause significant delay upon "battle recovery for reconnected player".
@ -463,46 +471,65 @@ func (pR *Room) StartBattle() {
} }
// [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)
for candidateToSendInputFrameId <= lastAllConfirmedInputFrameIdWithChange { for candidateToSendInputFrameId <= upperToSendInputFrameId {
tmp := pR.AllPlayerInputsBuffer.GetByFrameId(candidateToSendInputFrameId) tmp := pR.AllPlayerInputsBuffer.GetByFrameId(candidateToSendInputFrameId)
if nil == tmp { if nil == tmp {
panic(fmt.Sprintf("Required inputFrameId=%v for roomId=%v, playerId=%v doesn't exist! AllPlayerInputsBuffer=%v", candidateToSendInputFrameId, pR.Id, playerId, pR.AllPlayerInputsBufferString(false))) panic(fmt.Sprintf("Required inputFrameId=%v for roomId=%v, playerId=%v doesn't exist! AllPlayerInputsBuffer=%v", candidateToSendInputFrameId, pR.Id, playerId, pR.AllPlayerInputsBufferString(false)))
} }
f := tmp.(*pb.InputFrameDownsync) f := tmp.(*pb.InputFrameDownsync)
if pR.inputFrameIdDebuggable(candidateToSendInputFrameId) { if pR.inputFrameIdDebuggable(candidateToSendInputFrameId) {
debugSendingInputFrameId = candidateToSendInputFrameId
Logger.Debug("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(false)), zap.Any("ConfirmedList", f.ConfirmedList)) Logger.Debug("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(false)), zap.Any("ConfirmedList", f.ConfirmedList))
} }
toSendInputFrames = append(toSendInputFrames, f) toSendInputFrames = append(toSendInputFrames, f)
candidateToSendInputFrameId++ candidateToSendInputFrameId++
} }
if 0 >= len(toSendInputFrames) {
// [WARNING] When sending DOWNSYNC_MSG_ACT_FORCED_RESYNC, there MUST BE accompanying "toSendInputFrames" for calculating "refRenderFrameId"!
continue
}
indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1) indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1)
var joinMask uint64 = (1 << indiceInJoinIndexBooleanArr) var joinMask uint64 = (1 << indiceInJoinIndexBooleanArr)
if 0 < (unconfirmedMask & joinMask) { if 0 < (unconfirmedMask & joinMask) {
refRenderFrameId := pR.CurDynamicsRenderFrameId /*
[WARNING]
"refRenderFrameId" MUST BE CAPPED somehow by "candidateToSendInputFrameId-1", if frontend resyncs itself to a more advanced value than given below, upon the next renderFrame tick on the frontend it might generate non-consecutive "nextInputFrameId > frontend.recentInputCache.edFrameId+1".
If "NstDelayFrames" becomes larger, "pR.RenderFrameId - refRenderFrameId" possibly becomes larger because the force confirmation is delayed more.
Hence even upon resync, it's still possible that "refRenderFrameId < frontend.chaserRenderFrameId".
*/
refRenderFrameId := pR.ConvertToGeneratingRenderFrameId(upperToSendInputFrameId) + (1 << pR.InputScaleFrames) - 1
// [WARNING] The following inequalities are seldom true, but just to avoid that in good network condition the frontend resyncs itself to a "too advanced frontend.renderFrameId", and then starts upsyncing "too advanced inputFrameId".
if refRenderFrameId > pR.RenderFrameId { if refRenderFrameId > pR.RenderFrameId {
// [WARNING] To avoid that in good network condition the frontend resyncs itself to a "too advanced frontend.renderFrameId", and then starts upsyncing "too advanced inputFrameId".
refRenderFrameId = pR.RenderFrameId refRenderFrameId = pR.RenderFrameId
} }
refRenderFrame := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId).(*pb.RoomDownsyncFrame) if refRenderFrameId > pR.CurDynamicsRenderFrameId {
refRenderFrameId = pR.CurDynamicsRenderFrameId
}
tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId)
if nil == tmp {
panic(fmt.Sprintf("Required refRenderFrameId=%v for roomId=%v, playerId=%v, candidateToSendInputFrameId=%v doesn't exist! AllPlayerInputsBuffer=%v, RenderFrameBuffer=%v", refRenderFrameId, pR.Id, playerId, candidateToSendInputFrameId, pR.AllPlayerInputsBufferString(false), pR.RenderFrameBufferString()))
}
refRenderFrame := tmp.(*pb.RoomDownsyncFrame)
pR.sendSafely(refRenderFrame, toSendInputFrames, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId) pR.sendSafely(refRenderFrame, toSendInputFrames, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId)
} else { } else {
if 0 >= len(toSendInputFrames) {
continue
}
pR.sendSafely(nil, toSendInputFrames, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId) pR.sendSafely(nil, toSendInputFrames, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId)
} }
if -1 != debugSendingInputFrameId {
Logger.Info("inputFrame lifecycle#4[sent]:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("playerAckingInputFrameId", player.AckingInputFrameId), zap.Any("inputFrameId", debugSendingInputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString(false)))
}
atomic.StoreInt32(&(pR.Players[playerId].LastSentInputFrameId), candidateToSendInputFrameId-1) atomic.StoreInt32(&(pR.Players[playerId].LastSentInputFrameId), candidateToSendInputFrameId-1)
} }
} }
for 0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < pR.CurDynamicsRenderFrameId { renderFrameBuffLowerBound := pR.ConvertToGeneratingRenderFrameId(upperToSendInputFrameId) + (1 << pR.InputScaleFrames) - 1
if renderFrameBuffLowerBound > pR.RenderFrameId {
renderFrameBuffLowerBound = pR.RenderFrameId
}
if renderFrameBuffLowerBound > pR.CurDynamicsRenderFrameId {
renderFrameBuffLowerBound = pR.CurDynamicsRenderFrameId
}
for 0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < renderFrameBuffLowerBound {
_ = pR.RenderFrameBuffer.Pop() _ = pR.RenderFrameBuffer.Pop()
} }
@ -511,7 +538,7 @@ func (pR *Room) StartBattle() {
f := pR.AllPlayerInputsBuffer.Pop().(*pb.InputFrameDownsync) f := pR.AllPlayerInputsBuffer.Pop().(*pb.InputFrameDownsync)
if pR.inputFrameIdDebuggable(f.InputFrameId) { 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 // Popping of an "inputFrame" would be AFTER its being all being confirmed, because it requires the "inputFrame" to be all acked
Logger.Debug("inputFrame lifecycle#5[popped]:", zap.Any("roomId", pR.Id), zap.Any("inputFrameId", f.InputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString(false))) Logger.Debug("inputFrame lifecycle#4[popped]:", zap.Any("roomId", pR.Id), zap.Any("inputFrameId", f.InputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString(false)))
} }
} }
@ -585,13 +612,13 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
// 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.
// [WARNING] No need to use CAS for updating "inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr]", the upsync from frontend takes top priority. // [WARNING] No need to use CAS for updating "inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr]", the upsync from frontend takes top priority.
atomic.StoreUint64(&inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr], encodedInput); atomic.StoreUint64(&inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr], encodedInput)
newConfirmedList := (oldConfirmedList | joinMask) newConfirmedList := (oldConfirmedList | joinMask)
if swapped := atomic.CompareAndSwapUint64(&(inputFrameDownsync.ConfirmedList), oldConfirmedList, newConfirmedList); !swapped { if swapped := atomic.CompareAndSwapUint64(&(inputFrameDownsync.ConfirmedList), oldConfirmedList, newConfirmedList); !swapped {
// [WARNING] Upon this error, the actual input has already been updated, which is an expected result if it caused by the force confirmation from "battleMainLoop". // [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(fmt.Sprintf("Failed confirm CAS: roomId=%v, playerId=%v, clientInputFrameId=%v", pR.Id, playerId, clientInputFrameId)) Logger.Warn(fmt.Sprintf("Failed confirm CAS, might've been forced to all-confirmed: roomId=%v, playerId=%v, clientInputFrameId=%v", pR.Id, playerId, clientInputFrameId))
return return
} }
@ -606,7 +633,11 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
func (pR *Room) onInputFrameDownsyncAllConfirmed(inputFrameDownsync *pb.InputFrameDownsync, playerId int32) { func (pR *Room) onInputFrameDownsyncAllConfirmed(inputFrameDownsync *pb.InputFrameDownsync, playerId int32) {
inputFrameId := inputFrameDownsync.InputFrameId inputFrameId := inputFrameDownsync.InputFrameId
if -1 == pR.LastAllConfirmedInputFrameIdWithChange || false == pR.equalInputLists(inputFrameDownsync.InputList, pR.LastAllConfirmedInputList) { if -1 == pR.LastAllConfirmedInputFrameIdWithChange || false == pR.equalInputLists(inputFrameDownsync.InputList, pR.LastAllConfirmedInputList) {
Logger.Info(fmt.Sprintf("Key inputFrame change: roomId=%v, playerId=%v, newInputFrameId=%v, lastInputFrameId=%v, newInputList=%v, lastInputList=%v, AllPlayerInputsBuffer=%v", pR.Id, playerId, inputFrameId, pR.LastAllConfirmedInputFrameId, inputFrameDownsync.InputList, pR.LastAllConfirmedInputList, pR.AllPlayerInputsBufferString(false))) if -1 == playerId {
Logger.Info(fmt.Sprintf("Key inputFrame change: roomId=%v, newInputFrameId=%v, lastInputFrameId=%v, newInputList=%v, lastInputList=%v, AllPlayerInputsBuffer=%v", pR.Id, inputFrameId, pR.LastAllConfirmedInputFrameId, inputFrameDownsync.InputList, pR.LastAllConfirmedInputList, pR.AllPlayerInputsBufferString(false)))
} else {
Logger.Info(fmt.Sprintf("Key inputFrame change: roomId=%v, playerId=%v, newInputFrameId=%v, lastInputFrameId=%v, newInputList=%v, lastInputList=%v, AllPlayerInputsBuffer=%v", pR.Id, playerId, inputFrameId, pR.LastAllConfirmedInputFrameId, inputFrameDownsync.InputList, pR.LastAllConfirmedInputList, pR.AllPlayerInputsBufferString(false)))
}
atomic.StoreInt32(&(pR.LastAllConfirmedInputFrameIdWithChange), inputFrameId) atomic.StoreInt32(&(pR.LastAllConfirmedInputFrameIdWithChange), inputFrameId)
} }
atomic.StoreInt32(&(pR.LastAllConfirmedInputFrameId), inputFrameId) // [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), inputFrameId) // [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!
@ -614,12 +645,10 @@ func (pR *Room) onInputFrameDownsyncAllConfirmed(inputFrameDownsync *pb.InputFra
// To avoid potential misuse of pointers // To avoid potential misuse of pointers
pR.LastAllConfirmedInputList[i] = v pR.LastAllConfirmedInputList[i] = v
} }
if pR.inputFrameIdDebuggable(inputFrameId) { if -1 == playerId {
if -1 == playerId { Logger.Debug(fmt.Sprintf("inputFrame lifecycle#2[forced-allconfirmed]: roomId=%v, AllPlayerInputsBuffer=%v", pR.Id, pR.AllPlayerInputsBufferString(false)))
Logger.Info(fmt.Sprintf("inputFrame lifecycle#2[forced-allconfirmed]: roomId=%v, inputFrameId=%v, lastAllConfirmedInputFrameId=%v, AllPlayerInputsBuffer=%v", pR.Id, inputFrameId, pR.LastAllConfirmedInputFrameId, pR.AllPlayerInputsBufferString(false))) } else {
} else { Logger.Info(fmt.Sprintf("inputFrame lifecycle#2[allconfirmed]: roomId=%v, playerId=%v, AllPlayerInputsBuffer=%v", pR.Id, playerId, pR.AllPlayerInputsBufferString(false)))
Logger.Info(fmt.Sprintf("inputFrame lifecycle#2[allconfirmed]: roomId=%v, playerId=%v, inputFrameId=%v, lastAllConfirmedInputFrameId=%v, AllPlayerInputsBuffer=%v", pR.Id, playerId, inputFrameId, pR.LastAllConfirmedInputFrameId, pR.AllPlayerInputsBufferString(false)))
}
} }
} }
@ -648,7 +677,7 @@ func (pR *Room) StopBattleForSettlement() {
Players: toPbPlayers(pR.Players), Players: toPbPlayers(pR.Players),
CountdownNanos: -1, // TODO: Replace this magic constant! CountdownNanos: -1, // TODO: Replace this magic constant!
} }
pR.sendSafely(&assembledFrame, nil, DOWNSYNC_MSG_ACT_ROOM_FRAME, playerId) pR.sendSafely(&assembledFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_STOPPED, playerId)
} }
// Note that `pR.onBattleStoppedForSettlement` will be called by `battleMainLoop`. // Note that `pR.onBattleStoppedForSettlement` will be called by `battleMainLoop`.
} }
@ -1000,7 +1029,7 @@ func (pR *Room) sendSafely(roomDownsyncFrame *pb.RoomDownsyncFrame, toSendFrames
} }
if err := pR.PlayerDownsyncSessionDict[playerId].WriteMessage(websocket.BinaryMessage, theBytes); nil != err { if err := pR.PlayerDownsyncSessionDict[playerId].WriteMessage(websocket.BinaryMessage, theBytes); nil != err {
panic(fmt.Sprintf("Error sending downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount)) panic(fmt.Sprintf("Error sending downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v, err=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount, err))
} }
} }
@ -1053,11 +1082,11 @@ func (pR *Room) forceConfirmationIfApplicable() uint64 {
} }
inputFrameId2 := pR.ConvertToInputFrameId(renderFrameId1, 0) // The inputFrame to force confirmation (if necessary) inputFrameId2 := pR.ConvertToInputFrameId(renderFrameId1, 0) // The inputFrame to force confirmation (if necessary)
if inputFrameId2 < pR.LastAllConfirmedInputFrameId { if inputFrameId2 < pR.LastAllConfirmedInputFrameId {
// No need to force confirmation, the inputFrames already arrived // No need to force confirmation, the inputFrames already arrived
Logger.Debug(fmt.Sprintf("inputFrameId2=%v is already all-confirmed for roomId=%v[type#1], no need to force confirmation of it", inputFrameId2, pR.Id)) Logger.Debug(fmt.Sprintf("inputFrameId2=%v is already all-confirmed for roomId=%v[type#1], no need to force confirmation of it", inputFrameId2, pR.Id))
return 0 return 0
} }
tmp := pR.AllPlayerInputsBuffer.GetByFrameId(inputFrameId2) tmp := pR.AllPlayerInputsBuffer.GetByFrameId(inputFrameId2)
if nil == tmp { 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(false))) 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(false)))
@ -1067,7 +1096,7 @@ func (pR *Room) forceConfirmationIfApplicable() uint64 {
totPlayerCnt := uint32(pR.Capacity) totPlayerCnt := uint32(pR.Capacity)
allConfirmedMask := uint64((1 << totPlayerCnt) - 1) allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
if swapped := atomic.CompareAndSwapUint64(&(inputFrame2.ConfirmedList), allConfirmedMask, allConfirmedMask); swapped { if swapped := atomic.CompareAndSwapUint64(&(inputFrame2.ConfirmedList), allConfirmedMask, allConfirmedMask); swapped {
// This could happen if the frontend upsync command arrived between type#1 and type#2 checks. // This could happen if the frontend upsync command arrived between type#1 and type#2 checks.
Logger.Debug(fmt.Sprintf("inputFrameId2=%v is already all-confirmed for roomId=%v[type#2], no need to force confirmation of it", inputFrameId2, pR.Id)) Logger.Debug(fmt.Sprintf("inputFrameId2=%v is already all-confirmed for roomId=%v[type#2], no need to force confirmation of it", inputFrameId2, pR.Id))
return 0 return 0
} }
@ -1087,21 +1116,22 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
} }
Logger.Debug(fmt.Sprintf("Applying inputFrame dynamics: roomId=%v, room.RenderFrameId=%v, fromRenderFrameId=%v, toRenderFrameId=%v", pR.Id, pR.RenderFrameId, fromRenderFrameId, toRenderFrameId)) Logger.Debug(fmt.Sprintf("Applying inputFrame dynamics: roomId=%v, room.RenderFrameId=%v, fromRenderFrameId=%v, toRenderFrameId=%v", pR.Id, pR.RenderFrameId, fromRenderFrameId, toRenderFrameId))
totPlayerCnt := uint32(pR.Capacity) totPlayerCnt := uint32(pR.Capacity)
allConfirmedMask := uint64((1 << totPlayerCnt) - 1) allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
for collisionSysRenderFrameId := fromRenderFrameId; collisionSysRenderFrameId < toRenderFrameId; collisionSysRenderFrameId++ { for collisionSysRenderFrameId := fromRenderFrameId; collisionSysRenderFrameId < toRenderFrameId; collisionSysRenderFrameId++ {
delayedInputFrameId := pR.ConvertToInputFrameId(collisionSysRenderFrameId, pR.InputDelayFrames) delayedInputFrameId := pR.ConvertToInputFrameId(collisionSysRenderFrameId, pR.InputDelayFrames)
if 0 <= delayedInputFrameId { if 0 <= delayedInputFrameId {
if delayedInputFrameId > pR.LastAllConfirmedInputFrameId {
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(false)))
}
tmp := pR.AllPlayerInputsBuffer.GetByFrameId(delayedInputFrameId) tmp := pR.AllPlayerInputsBuffer.GetByFrameId(delayedInputFrameId)
if nil == tmp { 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(false))) 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(false)))
} }
delayedInputFrame := tmp.(*pb.InputFrameDownsync) delayedInputFrame := tmp.(*pb.InputFrameDownsync)
if swapped := atomic.CompareAndSwapUint64(&(delayedInputFrame.ConfirmedList), allConfirmedMask, allConfirmedMask); !swapped { // [WARNING] It's possible that by now "allConfirmedMask != delayedInputFrame.ConfirmedList && delayedInputFrameId <= pR.LastAllConfirmedInputFrameId", we trust "pR.LastAllConfirmedInputFrameId" as the TOP AUTHORITY.
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(false))) atomic.StoreUint64(&(delayedInputFrame.ConfirmedList), allConfirmedMask)
}
inputList := delayedInputFrame.InputList inputList := delayedInputFrame.InputList
// Ordered by joinIndex to guarantee determinism // Ordered by joinIndex to guarantee determinism

View File

@ -440,7 +440,7 @@
"array": [ "array": [
0, 0,
0, 0,
209.63606633899616, 216.6425019058577,
0, 0,
0, 0,
0, 0,

View File

@ -98,52 +98,35 @@ cc.Class({
default: null default: null
}, },
}, },
_inputFrameIdDebuggable(inputFrameId) { _inputFrameIdDebuggable(inputFrameId) {
return (0 == inputFrameId%10); return (0 == inputFrameId % 10);
}, },
_dumpToRenderCache: function(roomDownsyncFrame) { dumpToRenderCache: function(roomDownsyncFrame) {
const self = this; const self = this;
const minToKeepRenderFrameId = self.lastAllConfirmedRenderFrameId; const minToKeepRenderFrameId = self.lastAllConfirmedRenderFrameId;
while (0 < self.recentRenderCache.cnt && self.recentRenderCache.stFrameId < minToKeepRenderFrameId) { while (0 < self.recentRenderCache.cnt && self.recentRenderCache.stFrameId < minToKeepRenderFrameId) {
self.recentRenderCache.pop(); self.recentRenderCache.pop();
} }
if (self.recentRenderCache.stFrameId < minToKeepRenderFrameId) { const ret = self.recentRenderCache.setByFrameId(roomDownsyncFrame, roomDownsyncFrame.id);
console.warn("Weird dumping of RENDER frame: self.renderFrame=", self.renderFrame, ", self.recentInputCache=", self._stringifyRecentInputCache(false), ", self.recentRenderCache=", self._stringifyRecentRenderCache(false), ", self.lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", self.lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId); return ret;
}
const existing = self.recentRenderCache.getByFrameId(roomDownsyncFrame.id);
if (null != existing) {
existing.players = roomDownsyncFrame.players;
existing.sentAt = roomDownsyncFrame.sentAt;
existing.countdownNanos = roomDownsyncFrame.countdownNanos;
existing.treasures = roomDownsyncFrame.treasures;
existing.bullets = roomDownsyncFrame.bullets;
existing.speedShoes = roomDownsyncFrame.speedShoes;
existing.guardTowers = roomDownsyncFrame.guardTowers;
existing.playerMetas = roomDownsyncFrame.playerMetas;
} else {
self.recentRenderCache.put(roomDownsyncFrame);
}
}, },
_dumpToInputCache: function(inputFrameDownsync) { dumpToInputCache: function(inputFrameDownsync) {
const self = this; const self = this;
let minToKeepInputFrameId = self._convertToInputFrameId(self.lastAllConfirmedRenderFrameId, self.inputDelayFrames); // [WARNING] This could be different from "self.lastAllConfirmedInputFrameId". We'd like to keep the corresponding inputFrame for "self.lastAllConfirmedRenderFrameId" such that a rollback could place "self.chaserRenderFrameId = self.lastAllConfirmedRenderFrameId" for the worst case incorrect prediction. let minToKeepInputFrameId = self._convertToInputFrameId(self.lastAllConfirmedRenderFrameId, self.inputDelayFrames); // [WARNING] This could be different from "self.lastAllConfirmedInputFrameId". We'd like to keep the corresponding inputFrame for "self.lastAllConfirmedRenderFrameId" such that a rollback could place "self.chaserRenderFrameId = self.lastAllConfirmedRenderFrameId" for the worst case incorrect prediction.
if (minToKeepInputFrameId > self.lastAllConfirmedInputFrameId) minToKeepInputFrameId = self.lastAllConfirmedInputFrameId; if (minToKeepInputFrameId > self.lastAllConfirmedInputFrameId) {
minToKeepInputFrameId = self.lastAllConfirmedInputFrameId;
}
while (0 < self.recentInputCache.cnt && self.recentInputCache.stFrameId < minToKeepInputFrameId) { while (0 < self.recentInputCache.cnt && self.recentInputCache.stFrameId < minToKeepInputFrameId) {
self.recentInputCache.pop(); self.recentInputCache.pop();
} }
if (self.recentInputCache.stFrameId < minToKeepInputFrameId) { const ret = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId);
console.warn("Weird dumping of INPUT frame: self.renderFrame=", self.renderFrame, ", self.recentInputCache=", self._stringifyRecentInputCache(false), ", self.recentRenderCache=", self._stringifyRecentRenderCache(false), ", self.lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", self.lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId); if (-1 < self.lastAllConfirmedInputFrameId && self.recentInputCache.stFrameId > self.lastAllConfirmedInputFrameId) {
} console.error("Invalid input cache dumped! lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId, ", recentRenderCache=", self._stringifyRecentRenderCache(false), ", recentInputCache=", self._stringifyRecentInputCache(false));
const existing = self.recentInputCache.getByFrameId(inputFrameDownsync.inputFrameId);
if (null != existing) {
existing.inputList = inputFrameDownsync.inputList;
existing.confirmedList = inputFrameDownsync.confirmedList;
} else {
self.recentInputCache.put(inputFrameDownsync);
} }
return ret;
}, },
_convertToInputFrameId(renderFrameId, inputDelayFrames) { _convertToInputFrameId(renderFrameId, inputDelayFrames) {
@ -156,12 +139,12 @@ cc.Class({
}, },
shouldGenerateInputFrameUpsync(renderFrameId) { shouldGenerateInputFrameUpsync(renderFrameId) {
return ((renderFrameId & ((1 << this.inputScaleFrames)-1)) == 0); return ((renderFrameId & ((1 << this.inputScaleFrames) - 1)) == 0);
}, },
_allConfirmed(confirmedList) { _allConfirmed(confirmedList) {
return (confirmedList+1) == (1 << this.playerRichInfoDict.size); return (confirmedList + 1) == (1 << this.playerRichInfoDict.size);
}, },
_generateInputFrameUpsync(inputFrameId) { _generateInputFrameUpsync(inputFrameId) {
const self = this; const self = this;
@ -174,21 +157,21 @@ cc.Class({
const joinIndex = self.selfPlayerInfo.joinIndex; const joinIndex = self.selfPlayerInfo.joinIndex;
const discreteDir = self.ctrl.getDiscretizedDirection(); const discreteDir = self.ctrl.getDiscretizedDirection();
const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId); const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId);
const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice()); const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice());
prefabbedInputList[(joinIndex-1)] = discreteDir.encodedIdx; prefabbedInputList[(joinIndex - 1)] = discreteDir.encodedIdx;
const prefabbedInputFrameDownsync = { const prefabbedInputFrameDownsync = {
inputFrameId: inputFrameId, inputFrameId: inputFrameId,
inputList: prefabbedInputList, inputList: prefabbedInputList,
confirmedList: (1 << (self.selfPlayerInfo.joinIndex-1)) confirmedList: (1 << (self.selfPlayerInfo.joinIndex - 1))
}; };
self._dumpToInputCache(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames" self.dumpToInputCache(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
const previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex-1]); const previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]);
return [previousSelfInput, discreteDir.encodedIdx]; return [previousSelfInput, discreteDir.encodedIdx];
}, },
shouldSendInputFrameUpsyncBatch(prevSelfInput, currSelfInput, lastUpsyncInputFrameId, currInputFrameId) { shouldSendInputFrameUpsyncBatch(prevSelfInput, currSelfInput, lastUpsyncInputFrameId, currInputFrameId) {
/* /*
For a 2-player-battle, this "shouldUpsyncForEarlyAllConfirmedOnBackend" can be omitted, however for more players in a same battle, to avoid a "long time non-moving player" jamming the downsync of other moving players, we should use this flag. For a 2-player-battle, this "shouldUpsyncForEarlyAllConfirmedOnBackend" can be omitted, however for more players in a same battle, to avoid a "long time non-moving player" jamming the downsync of other moving players, we should use this flag.
@ -197,26 +180,31 @@ cc.Class({
*/ */
if (null == currSelfInput) return false; if (null == currSelfInput) return false;
const shouldUpsyncForEarlyAllConfirmedOnBackend = (currInputFrameId - lastUpsyncInputFrameId >= this.inputFrameUpsyncDelayTolerance); const shouldUpsyncForEarlyAllConfirmedOnBackend = (currInputFrameId - lastUpsyncInputFrameId >= this.inputFrameUpsyncDelayTolerance);
return shouldUpsyncForEarlyAllConfirmedOnBackend || (prevSelfInput != currSelfInput); return shouldUpsyncForEarlyAllConfirmedOnBackend || (prevSelfInput != currSelfInput);
}, },
sendInputFrameUpsyncBatch(inputFrameId) { sendInputFrameUpsyncBatch(latestLocalInputFrameId) {
// [WARNING] Why not just send the latest input? Because different player would have a different "inputFrameId" of changing its last input, and that could make the server not recognizing any "all-confirmed inputFrame"! // [WARNING] Why not just send the latest input? Because different player would have a different "latestLocalInputFrameId" of changing its last input, and that could make the server not recognizing any "all-confirmed inputFrame"!
const self = this; const self = this;
let inputFrameUpsyncBatch = []; let inputFrameUpsyncBatch = [];
for (let i = self.lastUpsyncInputFrameId+1; i <= inputFrameId; ++i) { let batchInputFrameIdSt = self.lastUpsyncInputFrameId + 1;
if (batchInputFrameIdSt < self.recentInputCache.stFrameId) {
// Upon resync, "self.lastUpsyncInputFrameId" might not have been updated properly.
batchInputFrameIdSt = self.recentInputCache.stFrameId;
}
for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) {
const inputFrameDownsync = self.recentInputCache.getByFrameId(i); const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
if (null == inputFrameDownsync) { if (null == inputFrameDownsync) {
console.warn("sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=", i, "; recentInputCache=", self._stringifyRecentInputCache(false)); console.error("sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=", i, ": latestLocalInputFrameId=", latestLocalInputFrameId, ", recentInputCache=", self._stringifyRecentInputCache(false));
} else { } else {
const inputFrameUpsync = { const inputFrameUpsync = {
inputFrameId: i, inputFrameId: i,
encodedDir: inputFrameDownsync.inputList[self.selfPlayerInfo.joinIndex-1], encodedDir: inputFrameDownsync.inputList[self.selfPlayerInfo.joinIndex - 1],
}; };
inputFrameUpsyncBatch.push(inputFrameUpsync); inputFrameUpsyncBatch.push(inputFrameUpsync);
} }
} }
const reqData = window.WsReq.encode({ const reqData = window.WsReq.encode({
msgId: Date.now(), msgId: Date.now(),
playerId: self.selfPlayerInfo.id, playerId: self.selfPlayerInfo.id,
@ -227,7 +215,7 @@ cc.Class({
inputFrameUpsyncBatch: inputFrameUpsyncBatch, inputFrameUpsyncBatch: inputFrameUpsyncBatch,
}).finish(); }).finish();
window.sendSafely(reqData); window.sendSafely(reqData);
self.lastUpsyncInputFrameId = inputFrameId; self.lastUpsyncInputFrameId = latestLocalInputFrameId;
}, },
onEnable() { onEnable() {
@ -244,12 +232,6 @@ cc.Class({
if (null == self.battleState || ALL_BATTLE_STATES.WAITING == self.battleState) { if (null == self.battleState || ALL_BATTLE_STATES.WAITING == self.battleState) {
window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
} }
if (null != window.handleRoomDownsyncFrame) {
window.handleRoomDownsyncFrame = null;
}
if (null != window.handleInputFrameDownsyncBatch) {
window.handleInputFrameDownsyncBatch = null;
}
if (null != window.handleBattleColliderInfo) { if (null != window.handleBattleColliderInfo) {
window.handleBattleColliderInfo = null; window.handleBattleColliderInfo = null;
} }
@ -309,7 +291,7 @@ cc.Class({
playerRichInfo.node.parent.removeChild(playerRichInfo.node); playerRichInfo.node.parent.removeChild(playerRichInfo.node);
} }
}); });
} }
self.playerRichInfoDict = new Map(); self.playerRichInfoDict = new Map();
// Clearing previous info of all players. [ENDS] // Clearing previous info of all players. [ENDS]
@ -317,12 +299,12 @@ cc.Class({
self.lastAllConfirmedRenderFrameId = -1; self.lastAllConfirmedRenderFrameId = -1;
self.lastAllConfirmedInputFrameId = -1; self.lastAllConfirmedInputFrameId = -1;
self.lastUpsyncInputFrameId = -1; self.lastUpsyncInputFrameId = -1;
self.chaserRenderFrameId = -1; // at any moment, "lastAllConfirmedRenderFrameId <= chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "handleInputFrameDownsyncBatch" self.chaserRenderFrameId = -1; // at any moment, "lastAllConfirmedRenderFrameId <= chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "onInputFrameDownsyncBatch"
self.recentRenderCache = new RingBuffer(1024); self.recentRenderCache = new RingBuffer(1024);
self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others". self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others".
self.recentInputCache = new RingBuffer(1024); self.recentInputCache = new RingBuffer(1024);
self.latestCollisionSys = new collisions.Collisions(); self.latestCollisionSys = new collisions.Collisions();
self.chaserCollisionSys = new collisions.Collisions(); self.chaserCollisionSys = new collisions.Collisions();
@ -330,7 +312,7 @@ cc.Class({
self.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used self.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used
self.latestCollisionSysMap = new Map(); self.latestCollisionSysMap = new Map();
self.chaserCollisionSysMap = new Map(); self.chaserCollisionSysMap = new Map();
self.transitToState(ALL_MAP_STATES.VISUAL); self.transitToState(ALL_MAP_STATES.VISUAL);
self.battleState = ALL_BATTLE_STATES.WAITING; self.battleState = ALL_BATTLE_STATES.WAITING;
@ -378,13 +360,11 @@ cc.Class({
const resultPanelScriptIns = self.resultPanelNode.getComponent("ResultPanel"); const resultPanelScriptIns = self.resultPanelNode.getComponent("ResultPanel");
resultPanelScriptIns.mapScriptIns = self; resultPanelScriptIns.mapScriptIns = self;
resultPanelScriptIns.onAgainClicked = () => { resultPanelScriptIns.onAgainClicked = () => {
self.battleState = ALL_BATTLE_STATES.WAITING; self.battleState = ALL_BATTLE_STATES.WAITING;
window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ ); window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ );
}; };
resultPanelScriptIns.onCloseDelegate = () => { resultPanelScriptIns.onCloseDelegate = () => {};
};
self.gameRuleNode = cc.instantiate(self.gameRulePrefab); self.gameRuleNode = cc.instantiate(self.gameRulePrefab);
self.gameRuleNode.width = self.canvasNode.width; self.gameRuleNode.width = self.canvasNode.width;
@ -416,13 +396,13 @@ cc.Class({
/** Init required prefab ended. */ /** Init required prefab ended. */
window.handleBattleColliderInfo = function(parsedBattleColliderInfo) { window.handleBattleColliderInfo = function(parsedBattleColliderInfo) {
self.inputDelayFrames = parsedBattleColliderInfo.inputDelayFrames; self.inputDelayFrames = parsedBattleColliderInfo.inputDelayFrames;
self.inputScaleFrames = parsedBattleColliderInfo.inputScaleFrames; self.inputScaleFrames = parsedBattleColliderInfo.inputScaleFrames;
self.inputFrameUpsyncDelayTolerance = parsedBattleColliderInfo.inputFrameUpsyncDelayTolerance; self.inputFrameUpsyncDelayTolerance = parsedBattleColliderInfo.inputFrameUpsyncDelayTolerance;
self.rollbackEstimatedDt = 1.0/parsedBattleColliderInfo.serverFps; self.rollbackEstimatedDt = 1.0 / parsedBattleColliderInfo.serverFps;
self.rollbackEstimatedDtMillis = 1000.0*self.rollbackEstimatedDt; self.rollbackEstimatedDtMillis = 1000.0 * self.rollbackEstimatedDt;
self.rollbackEstimatedDtToleranceMillis = self.rollbackEstimatedDtMillis/1000.0; self.rollbackEstimatedDtToleranceMillis = self.rollbackEstimatedDtMillis / 1000.0;
self.maxChasingRenderFramesPerUpdate = parsedBattleColliderInfo.maxChasingRenderFramesPerUpdate; self.maxChasingRenderFramesPerUpdate = parsedBattleColliderInfo.maxChasingRenderFramesPerUpdate;
const tiledMapIns = self.node.getComponent(cc.TiledMap); const tiledMapIns = self.node.getComponent(cc.TiledMap);
@ -433,7 +413,7 @@ cc.Class({
console.error(err); console.error(err);
return; return;
} }
/* /*
[WARNING] [WARNING]
@ -444,12 +424,12 @@ cc.Class({
tiledMapIns.tmxAsset = null; tiledMapIns.tmxAsset = null;
mapNode.removeAllChildren(); mapNode.removeAllChildren();
self._resetCurrentMatch(); self._resetCurrentMatch();
tiledMapIns.tmxAsset = tmxAsset; tiledMapIns.tmxAsset = tmxAsset;
const newMapSize = tiledMapIns.getMapSize(); const newMapSize = tiledMapIns.getMapSize();
const newTileSize = tiledMapIns.getTileSize(); const newTileSize = tiledMapIns.getTileSize();
self.node.setContentSize(newMapSize.width*newTileSize.width, newMapSize.height*newTileSize.height); self.node.setContentSize(newMapSize.width * newTileSize.width, newMapSize.height * newTileSize.height);
self.node.setPosition(cc.v2(0, 0)); self.node.setPosition(cc.v2(0, 0));
/* /*
* Deliberately hiding "ImageLayer"s. This dirty fix is specific to "CocosCreator v2.2.1", where it got back the rendering capability of "ImageLayer of Tiled", yet made incorrectly. In this game our "markers of ImageLayers" are rendered by dedicated prefabs with associated colliders. * Deliberately hiding "ImageLayer"s. This dirty fix is specific to "CocosCreator v2.2.1", where it got back the rendering capability of "ImageLayer of Tiled", yet made incorrectly. In this game our "markers of ImageLayers" are rendered by dedicated prefabs with associated colliders.
@ -458,24 +438,25 @@ cc.Class({
*/ */
const existingImageLayers = tiledMapIns.getObjectGroups(); const existingImageLayers = tiledMapIns.getObjectGroups();
for (let singleImageLayer of existingImageLayers) { for (let singleImageLayer of existingImageLayers) {
singleImageLayer.node.opacity = 0; singleImageLayer.node.opacity = 0;
} }
let barrierIdCounter = 0; let barrierIdCounter = 0;
const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node); const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node);
for (let boundaryObj of boundaryObjs.barriers) { for (let boundaryObj of boundaryObjs.barriers) {
const x0 = boundaryObj[0].x, y0 = boundaryObj[0].y; const x0 = boundaryObj[0].x,
y0 = boundaryObj[0].y;
let pts = []; let pts = [];
// TODO: Simplify this redundant coordinate conversion within "extractBoundaryObjects", but since this routine is only called once per battle, not urgent. // TODO: Simplify this redundant coordinate conversion within "extractBoundaryObjects", but since this routine is only called once per battle, not urgent.
for (let i = 0; i < boundaryObj.length; ++i) { for (let i = 0; i < boundaryObj.length; ++i) {
pts.push([boundaryObj[i].x-x0, boundaryObj[i].y-y0]); pts.push([boundaryObj[i].x - x0, boundaryObj[i].y - y0]);
} }
const newBarrierLatest = self.latestCollisionSys.createPolygon(x0, y0, pts); const newBarrierLatest = self.latestCollisionSys.createPolygon(x0, y0, pts);
const newBarrierChaser = self.chaserCollisionSys.createPolygon(x0, y0, pts); const newBarrierChaser = self.chaserCollisionSys.createPolygon(x0, y0, pts);
++barrierIdCounter; ++barrierIdCounter;
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter); const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
self.latestCollisionSysMap.set(collisionBarrierIndex, newBarrierLatest); self.latestCollisionSysMap.set(collisionBarrierIndex, newBarrierLatest);
self.chaserCollisionSysMap.set(collisionBarrierIndex, newBarrierChaser); self.chaserCollisionSysMap.set(collisionBarrierIndex, newBarrierChaser);
} }
self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')); self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer'));
@ -495,7 +476,7 @@ cc.Class({
self.backgroundMapTiledIns.tmxAsset = backgroundMapTmxAsset; self.backgroundMapTiledIns.tmxAsset = backgroundMapTmxAsset;
const newBackgroundMapSize = self.backgroundMapTiledIns.getMapSize(); const newBackgroundMapSize = self.backgroundMapTiledIns.getMapSize();
const newBackgroundMapTileSize = self.backgroundMapTiledIns.getTileSize(); const newBackgroundMapTileSize = self.backgroundMapTiledIns.getTileSize();
self.backgroundMapTiledIns.node.setContentSize(newBackgroundMapSize.width*newBackgroundMapTileSize.width, newBackgroundMapSize.height*newBackgroundMapTileSize.height); self.backgroundMapTiledIns.node.setContentSize(newBackgroundMapSize.width * newBackgroundMapTileSize.width, newBackgroundMapSize.height * newBackgroundMapTileSize.height);
self.backgroundMapTiledIns.node.setPosition(cc.v2(0, 0)); self.backgroundMapTiledIns.node.setPosition(cc.v2(0, 0));
const reqData = window.WsReq.encode({ const reqData = window.WsReq.encode({
@ -512,90 +493,6 @@ cc.Class({
self.hideGameRuleNode(); self.hideGameRuleNode();
self.transitToState(ALL_MAP_STATES.WAITING); self.transitToState(ALL_MAP_STATES.WAITING);
self._inputControlEnabled = false; self._inputControlEnabled = false;
let findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
window.handlePlayerAdded = function(rdf) {
// Update the "finding player" GUI and show it if not previously present
if (!self.findingPlayerNode.parent) {
self.showPopupInCanvas(self.findingPlayerNode);
}
findingPlayerScriptIns.updatePlayersInfo(rdf.playerMetas);
};
window.handleRoomDownsyncFrame = function(rdf) {
const frameId = rdf.id;
// Right upon establishment of the "PersistentSessionClient", we should receive an initial signal "BattleColliderInfo" earlier than any "RoomDownsyncFrame" containing "PlayerMeta" data.
switch (frameId) {
case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_READY_TO_START:
self.onBattleReadyToStart(rdf.playerMetas, false);
return;
case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START:
self.onBattleStartedOrResynced(rdf);
return;
}
// TODO: Inject a NetworkDoctor as introduced in https://app.yinxiang.com/shard/s61/nl/13267014/5c575124-01db-419b-9c02-ec81f78c6ddc/.
};
window.handleInputFrameDownsyncBatch = function(batch) {
if (ALL_BATTLE_STATES.IN_BATTLE != self.battleState
&& ALL_BATTLE_STATES.IN_SETTLEMENT != self.battleState) {
return;
}
// console.log("Received inputFrameDownsyncBatch=", batch, ", now correspondingLastLocalInputFrame=", self.recentInputCache.getByFrameId(batch[batch.length-1].inputFrameId));
let firstPredictedYetIncorrectInputFrameId = null;
let firstPredictedYetIncorrectInputFrameJoinIndex = null;
for (let k in batch) {
const inputFrameDownsync = batch[k];
const inputFrameDownsyncId = inputFrameDownsync.inputFrameId;
const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId);
if (null == localInputFrame) {
console.warn("handleInputFrameDownsyncBatch: recentInputCache is NOT having inputFrameDownsyncId=", inputFrameDownsyncId, "; now recentInputCache=", self._stringifyRecentInputCache(false));
} else {
if (null == firstPredictedYetIncorrectInputFrameId) {
for (let i in localInputFrame.inputList) {
if (localInputFrame.inputList[i] != inputFrameDownsync.inputList[i]) {
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
firstPredictedYetIncorrectInputFrameJoinIndex = (parseInt(i)+1);
break;
}
}
}
}
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
self._dumpToInputCache(inputFrameDownsync);
}
if (null != firstPredictedYetIncorrectInputFrameId) {
const inputFrameId1 = firstPredictedYetIncorrectInputFrameId;
const renderFrameId1 = self._convertToFirstUsedRenderFrameId(inputFrameId1, self.inputDelayFrames); // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId"
if (renderFrameId1 < self.renderFrameId) {
/*
A typical case is as follows.
--------------------------------------------------------
[self.lastAllConfirmedRenderFrameId] : 22
<renderFrameId1> : 36
<self.chaserRenderFrameId> : 62
[self.renderFrameId] : 64
--------------------------------------------------------
*/
if (renderFrameId1 < self.chaserRenderFrameId) {
// The actual rollback-and-chase would later be executed in update(dt).
console.warn("Mismatched input detected, resetting chaserRenderFrameId: inputFrameId1:", inputFrameId1, ", renderFrameId1:", renderFrameId1, ", chaserRenderFrameId before reset: ", self.chaserRenderFrameId);
self.chaserRenderFrameId = renderFrameId1;
} else {
// Deliberately left blank, chasing is ongoing.
}
} else {
// No need to rollback when "renderFrameId1 == self.renderFrameId", because the "corresponding delayedInputFrame for renderFrameId2" is NOT YET EXECUTED BY NOW, it just went through "++self.renderFrameId" in "update(dt)" and javascript-runtime is mostly single-threaded in our programmable range.
}
}
};
} }
// The player is now viewing "self.gameRuleNode" with button(s) to start an actual battle. -- YFLu // The player is now viewing "self.gameRuleNode" with button(s) to start an actual battle. -- YFLu
@ -608,15 +505,15 @@ cc.Class({
self.disableGameRuleNode(); self.disableGameRuleNode();
// The player is now possibly viewing "self.gameRuleNode" with no button, and should wait for `self.initAfterWSConnected` to be called. // The player is now possibly viewing "self.gameRuleNode" with no button, and should wait for `self.initAfterWSConnected` to be called.
self.battleState = ALL_BATTLE_STATES.WAITING; self.battleState = ALL_BATTLE_STATES.WAITING;
window.initPersistentSessionClient(self.initAfterWSConnected, expectedRoomId); window.initPersistentSessionClient(self.initAfterWSConnected, expectedRoomId);
} else if (null != boundRoomId) { } else if (null != boundRoomId) {
self.disableGameRuleNode(); self.disableGameRuleNode();
self.battleState = ALL_BATTLE_STATES.WAITING; self.battleState = ALL_BATTLE_STATES.WAITING;
window.initPersistentSessionClient(self.initAfterWSConnected, expectedRoomId); window.initPersistentSessionClient(self.initAfterWSConnected, expectedRoomId);
} else { } else {
self.showPopupInCanvas(self.gameRuleNode); self.showPopupInCanvas(self.gameRuleNode);
// Deliberately left blank. -- YFLu // Deliberately left blank. -- YFLu
} }
}, },
@ -652,10 +549,27 @@ cc.Class({
onBattleStartedOrResynced(rdf) { onBattleStartedOrResynced(rdf) {
// This function is also applicable to "re-joining". // This function is also applicable to "re-joining".
console.log('On battle started or resynced! renderFrameId=', rdf.id);
const self = window.mapIns; const self = window.mapIns;
const dumpRenderCacheRet = self.dumpToRenderCache(rdf);
if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) {
console.error("Something is wrong while setting the RingBuffer by frameId!");
return dumpRenderCacheRet;
}
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet) {
if (rdf.id < self.chaserRenderFrameId) {
// This "rdf.id = backend.refRenderFrameId" could be small, see comments around "room.go".
self.chaserRenderFrameId = rdf.id;
}
// In this case, we'll also got proper all-confirmed inputFrames for advancing the renderFrames in the coming "update(dt)"
return dumpRenderCacheRet;
}
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
console.log('On battle started or resynced! renderFrameId=', rdf.id);
self.renderFrameId = rdf.id; self.renderFrameId = rdf.id;
self.lastAllConfirmedRenderFrameId = rdf.id; self.lastRenderFrameIdTriggeredAt = performance.now();
self.lastAllConfirmedRenderFrameId = rdf.id;
self.chaserRenderFrameId = rdf.id; self.chaserRenderFrameId = rdf.id;
const players = rdf.players; const players = rdf.players;
@ -682,30 +596,105 @@ cc.Class({
self.countdownToBeginGameNode.parent.removeChild(self.countdownToBeginGameNode); self.countdownToBeginGameNode.parent.removeChild(self.countdownToBeginGameNode);
} }
self.transitToState(ALL_MAP_STATES.VISUAL); self.transitToState(ALL_MAP_STATES.VISUAL);
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
self.applyRoomDownsyncFrameDynamics(rdf); self.applyRoomDownsyncFrameDynamics(rdf);
self._dumpToRenderCache(rdf);
self.battleState = ALL_BATTLE_STATES.IN_BATTLE; // Starts the increment of "self.renderFrameId" in "self.update(dt)" return dumpRenderCacheRet;
self.lastRenderFrameIdTriggeredAt = performance.now(); },
if (null != window.boundRoomId) {
self.boundRoomIdLabel.string = window.boundRoomId; equalInputLists(lhs, rhs) {
if (null == lhs || null == rhs) return false;
if (lhs.length != rhs.length) return false;
for (let i in lhs) {
if (lhs[i] == rhs[i]) continue;
return false;
} }
return true;
},
onInputFrameDownsyncBatch(batch, dumpRenderCacheRet /* second param is default to null */ ) {
const self = this;
if (ALL_BATTLE_STATES.IN_BATTLE != self.battleState
&& ALL_BATTLE_STATES.IN_SETTLEMENT != self.battleState) {
return;
}
let firstPredictedYetIncorrectInputFrameId = null;
for (let k in batch) {
const inputFrameDownsync = batch[k];
const inputFrameDownsyncId = inputFrameDownsync.inputFrameId;
if (window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet) {
// Deliberately left blank, in this case "chaserRenderFrameId" is already reset to proper value.
} else {
const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId);
if (null == localInputFrame) {
console.warn("localInputFrame not existing: recentInputCache is NOT having inputFrameDownsyncId=", inputFrameDownsyncId, "; now recentInputCache=", self._stringifyRecentInputCache(false));
} else if (null == firstPredictedYetIncorrectInputFrameId && !self.equalInputLists(localInputFrame.inputList, inputFrameDownsync.inputList)) {
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
}
}
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
self.dumpToInputCache(inputFrameDownsync);
}
if (null != firstPredictedYetIncorrectInputFrameId) {
const inputFrameId1 = firstPredictedYetIncorrectInputFrameId;
const renderFrameId1 = self._convertToFirstUsedRenderFrameId(inputFrameId1, self.inputDelayFrames); // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId"
if (renderFrameId1 < self.renderFrameId) {
/*
A typical case is as follows.
--------------------------------------------------------
[self.lastAllConfirmedRenderFrameId] : 22
<renderFrameId1> : 36
<self.chaserRenderFrameId> : 62
[self.renderFrameId] : 64
--------------------------------------------------------
*/
if (renderFrameId1 < self.chaserRenderFrameId) {
// The actual rollback-and-chase would later be executed in update(dt).
console.warn("Mismatched input detected, resetting chaserRenderFrameId: inputFrameId1:", inputFrameId1, ", renderFrameId1:", renderFrameId1, ", chaserRenderFrameId before reset: ", self.chaserRenderFrameId);
self.chaserRenderFrameId = renderFrameId1;
} else {
// Deliberately left blank, chasing is ongoing.
}
} else {
// No need to rollback when "renderFrameId1 == self.renderFrameId", because the "corresponding delayedInputFrame for renderFrameId2" is NOT YET EXECUTED BY NOW, it just went through "++self.renderFrameId" in "update(dt)" and javascript-runtime is mostly single-threaded in our programmable range.
}
}
},
onPlayerAdded(rdf) {
const self = this;
// Update the "finding player" GUI and show it if not previously present
if (!self.findingPlayerNode.parent) {
self.showPopupInCanvas(self.findingPlayerNode);
}
let findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
findingPlayerScriptIns.updatePlayersInfo(rdf.playerMetas);
}, },
logBattleStats() { logBattleStats() {
const self = this; const self = this;
let s = []; let s = [];
s.push("Battle stats: lastUpsyncInputFrameId=" + self.lastUpsyncInputFrameId + ", lastAllConfirmedInputFrameId=" + self.lastAllConfirmedInputFrameId); s.push("Battle stats: lastUpsyncInputFrameId=" + self.lastUpsyncInputFrameId + ", lastAllConfirmedInputFrameId=" + self.lastAllConfirmedInputFrameId);
for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) { for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) {
const inputFrameDownsync = self.recentInputCache.getByFrameId(i); const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
s.push(JSON.stringify(inputFrameDownsync)); s.push(JSON.stringify(inputFrameDownsync));
} }
console.log(s.join('\n')); console.log(s.join('\n'));
}, },
onBattleStopped() { onBattleStopped() {
const self = this; const self = this;
if (ALL_BATTLE_STATES.IN_BATTLE != self.battleState) {
return;
}
self.countdownNanos = null; self.countdownNanos = null;
self.logBattleStats(); self.logBattleStats();
if (self.musicEffectManagerScriptIns) { if (self.musicEffectManagerScriptIns) {
@ -741,7 +730,10 @@ cc.Class({
newPlayerNode.active = true; newPlayerNode.active = true;
const playerScriptIns = newPlayerNode.getComponent("SelfPlayer"); const playerScriptIns = newPlayerNode.getComponent("SelfPlayer");
playerScriptIns.scheduleNewDirection({dx: 0, dy: 0}, true); playerScriptIns.scheduleNewDirection({
dx: 0,
dy: 0
}, true);
return [newPlayerNode, playerScriptIns]; return [newPlayerNode, playerScriptIns];
}, },
@ -750,45 +742,47 @@ cc.Class({
const self = this; const self = this;
if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) { if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) {
const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt; const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt;
if (elapsedMillisSinceLastFrameIdTriggered < (self.rollbackEstimatedDtMillis)) { if (elapsedMillisSinceLastFrameIdTriggered < (self.rollbackEstimatedDtMillis)) {
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered); // console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
return; return;
} }
try { try {
let st = performance.now(); let st = performance.now();
let prevSelfInput = null, currSelfInput = null; let prevSelfInput = null,
const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here currSelfInput = null;
if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) { const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here
const prevAndCurrInputs = self._generateInputFrameUpsync(noDelayInputFrameId); if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) {
prevSelfInput = prevAndCurrInputs[0]; const prevAndCurrInputs = self._generateInputFrameUpsync(noDelayInputFrameId);
currSelfInput = prevAndCurrInputs[1]; prevSelfInput = prevAndCurrInputs[0];
} currSelfInput = prevAndCurrInputs[1];
}
let t0 = performance.now(); let t0 = performance.now();
if (self.shouldSendInputFrameUpsyncBatch(prevSelfInput, currSelfInput, self.lastUpsyncInputFrameId, noDelayInputFrameId)) { if (self.shouldSendInputFrameUpsyncBatch(prevSelfInput, currSelfInput, self.lastUpsyncInputFrameId, noDelayInputFrameId)) {
// TODO: Is the following statement run asynchronously in an implicit manner? Should I explicitly run it asynchronously? // TODO: Is the following statement run asynchronously in an implicit manner? Should I explicitly run it asynchronously?
self.sendInputFrameUpsyncBatch(noDelayInputFrameId); self.sendInputFrameUpsyncBatch(noDelayInputFrameId);
} }
let t1 = performance.now(); let t1 = performance.now();
// Use "fractional-frame-chasing" to guarantee that "self.update(dt)" is not jammed by a "large range of frame-chasing". See `<proj-root>/ConcerningEdgeCases.md` for the motivation. // Use "fractional-frame-chasing" to guarantee that "self.update(dt)" is not jammed by a "large range of frame-chasing". See `<proj-root>/ConcerningEdgeCases.md` for the motivation.
const prevChaserRenderFrameId = self.chaserRenderFrameId; const prevChaserRenderFrameId = self.chaserRenderFrameId;
let nextChaserRenderFrameId = (prevChaserRenderFrameId + self.maxChasingRenderFramesPerUpdate); let nextChaserRenderFrameId = (prevChaserRenderFrameId + self.maxChasingRenderFramesPerUpdate);
if (nextChaserRenderFrameId > self.renderFrameId) nextChaserRenderFrameId = self.renderFrameId; if (nextChaserRenderFrameId > self.renderFrameId)
self.rollbackAndChase(prevChaserRenderFrameId, nextChaserRenderFrameId, self.chaserCollisionSys, self.chaserCollisionSysMap); nextChaserRenderFrameId = self.renderFrameId;
self.chaserRenderFrameId = nextChaserRenderFrameId; // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic! self.rollbackAndChase(prevChaserRenderFrameId, nextChaserRenderFrameId, self.chaserCollisionSys, self.chaserCollisionSysMap);
let t2 = performance.now(); self.chaserRenderFrameId = nextChaserRenderFrameId; // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
let t2 = performance.now();
// Inside "self.rollbackAndChase", the "self.latestCollisionSys" is ALWAYS ROLLED BACK to "self.recentRenderCache.get(self.renderFrameId)" before being applied dynamics from corresponding inputFrameDownsync, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now. // Inside "self.rollbackAndChase", the "self.latestCollisionSys" is ALWAYS ROLLED BACK to "self.recentRenderCache.get(self.renderFrameId)" before being applied dynamics from corresponding inputFrameDownsync, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now.
const rdf = self.rollbackAndChase(self.renderFrameId, self.renderFrameId+1, self.latestCollisionSys, self.latestCollisionSysMap); const rdf = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.latestCollisionSys, self.latestCollisionSysMap);
self.applyRoomDownsyncFrameDynamics(rdf); self.applyRoomDownsyncFrameDynamics(rdf);
let t3 = performance.now(); let t3 = performance.now();
} catch (err) { } catch (err) {
console.error("Error during Map.update", err); console.error("Error during Map.update", err);
} finally { } finally {
// Update countdown // Update countdown
if (null != self.countdownNanos) { if (null != self.countdownNanos) {
self.countdownNanos -= self.rollbackEstimatedDt*1000000000; self.countdownNanos -= self.rollbackEstimatedDt * 1000000000;
if (self.countdownNanos <= 0) { if (self.countdownNanos <= 0) {
self.onBattleStopped(self.playerRichInfoDict); self.onBattleStopped(self.playerRichInfoDict);
return; return;
@ -820,14 +814,16 @@ cc.Class({
const selfPlayerStr = cc.sys.localStorage.getItem("selfPlayer"); const selfPlayerStr = cc.sys.localStorage.getItem("selfPlayer");
if (null == selfPlayerStr) { if (null == selfPlayerStr) {
localClearance(); localClearance();
return; return;
} }
const selfPlayerInfo = JSON.parse(selfPlayerStr); const selfPlayerInfo = JSON.parse(selfPlayerStr);
try { try {
NetworkUtils.ajax({ NetworkUtils.ajax({
url: backendAddress.PROTOCOL + '://' + backendAddress.HOST + ':' + backendAddress.PORT + constants.ROUTE_PATH.API + constants.ROUTE_PATH.PLAYER + constants.ROUTE_PATH.VERSION + constants.ROUTE_PATH.INT_AUTH_TOKEN + constants.ROUTE_PATH.LOGOUT, url: backendAddress.PROTOCOL + '://' + backendAddress.HOST + ':' + backendAddress.PORT + constants.ROUTE_PATH.API + constants.ROUTE_PATH.PLAYER + constants.ROUTE_PATH.VERSION + constants.ROUTE_PATH.INT_AUTH_TOKEN + constants.ROUTE_PATH.LOGOUT,
type: "POST", type: "POST",
data: { intAuthToken: selfPlayerInfo.intAuthToken }, data: {
intAuthToken: selfPlayerInfo.intAuthToken
},
success: function(res) { success: function(res) {
if (res.ret != constants.RET_CODE.OK) { if (res.ret != constants.RET_CODE.OK) {
console.log("Logout failed: ", res); console.log("Logout failed: ", res);
@ -862,7 +858,7 @@ cc.Class({
onGameRule1v1ModeClicked(evt, cb) { onGameRule1v1ModeClicked(evt, cb) {
const self = this; const self = this;
self.battleState = ALL_BATTLE_STATES.WAITING; self.battleState = ALL_BATTLE_STATES.WAITING;
window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ ); window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ );
self.hideGameRuleNode(); self.hideGameRuleNode();
}, },
@ -888,8 +884,8 @@ cc.Class({
}; };
if (true == isSelfRejoining) { if (true == isSelfRejoining) {
hideFindingPlayersGUI(); hideFindingPlayersGUI();
} else { } else {
// Delay to hide the "finding player" GUI, then show a countdown clock // Delay to hide the "finding player" GUI, then show a countdown clock
window.setTimeout(() => { window.setTimeout(() => {
hideFindingPlayersGUI(); hideFindingPlayersGUI();
@ -902,10 +898,10 @@ 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 inputFrameAppliedOnPrevRenderFrame = ( const inputFrameAppliedOnPrevRenderFrame = (
0 > prevRenderFrameId 0 > prevRenderFrameId
? ?
null null
: :
self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(prevRenderFrameId, self.inputDelayFrames)) self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(prevRenderFrameId, self.inputDelayFrames))
@ -914,8 +910,8 @@ 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 > speedRefRenderFrameId 0 > speedRefRenderFrameId
? ?
null null
: :
self.recentRenderCache.getByFrameId(speedRefRenderFrameId) self.recentRenderCache.getByFrameId(speedRefRenderFrameId)
@ -924,32 +920,32 @@ cc.Class({
const rdf = { const rdf = {
id: renderFrameId, id: renderFrameId,
refFrameId: renderFrameId, refFrameId: renderFrameId,
players: {} players: {}
}; };
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
const joinIndex = playerRichInfo.joinIndex; const joinIndex = playerRichInfo.joinIndex;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex); const playerCollider = collisionSysMap.get(collisionPlayerIndex);
rdf.players[playerRichInfo.id] = { rdf.players[playerRichInfo.id] = {
id: playerRichInfo.id, id: playerRichInfo.id,
x: playerCollider.x, x: playerCollider.x,
y: playerCollider.y, y: playerCollider.y,
dir: self.ctrl.decodeDirection(null == inputFrameAppliedOnPrevRenderFrame ? 0 : inputFrameAppliedOnPrevRenderFrame.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 != inputFrameAppliedOnPrevRenderFrame && self._allConfirmed(inputFrameAppliedOnPrevRenderFrame.confirmedList) null != inputFrameAppliedOnPrevRenderFrame && self._allConfirmed(inputFrameAppliedOnPrevRenderFrame.confirmedList)
&& &&
self.lastAllConfirmedRenderFrameId >= prevRenderFrameId self.lastAllConfirmedRenderFrameId >= prevRenderFrameId
&& &&
rdf.id > self.lastAllConfirmedRenderFrameId rdf.id > self.lastAllConfirmedRenderFrameId
) { ) {
self.lastAllConfirmedRenderFrameId = rdf.id; self.lastAllConfirmedRenderFrameId = rdf.id;
self.chaserRenderFrameId = rdf.id; // it must be true that "chaserRenderFrameId >= lastAllConfirmedRenderFrameId" self.chaserRenderFrameId = rdf.id; // it must be true that "chaserRenderFrameId >= lastAllConfirmedRenderFrameId"
} }
self._dumpToRenderCache(rdf); self.dumpToRenderCache(rdf);
return rdf; return rdf;
}, },
@ -960,20 +956,20 @@ cc.Class({
const immediatePlayerInfo = rdf.players[playerId]; const immediatePlayerInfo = rdf.players[playerId];
playerRichInfo.node.setPosition(immediatePlayerInfo.x, immediatePlayerInfo.y); playerRichInfo.node.setPosition(immediatePlayerInfo.x, immediatePlayerInfo.y);
playerRichInfo.scriptIns.scheduleNewDirection(immediatePlayerInfo.dir, true); playerRichInfo.scriptIns.scheduleNewDirection(immediatePlayerInfo.dir, true);
playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed); playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed);
}); });
}, },
getCachedInputFrameDownsyncWithPrediction(inputFrameId) { getCachedInputFrameDownsyncWithPrediction(inputFrameId) {
const self = this; const self = this;
let inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId); let inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId);
if (null != inputFrameDownsync && -1 != self.lastAllConfirmedInputFrameId && inputFrameId > self.lastAllConfirmedInputFrameId) { if (null != inputFrameDownsync && -1 != self.lastAllConfirmedInputFrameId && inputFrameId > self.lastAllConfirmedInputFrameId) {
const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId); const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId);
for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) { for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) {
if (i == self.selfPlayerInfo.joinIndex-1) continue; if (i == self.selfPlayerInfo.joinIndex - 1) continue;
inputFrameDownsync.inputList[i] = lastAllConfirmedInputFrame.inputList[i]; inputFrameDownsync.inputList[i] = lastAllConfirmedInputFrame.inputList[i];
} }
} }
return inputFrameDownsync; return inputFrameDownsync;
}, },
@ -986,13 +982,13 @@ cc.Class({
const self = this; const self = this;
const renderFrameSt = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame" const renderFrameSt = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame"
if (null == renderFrameSt) { if (null == renderFrameSt) {
console.error("Couldn't find renderFrameId=", renderFrameIdSt, " to rollback, recentRenderCache=", self._stringifyRecentRenderCache(false)); console.error("Couldn't find renderFrameId=", renderFrameIdSt, " to rollback, lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId, ", recentRenderCache=", self._stringifyRecentRenderCache(false), ", recentInputCache=", self._stringifyRecentInputCache(false));
} }
/* /*
Reset "position" of players in "collisionSys" according to "renderFrameSt". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics. Reset "position" of players in "collisionSys" according to "renderFrameSt". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics.
*/ */
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
const joinIndex = playerRichInfo.joinIndex; const joinIndex = playerRichInfo.joinIndex;
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
const playerCollider = collisionSysMap.get(collisionPlayerIndex); const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const player = renderFrameSt.players[playerId]; const player = renderFrameSt.players[playerId];
@ -1008,30 +1004,29 @@ cc.Class({
for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) { for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) {
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 inputFrameDownsync = self.getCachedInputFrameDownsyncWithPrediction(j);
if (null == inputFrameDownsync) {
console.error("Failed to get cached inputFrameDownsync for renderFrameId=", i, ", inputFrameId=", j, "lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId, ", recentRenderCache=", self._stringifyRecentRenderCache(false), ", recentInputCache=", self._stringifyRecentInputCache(false));
}
const inputList = inputFrameDownsync.inputList;
for (let j in self.playerRichInfoArr) { for (let j in self.playerRichInfoArr) {
const joinIndex = parseInt(j) + 1; const joinIndex = parseInt(j) + 1;
const playerId = self.playerRichInfoArr[j].id; const playerId = self.playerRichInfoArr[j].id;
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];
const encodedInput = inputList[joinIndex-1]; const encodedInput = inputList[joinIndex - 1];
const decodedInput = self.ctrl.decodeDirection(encodedInput); const decodedInput = self.ctrl.decodeDirection(encodedInput);
const baseChange = player.speed*self.rollbackEstimatedDt*decodedInput.speedFactor; const baseChange = player.speed * self.rollbackEstimatedDt * decodedInput.speedFactor;
playerCollider.x += baseChange*decodedInput.dx; playerCollider.x += baseChange * decodedInput.dx;
playerCollider.y += baseChange*decodedInput.dy; playerCollider.y += baseChange * decodedInput.dy;
/*
if (0 < encodedInput) {
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?
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;
const playerCollider = collisionSysMap.get(collisionPlayerIndex); const playerCollider = collisionSysMap.get(collisionPlayerIndex);
const potentials = playerCollider.potentials(); const potentials = playerCollider.potentials();
@ -1044,10 +1039,10 @@ cc.Class({
} }
} }
} }
return self._createRoomDownsyncFrameLocally(renderFrameIdEd, collisionSys, collisionSysMap); return self._createRoomDownsyncFrameLocally(renderFrameIdEd, collisionSys, collisionSysMap);
}, },
_initPlayerRichInfoDict(players, playerMetas) { _initPlayerRichInfoDict(players, playerMetas) {
const self = this; const self = this;
for (let k in players) { for (let k in players) {
@ -1056,7 +1051,7 @@ cc.Class({
const immediatePlayerInfo = players[playerId]; const immediatePlayerInfo = players[playerId];
const immediatePlayerMeta = playerMetas[playerId]; const immediatePlayerMeta = playerMetas[playerId];
const nodeAndScriptIns = self.spawnPlayerNode(immediatePlayerInfo.joinIndex, immediatePlayerInfo.x, immediatePlayerInfo.y); const nodeAndScriptIns = self.spawnPlayerNode(immediatePlayerInfo.joinIndex, immediatePlayerInfo.x, immediatePlayerInfo.y);
self.playerRichInfoDict.set(playerId, immediatePlayerInfo); self.playerRichInfoDict.set(playerId, immediatePlayerInfo);
Object.assign(self.playerRichInfoDict.get(playerId), { Object.assign(self.playerRichInfoDict.get(playerId), {
node: nodeAndScriptIns[0], node: nodeAndScriptIns[0],
@ -1064,14 +1059,14 @@ cc.Class({
}); });
if (self.selfPlayerInfo.id == playerId) { if (self.selfPlayerInfo.id == playerId) {
self.selfPlayerInfo = Object.assign(self.selfPlayerInfo, immediatePlayerInfo); self.selfPlayerInfo = Object.assign(self.selfPlayerInfo, immediatePlayerInfo);
nodeAndScriptIns[1].showArrowTipNode(); nodeAndScriptIns[1].showArrowTipNode();
} }
} }
self.playerRichInfoArr = new Array(self.playerRichInfoDict.size); self.playerRichInfoArr = new Array(self.playerRichInfoDict.size);
self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { self.playerRichInfoDict.forEach((playerRichInfo, playerId) => {
self.playerRichInfoArr[playerRichInfo.joinIndex-1] = playerRichInfo; self.playerRichInfoArr[playerRichInfo.joinIndex - 1] = playerRichInfo;
}); });
}, },
_stringifyRecentInputCache(usefullOutput) { _stringifyRecentInputCache(usefullOutput) {

View File

@ -1,3 +1,7 @@
window.RING_BUFF_CONSECUTIVE_SET = 0;
window.RING_BUFF_NON_CONSECUTIVE_SET = 1;
window.RING_BUFF_FAILED_TO_SET = 2;
var RingBuffer = function(capacity) { var RingBuffer = function(capacity) {
this.ed = 0; // write index, open index this.ed = 0; // write index, open index
this.st = 0; // read index, closed index this.st = 0; // read index, closed index
@ -32,15 +36,15 @@ RingBuffer.prototype.pop = function() {
return item; return item;
}; };
RingBuffer.prototype.getByOffset = function(offsetFromSt) { RingBuffer.prototype.getArrIdxByOffset = function(offsetFromSt) {
if (0 == this.cnt) { if (0 > offsetFromSt || 0 == this.cnt) {
return null; return null;
} }
let arrIdx = this.st + offsetFromSt; let arrIdx = this.st + offsetFromSt;
if (this.st < this.ed) { if (this.st < this.ed) {
// case#1: 0...st...ed...n-1 // case#1: 0...st...ed...n-1
if (this.st <= arrIdx && arrIdx < this.ed) { if (this.st <= arrIdx && arrIdx < this.ed) {
return this.eles[arrIdx]; return arrIdx;
} }
} else { } else {
// if this.st >= this.sd // if this.st >= this.sd
@ -49,7 +53,7 @@ RingBuffer.prototype.getByOffset = function(offsetFromSt) {
arrIdx -= this.n arrIdx -= this.n
} }
if (arrIdx >= this.st || arrIdx < this.ed) { if (arrIdx >= this.st || arrIdx < this.ed) {
return this.eles[arrIdx]; return arrIdx;
} }
} }
@ -57,7 +61,40 @@ RingBuffer.prototype.getByOffset = function(offsetFromSt) {
}; };
RingBuffer.prototype.getByFrameId = function(frameId) { RingBuffer.prototype.getByFrameId = function(frameId) {
return this.getByOffset(frameId - this.stFrameId); const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
return (null == arrIdx ? null : this.eles[arrIdx]);
};
// [WARNING] During a battle, frontend could receive non-consecutive frames (either renderFrame or inputFrame) due to resync, the buffer should handle these frames properly.
RingBuffer.prototype.setByFrameId = function(item, frameId) {
if (frameId < this.stFrameId) {
console.error("Invalid putByFrameId#1: stFrameId=", stFrameId, ", edFrameId=", edFrameId, ", incoming item=", item);
return window.RING_BUFF_FAILED_TO_SET;
}
const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
if (null != arrIdx) {
this.eles[arrIdx] = item;
return window.RING_BUFF_CONSECUTIVE_SET;
}
// When "null == arrIdx", should it still be deemed consecutive if "frameId == edFrameId" prior to the reset?
let ret = window.RING_BUFF_CONSECUTIVE_SET;
if (this.edFrameId < frameId) {
this.st = this.ed = 0;
this.stFrameId = this.edFrameId = frameId;
this.cnt = 0;
ret = window.RING_BUFF_NON_CONSECUTIVE_SET;
}
this.eles[this.ed] = item
this.edFrameId++;
this.cnt++;
this.ed++;
if (this.ed >= this.n) {
this.ed -= this.n; // Deliberately not using "%" operator for performance concern
}
return ret;
}; };
module.exports = RingBuffer; module.exports = RingBuffer;

View File

@ -1,3 +1,5 @@
const RingBuffer = require('./RingBuffer');
window.UPSYNC_MSG_ACT_HB_PING = 1; window.UPSYNC_MSG_ACT_HB_PING = 1;
window.UPSYNC_MSG_ACT_PLAYER_CMD = 2; window.UPSYNC_MSG_ACT_PLAYER_CMD = 2;
window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK = 3; window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK = 3;
@ -8,7 +10,7 @@ window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = -1;
window.DOWNSYNC_MSG_ACT_BATTLE_START = 0; window.DOWNSYNC_MSG_ACT_BATTLE_START = 0;
window.DOWNSYNC_MSG_ACT_HB_REQ = 1; window.DOWNSYNC_MSG_ACT_HB_REQ = 1;
window.DOWNSYNC_MSG_ACT_INPUT_BATCH = 2; window.DOWNSYNC_MSG_ACT_INPUT_BATCH = 2;
window.DOWNSYNC_MSG_ACT_ROOM_FRAME = 3; window.DOWNSYNC_MSG_ACT_BATTLE_STOPPED = 3;
window.DOWNSYNC_MSG_ACT_FORCED_RESYNC = 4; window.DOWNSYNC_MSG_ACT_FORCED_RESYNC = 4;
@ -160,30 +162,28 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
window.handleHbRequirements(resp); // 获取boundRoomId并存储到localStorage window.handleHbRequirements(resp); // 获取boundRoomId并存储到localStorage
break; break;
case window.DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED: case window.DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED:
window.handlePlayerAdded(resp.rdf); mapIns.onPlayerAdded(resp.rdf);
break; break;
case window.DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED: case window.DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED:
// Deliberately left blank for now // Deliberately left blank for now
break; break;
case window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START: case window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START:
mapIns.onBattleReadyToStart(resp.rdf.playerMetas, false);
break;
case window.DOWNSYNC_MSG_ACT_BATTLE_START: case window.DOWNSYNC_MSG_ACT_BATTLE_START:
case window.DOWNSYNC_MSG_ACT_ROOM_FRAME: mapIns.onBattleStartedOrResynced(resp.rdf);
if (window.handleRoomDownsyncFrame) { break;
window.handleRoomDownsyncFrame(resp.rdf); case window.DOWNSYNC_MSG_ACT_BATTLE_STOPPED:
} mapIns.onBattleStopped();
break; break;
case window.DOWNSYNC_MSG_ACT_INPUT_BATCH: case window.DOWNSYNC_MSG_ACT_INPUT_BATCH:
if (window.handleInputFrameDownsyncBatch) { mapIns.onInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch);
window.handleInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch);
}
break; break;
case window.DOWNSYNC_MSG_ACT_FORCED_RESYNC: case window.DOWNSYNC_MSG_ACT_FORCED_RESYNC:
if (window.handleInputFrameDownsyncBatch && window.handleRoomDownsyncFrame) { console.warn("Got forced resync:", JSON.stringify(resp), " @localRenderFrameId=", mapIns.renderFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false));
console.warn("Got forced resync:", JSON.stringify(resp), " @localRenderFrameId=", mapIns.renderFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false)); // The following order of execution is important, because "onInputFrameDownsyncBatch" is only available when state is IN_BATTLE
// The following order of execution is important, because "handleInputFrameDownsyncBatch" is only available when state is IN_BATTLE const dumpRenderCacheRet = mapIns.onBattleStartedOrResynced(resp.rdf);
window.handleRoomDownsyncFrame(resp.rdf); mapIns.onInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch, dumpRenderCacheRet);
window.handleInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch);
}
break; break;
default: default:
break; break;