Fixes on resync.

This commit is contained in:
genxium 2022-12-01 11:35:56 +08:00
parent c6473db561
commit 348c889e14
6 changed files with 71 additions and 80 deletions

View File

@ -169,6 +169,7 @@ type Room struct {
PlayerDefaultSpeed int32 PlayerDefaultSpeed int32
BulletBattleLocalIdCounter int32 BulletBattleLocalIdCounter int32
dilutedRollbackEstimatedDtNanos int64
BattleColliderInfo // Compositing to send centralized magic numbers BattleColliderInfo // Compositing to send centralized magic numbers
} }
@ -465,7 +466,7 @@ func (pR *Room) StartBattle() {
dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt
} }
if 0 < unconfirmedMask { if 0 < unconfirmedMask {
Logger.Warn(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, room.LastAllConfirmedInputFrameId=%v, unconfirmedMask=%v", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LastAllConfirmedInputFrameId, unconfirmedMask)) Logger.Debug(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, room.LastAllConfirmedInputFrameId=%v, unconfirmedMask=%v", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LastAllConfirmedInputFrameId, unconfirmedMask))
// Otherwise no need to downsync immediately // Otherwise no need to downsync immediately
pR.downsyncToAllPlayers(pR.LastAllConfirmedInputFrameId, unconfirmedMask, false) pR.downsyncToAllPlayers(pR.LastAllConfirmedInputFrameId, unconfirmedMask, false)
} }
@ -477,7 +478,7 @@ func (pR *Room) StartBattle() {
if elapsedInCalculation > pR.RollbackEstimatedDtNanos { if elapsedInCalculation > pR.RollbackEstimatedDtNanos {
Logger.Warn(fmt.Sprintf("SLOW FRAME! Elapsed time statistics: roomId=%v, room.RenderFrameId=%v, elapsedInCalculation=%v ns, dynamicsDuration=%v ns, RollbackEstimatedDtNanos=%v", pR.Id, pR.RenderFrameId, elapsedInCalculation, dynamicsDuration, pR.RollbackEstimatedDtNanos)) Logger.Warn(fmt.Sprintf("SLOW FRAME! Elapsed time statistics: roomId=%v, room.RenderFrameId=%v, elapsedInCalculation=%v ns, dynamicsDuration=%v ns, RollbackEstimatedDtNanos=%v", pR.Id, pR.RenderFrameId, elapsedInCalculation, dynamicsDuration, pR.RollbackEstimatedDtNanos))
} }
time.Sleep(time.Duration(pR.RollbackEstimatedDtNanos - elapsedInCalculation)) time.Sleep(time.Duration(pR.dilutedRollbackEstimatedDtNanos - elapsedInCalculation))
} }
} }
@ -714,6 +715,8 @@ func (pR *Room) OnDismissed() {
pR.ServerFps = 60 pR.ServerFps = 60
pR.RollbackEstimatedDtMillis = 16.667 // Use fixed-and-low-precision to mitigate the inconsistent floating-point-number issue between Golang and JavaScript pR.RollbackEstimatedDtMillis = 16.667 // Use fixed-and-low-precision to mitigate the inconsistent floating-point-number issue between Golang and JavaScript
pR.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for logging FAST FRAME pR.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for logging FAST FRAME
dilutedServerFps := int64(58)
pR.dilutedRollbackEstimatedDtNanos = pR.RollbackEstimatedDtNanos * (int64(pR.ServerFps) / dilutedServerFps)
pR.BattleDurationFrames = 30 * pR.ServerFps pR.BattleDurationFrames = 30 * pR.ServerFps
pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1) pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1)
pR.InputFrameUpsyncDelayTolerance = 2 pR.InputFrameUpsyncDelayTolerance = 2
@ -921,7 +924,7 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool {
This function is triggered by an upsync message via WebSocket, thus downsync sending is also available by now. This function is triggered by an upsync message via WebSocket, thus downsync sending is also available by now.
*/ */
thatPlayerBattleState := atomic.LoadInt32(&(thatPlayer.BattleState)) thatPlayerBattleState := atomic.LoadInt32(&(thatPlayer.BattleState))
Logger.Info(fmt.Sprintf("OnPlayerBattleColliderAcked-middle: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, thatPlayerId=%v, thatPlayerBattleState=%v", pR.Id, pR.State, targetPlayer.Id, targetPlayer.BattleState, thatPlayer.Id, thatPlayerBattleState)) Logger.Debug(fmt.Sprintf("OnPlayerBattleColliderAcked-middle: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, thatPlayerId=%v, thatPlayerBattleState=%v", pR.Id, pR.State, targetPlayer.Id, targetPlayer.BattleState, thatPlayer.Id, thatPlayerBattleState))
if thatPlayerId == targetPlayer.Id || (PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK == thatPlayerBattleState || PlayerBattleStateIns.ACTIVE == thatPlayerBattleState) { if thatPlayerId == targetPlayer.Id || (PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK == thatPlayerBattleState || PlayerBattleStateIns.ACTIVE == thatPlayerBattleState) {
Logger.Debug(fmt.Sprintf("OnPlayerBattleColliderAcked-sending DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, capacity=%v, EffectivePlayerCount=%v", pR.Id, pR.State, targetPlayer.Id, targetPlayer.BattleState, pR.Capacity, pR.EffectivePlayerCount)) Logger.Debug(fmt.Sprintf("OnPlayerBattleColliderAcked-sending DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED: roomId=%v, roomState=%v, targetPlayerId=%v, targetPlayerBattleState=%v, capacity=%v, EffectivePlayerCount=%v", pR.Id, pR.State, targetPlayer.Id, targetPlayer.BattleState, pR.Capacity, pR.EffectivePlayerCount))
pR.sendSafely(playerAckedFrame, nil, DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED, thatPlayer.Id) pR.sendSafely(playerAckedFrame, nil, DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED, thatPlayer.Id)
@ -1448,26 +1451,28 @@ func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, upperToSe
lowerToSentInputFrameId := player.LastSentInputFrameId + 1 lowerToSentInputFrameId := player.LastSentInputFrameId + 1
/* /*
[WARNING] [WARNING]
Upon resynced on frontend, "refRenderFrameId" MUST BE CAPPED somehow by "upperToSendInputFrameId", if frontend resyncs itself to a more advanced value than given below, upon the next renderFrame tick on the frontend it might generate non-consecutive "nextInputFrameId > frontend.recentInputCache.edFrameId+1". Upon resynced on frontend, "refRenderFrameId" is now set to as advanced as possible, and it's the frontend's responsibility now to pave way for the "gap inputFrames"
If "NstDelayFrames" becomes larger, "pR.RenderFrameId - refRenderFrameId" possibly becomes larger because the force confirmation is delayed more. If "NstDelayFrames" becomes larger, "pR.RenderFrameId - refRenderFrameId" possibly becomes larger because the force confirmation is delayed more.
Upon resync, it's still possible that "refRenderFrameId < frontend.chaserRenderFrameId" -- and this is allowed. Upon resync, it's still possible that "refRenderFrameId < frontend.chaserRenderFrameId" -- and this is allowed.
*/ */
refRenderFrameId := pR.ConvertToGeneratingRenderFrameId(upperToSendInputFrameId + 1) // for the frontend to jump immediately into generating & upsyncing the next input frame, thus getting rid of "resync avalanche" refRenderFrameId := pR.CurDynamicsRenderFrameId - 1
if refRenderFrameId > pR.RenderFrameId {
refRenderFrameId = pR.RenderFrameId
}
if MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId { shouldResync1 := (MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId)
shouldResync2 := (0 < (unconfirmedMask & uint64(1<<uint32(player.JoinIndex-1)))) // This condition is critical, if we don't send resync upon this condition, the "reconnected or slowly-clocking player" might never get its input synced
// shouldResync2 := (0 < unconfirmedMask) // An easier version of the above, might keep sending "refRenderFrame"s to still connected players when any player is disconnected
shouldResyncOverall := (shouldResync1 || shouldResync2)
if shouldResyncOverall {
// A rejoined player, should guarantee that when it resyncs to "refRenderFrameId" a matching inputFrame to apply exists // A rejoined player, should guarantee that when it resyncs to "refRenderFrameId" a matching inputFrame to apply exists
lowerToSentInputFrameId = pR.ConvertToInputFrameId(refRenderFrameId, pR.InputDelayFrames) lowerToSentInputFrameId = pR.ConvertToInputFrameId(refRenderFrameId, pR.InputDelayFrames)
Logger.Debug(fmt.Sprintf("Resetting refRenderFrame for rejoined player: roomId=%v, renderFrameId=%v, playerId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v", pR.Id, pR.RenderFrameId, playerId, refRenderFrameId, lowerToSentInputFrameId, upperToSendInputFrameId)) Logger.Info(fmt.Sprintf("Resyncing player: roomId=%v, playerId=%v, playerJoinIndex=%v, renderFrameId=%v, curDynamicsRenderFrameId=%v, playerLastSentInputFrameId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v", pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, refRenderFrameId, lowerToSentInputFrameId, upperToSendInputFrameId))
// TODO: What if "lowerToSentInputFrameId > pR.InputsBuffer.StFrameId" now? // TODO: What if "lowerToSentInputFrameId > pR.InputsBuffer.StFrameId" now?
} }
if lowerToSentInputFrameId > upperToSendInputFrameId { if lowerToSentInputFrameId > upperToSendInputFrameId {
Logger.Warn(fmt.Sprintf("Not sending due to potentially empty toSendInputFrameDownsyncs: roomId=%v, playerId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, refRenderFrameId, lowerToSentInputFrameId, upperToSendInputFrameId, player.LastSentInputFrameId, player.AckingInputFrameId)) Logger.Debug(fmt.Sprintf("Not sending due to potentially empty toSendInputFrameDownsyncs: roomId=%v, playerId=%v, playerLastSentInputFrameId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, player.LastSentInputFrameId, refRenderFrameId, lowerToSentInputFrameId, upperToSendInputFrameId, player.LastSentInputFrameId, player.AckingInputFrameId))
return return
} }
@ -1481,7 +1486,7 @@ func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, upperToSe
j, toSendInputFrameDownsyncs := pR.InputsBuffer.cloneInputFrameDownsyncsByFrameIdRange(lowerToSentInputFrameId, upperToSendInputFrameId+1, theInputsBufferLockToUse) j, toSendInputFrameDownsyncs := pR.InputsBuffer.cloneInputFrameDownsyncsByFrameIdRange(lowerToSentInputFrameId, upperToSendInputFrameId+1, theInputsBufferLockToUse)
if 0 >= len(toSendInputFrameDownsyncs) { if 0 >= len(toSendInputFrameDownsyncs) {
Logger.Debug(fmt.Sprintf("Not sending due to actually empty toSendInputFrameDownsyncs: roomId=%v, playerId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v, j=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, refRenderFrameId, lowerToSentInputFrameId, upperToSendInputFrameId, j, player.LastSentInputFrameId, player.AckingInputFrameId)) Logger.Debug(fmt.Sprintf("Not sending due to actually empty toSendInputFrameDownsyncs: roomId=%v, playerId=%v, playerLastSentInputFrameId=%v, refRenderFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v, j=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, player.LastSentInputFrameId, refRenderFrameId, lowerToSentInputFrameId, upperToSendInputFrameId, j, player.LastSentInputFrameId, player.AckingInputFrameId))
return return
} }
@ -1490,17 +1495,17 @@ func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, upperToSe
1. when player with a slower frontend clock lags significantly behind and thus wouldn't get its inputUpsync recognized due to faster "forceConfirmation" 1. when player with a slower frontend clock lags significantly behind and thus wouldn't get its inputUpsync recognized due to faster "forceConfirmation"
2. reconnection 2. reconnection
*/ */
shouldResync1 := (MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId) if pR.BackendDynamicsEnabled && shouldResyncOverall {
shouldResync2 := (0 < (unconfirmedMask & uint64(1<<uint32(player.JoinIndex-1)))) // This condition is critical, if we don't send resync upon this condition, the "reconnected or slowly-clocking player" might never get its input synced
// shouldResync2 := (0 < unconfirmedMask) // An easier version of the above, might keep sending "refRenderFrame"s to still connected players when any player is disconnected
if pR.BackendDynamicsEnabled && (shouldResync1 || shouldResync2) {
tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId) tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId)
if nil == tmp { if nil == tmp {
panic(fmt.Sprintf("Required refRenderFrameId=%v for roomId=%v, renderFrameId=%v, playerId=%v, j=%v doesn't exist! InputsBuffer=%v, RenderFrameBuffer=%v", refRenderFrameId, pR.Id, pR.RenderFrameId, playerId, j, pR.InputsBufferString(false), pR.RenderFrameBufferString())) panic(fmt.Sprintf("Required refRenderFrameId=%v for roomId=%v, renderFrameId=%v, playerId=%v, playerLastSentInputFrameId=%v, j=%v doesn't exist! InputsBuffer=%v, RenderFrameBuffer=%v", refRenderFrameId, pR.Id, pR.RenderFrameId, playerId, player.LastSentInputFrameId, j, pR.InputsBufferString(false), pR.RenderFrameBufferString()))
} }
Logger.Warn(fmt.Sprintf("Sending refRenderFrameId=%v for roomId=%v, renderFrameId=%v, curDynamicsRenderFrameId=%v, playerId=%v, j=%v: InputsBuffer=%v", refRenderFrameId, pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, playerId, j, pR.InputsBufferString(false))) Logger.Warn(fmt.Sprintf("Sending refRenderFrameId=%v for roomId=%v, , playerId=%v, playerJoinIndex=%v, renderFrameId=%v, curDynamicsRenderFrameId=%v, playerLastSentInputFrameId=%v, lowerToSentInputFrameId=%v, upperToSendInputFrameId=%v, j=%v: InputsBuffer=%v", refRenderFrameId, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, lowerToSentInputFrameId, upperToSendInputFrameId, j, pR.InputsBufferString(false)))
refRenderFrame := tmp.(*RoomDownsyncFrame) refRenderFrame := tmp.(*RoomDownsyncFrame)
for playerId, player := range pR.Players {
refRenderFrame.Players[playerId].ColliderRadius = player.ColliderRadius // hardcoded for now
}
refRenderFrame.BackendUnconfirmedMask = unconfirmedMask refRenderFrame.BackendUnconfirmedMask = unconfirmedMask
pR.sendSafely(refRenderFrame, toSendInputFrameDownsyncs, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId) pR.sendSafely(refRenderFrame, toSendInputFrameDownsyncs, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId)
} else { } else {

View File

@ -440,7 +440,7 @@
"array": [ "array": [
0, 0,
0, 0,
342.9460598986377, 216.50635094610968,
0, 0,
0, 0,
0, 0,

View File

@ -454,7 +454,7 @@
"array": [ "array": [
0, 0,
0, 0,
342.9460598986377, 216.50635094610968,
0, 0,
0, 0,
0, 0,

View File

@ -104,32 +104,6 @@ cc.Class({
return (0 == inputFrameId % 10); return (0 == inputFrameId % 10);
}, },
dumpToRenderCache: function(rdf) {
const self = this;
const minToKeepRenderFrameId = self.lastAllConfirmedRenderFrameId - 1; // Keep at least 1 prev render frame for anim triggering
while (0 < self.recentRenderCache.cnt && self.recentRenderCache.stFrameId < minToKeepRenderFrameId) {
self.recentRenderCache.pop();
}
const [ret, oldStFrameId, oldEdFrameId] = self.recentRenderCache.setByFrameId(rdf, rdf.id);
return ret;
},
dumpToInputCache: function(inputFrameDownsync) {
const self = this;
let minToKeepInputFrameId = self._convertToInputFrameId(self.lastAllConfirmedRenderFrameId, self.inputDelayFrames) - self.spAtkLookupFrames; // [WARNING] This could be different from "self.lastAllConfirmedInputFrameId". We'd like to keep the corresponding delayedInputFrame for "self.lastAllConfirmedRenderFrameId" such that a rollback could place "self.chaserRenderFrameId = self.lastAllConfirmedRenderFrameId" for the worst case incorrect prediction.
if (minToKeepInputFrameId > self.lastAllConfirmedInputFrameId) {
minToKeepInputFrameId = self.lastAllConfirmedInputFrameId;
}
while (0 < self.recentInputCache.cnt && self.recentInputCache.stFrameId < minToKeepInputFrameId) {
self.recentInputCache.pop();
}
const [ret, oldStFrameId, oldEdFrameId] = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId);
if (window.RING_BUFF_FAILED_TO_SET == ret) {
throw `Failed to dump input cache (maybe recentInputCache too small)! inputFrameDownsync.inputFrameId=${inputFrameDownsync.inputFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
return ret;
},
_convertToInputFrameId(renderFrameId, inputDelayFrames) { _convertToInputFrameId(renderFrameId, inputDelayFrames) {
if (renderFrameId < inputDelayFrames) return 0; if (renderFrameId < inputDelayFrames) return 0;
return ((renderFrameId - inputDelayFrames) >> this.inputScaleFrames); return ((renderFrameId - inputDelayFrames) >> this.inputScaleFrames);
@ -156,29 +130,31 @@ cc.Class({
throw `noDelayInputFrameId=${inputFrameId} couldn't be generated: recentInputCache=${self._stringifyRecentInputCache(false)}`; throw `noDelayInputFrameId=${inputFrameId} couldn't be generated: recentInputCache=${self._stringifyRecentInputCache(false)}`;
} }
let previousSelfInput = null,
currSelfInput = null;
const joinIndex = self.selfPlayerInfo.joinIndex; const joinIndex = self.selfPlayerInfo.joinIndex;
const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId); // [WARNING] The while-loop here handles a situation where the "resync rdf & accompaniedInputFrameDownsyncBatch" mismatched and we have to predict some "gap-inputFrames"!
const previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]); while (self.recentInputCache.edFrameId <= inputFrameId) {
// TODO: find some kind of synchronization mechanism against "onInputFrameDownsyncBatch"!
const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId - 1);
previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]);
// If "forceConfirmation" is active on backend, we shouldn't override the already downsynced "inputFrameDownsync"s. // If "forceConfirmation" is active on backend, there's a chance that the already downsynced "inputFrameDownsync"s are ahead of a locally generating inputFrameId, in this case we respect the downsynced one.
const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId); const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId);
if (null != existingInputFrame && self._allConfirmed(existingInputFrame.confirmedList)) { if (null != existingInputFrame && self._allConfirmed(existingInputFrame.confirmedList)) {
console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache and is all-confirmed: recentInputCache=${self._stringifyRecentInputCache(false)}`); console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache and is all-confirmed: recentInputCache=${self._stringifyRecentInputCache(false)}`);
return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]]; return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]];
} }
const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice()); const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice());
const currSelfInput = self.ctrl.getEncodedInput(); currSelfInput = self.ctrl.getEncodedInput();
prefabbedInputList[(joinIndex - 1)] = currSelfInput; prefabbedInputList[(joinIndex - 1)] = currSelfInput;
const prefabbedInputFrameDownsync = window.pb.protos.InputFrameDownsync.create({ const prefabbedInputFrameDownsync = window.pb.protos.InputFrameDownsync.create({
inputFrameId: inputFrameId, inputFrameId: self.recentInputCache.edFrameId,
inputList: prefabbedInputList, inputList: prefabbedInputList,
confirmedList: (1 << (self.selfPlayerInfo.joinIndex - 1)) confirmedList: (1 << (self.selfPlayerInfo.joinIndex - 1))
}); });
self.dumpToInputCache(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames" self.recentInputCache.put(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
if (inputFrameId >= self.recentInputCache.edFrameId) {
throw `noDelayInputFrameId=${inputFrameId} seems not properly dumped #1: recentInputCache=${self._stringifyRecentInputCache(false)}`;
} }
return [previousSelfInput, currSelfInput]; return [previousSelfInput, currSelfInput];
@ -328,6 +304,8 @@ cc.Class({
self.collisionBulletIndexPrefix = (1 << 15); // For tracking the movements of bullets self.collisionBulletIndexPrefix = (1 << 15); // For tracking the movements of bullets
self.collisionSysMap = new Map(); self.collisionSysMap = new Map();
console.log(`collisionSys & collisionSysMap reset`);
self.transitToState(ALL_MAP_STATES.VISUAL); self.transitToState(ALL_MAP_STATES.VISUAL);
self.battleState = ALL_BATTLE_STATES.WAITING; self.battleState = ALL_BATTLE_STATES.WAITING;
@ -389,8 +367,7 @@ cc.Class({
window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ ); window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ );
}; };
resultPanelScriptIns.onCloseDelegate = () => { resultPanelScriptIns.onCloseDelegate = () => {};
};
self.gameRuleNode = cc.instantiate(self.gameRulePrefab); self.gameRuleNode = cc.instantiate(self.gameRulePrefab);
self.gameRuleNode.width = self.canvasNode.width; self.gameRuleNode.width = self.canvasNode.width;
@ -422,6 +399,7 @@ cc.Class({
/** Init required prefab ended. */ /** Init required prefab ended. */
window.handleBattleColliderInfo = function(parsedBattleColliderInfo) { window.handleBattleColliderInfo = function(parsedBattleColliderInfo) {
console.log(`Received parsedBattleColliderInfo via ws`);
// TODO: Upon reconnection, the backend might have already been sending down data that'd trigger "onRoomDownsyncFrame & onInputFrameDownsyncBatch", but frontend could reject those data due to "battleState != PlayerBattleState.ACTIVE". // TODO: Upon reconnection, the backend might have already been sending down data that'd trigger "onRoomDownsyncFrame & onInputFrameDownsyncBatch", but frontend could reject those data due to "battleState != PlayerBattleState.ACTIVE".
Object.assign(self, parsedBattleColliderInfo); Object.assign(self, parsedBattleColliderInfo);
self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis; self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis;
@ -469,7 +447,7 @@ cc.Class({
const x0 = boundaryObj.anchor.x, const x0 = boundaryObj.anchor.x,
y0 = boundaryObj.anchor.y; y0 = boundaryObj.anchor.y;
const newBarrier = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => { const newBarrierCollider = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => {
return [p.x, p.y]; return [p.x, p.y];
})); }));
@ -504,10 +482,10 @@ cc.Class({
} }
} }
// console.log("Created barrier: ", newBarrier);
++barrierIdCounter; ++barrierIdCounter;
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter); const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
self.collisionSysMap.set(collisionBarrierIndex, newBarrier); self.collisionSysMap.set(collisionBarrierIndex, newBarrierCollider);
console.log(`Created new barrier collider: ${newBarrierCollider}`);
} }
self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')); self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer'));
@ -520,6 +498,7 @@ cc.Class({
act: window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK, act: window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK,
}).finish(); }).finish();
window.sendSafely(reqData); window.sendSafely(reqData);
console.log(`Sent UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK via ws`);
}); });
}; };
@ -594,7 +573,7 @@ cc.Class({
const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id); const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id);
const shouldForceDumping2 = (rdf.id > self.renderFrameId + self.renderFrameIdLagTolerance); const shouldForceDumping2 = (rdf.id > self.renderFrameId + self.renderFrameIdLagTolerance);
const dumpRenderCacheRet = (shouldForceDumping1 || shouldForceDumping2) ? self.dumpToRenderCache(rdf) : window.RING_BUFF_CONSECUTIVE_SET; const [dumpRenderCacheRet, oldStRenderFrameId, oldEdRenderFrameId] = (shouldForceDumping1 || shouldForceDumping2) ? self.recentRenderCache.setByFrameId(rdf, rdf.id) : [window.RING_BUFF_CONSECUTIVE_SET, null, null];
if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) { if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) {
throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.id=${rdf.id}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`; throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.id=${rdf.id}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
} }
@ -625,6 +604,7 @@ cc.Class({
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id) { if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id) {
console.log('On battle started! renderFrameId=', rdf.id); console.log('On battle started! renderFrameId=', rdf.id);
} else { } else {
self.hideFindingPlayersGUI(rdf);
self.onInputFrameDownsyncBatch(accompaniedInputFrameDownsyncBatch); // Important to do this step before setting IN_BATTLE self.onInputFrameDownsyncBatch(accompaniedInputFrameDownsyncBatch); // Important to do this step before setting IN_BATTLE
console.warn(`Got resync@localRenderFrameId=${self.renderFrameId} -> rdf.id=${rdf.id} & rdf.backendUnconfirmedMask=${rdf.backendUnconfirmedMask}, @lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${self.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}`); console.warn(`Got resync@localRenderFrameId=${self.renderFrameId} -> rdf.id=${rdf.id} & rdf.backendUnconfirmedMask=${rdf.backendUnconfirmedMask}, @lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${self.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}`);
} }
@ -671,6 +651,7 @@ cc.Class({
}, },
onInputFrameDownsyncBatch(batch) { onInputFrameDownsyncBatch(batch) {
// TODO: find some kind of synchronization mechanism against "_generateInputFrameUpsync"!
const self = this; const self = this;
if (!self.recentInputCache) { if (!self.recentInputCache) {
return; return;
@ -698,7 +679,10 @@ cc.Class({
} }
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase". // [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1; inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1;
self.dumpToInputCache(inputFrameDownsync); const [ret, oldStFrameId, oldEdFrameId] = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId);
if (window.RING_BUFF_FAILED_TO_SET == ret) {
throw `Failed to dump input cache (maybe recentInputCache too small)! inputFrameDownsync.inputFrameId=${inputFrameDownsync.inputFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
} }
if (null == firstPredictedYetIncorrectInputFrameId) return; if (null == firstPredictedYetIncorrectInputFrameId) return;
@ -794,6 +778,8 @@ cc.Class({
newPlayerCollider.data = playerDownsyncInfo; newPlayerCollider.data = playerDownsyncInfo;
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider); self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
console.log(`Created new player collider: joinIndex=${joinIndex}, colliderRadius=${playerDownsyncInfo.colliderRadius}`);
safelyAddChild(self.node, newPlayerNode); safelyAddChild(self.node, newPlayerNode);
setLocalZOrder(newPlayerNode, 5); setLocalZOrder(newPlayerNode, 5);
@ -1266,7 +1252,7 @@ cc.Class({
// Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic! // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
self.chaserRenderFrameId = latestRdf.id; self.chaserRenderFrameId = latestRdf.id;
} }
self.dumpToRenderCache(latestRdf); self.recentRenderCache.setByFrameId(latestRdf, latestRdf.id);
++i; ++i;
} while (i < renderFrameIdEd); } while (i < renderFrameIdEd);

View File

@ -33,6 +33,7 @@ cc.Class({
self.inputScaleFrames = 2; self.inputScaleFrames = 2;
self.inputFrameUpsyncDelayTolerance = 2; self.inputFrameUpsyncDelayTolerance = 2;
self.renderCacheSize = 1024;
self.rollbackEstimatedDt = 0.016667; self.rollbackEstimatedDt = 0.016667;
self.rollbackEstimatedDtMillis = 16.667; self.rollbackEstimatedDtMillis = 16.667;
self.rollbackEstimatedDtNanos = 16666666; self.rollbackEstimatedDtNanos = 16666666;

View File

@ -134,7 +134,7 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
clientSession.binaryType = 'arraybuffer'; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob" clientSession.binaryType = 'arraybuffer'; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob"
clientSession.onopen = function(evt) { clientSession.onopen = function(evt) {
console.log("The WS clientSession is opened. clientSession.id=", clientSession.id); console.log("The WS clientSession is opened.");
window.clientSession = clientSession; window.clientSession = clientSession;
if (null == onopenCb) return; if (null == onopenCb) return;
onopenCb(); onopenCb();
@ -171,7 +171,6 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
${JSON.stringify(resp, null, 2)}`); ${JSON.stringify(resp, null, 2)}`);
return; return;
} }
mapIns.hideFindingPlayersGUI(resp.rdf);
const inputFrameIdConsecutive = (resp.inputFrameDownsyncBatch[0].inputFrameId == mapIns.lastAllConfirmedInputFrameId + 1); const inputFrameIdConsecutive = (resp.inputFrameDownsyncBatch[0].inputFrameId == mapIns.lastAllConfirmedInputFrameId + 1);
mapIns.onRoomDownsyncFrame(resp.rdf, resp.inputFrameDownsyncBatch); mapIns.onRoomDownsyncFrame(resp.rdf, resp.inputFrameDownsyncBatch);
break; break;