Compare commits

..

5 Commits

Author SHA1 Message Date
genxium
14bb6fa1ea Fixes for frontend chaser. 2022-12-22 11:35:18 +08:00
genxium
3c15e21652 Added prediction fault detection in frontend. 2022-12-20 23:51:53 +08:00
genxium
727e66787f Added inactive watchdog hint in frontend. 2022-12-20 16:01:44 +08:00
genxium
9eb6ad26ef Fixed non-integer barrier boundaries. 2022-12-20 07:41:20 +08:00
genxium
847607f3e6 Further enhanced comments. 2022-12-19 20:49:34 +08:00
12 changed files with 328 additions and 284 deletions

View File

@@ -1,28 +1,29 @@
{
"RET_CODE": {
"__comment__":"基础",
"OK": 9000,
"UNKNOWN_ERROR": 9001,
"INVALID_REQUEST_PARAM": 9002,
"IS_TEST_ACC": 9003,
"MYSQL_ERROR": 9004,
"NONEXISTENT_ACT": 9005,
"LACK_OF_DIAMOND": 9006,
"LACK_OF_GOLD": 9007,
"LACK_OF_ENERGY": 9008,
"NONEXISTENT_ACT_HANDLER": 9009,
"LOCALLY_NO_AVAILABLE_ROOM": 9010,
"LOCALLY_NO_SPECIFIED_ROOM": 9011,
"PLAYER_NOT_ADDABLE_TO_ROOM": 9012,
"PLAYER_NOT_READDABLE_TO_ROOM": 9013,
"PLAYER_NOT_FOUND": 9014,
"PLAYER_CHEATING": 9015,
"WECHAT_SERVER_ERROR": 9016,
"IS_BOT_ACC": 9017,
"__comment__": "Websocket",
"OK": 3000,
"UNKNOWN_ERROR": 3001,
"INVALID_REQUEST_PARAM": 3002,
"IS_TEST_ACC": 3003,
"MYSQL_ERROR": 3004,
"NONEXISTENT_ACT": 3005,
"LACK_OF_DIAMOND": 3006,
"LACK_OF_GOLD": 3007,
"LACK_OF_ENERGY": 3008,
"NONEXISTENT_ACT_HANDLER": 3009,
"LOCALLY_NO_AVAILABLE_ROOM": 3010,
"LOCALLY_NO_SPECIFIED_ROOM": 3011,
"PLAYER_NOT_ADDABLE_TO_ROOM": 3012,
"PLAYER_NOT_READDABLE_TO_ROOM": 3013,
"PLAYER_NOT_FOUND": 3014,
"PLAYER_CHEATING": 3015,
"WECHAT_SERVER_ERROR": 3016,
"IS_BOT_ACC": 3017,
"ACTIVE_WATCHDOG": 3018,
"BATTLE_STOPPED": 3019,
"CLIENT_MISMATCHED_RENDER_FRAME": 3020,
"__comment__":"SMS",
"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY": 5001,
"SMS_CAPTCHA_NOT_MATCH": 5002,
"__comment__": "OTHERS",
"INVALID_TOKEN": 2001,
"DUPLICATED": 2002,
@@ -39,12 +40,15 @@
"FAILED_TO_DELETE": 2015,
"FAILED_TO_CREATE": 2016,
"INCORRECT_PHONE_NUMBER": 2018,
"INSUFFICIENT_MEM_TO_ALLOCATE_CONNECTION": 3001,
"PASSWORD_RESET_CODE_GENERATION_PER_EMAIL_TOO_FREQUENTLY": 4000,
"TRADE_CREATION_TOO_FREQUENTLY": 4002,
"MAP_NOT_UNLOCKED": 4003,
"INSUFFICIENT_MEM_TO_ALLOCATE_CONNECTION": 2019,
"PASSWORD_RESET_CODE_GENERATION_PER_EMAIL_TOO_FREQUENTLY": 2020,
"TRADE_CREATION_TOO_FREQUENTLY": 2021,
"MAP_NOT_UNLOCKED": 2022,
"GET_SMS_CAPTCHA_RESP_ERROR_CODE": 2023,
"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY": 2024,
"SMS_CAPTCHA_NOT_MATCH": 2025,
"GET_SMS_CAPTCHA_RESP_ERROR_CODE": 5003,
"NOT_IMPLEMENTED_YET": 65535
},
"AUTH_CHANNEL": {
@@ -62,6 +66,6 @@
},
"WS": {
"INTERVAL_TO_PING": 2000,
"WILL_KICK_IF_INACTIVE_FOR": 6000
"WILL_KICK_IF_INACTIVE_FOR": 4000
}
}

View File

@@ -15,10 +15,13 @@ type constants struct {
SmsValidResendPeriodSeconds int `json:"SMS_VALID_RESEND_PERIOD_SECONDS"`
} `json:"PLAYER"`
RetCode struct {
ActiveWatchdog int `json:"ACTIVE_WATCHDOG"`
BattleStopped int `json:"BATTLE_STOPPED"`
Duplicated int `json:"DUPLICATED"`
FailedToCreate int `json:"FAILED_TO_CREATE"`
FailedToDelete int `json:"FAILED_TO_DELETE"`
FailedToUpdate int `json:"FAILED_TO_UPDATE"`
GetSmsCaptchaRespErrorCode int `json:"GET_SMS_CAPTCHA_RESP_ERROR_CODE"`
IncorrectCaptcha int `json:"INCORRECT_CAPTCHA"`
IncorrectHandle int `json:"INCORRECT_HANDLE"`
IncorrectPassword int `json:"INCORRECT_PASSWORD"`
@@ -28,27 +31,26 @@ type constants struct {
InvalidEmailLiteral int `json:"INVALID_EMAIL_LITERAL"`
InvalidRequestParam int `json:"INVALID_REQUEST_PARAM"`
InvalidToken int `json:"INVALID_TOKEN"`
IsTestAcc int `json:"IS_TEST_ACC"`
IsBotAcc int `json:"IS_BOT_ACC"`
IsTestAcc int `json:"IS_TEST_ACC"`
LackOfDiamond int `json:"LACK_OF_DIAMOND"`
LackOfEnergy int `json:"LACK_OF_ENERGY"`
LackOfGold int `json:"LACK_OF_GOLD"`
LocallyNoAvailableRoom int `json:"LOCALLY_NO_AVAILABLE_ROOM"`
LocallyNoSpecifiedRoom int `json:"LOCALLY_NO_SPECIFIED_ROOM"`
MapNotUnlocked int `json:"MAP_NOT_UNLOCKED"`
MysqlError int `json:"MYSQL_ERROR"`
GetSmsCaptchaRespErrorCode int `json:"GET_SMS_CAPTCHA_RESP_ERROR_CODE"`
NewHandleConflict int `json:"NEW_HANDLE_CONFLICT"`
NonexistentAct int `json:"NONEXISTENT_ACT"`
NonexistentActHandler int `json:"NONEXISTENT_ACT_HANDLER"`
LocallyNoAvailableRoom int `json:"LOCALLY_NO_AVAILABLE_ROOM"`
LocallyNoSpecifiedRoom int `json:"LOCALLY_NO_SPECIFIED_ROOM"`
PlayerNotAddableToRoom int `json:"PLAYER_NOT_ADDABLE_TO_ROOM"`
PlayerNotReAddableToRoom int `json:"PLAYER_NOT_READDABLE_TO_ROOM"`
PlayerNotFound int `json:"PLAYER_NOT_FOUND"`
PlayerCheating int `json:"PLAYER_CHEATING"`
NotImplementedYet int `json:"NOT_IMPLEMENTED_YET"`
NoAssociatedEmail int `json:"NO_ASSOCIATED_EMAIL"`
Ok int `json:"OK"`
PasswordResetCodeGenerationPerEmailTooFrequently int `json:"PASSWORD_RESET_CODE_GENERATION_PER_EMAIL_TOO_FREQUENTLY"`
PlayerCheating int `json:"PLAYER_CHEATING"`
PlayerNotAddableToRoom int `json:"PLAYER_NOT_ADDABLE_TO_ROOM"`
PlayerNotFound int `json:"PLAYER_NOT_FOUND"`
PlayerNotReaddableToRoom int `json:"PLAYER_NOT_READDABLE_TO_ROOM"`
SendEmailTimeout int `json:"SEND_EMAIL_TIMEOUT"`
SmsCaptchaNotMatch int `json:"SMS_CAPTCHA_NOT_MATCH"`
SmsCaptchaRequestedTooFrequently int `json:"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY"`

View File

@@ -170,9 +170,10 @@ type Room struct {
LastAllConfirmedInputList []uint64
JoinIndexBooleanArr []bool
BackendDynamicsEnabled bool
LastRenderFrameIdTriggeredAt int64
PlayerDefaultSpeed int32
BackendDynamicsEnabled bool
ForceAllResyncOnAnyActiveSlowTicker bool
LastRenderFrameIdTriggeredAt int64
PlayerDefaultSpeed int32
BulletBattleLocalIdCounter int32
dilutedRollbackEstimatedDtNanos int64
@@ -210,7 +211,7 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
pR.PlayerSignalToCloseDict[playerId] = signalToCloseConnOfThisPlayer
newWatchdog := NewWatchdog(ConstVals.Ws.WillKickIfInactiveFor, func() {
Logger.Warn("Conn inactive watchdog triggered#1:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount))
signalToCloseConnOfThisPlayer(Constants.RetCode.UnknownError, "")
signalToCloseConnOfThisPlayer(Constants.RetCode.ActiveWatchdog, "")
})
newWatchdog.Stop()
pR.PlayerActiveWatchdogDict[playerId] = newWatchdog
@@ -247,7 +248,7 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
pR.PlayerSignalToCloseDict[playerId] = signalToCloseConnOfThisPlayer
pR.PlayerActiveWatchdogDict[playerId] = NewWatchdog(ConstVals.Ws.WillKickIfInactiveFor, func() {
Logger.Warn("Conn inactive watchdog triggered#2:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount))
signalToCloseConnOfThisPlayer(Constants.RetCode.UnknownError, "")
signalToCloseConnOfThisPlayer(Constants.RetCode.ActiveWatchdog, "")
}) // For ReAdded player the new watchdog starts immediately
Logger.Warn("ReAddPlayerIfPossible finished.", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("joinIndex", pEffectiveInRoomPlayerInstance.JoinIndex), zap.Any("playerBattleState", pEffectiveInRoomPlayerInstance.BattleState), zap.Any("roomState", pR.State), zap.Any("roomEffectivePlayerCount", pR.EffectivePlayerCount), zap.Any("AckingFrameId", pEffectiveInRoomPlayerInstance.AckingFrameId), zap.Any("AckingInputFrameId", pEffectiveInRoomPlayerInstance.AckingInputFrameId), zap.Any("LastSentInputFrameId", pEffectiveInRoomPlayerInstance.LastSentInputFrameId))
@@ -379,11 +380,10 @@ func (pR *Room) InputsBufferString(allDetails bool) string {
break
}
f := tmp.(*InputFrameDownsync)
//s = append(s, fmt.Sprintf("{inputFrameId: %v, inputList: %v, &inputList: %p, confirmedList: %v}", f.InputFrameId, f.InputList, &(f.InputList), f.ConfirmedList))
s = append(s, fmt.Sprintf("{inputFrameId: %v, inputList: %v, confirmedList: %v}", f.InputFrameId, f.InputList, f.ConfirmedList))
s = append(s, fmt.Sprintf("{\"inputFrameId\":%d,\"inputList\":%v,\"confirmedList\":\"%d\"}", f.InputFrameId, f.InputList, f.ConfirmedList))
}
return strings.Join(s, "; ")
return strings.Join(s, "\n")
} else {
return fmt.Sprintf("{renderFrameId: %d, stInputFrameId: %d, edInputFrameId: %d, lastAllConfirmedInputFrameIdWithChange: %d, lastAllConfirmedInputFrameId: %d}", pR.RenderFrameId, pR.InputsBuffer.StFrameId, pR.InputsBuffer.EdFrameId, pR.LastAllConfirmedInputFrameIdWithChange, pR.LastAllConfirmedInputFrameId)
}
@@ -771,12 +771,13 @@ func (pR *Room) OnDismissed() {
pR.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for logging FAST FRAME
dilutedServerFps := float64(58.0) // Don't set this value too small, otherwise we might miss force confirmation needs for slow tickers!
pR.dilutedRollbackEstimatedDtNanos = int64(float64(pR.RollbackEstimatedDtNanos) * float64(pR.ServerFps) / dilutedServerFps)
pR.BattleDurationFrames = 120 * pR.ServerFps
pR.BattleDurationFrames = 60 * pR.ServerFps
pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1)
pR.InputFrameUpsyncDelayTolerance = (pR.NstDelayFrames >> pR.InputScaleFrames) - 1 // this value should be strictly smaller than (NstDelayFrames >> InputScaleFrames), otherwise "type#1 forceConfirmation" might become a lag avalanche
pR.MaxChasingRenderFramesPerUpdate = 12 // Don't set this value too high to avoid exhausting frontend CPU within a single frame
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
pR.ForceAllResyncOnAnyActiveSlowTicker = true // See tradeoff discussion in "downsyncToAllPlayers"
punchSkillId := int32(1)
pR.MeleeSkillConfig = make(map[int32]*MeleeBullet, 0)
pR.MeleeSkillConfig[punchSkillId] = &MeleeBullet{
@@ -877,7 +878,7 @@ func (pR *Room) OnPlayerDisconnected(playerId int32) {
default:
atomic.StoreInt32(&(pR.Players[playerId].BattleState), PlayerBattleStateIns.DISCONNECTED)
pR.clearPlayerNetworkSession(playerId) // Still need clear the network session pointers, because "OnPlayerDisconnected" is only triggered from "signalToCloseConnOfThisPlayer" in "ws/serve.go", when the same player reconnects the network session pointers will be re-assigned
Logger.Warn("OnPlayerDisconnected finished:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("playerBattleState", pR.Players[playerId].BattleState), zap.Any("nowRoomBattleState", pR.State), zap.Any("nowRoomEffectivePlayerCount", pR.EffectivePlayerCount))
Logger.Warn("OnPlayerDisconnected finished:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("playerBattleState", pR.Players[playerId].BattleState), zap.Any("nowRoomBattleState", pR.State), zap.Any("nowRoomEffectivePlayerCount", pR.EffectivePlayerCount), zap.Any("now InputsBuffer", pR.InputsBufferString(true)))
}
}
@@ -1063,7 +1064,7 @@ func (pR *Room) shouldPrefabInputFrameDownsync(prevRenderFrameId int32, renderFr
return false, -1
}
func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *InputFrameDownsync {
func (pR *Room) getOrPrefabInputFrameDownsync(inputFrameId int32) *InputFrameDownsync {
/*
[WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked.
@@ -1106,6 +1107,7 @@ func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *InputFrameDownsync
func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*InputFrameUpsync, playerId int32, player *Player) *InputsBufferSnapshot {
// [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked!
// Step#1, put the received "inputFrameUpsyncBatch" into "pR.InputsBuffer"
for _, inputFrameUpsync := range inputFrameUpsyncBatch {
clientInputFrameId := inputFrameUpsync.InputFrameId
if clientInputFrameId < pR.InputsBuffer.StFrameId {
@@ -1121,22 +1123,17 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*InputFrame
Logger.Warn(fmt.Sprintf("Dropping too advanced inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v; is this player cheating?", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
continue
}
var targetInputFrameDownsync *InputFrameDownsync = nil
if clientInputFrameId == pR.InputsBuffer.EdFrameId {
targetInputFrameDownsync = pR.prefabInputFrameDownsync(clientInputFrameId)
Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-Prefabbed new inputFrameDownsync from inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
} else {
targetInputFrameDownsync = pR.InputsBuffer.GetByFrameId(clientInputFrameId).(*InputFrameDownsync)
Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-stuffing inputFrameDownsync from inputFrameUpsync: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
}
// by now "clientInputFrameId <= pR.InputsBuffer.EdFrameId"
targetInputFrameDownsync := pR.getOrPrefabInputFrameDownsync(clientInputFrameId)
targetInputFrameDownsync.InputList[player.JoinIndex-1] = inputFrameUpsync.Encoded
targetInputFrameDownsync.ConfirmedList |= uint64(1 << uint32(player.JoinIndex-1))
if inputFrameUpsync.InputFrameId > pR.LatestPlayerUpsyncedInputFrameId {
pR.LatestPlayerUpsyncedInputFrameId = inputFrameUpsync.InputFrameId
if clientInputFrameId > pR.LatestPlayerUpsyncedInputFrameId {
pR.LatestPlayerUpsyncedInputFrameId = clientInputFrameId
}
}
// Step#2, mark confirmation without forcing
newAllConfirmedCount := int32(0)
inputFrameId1 := pR.LastAllConfirmedInputFrameId + 1
totPlayerCnt := uint32(pR.Capacity)
@@ -1154,32 +1151,32 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*InputFrame
for _, player := range pR.PlayersArr {
thatPlayerBattleState := atomic.LoadInt32(&(player.BattleState))
thatPlayerJoinMask := uint64(1 << uint32(player.JoinIndex-1))
if 0 == (inputFrameDownsync.ConfirmedList & thatPlayerJoinMask) {
if thatPlayerBattleState == PlayerBattleStateIns.ACTIVE {
shouldBreakConfirmation = true // Could be an `ACTIVE SLOW TICKER` here, but no action needed for now
break
} else {
Logger.Debug(fmt.Sprintf("markConfirmationIfApplicable for roomId=%v, skipping UNCONFIRMED BUT INACTIVE player(id:%v, joinIndex:%v) while checking inputFrameId=[%v, %v): InputsBuffer=%v", pR.Id, player.Id, player.JoinIndex, inputFrameId1, pR.InputsBuffer.EdFrameId, pR.InputsBufferString(false)))
}
isSlowTicker := (0 == (inputFrameDownsync.ConfirmedList & thatPlayerJoinMask))
isActiveSlowTicker := (isSlowTicker && thatPlayerBattleState == PlayerBattleStateIns.ACTIVE)
if isActiveSlowTicker {
shouldBreakConfirmation = true // Could be an `ACTIVE SLOW TICKER` here, but no action needed for now
break
}
Logger.Debug(fmt.Sprintf("markConfirmationIfApplicable for roomId=%v, skipping UNCONFIRMED BUT INACTIVE player(id:%v, joinIndex:%v) while checking inputFrameId=[%v, %v): InputsBuffer=%v", pR.Id, player.Id, player.JoinIndex, inputFrameId1, pR.InputsBuffer.EdFrameId, pR.InputsBufferString(false)))
}
}
if shouldBreakConfirmation {
break
} else {
newAllConfirmedCount += 1
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
}
newAllConfirmedCount += 1
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
}
if 0 < newAllConfirmedCount {
/*
[WARNING]
[WARNING]
If "pR.InputsBufferLock" was previously held by "doBattleMainLoopPerTickBackendDynamicsWithProperLocking", then "snapshotStFrameId" would be just (pR.LastAllConfirmedInputFrameId - newAllConfirmedCount).
If "pR.InputsBufferLock" was previously held by "doBattleMainLoopPerTickBackendDynamicsWithProperLocking", then "snapshotStFrameId" would be just (LastAllConfirmedInputFrameId - newAllConfirmedCount).
However if "pR.InputsBufferLock" was previously held by another "OnBattleCmdReceived", "snapshotStFrameId" might be smaller than (pR.LastAllConfirmedInputFrameId - newAllConfirmedCount)!
However if "pR.InputsBufferLock" was previously held by another "OnBattleCmdReceived", the proper value for "snapshotStFrameId" might be smaller than (pR.LastAllConfirmedInputFrameId - newAllConfirmedCount) -- but why? Especially when we've already wrapped this whole function in "InputsBufferLock", the order of "markConfirmationIfApplicable" generated snapshots is preserved for sending, isn't (LastAllConfirmedInputFrameId - newAllConfirmedCount) good enough here?
Unfortunately no, for a reconnected player to get recovered asap (of course with BackendDynamicsEnabled), we put a check of READDED_BATTLE_COLLIDER_ACKED in "downsyncToSinglePlayer" -- which could be called right after "markConfirmationIfApplicable" yet without going through "forceConfirmationIfApplicable" -- and if a READDED_BATTLE_COLLIDER_ACKED player is found there we need a proper "(refRenderFrameId, snapshotStFrameId)" pair for that player!
*/
snapshotStFrameId := (pR.LastAllConfirmedInputFrameId - newAllConfirmedCount)
refRenderFrameIdIfNeeded := pR.CurDynamicsRenderFrameId - 1
@@ -1213,7 +1210,7 @@ func (pR *Room) forceConfirmationIfApplicable(prevRenderFrameId int32) uint64 {
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
}
if 0 < unconfirmedMask {
Logger.Warn(fmt.Sprintf("[type#1 forceConfirmation] For roomId=%d@renderFrameId=%d, curDynamicsRenderFrameId=%d, LatestPlayerUpsyncedInputFrameId:%d, LastAllConfirmedInputFrameId:%d, (pR.NstDelayFrames >> pR.InputScaleFrames):%d, InputFrameUpsyncDelayTolerance:%d, unconfirmedMask=%d; there's a slow ticker suspect, forcing all-confirmation", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LatestPlayerUpsyncedInputFrameId, oldLastAllConfirmedInputFrameId, (pR.NstDelayFrames >> pR.InputScaleFrames), pR.InputFrameUpsyncDelayTolerance, unconfirmedMask))
Logger.Debug(fmt.Sprintf("[type#1 forceConfirmation] For roomId=%d@renderFrameId=%d, curDynamicsRenderFrameId=%d, LatestPlayerUpsyncedInputFrameId:%d, LastAllConfirmedInputFrameId:%d, (pR.NstDelayFrames >> pR.InputScaleFrames):%d, InputFrameUpsyncDelayTolerance:%d, unconfirmedMask=%d; there's a slow ticker suspect, forcing all-confirmation", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LatestPlayerUpsyncedInputFrameId, oldLastAllConfirmedInputFrameId, (pR.NstDelayFrames >> pR.InputScaleFrames), pR.InputFrameUpsyncDelayTolerance, unconfirmedMask))
}
} else {
// Type#2 helps resolve the edge case when all players are disconnected temporarily
@@ -1356,9 +1353,9 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
}
if !characStateAlreadyInAir && characStateIsInterruptWaivable {
thatPlayerInNextFrame.VelY = pR.JumpingInitVelY
// if 1 == currPlayerDownsync.JoinIndex {
// Logger.Info(fmt.Sprintf("playerId=%v, joinIndex=%v jumped at {renderFrame.id: %d, virtualX: %d, virtualY: %d, nextVelX: %d, nextVelY: %d, nextCharacterState=%d, inAir=%v}, delayedInputFrame.id=%d", playerId, joinIndex, currRenderFrame.Id, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, thatPlayerInNextFrame.CharacterState, currPlayerDownsync.InAir, delayedInputFrame.InputFrameId))
// }
if 1 == currPlayerDownsync.JoinIndex {
Logger.Info(fmt.Sprintf("playerId=%v, joinIndex=%v jumped at {renderFrame.id: %d, virtualX: %d, virtualY: %d, nextVelX: %d, nextVelY: %d, nextCharacterState=%d, inAir=%v}, delayedInputFrame.id=%d", playerId, joinIndex, currRenderFrame.Id, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, thatPlayerInNextFrame.CharacterState, currPlayerDownsync.InAir, delayedInputFrame.InputFrameId))
}
}
}
@@ -1619,9 +1616,9 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
if 1 == thatPlayerInNextFrame.JoinIndex {
if currPlayerDownsync.InAir && !thatPlayerInNextFrame.InAir {
//Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d fallStopping#2:\n{nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}\n\tcalculated from <- playerColliderPos=%v, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, RectCenterStr(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y))
Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d fallStopping#2:\n{nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}\n\tcalculated from <- playerColliderPos=%v, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, RectCenterStr(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y))
} else if !currPlayerDownsync.InAir && thatPlayerInNextFrame.InAir {
//Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d took off:\n{nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}\n\tcalculated from <- playerColliderPos=%v, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, RectCenterStr(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y))
Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d took off:\n{nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}\n\tcalculated from <- playerColliderPos=%v, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, RectCenterStr(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y))
} else if thatPlayerInNextFrame.InAir && (0 != thatPlayerInNextFrame.VelY) {
//Logger.Info(fmt.Sprintf("playerId=%d, joinIndex=%d inAir trajectory:\n{nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}\n\tcalculated from <- playerColliderPos=%v, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, RectCenterStr(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y))
}
@@ -1696,7 +1693,7 @@ func (pR *Room) doBattleMainLoopPerTickBackendDynamicsWithProperLocking(prevRend
if ok, thatRenderFrameId := pR.shouldPrefabInputFrameDownsync(prevRenderFrameId, pR.RenderFrameId); ok {
noDelayInputFrameId := pR.ConvertToInputFrameId(thatRenderFrameId, 0)
pR.prefabInputFrameDownsync(noDelayInputFrameId)
pR.getOrPrefabInputFrameDownsync(noDelayInputFrameId)
}
// Force setting all-confirmed of buffered inputFrames periodically, kindly note that if "pR.BackendDynamicsEnabled", what we want to achieve is "recovery upon reconnection", which certainly requires "forceConfirmationIfApplicable" to move "pR.LastAllConfirmedInputFrameId" forward as much as possible
@@ -1767,18 +1764,16 @@ func (pR *Room) downsyncToAllPlayers(inputsBufferSnapshot *InputsBufferSnapshot)
break
}
/*
[WARNING]
[WARNING] There's a tradeoff for setting/unsetting "ForceAllResyncOnAnyActiveSlowTicker" here, if the `ACTIVE SLOW TICKER` doesn't resume for a long period of time, the current approach is to kick it out by "connWatchdog" instead of forcing resync of all players in the same battle all the way along.
There's a tradeoff here, if the `ACTIVE SLOW TICKER` doesn't resume for a long period of time, the current approach is to kick it out by "connWatchdog" instead of forcing resync of all players in the same battle all the way along.
[FIXME]
In practice, I tested in internet environment by toggling player#1 "CPU throttling: 1x -> 4x -> 1x -> 6x -> 1x" and checked the logs of all players which showed that "all received inputFrameIds are consecutive for all players", yet not forcing resync of all players here still result in occasional inconsistent graphics for the `ACTIVE NORMAL TICKER`s.
[FIXME]
In practice, I tested in internet environment by toggling player#1 "CPU throttling: 1x -> 4x -> 1x -> 6x -> 1x" and checked the logs of all players which showed that "all received inputFrameIds are consecutive for all players", yet not forcing resync of all players here still result in occasional inconsistent graphics for the `ACTIVE NORMAL TICKER`s.
More investigation into this issue is needed, it's possible that the inconsistent graphics is just a result of difference of backend/frontend collision calculations, yet before it's totally resolved we'd keep forcing resync here.
More investigation into this issue is needed, it's possible that the inconsistent graphics is just a result of difference of backend/frontend collision calculations, yet before it's totally resolved we'd keep forcing resync here.
*/
thatPlayerJoinMask := uint64(1 << uint32(player.JoinIndex-1))
isActiveSlowTicker := (0 < (thatPlayerJoinMask & inputsBufferSnapshot.UnconfirmedMask)) && (PlayerBattleStateIns.ACTIVE == playerBattleState)
if isActiveSlowTicker {
if pR.ForceAllResyncOnAnyActiveSlowTicker && isActiveSlowTicker {
inputsBufferSnapshot.ShouldForceResync = true
break
}

View File

@@ -373,7 +373,7 @@ func Serve(c *gin.Context) {
// TODO: Is there any potential edge-trigger improvement like the epoll approach mentioned above for the following statement? See discussion in https://github.com/gorilla/websocket/issues/122
_, bytes, err := conn.ReadMessage()
if nil != err {
Logger.Error("About to `signalToCloseConnOfThisPlayer`", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Error(err))
Logger.Error("About to `signalToCloseConnOfThisPlayer` due to conn.ReadMessage err", zap.Any("roomId", pRoom.Id), zap.Any("playerId", playerId), zap.Error(err))
signalToCloseConnOfThisPlayer(Constants.RetCode.UnknownError, "")
return nil
}

View File

@@ -377,10 +377,6 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToP
}
for _, singleObjInTmxFile := range objGroup.Objects {
if nil == singleObjInTmxFile.Properties.Property || "boundary_type" != singleObjInTmxFile.Properties.Property[0].Name || "barrier" != singleObjInTmxFile.Properties.Property[0].Value {
continue
}
if nil == singleObjInTmxFile.Polyline {
pts := make([]*Vec2D, 4)
s := make([]string, 0)

View File

@@ -23,9 +23,9 @@ var constants = {
FILE_NAME: {
TREASURE_PICKEDUP: "TreasurePicked",
CRASHED_BY_TRAP_BULLET: "CrashedByTrapBullet",
HIGH_SCORE_TREASURE_PICKED:"HighScoreTreasurePicked",
COUNT_DOWN_10SEC_TO_END:"countDown10SecToEnd",
BGM: "BGM"
HIGH_SCORE_TREASURE_PICKED: "HighScoreTreasurePicked",
COUNT_DOWN_10SEC_TO_END: "countDown10SecToEnd",
BGM: "BGM"
}
},
ROUTE_PATH: (_ROUTE_PATH = {
@@ -55,39 +55,41 @@ var constants = {
},
RET_CODE: {
/**
* NOTE: The "RET_CODE"s from 1000-1015 are reserved for the websocket "WebsocketStdCloseCode"s.
* NOTE: The "RET_CODE"s from 1000-1015 are reserved for the websocket "WebsocketStdCloseCode"s, custom codes should be between 3000-4999
*
* References
* - https://tools.ietf.org/html/rfc6455#section-7.4
* - https://godoc.org/github.com/gorilla/websocket#pkg-constants.
*/
"__comment__": "基础",
"OK": 9000,
"UNKNOWN_ERROR": 9001,
"INVALID_REQUEST_PARAM": 9002,
"IS_TEST_ACC": 9003,
"MYSQL_ERROR": 9004,
"NONEXISTENT_ACT": 9005,
"LACK_OF_DIAMOND": 9006,
"LACK_OF_GOLD": 9007,
"LACK_OF_ENERGY": 9008,
"NONEXISTENT_ACT_HANDLER": 9009,
"LOCALLY_NO_AVAILABLE_ROOM": 9010,
"LOCALLY_NO_SPECIFIED_ROOM": 9011,
"PLAYER_NOT_ADDABLE_TO_ROOM": 9012,
"PLAYER_NOT_READDABLE_TO_ROOM": 9013,
"PLAYER_NOT_FOUND": 9014,
"PLAYER_CHEATING": 9015,
"__comment__": "Websocket",
"OK": 3000,
"UNKNOWN_ERROR": 3001,
"INVALID_REQUEST_PARAM": 3002,
"IS_TEST_ACC": 3003,
"MYSQL_ERROR": 3004,
"NONEXISTENT_ACT": 3005,
"LACK_OF_DIAMOND": 3006,
"LACK_OF_GOLD": 3007,
"LACK_OF_ENERGY": 3008,
"NONEXISTENT_ACT_HANDLER": 3009,
"LOCALLY_NO_AVAILABLE_ROOM": 3010,
"LOCALLY_NO_SPECIFIED_ROOM": 3011,
"PLAYER_NOT_ADDABLE_TO_ROOM": 3012,
"PLAYER_NOT_READDABLE_TO_ROOM": 3013,
"PLAYER_NOT_FOUND": 3014,
"PLAYER_CHEATING": 3015,
"WECHAT_SERVER_ERROR": 3016,
"IS_BOT_ACC": 3017,
"ACTIVE_WATCHDOG": 3018,
"BATTLE_STOPPED": 3019,
"CLIENT_MISMATCHED_RENDER_FRAME": 3020,
"__comment__": "SMS",
"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY": 5001,
"SMS_CAPTCHA_NOT_MATCH": 5002,
"__comment__": "OTHERS",
"INVALID_TOKEN": 2001,
"DUPLICATED": 2002,
"INCORRECT_HANDLE": 2004,
"NONEXISTENT_HANDLE": 2005,
"INCORRECT_PASSWORD": 2006,
"INCORRECT_CAPTCHA": 2007,
"INVALID_EMAIL_LITERAL": 2008,
@@ -99,9 +101,14 @@ var constants = {
"FAILED_TO_DELETE": 2015,
"FAILED_TO_CREATE": 2016,
"INCORRECT_PHONE_NUMBER": 2018,
"PASSWORD_RESET_CODE_GENERATION_PER_EMAIL_TOO_FREQUENTLY": 4000,
"TRADE_CREATION_TOO_FREQUENTLY": 4002,
"MAP_NOT_UNLOCKED": 4003,
"INSUFFICIENT_MEM_TO_ALLOCATE_CONNECTION": 2019,
"PASSWORD_RESET_CODE_GENERATION_PER_EMAIL_TOO_FREQUENTLY": 2020,
"TRADE_CREATION_TOO_FREQUENTLY": 2021,
"MAP_NOT_UNLOCKED": 2022,
"GET_SMS_CAPTCHA_RESP_ERROR_CODE": 2023,
"SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY": 2024,
"SMS_CAPTCHA_NOT_MATCH": 2025,
"NOT_IMPLEMENTED_YET": 65535
},

View File

@@ -9,10 +9,10 @@
</data>
</layer>
<objectgroup id="1" name="PlayerStartingPos">
<object id="135" x="1290" y="1401.33">
<object id="135" x="999" y="1608">
<point/>
</object>
<object id="137" x="1648.33" y="1430.67">
<object id="137" x="875" y="1450">
<point/>
</object>
</objectgroup>
@@ -25,7 +25,7 @@
<property name="boundary_type" value="barrier"/>
</properties>
</object>
<object id="55" x="735" y="1552" width="113.5" height="15.5">
<object id="55" x="736" y="1552" width="112" height="16">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
@@ -80,12 +80,12 @@
<property name="boundary_type" value="barrier"/>
</properties>
</object>
<object id="73" x="783.75" y="1567.5" width="96.25" height="15.5">
<object id="73" x="784" y="1568" width="96" height="16">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>
</object>
<object id="74" x="815.75" y="1583.75" width="96.25" height="15.5">
<object id="74" x="816" y="1584" width="96" height="16">
<properties>
<property name="boundary_type" value="barrier"/>
</properties>

View File

@@ -440,7 +440,7 @@
"array": [
0,
0,
216.05530045313827,
210.4441731196186,
0,
0,
0,

View File

@@ -460,7 +460,7 @@
"array": [
0,
0,
216.05530045313827,
216.50635094610968,
0,
0,
0,

View File

@@ -11,6 +11,7 @@ window.ALL_MAP_STATES = {
};
window.ALL_BATTLE_STATES = {
NONE: -1,
WAITING: 0,
IN_BATTLE: 1,
IN_SETTLEMENT: 2,
@@ -98,6 +99,9 @@ cc.Class({
bulletTriggerEnabled: {
default: false
},
closeOnForcedtoResyncNotSelf: {
default: true
},
},
_inputFrameIdDebuggable(inputFrameId) {
@@ -121,7 +125,8 @@ cc.Class({
return (confirmedList + 1) == (1 << this.playerRichInfoDict.size);
},
_generateInputFrameUpsync(inputFrameId) {
getOrPrefabInputFrameUpsync(inputFrameId) {
// TODO: find some kind of synchronization mechanism against "onInputFrameDownsyncBatch"!
const self = this;
if (
null == self.ctrl ||
@@ -133,28 +138,27 @@ cc.Class({
let previousSelfInput = null,
currSelfInput = null;
const joinIndex = self.selfPlayerInfo.joinIndex;
// [WARNING] The while-loop here handles a situation where the "resync rdf & accompaniedInputFrameDownsyncBatch" mismatched and we have to predict some "gap-inputFrames"!
while (self.recentInputCache.edFrameId <= inputFrameId) {
// TODO: find some kind of synchronization mechanism against "onInputFrameDownsyncBatch"!
const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId - 1);
previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]);
const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId);
const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId - 1);
previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]);
if (null != existingInputFrame) {
// This could happen upon either [type#1] or [type#2] forceConfirmation, where "refRenderFrame" is accompanied by some "inputFrameDownsyncs". The check here also guarantees that we don't override history
console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache: recentInputCache=${self._stringifyRecentInputCache(false)}`);
return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]];
}
// If "forceConfirmation" is active on backend, there's a chance that the already downsynced "inputFrameDownsync"s are ahead of a locally generating inputFrameId, in this case we respect the downsynced one.
const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId);
if (null != existingInputFrame && self._allConfirmed(existingInputFrame.confirmedList)) {
console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache and is all-confirmed: recentInputCache=${self._stringifyRecentInputCache(false)}`);
return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]];
}
const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice());
currSelfInput = self.ctrl.getEncodedInput();
prefabbedInputList[(joinIndex - 1)] = currSelfInput;
const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice());
currSelfInput = self.ctrl.getEncodedInput(); // When "null == existingInputFrame", it'd be safe to say that the realtime "self.ctrl.getEncodedInput()" is for the requested "inputFrameId"
prefabbedInputList[(joinIndex - 1)] = currSelfInput;
while (self.recentInputCache.edFrameId <= inputFrameId) {
// Fill the gap
const prefabbedInputFrameDownsync = window.pb.protos.InputFrameDownsync.create({
inputFrameId: self.recentInputCache.edFrameId,
inputList: prefabbedInputList,
confirmedList: (1 << (self.selfPlayerInfo.joinIndex - 1))
});
self.recentInputCache.put(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
self.recentInputCache.put(prefabbedInputFrameDownsync);
}
return [previousSelfInput, currSelfInput];
@@ -200,7 +204,6 @@ cc.Class({
playerId: self.selfPlayerInfo.id,
act: window.UPSYNC_MSG_ACT_PLAYER_CMD,
joinIndex: self.selfPlayerInfo.joinIndex,
ackingFrameId: self.lastAllConfirmedRenderFrameId,
ackingInputFrameId: self.lastAllConfirmedInputFrameId,
inputFrameUpsyncBatch: inputFrameUpsyncBatch,
}).finish();
@@ -222,15 +225,19 @@ cc.Class({
onDestroy() {
const self = this;
console.warn("+++++++ Map onDestroy()");
if (null == self.battleState || ALL_BATTLE_STATES.WAITING == self.battleState) {
if (null == self.battleState || ALL_BATTLE_STATES.IN_BATTLE != self.battleState) {
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
}
if (null != window.handleBattleColliderInfo) {
window.handleBattleColliderInfo = null;
}
if (null != window.handleClientSessionError) {
window.handleClientSessionError = null;
}
},
onManualRejoinRequired(labelString) {
const self = this;
self.battleState = ALL_BATTLE_STATES.NONE; // Effectively stops "update(dt)"
self.showPopupInCanvas(self.gameRuleNode);
self.popupSimplePressToGo(labelString, false);
},
popupSimplePressToGo(labelString, hideYesButton) {
@@ -288,10 +295,9 @@ cc.Class({
self.renderFrameId = 0; // After battle started
self.bulletBattleLocalIdCounter = 0;
self.lastAllConfirmedRenderFrameId = -1;
self.lastAllConfirmedInputFrameId = -1;
self.lastUpsyncInputFrameId = -1;
self.chaserRenderFrameId = -1; // at any moment, "lastAllConfirmedRenderFrameId <= chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "onInputFrameDownsyncBatch"
self.chaserRenderFrameId = -1; // at any moment, "chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "onInputFrameDownsyncBatch"
self.recentRenderCache = new RingBuffer(self.renderCacheSize);
@@ -310,6 +316,8 @@ cc.Class({
self.battleState = ALL_BATTLE_STATES.WAITING;
self.othersForcedDownsyncRenderFrameDict = new Map();
self.countdownNanos = null;
if (self.countdownLabel) {
self.countdownLabel.string = "";
@@ -334,16 +342,6 @@ cc.Class({
self.showCriticalCoordinateLabels = false;
console.warn("+++++++ Map onLoad()");
window.handleClientSessionError = function() {
console.warn('+++++++ Common handleClientSessionError()');
if (ALL_BATTLE_STATES.IN_SETTLEMENT == self.battleState) {
console.log("Battled ended by settlement");
} else {
console.warn("Connection lost, going back to login page");
window.clearLocalStorageAndBackToLoginScene(true);
}
};
const mapNode = self.node;
const canvasNode = mapNode.parent;
@@ -579,6 +577,7 @@ cc.Class({
onRoomDownsyncFrame(rdf, accompaniedInputFrameDownsyncBatch) {
// This function is also applicable to "re-joining".
const self = window.mapIns;
self.onInputFrameDownsyncBatch(accompaniedInputFrameDownsyncBatch); // Important to do this step before setting IN_BATTLE
if (!self.recentRenderCache) {
return;
}
@@ -586,17 +585,27 @@ cc.Class({
return;
}
const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id);
const shouldForceDumping2 = (rdf.id > self.renderFrameId + self.renderFrameIdLagTolerance);
const shouldForceResync = rdf.shouldForceResync;
let shouldForceDumping2 = (rdf.id >= self.renderFrameId + self.renderFrameIdLagTolerance);
let shouldForceResync = rdf.shouldForceResync;
const notSelfUnconfirmed = (0 == (rdf.backendUnconfirmedMask & (1 << (self.selfPlayerInfo.joinIndex - 1))));
if (notSelfUnconfirmed) {
shouldForceDumping2 = false;
shouldForceResync = false;
self.othersForcedDownsyncRenderFrameDict.set(rdf.id, rdf);
}
/*
TODO
If "BackendUnconfirmedMask" is non-all-1 and contains the current player, show a label/button to hint manual reconnection. Note that the continuity of "recentInputCache" is not a good indicator, because due to network delay upon a [type#1 forceConfirmation] a player might just lag in upsync networking and have all consecutive inputFrameIds locally.
*/
const [dumpRenderCacheRet, oldStRenderFrameId, oldEdRenderFrameId] = (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) ? self.recentRenderCache.setByFrameId(rdf, rdf.id) : [window.RING_BUFF_CONSECUTIVE_SET, null, null];
if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) {
throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.id=${rdf.id}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.id=${rdf.id}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
if (!shouldForceResync && (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet)) {
/*
Don't change
- lastAllConfirmedRenderFrameId, it's updated only in "rollbackAndChase" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
- chaserRenderFrameId, it's updated only in "rollbackAndChase & onInputFrameDownsyncBatch" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
*/
return dumpRenderCacheRet;
@@ -614,21 +623,19 @@ cc.Class({
}
}
if (null == self.renderFrameId || self.renderFrameId <= rdf.id || shouldForceResync) {
if (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) {
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id) {
console.log('On battle started! renderFrameId=', rdf.id);
} else {
self.hideFindingPlayersGUI(rdf);
self.onInputFrameDownsyncBatch(accompaniedInputFrameDownsyncBatch); // Important to do this step before setting IN_BATTLE
console.warn(`Got resync@localRenderFrameId=${self.renderFrameId} -> rdf.id=${rdf.id} & rdf.backendUnconfirmedMask=${rdf.backendUnconfirmedMask}, @lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${self.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}`);
console.warn(`Got resync@localRenderFrameId=${self.renderFrameId} -> rdf.id=${rdf.id} & rdf.backendUnconfirmedMask=${rdf.backendUnconfirmedMask}, @lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${self.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}`);
}
self.renderFrameId = rdf.id;
self.lastRenderFrameIdTriggeredAt = performance.now();
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
self.lastAllConfirmedRenderFrameId = rdf.id;
// In this case it must be true that "rdf.id > chaserRenderFrameId".
self.chaserRenderFrameId = rdf.id;
const candidateLastAllConfirmedInputFrame = self._convertToInputFrameId(rdf.id - 1, self.inputDelayFrames);
if (self.lastAllConfirmedInputFrame < candidateLastAllConfirmedInputFrame) {
@@ -666,8 +673,45 @@ cc.Class({
return true;
},
equalPlayers(lhs, rhs) {
if (null == lhs || null == rhs) return false;
if (lhs.virtualGridX != rhs.virtualGridX) return false;
if (lhs.virtualGridY != rhs.virtualGridY) return false;
if (lhs.dirX != rhs.dirX) return false;
if (lhs.dirY != rhs.dirY) return false;
if (lhs.velX != rhs.velX) return false;
if (lhs.velY != rhs.velY) return false;
if (lhs.speed != rhs.speed) return false;
if (lhs.framesToRecover != rhs.framesToRecover) return false;
if (lhs.hp != rhs.hp) return false;
if (lhs.maxHp != rhs.maxHp) return false;
if (lhs.characterState != rhs.characterState) return false;
if (lhs.inAir != rhs.inAir) return false;
return true;
},
equalMeleeBullets(lhs, rhs) {
if (null == lhs || null == rhs) return false;
if (lhs.battleLocalId != rhs.battleLocalId) return false;
if (lhs.offenderPlayerId != rhs.offenderPlayerId) return false;
if (lhs.offenderJoinIndex != rhs.offenderJoinIndex) return false;
if (lhs.originatedRenderFrameId != rhs.originatedRenderFrameId) return false;
return true;
},
equalRoomDownsyncFrames(lhs, rhs) {
if (null == lhs || null == rhs) return false;
for (let k in lhs.players) {
if (!this.equalPlayers(lhs.players[k], rhs.players[k])) return false;
}
for (let k in lhs.meleeBullets) {
if (!this.equalMeleeBullets(lhs.meleeBullets[k], rhs.meleeBullets[k])) return false;
}
return true;
},
onInputFrameDownsyncBatch(batch) {
// TODO: find some kind of synchronization mechanism against "_generateInputFrameUpsync"!
// TODO: find some kind of synchronization mechanism against "getOrPrefabInputFrameUpsync"!
const self = this;
if (!self.recentInputCache) {
return;
@@ -680,9 +724,10 @@ cc.Class({
for (let k in batch) {
const inputFrameDownsync = batch[k];
const inputFrameDownsyncId = inputFrameDownsync.inputFrameId;
if (inputFrameDownsyncId < self.lastAllConfirmedInputFrameId) {
if (inputFrameDownsyncId <= self.lastAllConfirmedInputFrameId) {
continue;
}
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId);
if (null != localInputFrame
@@ -693,11 +738,10 @@ cc.Class({
) {
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
}
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1;
const [ret, oldStFrameId, oldEdFrameId] = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId);
if (window.RING_BUFF_FAILED_TO_SET == ret) {
throw `Failed to dump input cache (maybe recentInputCache too small)! inputFrameDownsync.inputFrameId=${inputFrameDownsync.inputFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
throw `Failed to dump input cache (maybe recentInputCache too small)! inputFrameDownsync.inputFrameId=${inputFrameDownsync.inputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
}
@@ -711,8 +755,6 @@ cc.Class({
/*
A typical case is as follows.
--------------------------------------------------------
[self.lastAllConfirmedRenderFrameId] : 22
<renderFrameId1> : 36
@@ -722,7 +764,8 @@ cc.Class({
--------------------------------------------------------
*/
// The actual rollback-and-chase would later be executed in update(dt).
console.warn(`Mismatched input detected, resetting chaserRenderFrameId: ${self.chaserRenderFrameId}->${renderFrameId1} by firstPredictedYetIncorrectInputFrameId: ${inputFrameId1}`);
console.warn(`Mismatched input detected, resetting chaserRenderFrameId: ${self.chaserRenderFrameId}->${renderFrameId1} by firstPredictedYetIncorrectInputFrameId: ${inputFrameId1}
lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`);
self.chaserRenderFrameId = renderFrameId1;
},
@@ -739,7 +782,7 @@ cc.Class({
logBattleStats() {
const self = this;
let s = [];
s.push(`Battle stats: renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
s.push(`Battle stats: renderFrameId=${self.renderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) {
const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
@@ -754,6 +797,8 @@ cc.Class({
if (ALL_BATTLE_STATES.IN_BATTLE != self.battleState) {
return;
}
window.closeWSConnection(constants.RET_CODE.BATTLE_STOPPED);
self.battleState = ALL_BATTLE_STATES.IN_SETTLEMENT;
self.countdownNanos = null;
self.logBattleStats();
if (self.musicEffectManagerScriptIns) {
@@ -764,7 +809,6 @@ cc.Class({
const resultPanelScriptIns = resultPanelNode.getComponent("ResultPanel");
resultPanelScriptIns.showPlayerInfo(self.playerRichInfoDict);
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
self.battleState = ALL_BATTLE_STATES.IN_SETTLEMENT;
self.showPopupInCanvas(resultPanelNode);
// Clear player info
@@ -827,7 +871,7 @@ cc.Class({
currSelfInput = null;
const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here
if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) {
[prevSelfInput, currSelfInput] = self._generateInputFrameUpsync(noDelayInputFrameId);
[prevSelfInput, currSelfInput] = self.getOrPrefabInputFrameUpsync(noDelayInputFrameId);
}
let t0 = performance.now();
@@ -843,11 +887,16 @@ cc.Class({
if (nextChaserRenderFrameId > self.renderFrameId) {
nextChaserRenderFrameId = self.renderFrameId;
}
self.rollbackAndChase(prevChaserRenderFrameId, nextChaserRenderFrameId, self.collisionSys, self.collisionSysMap, true);
if (prevChaserRenderFrameId < nextChaserRenderFrameId) {
// Do not execute "rollbackAndChase" when "prevChaserRenderFrameId == nextChaserRenderFrameId", otherwise if "nextChaserRenderFrameId == self.renderFrameId" we'd be wasting computing power once.
self.rollbackAndChase(prevChaserRenderFrameId, nextChaserRenderFrameId, self.collisionSys, self.collisionSysMap, true);
}
let t2 = performance.now();
// Inside the following "self.rollbackAndChase" actually ROLLS FORWARD w.r.t. the corresponding delayedInputFrame, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now.
const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
const latestRdfResults = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
let prevRdf = latestRdfResults[0],
rdf = latestRdfResults[1];
/*
const nonTrivialChaseEnded = (prevChaserRenderFrameId < nextChaserRenderFrameId && nextChaserRenderFrameId == self.renderFrameId);
if (nonTrivialChaseEnded) {
@@ -855,6 +904,20 @@ cc.Class({
}
*/
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
if (self.othersForcedDownsyncRenderFrameDict.has(rdf.id)) {
const delayedInputFrameId = self._convertToInputFrameId(rdf.id, 0);
const othersForcedDownsyncRenderFrame = self.othersForcedDownsyncRenderFrameDict.get(rdf.id);
if (self.lastAllConfirmedInputFrameId >= delayedInputFrameId && !self.equalRoomDownsyncFrames(othersForcedDownsyncRenderFrame, rdf)) {
console.warn(`Mismatched render frame@rdf.id=${rdf.id} w/ inputFrameId=${delayedInputFrameId}, @lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${self.chaserRenderFrameId}:
rdf=${JSON.stringify(rdf)}
othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame)}
recentRenderCache=${self._stringifyRecentRenderCache(true)}
recentInputCache=${self._stringifyRecentInputCache(true)}`);
closeWSConnection(constants.RET_CODE.CLIENT_MISMATCHED_RENDER_FRAME, "");
self.onManualRejoinRequired("[DEBUG] CLIENT_MISMATCHED_RENDER_FRAME");
rdf = othersForcedDownsyncRenderFrame;
}
}
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
self.showDebugBoundaries(rdf);
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
@@ -940,6 +1003,7 @@ cc.Class({
showPopupInCanvas(toShowNode) {
const self = this;
toShowNode.active = true;
self.disableInputControls();
self.transitToState(ALL_MAP_STATES.SHOWING_MODAL_POPUP);
safelyAddChild(self.widgetsAboveAllNode, toShowNode);
@@ -1077,11 +1141,13 @@ cc.Class({
getCachedInputFrameDownsyncWithPrediction(inputFrameId) {
const self = this;
const inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId);
const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId);
if (null != inputFrameDownsync && null != lastAllConfirmedInputFrame && inputFrameId > self.lastAllConfirmedInputFrameId) {
for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) {
if (i == (self.selfPlayerInfo.joinIndex - 1)) continue;
inputFrameDownsync.inputList[i] = (lastAllConfirmedInputFrame.inputList[i] & 15); // Don't predict attack input!
if (null != inputFrameDownsync && inputFrameId > self.lastAllConfirmedInputFrameId) {
const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId);
if (null != lastAllConfirmedInputFrame) {
for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) {
if (i == (self.selfPlayerInfo.joinIndex - 1)) continue;
inputFrameDownsync.inputList[i] = (lastAllConfirmedInputFrame.inputList[i] & 15); // Don't predict attack input!
}
}
}
@@ -1322,9 +1388,11 @@ cc.Class({
if (1 == joinIndex) {
if (fallStopping) {
/*
console.info(`playerId=${playerId}, joinIndex=${thatPlayerInNextFrame.joinIndex} fallStopping#1:
{renderFrame.id: ${currRenderFrame.id}, possiblyFallStoppedOnAnotherPlayer: ${possiblyFallStoppedOnAnotherPlayer}}
playerColliderPos=${self.stringifyColliderCenterInWorld(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding)}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}, overlayMag=${result.overlap.toFixed(4)}`);
*/
} else if (currPlayerDownsync.inAir && isBarrier && !landedOnGravityPushback) {
/*
console.warn(`playerId=${playerId}, joinIndex=${currPlayerDownsync.joinIndex} inAir & pushed back by barrier & not landed:
@@ -1456,39 +1524,28 @@ playerColliderPos=${self.stringifyColliderCenterInWorld(playerCollider, halfColl
const self = this;
let prevLatestRdf = null,
latestRdf = null;
for (let i = renderFrameIdSt; i < renderFrameIdEd; i++) {
latestRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
if (null == latestRdf) {
console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`);
return [prevLatestRdf, latestRdf];
const currRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing" and using Firefox, this function could be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
if (null == currRdf) {
throw `Couldn't find renderFrame for i=${i} to rollback (are you using Firefox?), self.renderFrameId=${self.renderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`;
}
const j = self._convertToInputFrameId(i, self.inputDelayFrames);
const delayedInputFrame = self.getCachedInputFrameDownsyncWithPrediction(j);
const delayedInputFrame = self.recentInputCache.getByFrameId(j); // Don't make prediction here, the inputFrameDownsyncs in recentInputCache was already predicted while prefabbing
if (null == delayedInputFrame) {
// Shouldn't happen!
throw `Failed to get cached delayedInputFrame for i=${i}, j=${j}, renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
prevLatestRdf = latestRdf;
latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, prevLatestRdf, collisionSys, collisionSysMap);
if (
self._allConfirmed(delayedInputFrame.confirmedList)
&&
latestRdf.id > self.lastAllConfirmedRenderFrameId
) {
// We got a more up-to-date "all-confirmed-render-frame".
self.lastAllConfirmedRenderFrameId = latestRdf.id;
if (latestRdf.id > self.chaserRenderFrameId) {
// it must be true that "chaserRenderFrameId >= lastAllConfirmedRenderFrameId", regardeless of the "isChasing" param
self.chaserRenderFrameId = latestRdf.id;
}
throw `Failed to get cached delayedInputFrame for i=${i}, j=${j}, renderFrameId=${self.renderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
const nextRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRdf, collisionSys, collisionSysMap);
if (true == isChasing) {
// Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
self.chaserRenderFrameId = latestRdf.id;
// [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic!
self.chaserRenderFrameId = nextRdf.id;
} else if (nextRdf.id == self.chaserRenderFrameId + 1) {
self.chaserRenderFrameId = nextRdf.id; // To avoid redundant calculation
}
self.recentRenderCache.setByFrameId(latestRdf, latestRdf.id);
self.recentRenderCache.setByFrameId(nextRdf, nextRdf.id);
prevLatestRdf = currRdf;
latestRdf = nextRdf;
}
return [prevLatestRdf, latestRdf];

View File

@@ -116,31 +116,28 @@ TileCollisionManager.prototype.extractBoundaryObjects = function(withTiledMapNod
const allObjectGroups = tiledMapIns.getObjectGroups();
for (let i = 0; i < allObjectGroups.length; ++i) {
var objectGroup = allObjectGroups[i];
if ("PlayerStartingPos" == objectGroup.getGroupName()) {
var allObjects = objectGroup.getObjects();
for (let j = 0; j < allObjects.length; ++j) {
const cccMaskedX = allObjects[j].x,
cccMaskedY = allObjects[j].y;
const origX = cccMaskedX,
origY = withTiledMapNode.getContentSize().height - cccMaskedY; // FIXME: I don't know why CocosCreator did this, it's stupid and MIGHT NOT WORK IN ISOMETRIC orientation!
let wpos = this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, cc.v2(origX, origY));
toRet.playerStartingPositions.push(wpos);
}
continue;
}
if ("barrier_and_shelter" != objectGroup.getProperty("type")) continue;
var allObjects = objectGroup.getObjects();
for (let j = 0; j < allObjects.length; ++j) {
let object = allObjects[j];
let gid = object.gid;
if (0 < gid) {
continue;
}
const boundaryType = object.boundary_type;
let toPushBarrier = [];
toPushBarrier.boundaryType = boundaryType;
switch (boundaryType) {
case "barrier":
switch (objectGroup.getGroupName()) {
case "PlayerStartingPos":
for (let j = 0; j < allObjects.length; ++j) {
const cccMaskedX = allObjects[j].x,
cccMaskedY = allObjects[j].y;
const origX = cccMaskedX,
origY = withTiledMapNode.getContentSize().height - cccMaskedY; // FIXME: I don't know why CocosCreator did this, it's stupid and MIGHT NOT WORK IN ISOMETRIC orientation!
let wpos = this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, cc.v2(origX, origY));
toRet.playerStartingPositions.push(wpos);
}
break;
case "Barrier":
for (let j = 0; j < allObjects.length; ++j) {
let object = allObjects[j];
let gid = object.gid;
if (0 < gid) {
continue;
}
const boundaryType = object.boundary_type;
let toPushBarrier = [];
toPushBarrier.boundaryType = boundaryType;
let polylinePoints = object.polylinePoints;
if (null == polylinePoints) {
polylinePoints = [{
@@ -163,10 +160,8 @@ TileCollisionManager.prototype.extractBoundaryObjects = function(withTiledMapNod
}
toPushBarrier.anchor = this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, object.offset); // DON'T use "(object.x, object.y)" which are wrong/meaningless!
toRet.barriers.push(toPushBarrier);
break;
default:
break;
}
}
break;
}
}

View File

@@ -12,7 +12,6 @@ window.DOWNSYNC_MSG_ACT_INPUT_BATCH = 2;
window.DOWNSYNC_MSG_ACT_BATTLE_STOPPED = 3;
window.DOWNSYNC_MSG_ACT_FORCED_RESYNC = 4;
window.sendSafely = function(msgStr) {
/**
* - "If the data can't be sent (for example, because it needs to be buffered but the buffer is full), the socket is closed automatically."
@@ -28,10 +27,13 @@ window.sendUint8AsBase64Safely = function(msgUint8Arr) {
window.clientSession.send(_uint8ToBase64(msgUint8Arr));
}
window.closeWSConnection = function() {
if (null == window.clientSession || window.clientSession.readyState != WebSocket.OPEN) return;
window.closeWSConnection = function(code, reason) {
if (null == window.clientSession || window.clientSession.readyState != WebSocket.OPEN) {
console.log(`"window.clientSession" is already closed or destroyed.`);
return;
}
console.log(`Closing "window.clientSession" from the client-side.`);
window.clientSession.close();
window.clientSession.close(code, reason);
}
window.getBoundRoomIdFromPersistentStorage = function() {
@@ -144,7 +146,7 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
if (null == evt || null == evt.data) {
return;
}
// FIXME: In practice, it seems like the thread invoking "onmessage" could be different from "Map.update(dt)", which makes it necessary to guard "recentRenderCache & recentInputCache" for "_generateInputFrameUpsync & rollbackAndChase & onRoomDownsyncFrame & onInputFrameDownsyncBatch" to avoid mysterious RAM contamination, but there's no explicit mutex in JavaScript for browsers.
// FIXME: In practice, it seems like the thread invoking "onmessage" could be different from "Map.update(dt)", which makes it necessary to guard "recentRenderCache & recentInputCache" for "getOrPrefabInputFrameUpsync & rollbackAndChase & onRoomDownsyncFrame & onInputFrameDownsyncBatch" to avoid mysterious RAM contamination, but there's no explicit mutex in JavaScript for browsers -- this issue is found in Firefox (108.0.1, 64-bit, Windows 11), but not in Chrome (108.0.5359.125, Official Build, 64-bit, Windows 11) -- just breakpoint in "Map.rollbackAndChase" then see whether the logs of "onmessage" can still be printed and whether the values of "recentRenderCache & recentInputCache" change in console).
try {
const resp = window.pb.protos.WsResp.decode(new Uint8Array(evt.data));
//console.log(`Got non-empty onmessage decoded: resp.act=${resp.act}`);
@@ -184,39 +186,37 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
clientSession.onerror = function(evt) {
console.error("Error caught on the WS clientSession: ", evt);
if (window.handleClientSessionError) {
window.handleClientSessionError();
}
window.clearLocalStorageAndBackToLoginScene(true);
};
clientSession.onclose = function(evt) {
// [WARNING] The callback "onclose" might be called AFTER the webpage is refreshed with "1001 == evt.code".
console.warn("The WS clientSession is closed: ", evt, clientSession);
if (false == evt.wasClean) {
/*
Chrome doesn't allow the use of "CustomCloseCode"s (yet) and will callback with a "WebsocketStdCloseCode 1006" and "false == evt.wasClean" here. See https://tools.ietf.org/html/rfc6455#section-7.4 for more information.
*/
if (window.handleClientSessionError) {
window.handleClientSessionError();
}
} else {
switch (evt.code) {
case constants.RET_CODE.PLAYER_NOT_ADDABLE_TO_ROOM:
case constants.RET_CODE.PLAYER_NOT_READDABLE_TO_ROOM:
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
break;
case constants.RET_CODE.UNKNOWN_ERROR:
case constants.RET_CODE.MYSQL_ERROR:
case constants.RET_CODE.PLAYER_NOT_FOUND:
case constants.RET_CODE.PLAYER_CHEATING:
case 1006: // Peer(i.e. the backend) gone unexpectedly
if (window.handleClientSessionError) {
window.handleClientSessionError();
}
break;
default:
break;
}
console.warn(`The WS clientSession is closed: evt=${JSON.stringify(evt)}, evt.code=${evt.code}`);
switch (evt.code) {
case constants.RET_CODE.CLIENT_MISMATCHED_RENDER_FRAME:
break;
case constants.RET_CODE.BATTLE_STOPPED:
// deliberately do nothing
break;
case constants.RET_CODE.PLAYER_NOT_ADDABLE_TO_ROOM:
case constants.RET_CODE.PLAYER_NOT_READDABLE_TO_ROOM:
window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); // To favor the player to join other rooms
mapIns.onManualRejoinRequired("Couldn't join any room at the moment, please retry");
break;
case constants.RET_CODE.ACTIVE_WATCHDOG:
mapIns.onManualRejoinRequired("Disconnected due to long-time inactivity, please rejoin");
break;
case constants.RET_CODE.UNKNOWN_ERROR:
case constants.RET_CODE.MYSQL_ERROR:
case constants.RET_CODE.PLAYER_NOT_FOUND:
case constants.RET_CODE.PLAYER_CHEATING:
case 1006: // Peer(i.e. the backend) gone unexpectedly
console.warn(`recentRenderCache=${mapIns._stringifyRecentRenderCache(true)}
recentInputCache=${mapIns._stringifyRecentInputCache(true)}`);
window.clearLocalStorageAndBackToLoginScene(true);
break;
default:
break;
}
};
};
@@ -227,19 +227,7 @@ window.clearLocalStorageAndBackToLoginScene = function(shouldRetainBoundRoomIdIn
if (window.mapIns && window.mapIns.musicEffectManagerScriptIns) {
window.mapIns.musicEffectManagerScriptIns.stopAllMusic();
}
/**
* Here I deliberately removed the callback in the "common `handleClientSessionError` callback"
* within which another invocation to `clearLocalStorageAndBackToLoginScene` will be made.
*
* It'll be re-assigned to the common one upon reentrance of `Map.onLoad`.
*
* -- YFLu 2019-04-06
*/
window.handleClientSessionError = () => {
console.warn("+++++++ Special handleClientSessionError() assigned within `clearLocalStorageAndBackToLoginScene`");
// TBD.
window.handleClientSessionError = null; // To ensure that it's called at most once.
};
window.closeWSConnection();
window.clearSelfPlayer();
if (true != shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage) {