diff --git a/battle_srv/models/room.go b/battle_srv/models/room.go index 5df0f3c..2faba1b 100644 --- a/battle_srv/models/room.go +++ b/battle_srv/models/room.go @@ -188,6 +188,7 @@ type Room struct { StageTileH int32 RawBattleStrToVec2DListMap StrToVec2DListMap RawBattleStrToPolygon2DListMap StrToPolygon2DListMap + BackendDynamicsEnabled bool } const ( @@ -459,17 +460,12 @@ func (pR *Room) StartBattle() { } // Force setting all-confirmed of buffered inputFrames periodically - unconfirmedMask := pR.forceConfirmationIfApplicable() - - dynamicsDuration := int64(0) - if 0 <= pR.LastAllConfirmedInputFrameId { - dynamicsStartedAt := utils.UnixtimeNano() - // Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId" - nextDynamicsRenderFrameId := pR.ConvertToLastUsedRenderFrameId(pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames) - Logger.Debug(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, LastAllConfirmedInputFrameId=%v, InputDelayFrames=%v, nextDynamicsRenderFrameId=%v", pR.Id, pR.RenderFrameId, pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames, nextDynamicsRenderFrameId)) - pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId) - dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt - } + unconfirmedMask := uint64(0) + if pR.BackendDynamicsEnabled { + unconfirmedMask = pR.forceConfirmationIfApplicable() + } else { + pR.markConfirmationIfApplicable() + } upperToSendInputFrameId := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameId)) /* @@ -481,13 +477,26 @@ func (pR *Room) StartBattle() { 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 { refRenderFrameId = pR.RenderFrameId } - if refRenderFrameId > pR.CurDynamicsRenderFrameId { - refRenderFrameId = pR.CurDynamicsRenderFrameId - } + + dynamicsDuration := int64(0) + if pR.BackendDynamicsEnabled { + if 0 <= pR.LastAllConfirmedInputFrameId { + dynamicsStartedAt := utils.UnixtimeNano() + // Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId" + nextDynamicsRenderFrameId := pR.ConvertToLastUsedRenderFrameId(pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames) + Logger.Debug(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, LastAllConfirmedInputFrameId=%v, InputDelayFrames=%v, nextDynamicsRenderFrameId=%v", pR.Id, pR.RenderFrameId, pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames, nextDynamicsRenderFrameId)) + pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId) + dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt + } + + // [WARNING] The following inequality 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.CurDynamicsRenderFrameId { + refRenderFrameId = pR.CurDynamicsRenderFrameId + } + } for playerId, player := range pR.Players { 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". @@ -537,7 +546,7 @@ func (pR *Room) StartBattle() { indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1) var joinMask uint64 = (1 << indiceInJoinIndexBooleanArr) - if MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId || 0 < (unconfirmedMask&joinMask) { + if pR.BackendDynamicsEnabled && (MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId || 0 < (unconfirmedMask&joinMask)) { // [WARNING] Even upon "MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED", it could be true that "0 == (unconfirmedMask & joinMask)"! tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId) if nil == tmp { @@ -801,6 +810,8 @@ func (pR *Room) OnDismissed() { pR.InputFrameUpsyncDelayTolerance = 2 pR.MaxChasingRenderFramesPerUpdate = 10 + pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work! + pR.ChooseStage() pR.EffectivePlayerCount = 0 @@ -1068,6 +1079,43 @@ func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *pb.InputFrameDowns return currInputFrameDownsync } +func (pR *Room) markConfirmationIfApplicable() { + inputFrameId1 := pR.LastAllConfirmedInputFrameId+1 + gap := int32(4) // This value is hardcoded and doesn't need be much bigger, because the backend side is supposed to never lag when "false == BackendDynamicsEnabled". + inputFrameId2 := inputFrameId1+gap + if inputFrameId2 > pR.InputsBuffer.EdFrameId { + inputFrameId2 = pR.InputsBuffer.EdFrameId + } + + totPlayerCnt := uint32(pR.Capacity) + allConfirmedMask := uint64((1 << totPlayerCnt) - 1) + for inputFrameId := inputFrameId1; inputFrameId < inputFrameId2; inputFrameId++ { + tmp := pR.InputsBuffer.GetByFrameId(inputFrameId) + if nil == tmp { + panic(fmt.Sprintf("inputFrameId=%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! InputsBuffer=%v", inputFrameId, pR.Id, pR.InputsBufferString(false))) + } + inputFrameDownsync := tmp.(*pb.InputFrameDownsync) + for _, player := range pR.Players { + bufIndex := pR.toDiscreteInputsBufferIndex(inputFrameId, player.JoinIndex) + tmp, loaded := pR.DiscreteInputsBuffer.LoadAndDelete(bufIndex) // It's safe to "LoadAndDelete" here because the "inputFrameUpsync" of this player is already remembered by the corresponding "inputFrameDown". + if !loaded { + continue + } + inputFrameUpsync := tmp.(*pb.InputFrameUpsync) + indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1) + inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr] = pR.EncodeUpsyncCmd(inputFrameUpsync) + inputFrameDownsync.ConfirmedList |= (1 << indiceInJoinIndexBooleanArr) + } + + // Force confirmation of "inputFrame2" + if allConfirmedMask == inputFrameDownsync.ConfirmedList { + pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1) + } else { + break + } + } +} + func (pR *Room) forceConfirmationIfApplicable() uint64 { // Force confirmation of non-all-confirmed inputFrame EXACTLY ONE AT A TIME, returns the non-confirmed mask of players, e.g. in a 4-player-battle returning 1001 means that players with JoinIndex=1 and JoinIndex=4 are non-confirmed for inputFrameId2 renderFrameId1 := (pR.RenderFrameId - pR.NstDelayFrames) // the renderFrameId which should've been rendered on frontend