mirror of
https://github.com/genxium/DelayNoMore
synced 2025-10-09 00:26:39 +00:00
Added network doctor stats.
This commit is contained in:
@@ -113,6 +113,10 @@ cc.Class({
|
||||
type: cc.Label,
|
||||
default: null
|
||||
},
|
||||
skippedRenderFrameCntLabel: {
|
||||
type: cc.Label,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
|
||||
_inputFrameIdDebuggable(inputFrameId) {
|
||||
@@ -351,6 +355,7 @@ cc.Class({
|
||||
self.rdfIdToActuallyUsedInput = new Map();
|
||||
|
||||
self.networkDoctor = new NetworkDoctor(20);
|
||||
self.skipRenderFrameFlag = false;
|
||||
|
||||
self.countdownNanos = null;
|
||||
if (self.countdownLabel) {
|
||||
@@ -702,6 +707,7 @@ cc.Class({
|
||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||
// In this case it must be true that "rdf.id > chaserRenderFrameId".
|
||||
self.chaserRenderFrameId = rdf.Id;
|
||||
self.networkDoctor.logRollbackFrames(0);
|
||||
|
||||
const canvasNode = self.canvasNode;
|
||||
self.ctrl = canvasNode.getComponent("TouchEventsManager");
|
||||
@@ -852,12 +858,12 @@ cc.Class({
|
||||
--------------------------------------------------------
|
||||
*/
|
||||
// The actual rollback-and-chase would later be executed in update(dt).
|
||||
self.networkDoctor.immediateRollbackFrames = (self.renderFrameId - renderFrameId1);
|
||||
console.log(`Mismatched input detected, resetting chaserRenderFrameId: ${self.chaserRenderFrameId}->${renderFrameId1} by firstPredictedYetIncorrectInputFrameId: ${firstPredictedYetIncorrectInputFrameId}
|
||||
lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}
|
||||
recentInputCache=${self._stringifyRecentInputCache(false)}
|
||||
batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inputFrameId}]`);
|
||||
self.chaserRenderFrameId = renderFrameId1;
|
||||
self.networkDoctor.logRollbackFrames(self.renderFrameId - self.chaserRenderFrameId);
|
||||
},
|
||||
|
||||
onPeerInputFrameUpsync(peerJoinIndex, batch /* []*pb.InputFrameDownsync */ ) {
|
||||
@@ -874,7 +880,7 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
|
||||
return;
|
||||
}
|
||||
|
||||
self.networkDoctor.logPeerInputFrameUpsync(batch[0].inputFrameId, batch[batch.length - 1].inputFrameId);
|
||||
let effCnt = 0;
|
||||
//console.log(`Received peer inputFrameUpsync batch w/ inputFrameId in [${batch[0].inputFrameId}, ${batch[batch.length - 1].inputFrameId}] for prediction assistance`);
|
||||
for (let k in batch) {
|
||||
const inputFrameDownsync = batch[k];
|
||||
@@ -882,11 +888,15 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
|
||||
if (inputFrameDownsyncId <= self.lastAllConfirmedInputFrameId) {
|
||||
continue;
|
||||
}
|
||||
effCnt += 1;
|
||||
self.getOrPrefabInputFrameUpsync(inputFrameDownsyncId); // Make sure that inputFrame exists locally
|
||||
const existingInputFrame = self.recentInputCache.GetByFrameId(inputFrameDownsyncId);
|
||||
existingInputFrame.InputList[peerJoinIndex - 1] = inputFrameDownsync.inputList[peerJoinIndex - 1]; // No need to change "confirmedList", leave it to "onInputFrameDownsyncBatch" -- we're just helping prediction here
|
||||
self.recentInputCache.SetByFrameId(existingInputFrame, inputFrameDownsyncId);
|
||||
}
|
||||
if (0 < effCnt) {
|
||||
self.networkDoctor.logPeerInputFrameUpsync(batch[0].inputFrameId, batch[batch.length - 1].inputFrameId);
|
||||
}
|
||||
},
|
||||
|
||||
onPlayerAdded(rdf /* pb.RoomDownsyncFrame */ ) {
|
||||
@@ -960,6 +970,11 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
|
||||
|
||||
Kindly note that Significantly different network bandwidths or delay fluctuations would result in frequent [type#1 forceConfirmation] too, but CAUSE FROM DIFFERENT LOCAL "update(dt)" RATE SHOULD BE THE FIRST TO INVESTIGATE AND ELIMINATE -- because we have control on it, but no one has control on the internet.
|
||||
*/
|
||||
if (self.skipRenderFrameFlag) {
|
||||
self.networkDoctor.logSkippedRenderFrameCnt();
|
||||
self.skipRenderFrameFlag = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let st = performance.now();
|
||||
const noDelayInputFrameId = gopkgs.ConvertToNoDelayInputFrameId(self.renderFrameId);
|
||||
@@ -996,6 +1011,7 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
|
||||
|
||||
// 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 latestRdfResults = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.gopkgsCollisionSys, self.gopkgsCollisionSysMap, false);
|
||||
self.networkDoctor.logRollbackFrames(self.renderFrameId - self.chaserRenderFrameId);
|
||||
let prevRdf = latestRdfResults[0],
|
||||
rdf = latestRdfResults[1];
|
||||
/*
|
||||
@@ -1026,6 +1042,7 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
||||
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
|
||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||
let t3 = performance.now();
|
||||
self.skipRenderFrameFlag = self.networkDoctor.isTooFast();
|
||||
} catch (err) {
|
||||
console.error("Error during Map.update", err);
|
||||
self.onBattleStopped(); // TODO: Popup to ask player to refresh browser
|
||||
@@ -1520,9 +1537,41 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
|
||||
|
||||
showNetworkDoctorLabels() {
|
||||
const self = this;
|
||||
self.sendingQLabel.string = self.networkDoctor.statSending();
|
||||
self.inputFrameDownsyncQLabel.string = self.networkDoctor.statInputFrameDownsync();
|
||||
self.peerInputFrameUpsyncQLabel.string = self.networkDoctor.statPeerInputFrameUpsync();
|
||||
self.rollbackFramesLabel.string = self.networkDoctor.statRollbackFrames();
|
||||
const [sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt] = self.networkDoctor.stats();
|
||||
if (self.sendingQLabel) {
|
||||
self.sendingQLabel.string = `${sendingFps} fps sending`;
|
||||
if (sendingFps < self.networkDoctor.inputRateThreshold) {
|
||||
self.sendingQLabel.node.color = cc.Color.RED;
|
||||
} else {
|
||||
self.sendingQLabel.node.color = cc.Color.WHITE;
|
||||
}
|
||||
}
|
||||
if (self.inputFrameDownsyncQLabel) {
|
||||
self.inputFrameDownsyncQLabel.string = `${srvDownsyncFps} fps srv-downsync`;
|
||||
if (srvDownsyncFps < self.networkDoctor.inputRateThreshold) {
|
||||
self.inputFrameDownsyncQLabel.node.color = cc.Color.RED;
|
||||
} else {
|
||||
self.inputFrameDownsyncQLabel.node.color = cc.Color.WHITE;
|
||||
}
|
||||
}
|
||||
if (self.peerInputFrameUpsyncQLabel) {
|
||||
self.peerInputFrameUpsyncQLabel.string = `${peerUpsyncFps} fps peer-upsync`;
|
||||
if (peerUpsyncFps > self.networkDoctor.peerUpsyncFps) {
|
||||
self.peerInputFrameUpsyncQLabel.node.color = cc.Color.RED;
|
||||
} else {
|
||||
self.peerInputFrameUpsyncQLabel.node.color = cc.Color.WHITE;
|
||||
}
|
||||
}
|
||||
if (self.rollbackFramesLabel) {
|
||||
self.rollbackFramesLabel.string = `rollbackFrames: ${rollbackFrames}`
|
||||
if (rollbackFrames > self.networkDoctor.rollbackFramesThreshold) {
|
||||
self.rollbackFramesLabel.node.color = cc.Color.RED;
|
||||
} else {
|
||||
self.rollbackFramesLabel.node.color = cc.Color.WHITE;
|
||||
}
|
||||
}
|
||||
if (self.skippedRenderFrameCntLabel) {
|
||||
self.skippedRenderFrameCntLabel.string = `${skippedRenderFrameCnt} frames skipped`
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@@ -10,6 +10,11 @@ NetworkDoctor.prototype.reset = function(capacity) {
|
||||
this.peerInputFrameUpsyncQ = new RingBuffer(capacity);
|
||||
this.peerInputFrameUpsyncCnt = 0;
|
||||
this.immediateRollbackFrames = 0;
|
||||
this.skippedRenderFrameCnt = 0;
|
||||
|
||||
this.inputRateThreshold = gopkgs.ConvertToNoDelayInputFrameId(60);
|
||||
this.peerUpsyncThreshold = 8;
|
||||
this.rollbackFramesThreshold = 8; // Roughly the same as TurnAroundFramesToRecover
|
||||
};
|
||||
|
||||
NetworkDoctor.prototype.logSending = function(stFrameId, edFrameId) {
|
||||
@@ -40,35 +45,50 @@ NetworkDoctor.prototype.logPeerInputFrameUpsync = function(stFrameId, edFrameId)
|
||||
this.peerInputFrameUpsyncCnt += (edFrameId - stFrameId + 1);
|
||||
};
|
||||
|
||||
NetworkDoctor.prototype.statSending = function() {
|
||||
if (1 >= this.sendingQ.cnt) return `0 fps sending`;
|
||||
const st = this.sendingQ.getByFrameId(this.sendingQ.stFrameId);
|
||||
const ed = this.sendingQ.getByFrameId(this.sendingQ.edFrameId - 1);
|
||||
const elapsedMillis = ed.t - st.t;
|
||||
const fps = Math.round((ed.j - st.i) * 1000 / elapsedMillis);
|
||||
return `${fps} fps sending`;
|
||||
NetworkDoctor.prototype.logRollbackFrames = function(x) {
|
||||
this.immediateRollbackFrames = x;
|
||||
};
|
||||
|
||||
NetworkDoctor.prototype.statInputFrameDownsync = function() {
|
||||
if (1 >= this.inputFrameDownsyncQ.cnt) return `0 fps srv downsync`;
|
||||
const st = this.inputFrameDownsyncQ.getByFrameId(this.inputFrameDownsyncQ.stFrameId);
|
||||
const ed = this.inputFrameDownsyncQ.getByFrameId(this.inputFrameDownsyncQ.edFrameId - 1);
|
||||
const elapsedMillis = ed.t - st.t;
|
||||
const fps = Math.round((ed.j - st.i) * 1000 / elapsedMillis);
|
||||
return `${fps} fps srv downsync`;
|
||||
NetworkDoctor.prototype.stats = function() {
|
||||
let sendingFps = 0,
|
||||
srvDownsyncFps = 0,
|
||||
peerUpsyncFps = 0,
|
||||
rollbackFrames = this.immediateRollbackFrames;
|
||||
if (1 < this.sendingQ.cnt) {
|
||||
const st = this.sendingQ.getByFrameId(this.sendingQ.stFrameId);
|
||||
const ed = this.sendingQ.getByFrameId(this.sendingQ.edFrameId - 1);
|
||||
const elapsedMillis = ed.t - st.t;
|
||||
sendingFps = Math.round((ed.j - st.i) * 1000 / elapsedMillis);
|
||||
}
|
||||
if (1 < this.inputFrameDownsyncQ.cnt) {
|
||||
const st = this.inputFrameDownsyncQ.getByFrameId(this.inputFrameDownsyncQ.stFrameId);
|
||||
const ed = this.inputFrameDownsyncQ.getByFrameId(this.inputFrameDownsyncQ.edFrameId - 1);
|
||||
const elapsedMillis = ed.t - st.t;
|
||||
srvDownsyncFps = Math.round((ed.j - st.i) * 1000 / elapsedMillis);
|
||||
}
|
||||
if (1 < this.peerInputFrameUpsyncQ.cnt) {
|
||||
const st = this.peerInputFrameUpsyncQ.getByFrameId(this.peerInputFrameUpsyncQ.stFrameId);
|
||||
const ed = this.peerInputFrameUpsyncQ.getByFrameId(this.peerInputFrameUpsyncQ.edFrameId - 1);
|
||||
const elapsedMillis = ed.t - st.t;
|
||||
peerUpsyncFps = Math.round(this.peerInputFrameUpsyncCnt * 1000 / elapsedMillis);
|
||||
}
|
||||
return [sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, this.skippedRenderFrameCnt];
|
||||
};
|
||||
|
||||
NetworkDoctor.prototype.statPeerInputFrameUpsync = function() {
|
||||
if (1 >= this.peerInputFrameUpsyncQ.cnt) return `0 fps peer upsync`;
|
||||
const st = this.peerInputFrameUpsyncQ.getByFrameId(this.peerInputFrameUpsyncQ.stFrameId);
|
||||
const ed = this.peerInputFrameUpsyncQ.getByFrameId(this.peerInputFrameUpsyncQ.edFrameId - 1);
|
||||
const elapsedMillis = ed.t - st.t;
|
||||
const fps = Math.round(this.peerInputFrameUpsyncCnt * 1000 / elapsedMillis);
|
||||
return `${fps} fps peer upsync`;
|
||||
};
|
||||
NetworkDoctor.prototype.logSkippedRenderFrameCnt = function() {
|
||||
this.skippedRenderFrameCnt += 1;
|
||||
}
|
||||
|
||||
NetworkDoctor.prototype.statRollbackFrames = function() {
|
||||
return `${this.immediateRollbackFrames} rollback frames`;
|
||||
NetworkDoctor.prototype.isTooFast = function() {
|
||||
const [sendingFps, srvDownsyncFps, peerUpsyncFps, rollbackFrames, skippedRenderFrameCnt] = this.stats();
|
||||
if (sendingFps >= this.inputRateThreshold && srvDownsyncFps >= this.inputRateThreshold) {
|
||||
// At least my network is OK for both TX & RX directions.
|
||||
if (rollbackFrames >= this.rollbackFramesThreshold && peerUpsyncFps < this.peerUpsyncThreshold) {
|
||||
// I got many frames rolled back while none of my peers effectively helped my preciction.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports = NetworkDoctor;
|
||||
|
@@ -98,7 +98,7 @@ cc.Class({
|
||||
const p2Vpos = gopkgs.WorldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y);
|
||||
const colliderRadiusV = gopkgs.WorldToVirtualGridPos(12.0, 0);
|
||||
|
||||
const speciesIdList = [4096, 1];
|
||||
const speciesIdList = [1, 4096];
|
||||
const chConfigsOrderedByJoinIndex = gopkgs.GetCharacterConfigsOrderedByJoinIndex(speciesIdList);
|
||||
|
||||
const startRdf = window.pb.protos.RoomDownsyncFrame.create({
|
||||
|
Reference in New Issue
Block a user