Updated documentation.

This commit is contained in:
genxium 2022-09-25 23:21:12 +08:00
parent cccbeb1c29
commit 114e6b0501
3 changed files with 10 additions and 9 deletions

View File

@ -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 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#2: renderFrameId = 240
- player#3: renderFrameId = 239 - player#3: renderFrameId = 239
- player#4: renderFrameId = 242 - 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 `<proj-root>/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. 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.

View File

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

View File

@ -631,7 +631,7 @@ cc.Class({
-------------------------------------------------------- --------------------------------------------------------
*/ */
if (renderFrameId1 < self.chaserRenderFrameId) { 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); console.warn("Mismatched input detected, resetting chaserRenderFrameId: inputFrameId1:", inputFrameId1, ", renderFrameId1:", renderFrameId1, ", chaserRenderFrameId before reset: ", self.chaserRenderFrameId);
self.chaserRenderFrameId = renderFrameId1; self.chaserRenderFrameId = renderFrameId1;
} else { } else {
@ -798,15 +798,16 @@ cc.Class({
} }
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.
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) 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! self.chaserRenderFrameId = nextChaserRenderFrameId; // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
let t2 = performance.now(); 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. // 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.rollbackAndReplay(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();
@ -1011,7 +1012,7 @@ cc.Class({
return inputFrameDownsync; return inputFrameDownsync;
}, },
rollbackAndReplay(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap) { rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap) {
if (renderFrameSt >= renderFrameIdEd) { if (renderFrameSt >= renderFrameIdEd) {
return; return;
} }