diff --git a/battle_srv/models/room.go b/battle_srv/models/room.go index 3d2443b..ea2d4fe 100644 --- a/battle_srv/models/room.go +++ b/battle_srv/models/room.go @@ -156,10 +156,9 @@ type Room struct { TmxPointsMap StrToVec2DListMap TmxPolygonsMap StrToPolygon2DListMap - rdfIdToActuallyUsedInput map[int32]*pb.InputFrameDownsync - allowUpdateInputFrameInPlaceUponDynamics bool - LastIndividuallyConfirmedInputFrameId []int32 - LastIndividuallyConfirmedInputList []uint64 + rdfIdToActuallyUsedInput map[int32]*pb.InputFrameDownsync + LastIndividuallyConfirmedInputFrameId []int32 + LastIndividuallyConfirmedInputList []uint64 BattleUdpTunnelLock sync.Mutex BattleUdpTunnelAddr *pb.PeerUdpAddr @@ -808,7 +807,6 @@ func (pR *Room) OnDismissed() { pR.RenderFrameBuffer = resolv.NewRingBuffer(pR.RenderCacheSize) pR.InputsBuffer = resolv.NewRingBuffer((pR.RenderCacheSize >> 1) + 1) pR.rdfIdToActuallyUsedInput = make(map[int32]*pb.InputFrameDownsync) - pR.allowUpdateInputFrameInPlaceUponDynamics = true pR.LastIndividuallyConfirmedInputFrameId = make([]int32, pR.Capacity) for i := 0; i < pR.Capacity; i++ { pR.LastIndividuallyConfirmedInputFrameId[i] = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED @@ -847,7 +845,7 @@ 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(serverFps) / dilutedServerFps) - pR.BattleDurationFrames = int32(60 * serverFps) + pR.BattleDurationFrames = int32(20 * serverFps) //pR.BattleDurationFrames = int32(20 * serverFps) pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1) pR.InputFrameUpsyncDelayTolerance = battle.ConvertToNoDelayInputFrameId(pR.NstDelayFrames) - 1 // this value should be strictly smaller than (NstDelayFrames >> InputScaleFrames), otherwise "type#1 forceConfirmation" might become a lag avalanche @@ -856,7 +854,7 @@ func (pR *Room) OnDismissed() { pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work! pR.ForceAllResyncOnAnyActiveSlowTicker = true // See tradeoff discussion in "downsyncToAllPlayers" - pR.FrameDataLoggingEnabled = false // [WARNING] DON'T ENABLE ON LONG BATTLE DURATION! It consumes A LOT OF MEMORY! + pR.FrameDataLoggingEnabled = true // [WARNING] DON'T ENABLE ON LONG BATTLE DURATION! It consumes A LOT OF MEMORY! pR.BattleUdpTunnelLock.Lock() pR.BattleUdpTunnel = nil pR.BattleUdpTunnelAddr = nil @@ -1309,9 +1307,6 @@ func (pR *Room) forceConfirmationIfApplicable(prevRenderFrameId int32) uint64 { panic(fmt.Sprintf("inputFrameId=%v doesn't exist for roomId=%v! InputsBuffer=%v", j, pR.Id, pR.InputsBufferString(false))) } inputFrameDownsync := tmp.(*battle.InputFrameDownsync) - if pR.allowUpdateInputFrameInPlaceUponDynamics { - battle.UpdateInputFrameInPlaceUponDynamics(j, pR.Capacity, inputFrameDownsync.ConfirmedList, inputFrameDownsync.InputList, pR.LastIndividuallyConfirmedInputFrameId, pR.LastIndividuallyConfirmedInputList, int32(MAGIC_JOIN_INDEX_INVALID)) - } unconfirmedMask |= (allConfirmedMask ^ inputFrameDownsync.ConfirmedList) inputFrameDownsync.ConfirmedList = allConfirmedMask pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1) @@ -1392,7 +1387,7 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende } } - battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame.Id, pR.Space, pR.CollisionSysMap, pR.SpaceOffsetX, pR.SpaceOffsetY, pR.CharacterConfigsArr, pR.RenderFrameBuffer, pR.collisionHolder, pR.effPushbacks, pR.hardPushbackNormsArr, pR.jumpedOrNotList, pR.dynamicRectangleColliders, pR.LastIndividuallyConfirmedInputFrameId, pR.LastIndividuallyConfirmedInputList, false, MAGIC_JOIN_INDEX_INVALID) // "allowUpdateInputFrameInPlaceUponDynamics" is instead used in "forceConfirmationIfApplicable" + battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame.Id, pR.Space, pR.CollisionSysMap, pR.SpaceOffsetX, pR.SpaceOffsetY, pR.CharacterConfigsArr, pR.RenderFrameBuffer, pR.collisionHolder, pR.effPushbacks, pR.hardPushbackNormsArr, pR.jumpedOrNotList, pR.dynamicRectangleColliders, pR.LastIndividuallyConfirmedInputFrameId, pR.LastIndividuallyConfirmedInputList, false, MAGIC_JOIN_INDEX_INVALID) // DON'T mutate inputs upon dynamics on backend to avoid complicating the edge cases pR.CurDynamicsRenderFrameId++ } } diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index d15fcf3..3a227ba 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -461,7 +461,7 @@ "array": [ 0, 0, - 210.4441731196186, + 209.57814771583418, 0, 0, 0, diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index a790bf7..ae5913e 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -713,9 +713,9 @@ cc.Class({ if (notSelfUnconfirmed) { shouldForceDumping2 = false; shouldForceResync = false; - self.othersForcedDownsyncRenderFrameDict.set(rdfId, rdf); + self.othersForcedDownsyncRenderFrameDict.set(rdfId, [pbRdf, rdf]); if (CC_DEBUG) { - console.warn(`Someone else is forced to resync! renderFrameId=${rdf.GetId()} + console.warn(`Someone else is forced to resync! renderFrameId=${rdfId} backendUnconfirmedMask=${pbRdf.backendUnconfirmedMask} accompaniedInputFrameDownsyncBatchRange=[${null == accompaniedInputFrameDownsyncBatch ? null : accompaniedInputFrameDownsyncBatch[0].inputFrameId}, ${null == accompaniedInputFrameDownsyncBatch ? null : accompaniedInputFrameDownsyncBatch[accompaniedInputFrameDownsyncBatch.length - 1].inputFrameId}]`); } @@ -947,7 +947,7 @@ accompaniedInputFrameDownsyncBatchRange=[${accompaniedInputFrameDownsyncBatch[0] _handleIncorrectlyRenderedPrediction(firstPredictedYetIncorrectInputFrameId, batch, fromUDP) { if (null == firstPredictedYetIncorrectInputFrameId) return; const self = this; - const renderFrameId1 = gopkgs.ConvertToFirstUsedRenderFrameId(firstPredictedYetIncorrectInputFrameId) - 1; + const renderFrameId1 = gopkgs.ConvertToFirstUsedRenderFrameId(firstPredictedYetIncorrectInputFrameId); if (renderFrameId1 >= self.chaserRenderFrameId) return; /* @@ -1164,25 +1164,25 @@ fromUDP=${fromUDP}`); rollbackFrames = 0; } self.networkDoctor.logRollbackFrames(rollbackFrames); - let prevRdf = latestRdfResults[0], - rdf = latestRdfResults[1]; + let prevRdf = latestRdfResults[0], // Having "prevRdf.Id == self.renderFrameId" + rdf = latestRdfResults[1]; // Having "rdf.Id == self.renderFrameId+1" /* const nonTrivialChaseEnded = (prevChaserRenderFrameId < nextChaserRenderFrameId && nextChaserRenderFrameId == self.renderFrameId); if (nonTrivialChaseEnded) { console.debug("Non-trivial chase ended, prevChaserRenderFrameId=" + prevChaserRenderFrameId + ", nextChaserRenderFrameId=" + nextChaserRenderFrameId); } */ - // [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.GetId())) { - const delayedInputFrameId = gopkgs.ConvertToDelayedInputFrameId(rdf.GetId()); - const othersForcedDownsyncRenderFrame = self.othersForcedDownsyncRenderFrameDict.get(rdf.GetId()); + const [pbOthersForcedDownsyncRenderFrame, othersForcedDownsyncRenderFrame] = self.othersForcedDownsyncRenderFrameDict.get(rdf.GetId()); if (self.lastAllConfirmedInputFrameId >= delayedInputFrameId && !self.equalRoomDownsyncFrames(othersForcedDownsyncRenderFrame, rdf)) { if (CC_DEBUG) { console.warn(`Mismatched render frame@rdf.id=${rdf.GetId()} w/ inputFrameId=${delayedInputFrameId}: rdf=${self._stringifyGopkgRdfForFrameDataLogging(rdf)} othersForcedDownsyncRenderFrame=${self._stringifyGopkgRdfForFrameDataLogging(othersForcedDownsyncRenderFrame)}`); } - rdf = othersForcedDownsyncRenderFrame; + // [WARNING] When this happens, something is intrinsically wrong -- to avoid having an inconsistent history in the "recentRenderCache", thus a wrong prediction all the way from here on, clear the history! + pbOthersForcedDownsyncRenderFrame.backendUnconfirmedMask = ((1 << window.boundRoomCapacity) - 1); + self.onRoomDownsyncFrame(pbOthersForcedDownsyncRenderFrame, null); self.othersForcedDownsyncRenderFrameDict.delete(rdf.GetId()); } } @@ -1438,19 +1438,11 @@ othersForcedDownsyncRenderFrame=${self._stringifyGopkgRdfForFrameDataLogging(oth const j = gopkgs.ConvertToDelayedInputFrameId(i); const delayedInputFrame = gopkgs.GetInputFrameDownsync(self.recentInputCache, j); - const allowUpdateInputFrameInPlaceUponDynamics = (!isChasing); + const allowUpdateInputFrameInPlaceUponDynamics = (!isChasing); // [WARNING] Input mutation could trigger chasing on frontend, thus don't trigger mutation when chasing to avoid confusing recursion. const hasInputFrameUpdatedOnDynamics = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, i, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex, self.recentRenderCache, self.collisionHolder, self.effPushbacks, self.hardPushbackNormsArr, self.jumpedOrNotList, self.dynamicRectangleColliders, self.lastIndividuallyConfirmedInputFrameId, self.lastIndividuallyConfirmedInputList, allowUpdateInputFrameInPlaceUponDynamics, self.selfPlayerInfo.joinIndex); if (hasInputFrameUpdatedOnDynamics) { const ii = gopkgs.ConvertToFirstUsedRenderFrameId(j); if (ii < i) { - /* - [WARNING] - If we don't rollback at this spot, when the mutated "delayedInputFrame.inputList" a.k.a. "inputFrame#j" matches the later downsynced version, rollback WOULDN'T be triggered for the incorrectly rendered "renderFrame#(ii+1)", and it would STAY IN HISTORY FOREVER -- as the history becomes incorrect, EVERY LATEST renderFrame since "inputFrame#j" was mutated would be ALWAYS incorrectly rendering too! - - The backend counterpart doesn't need this rollback because - 1. Backend only applies all-confirmed inputFrames to calc dynamics. - 2. Backend applies an all-confirmed inputFrame to all applicable render frames at once. - */ self._handleIncorrectlyRenderedPrediction(j, null, false); } } diff --git a/frontend/build-templates/jsb-link/frameworks/runtime-src/Classes/udp_session.cpp b/frontend/build-templates/jsb-link/frameworks/runtime-src/Classes/udp_session.cpp index a8368f7..f28acb8 100644 --- a/frontend/build-templates/jsb-link/frameworks/runtime-src/Classes/udp_session.cpp +++ b/frontend/build-templates/jsb-link/frameworks/runtime-src/Classes/udp_session.cpp @@ -372,7 +372,7 @@ bool DelayNoMore::UdpSession::pollUdpRecvRingBuff() { // This function is called by GameThread 60 fps. //uv_mutex_lock(&recvRingBuffLock); - while (true) { + while (true && NULL != recvLoop) { RecvWork f; bool res = recvRingBuff->pop(&f); if (!res) { diff --git a/frontend/settings/project.json b/frontend/settings/project.json index bbacb33..c4f5203 100644 --- a/frontend/settings/project.json +++ b/frontend/settings/project.json @@ -76,7 +76,7 @@ "shelter_z_reducer", "shelter" ], - "last-module-event-record-time": 1677337364473, + "last-module-event-record-time": 1678432182471, "simulator-orientation": false, "simulator-resolution": { "height": 640,