diff --git a/ConcerningEdgeCases.md b/ConcerningEdgeCases.md index 825888f..d9a84b8 100644 --- a/ConcerningEdgeCases.md +++ b/ConcerningEdgeCases.md @@ -1,11 +1,11 @@ Under the current "input delay" algorithm, the lag of a single player would cause all the other players to receive outdated commands, e.g. when at a certain moment -- player#1: renderFrameId = 100, significantly lagged due to local CPU being overheated +- player#1: renderFrameId = 100, significantly lagged due to local CPU overheated - player#2: renderFrameId = 240 - player#3: renderFrameId = 239 - player#4: renderFrameId = 242 -players #2, #3 #4 would receive "outdated(in their subjective feelings)" from then on, and be forced to rollback many frames. +players #2, #3 #4 would receive "outdated(in their subjective feelings) but all-confirmed commands" from then on, thus forced to rollback and chase many frames - the lag due to "large range of frame-chasing" would then further deteriorate the situation - like an avalanche. -In a "no-server & p2p" setup, I couldn't think of a proper way to cope with such edge case. On the frontend we could only mitigate the impact to players #2, #3, #4 by certain buffering mechanisms. +In a "no-server & p2p" setup, I couldn't think of a proper way to cope with such edge case. Solely on the frontend we could only mitigate the impact to players #2, #3, #4, e.g. a potential lag due to "large range of frame-chasing" is proactively avoided in `/frontend/assets/scripts/Map.js, function update(dt)` for more information. However in a "server as authority" setup, the server could force confirming an inputFrame without player#1's upsync, and notify player#1 to apply a "roomDownsyncFrame" as well as drop all its outdated local inputFrames. diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index 020e9a8..31ccba3 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -440,7 +440,7 @@ "array": [ 0, 0, - 216.05530045313827, + 209.73151519075364, 0, 0, 0, diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index 507a562..8706c26 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -631,7 +631,7 @@ cc.Class({ -------------------------------------------------------- */ if (renderFrameId1 < self.chaserRenderFrameId) { - // The actual rollback-and-replay would later be executed in update(dt). + // 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 { @@ -798,15 +798,16 @@ cc.Class({ } 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 `/ConcerningEdgeCases.md` for the motivation. const prevChaserRenderFrameId = self.chaserRenderFrameId; let nextChaserRenderFrameId = (prevChaserRenderFrameId + self.maxChasingRenderFramesPerUpdate); if (nextChaserRenderFrameId > self.renderFrameId) nextChaserRenderFrameId = self.renderFrameId; - self.rollbackAndReplay(prevChaserRenderFrameId, nextChaserRenderFrameId, self.chaserCollisionSys, self.chaserCollisionSysMap); + self.rollbackAndChase(prevChaserRenderFrameId, nextChaserRenderFrameId, self.chaserCollisionSys, self.chaserCollisionSysMap); self.chaserRenderFrameId = nextChaserRenderFrameId; // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic! let t2 = performance.now(); - // Inside "self.rollbackAndReplay", 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.rollbackAndReplay(self.renderFrameId, self.renderFrameId+1, self.latestCollisionSys, self.latestCollisionSysMap); + // 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); self.applyRoomDownsyncFrameDynamics(rdf); let t3 = performance.now(); @@ -1011,7 +1012,7 @@ cc.Class({ return inputFrameDownsync; }, - rollbackAndReplay(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap) { + rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap) { if (renderFrameSt >= renderFrameIdEd) { return; }