Fixed multiple error handling spots.

This commit is contained in:
genxium 2022-11-29 21:32:18 +08:00
parent 080a384ade
commit 1f5802ee14
10 changed files with 201 additions and 124 deletions

View File

@ -21,6 +21,10 @@ func NewRingBuffer(n int32) *RingBuffer {
}
func (rb *RingBuffer) Put(pItem interface{}) {
for rb.Cnt >= rb.N-1 {
// Make room for the new element
rb.Pop()
}
rb.Eles[rb.Ed] = pItem
rb.EdFrameId++
rb.Cnt++
@ -69,5 +73,8 @@ func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} {
}
func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} {
if frameId >= rb.EdFrameId {
return nil
}
return rb.GetByOffset(frameId - rb.StFrameId)
}

View File

@ -335,10 +335,6 @@ func (pR *Room) ConvertToGeneratingRenderFrameId(inputFrameId int32) int32 {
return (inputFrameId << pR.InputScaleFrames)
}
func (pR *Room) ConvertToJustBeforeNextGeneratingRenderFrameId(inputFrameId int32) int32 {
return (inputFrameId << pR.InputScaleFrames) + (1 << pR.InputScaleFrames) - 1
}
func (pR *Room) ConvertToFirstUsedRenderFrameId(inputFrameId int32, inputDelayFrames int32) int32 {
return ((inputFrameId << pR.InputScaleFrames) + inputDelayFrames)
}
@ -413,7 +409,6 @@ func (pR *Room) StartBattle() {
pR.onBattleStoppedForSettlement()
}()
battleStartedAtNanos := utils.UnixtimeNano()
pR.LastRenderFrameIdTriggeredAt = utils.UnixtimeNano()
Logger.Info("The `battleMainLoop` is started for:", zap.Any("roomId", pR.Id))
@ -422,11 +417,7 @@ func (pR *Room) StartBattle() {
elapsedNanosSinceLastFrameIdTriggered := stCalculation - pR.LastRenderFrameIdTriggeredAt
if elapsedNanosSinceLastFrameIdTriggered < pR.dilutedRollbackEstimatedDtNanos {
totalElapsedNanos := (stCalculation - battleStartedAtNanos)
serverFpsByFar := float64(pR.RenderFrameId) * float64(1000000000) / float64(totalElapsedNanos)
Logger.Info(fmt.Sprintf("Avoiding too fast frame@roomId=%v, renderFrameId=%v, totalElapsedNanos=%v, serverFpsByFar=%v: elapsedNanosSinceLastFrameIdTriggered=%v", pR.Id, pR.RenderFrameId, totalElapsedNanos, serverFpsByFar, elapsedNanosSinceLastFrameIdTriggered))
time.Sleep(time.Duration(pR.dilutedRollbackEstimatedDtNanos - elapsedNanosSinceLastFrameIdTriggered))
continue
Logger.Info(fmt.Sprintf("renderFrameId=%v@roomId=%v: Is backend running too fast? elapsedNanosSinceLastFrameIdTriggered=%v", pR.RenderFrameId, pR.Id, elapsedNanosSinceLastFrameIdTriggered))
}
if pR.RenderFrameId > pR.BattleDurationFrames {
@ -461,7 +452,7 @@ func (pR *Room) StartBattle() {
Upon resync, it's still possible that "refRenderFrameId < frontend.chaserRenderFrameId" -- and this is allowed.
*/
refRenderFrameId := pR.ConvertToJustBeforeNextGeneratingRenderFrameId(upperToSendInputFrameId)
refRenderFrameId := pR.ConvertToGeneratingRenderFrameId(upperToSendInputFrameId + 1) // for the frontend to jump immediately into generating & upsyncing the next input frame, thus getting rid of "resync avalanche"
dynamicsDuration := int64(0)
if pR.BackendDynamicsEnabled {
@ -480,10 +471,13 @@ func (pR *Room) StartBattle() {
}
for playerId, player := range pR.Players {
if swapped := atomic.CompareAndSwapInt32(&player.BattleState, PlayerBattleStateIns.ACTIVE, PlayerBattleStateIns.ACTIVE); !swapped {
// [WARNING] DON'T send anything if the player is not yet active, because it could jam the channel and cause significant delay upon "battle recovery for reconnected player".
currPlayerBattleState := atomic.LoadInt32(&(player.BattleState))
if PlayerBattleStateIns.DISCONNECTED == currPlayerBattleState || PlayerBattleStateIns.LOST == currPlayerBattleState {
// [WARNING] DON'T try to send any message to an inactive player!
continue
}
if 0 == pR.RenderFrameId {
kickoffFrame := pR.RenderFrameBuffer.GetByFrameId(0).(*RoomDownsyncFrame)
pR.sendSafely(kickoffFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId)
@ -531,14 +525,15 @@ func (pR *Room) StartBattle() {
2. reconnection
*/
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
// 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)
if nil == tmp {
panic(fmt.Sprintf("Required refRenderFrameId=%v for roomId=%v, playerId=%v, candidateToSendInputFrameId=%v doesn't exist! InputsBuffer=%v, RenderFrameBuffer=%v", refRenderFrameId, pR.Id, playerId, candidateToSendInputFrameId, pR.InputsBufferString(false), pR.RenderFrameBufferString()))
}
refRenderFrame := tmp.(*RoomDownsyncFrame)
refRenderFrame.BackendUnconfirmedMask = unconfirmedMask
pR.sendSafely(refRenderFrame, toSendInputFrames, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId)
} else {
pR.sendSafely(nil, toSendInputFrames, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId)
@ -549,7 +544,7 @@ func (pR *Room) StartBattle() {
if pR.BackendDynamicsEnabled {
// Evict no longer required "RenderFrameBuffer"
for pR.RenderFrameBuffer.N < pR.RenderFrameBuffer.Cnt || (0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < refRenderFrameId) {
for 0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < refRenderFrameId {
_ = pR.RenderFrameBuffer.Pop()
}
}
@ -572,7 +567,7 @@ func (pR *Room) StartBattle() {
if minLastSentInputFrameId < minToKeepInputFrameId {
minToKeepInputFrameId = minLastSentInputFrameId
}
for pR.InputsBuffer.N < pR.InputsBuffer.Cnt || (0 < pR.InputsBuffer.Cnt && pR.InputsBuffer.StFrameId < minToKeepInputFrameId) {
for 0 < pR.InputsBuffer.Cnt && pR.InputsBuffer.StFrameId < minToKeepInputFrameId {
f := pR.InputsBuffer.Pop().(*InputFrameDownsync)
if pR.inputFrameIdDebuggable(f.InputFrameId) {
// Popping of an "inputFrame" would be AFTER its being all being confirmed, because it requires the "inputFrame" to be all acked
@ -798,17 +793,17 @@ func (pR *Room) OnDismissed() {
pR.RenderFrameId = 0
pR.CurDynamicsRenderFrameId = 0
pR.InputDelayFrames = 8
pR.NstDelayFrames = pR.InputDelayFrames
pR.NstDelayFrames = 4
pR.InputScaleFrames = uint32(2)
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.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for preventing FAST FRAME
dilutionFactor := 12
dilutionFactor := 24
pR.dilutedRollbackEstimatedDtNanos = int64(16666666 * (dilutionFactor) / (dilutionFactor - 1)) // [WARNING] Only used in controlling "battleMainLoop" to be keep a frame rate lower than that of the frontends, such that upon resync(i.e. BackendDynamicsEnabled=true), the frontends would have bigger chances to keep up with or even surpass the backend calculation
pR.BattleDurationFrames = 30 * pR.ServerFps
pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1)
pR.InputFrameUpsyncDelayTolerance = 2
pR.MaxChasingRenderFramesPerUpdate = 5
pR.MaxChasingRenderFramesPerUpdate = 8
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
pR.BackendDynamicsForceConfirmationEnabled = (pR.BackendDynamicsEnabled && true)
@ -874,8 +869,9 @@ func (pR *Room) OnPlayerDisconnected(playerId int32) {
}
}()
if _, existent := pR.Players[playerId]; existent {
switch pR.Players[playerId].BattleState {
if player, existent := pR.Players[playerId]; existent {
currPlayerBattleState := atomic.LoadInt32(&(player.BattleState))
switch currPlayerBattleState {
case PlayerBattleStateIns.DISCONNECTED:
case PlayerBattleStateIns.LOST:
case PlayerBattleStateIns.EXPELLED_DURING_GAME:
@ -889,17 +885,18 @@ func (pR *Room) OnPlayerDisconnected(playerId int32) {
return
}
switch pR.State {
currRoomBattleState := atomic.LoadInt32(&(pR.State))
switch currRoomBattleState {
case RoomBattleStateIns.WAITING:
pR.onPlayerLost(playerId)
delete(pR.Players, playerId) // Note that this statement MUST be put AFTER `pR.onPlayerLost(...)` to avoid nil pointer exception.
if 0 == pR.EffectivePlayerCount {
pR.State = RoomBattleStateIns.IDLE
atomic.StoreInt32(&(pR.State), RoomBattleStateIns.IDLE)
}
pR.updateScore()
Logger.Info("Player disconnected while room is at RoomBattleStateIns.WAITING:", zap.Any("playerId", playerId), zap.Any("roomId", pR.Id), zap.Any("nowRoomBattleState", pR.State), zap.Any("nowRoomEffectivePlayerCount", pR.EffectivePlayerCount))
default:
pR.Players[playerId].BattleState = PlayerBattleStateIns.DISCONNECTED
atomic.StoreInt32(&(pR.Players[playerId].BattleState), PlayerBattleStateIns.DISCONNECTED)
pR.clearPlayerNetworkSession(playerId) // Still need clear the network session pointers, because "OnPlayerDisconnected" is only triggered from "signalToCloseConnOfThisPlayer" in "ws/serve.go", when the same player reconnects the network session pointers will be re-assigned
Logger.Info("Player disconnected from room:", zap.Any("playerId", playerId), zap.Any("playerBattleState", pR.Players[playerId].BattleState), zap.Any("roomId", pR.Id), zap.Any("nowRoomBattleState", pR.State), zap.Any("nowRoomEffectivePlayerCount", pR.EffectivePlayerCount))
}
@ -912,7 +909,7 @@ func (pR *Room) onPlayerLost(playerId int32) {
}
}()
if player, existent := pR.Players[playerId]; existent {
player.BattleState = PlayerBattleStateIns.LOST
atomic.StoreInt32(&(player.BattleState), PlayerBattleStateIns.LOST)
pR.clearPlayerNetworkSession(playerId)
pR.EffectivePlayerCount--
indiceInJoinIndexBooleanArr := int(player.JoinIndex - 1)

View File

@ -1059,10 +1059,11 @@ type RoomDownsyncFrame struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Players map[int32]*PlayerDownsync `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
CountdownNanos int64 `protobuf:"varint,3,opt,name=countdownNanos,proto3" json:"countdownNanos,omitempty"`
MeleeBullets []*MeleeBullet `protobuf:"bytes,4,rep,name=meleeBullets,proto3" json:"meleeBullets,omitempty"` // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Players map[int32]*PlayerDownsync `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
CountdownNanos int64 `protobuf:"varint,3,opt,name=countdownNanos,proto3" json:"countdownNanos,omitempty"`
MeleeBullets []*MeleeBullet `protobuf:"bytes,4,rep,name=meleeBullets,proto3" json:"meleeBullets,omitempty"` // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
BackendUnconfirmedMask uint64 `protobuf:"varint,5,opt,name=backendUnconfirmedMask,proto3" json:"backendUnconfirmedMask,omitempty"` // Indexed by "joinIndex", same compression concern as stated in InputFrameDownsync
}
func (x *RoomDownsyncFrame) Reset() {
@ -1125,6 +1126,13 @@ func (x *RoomDownsyncFrame) GetMeleeBullets() []*MeleeBullet {
return nil
}
func (x *RoomDownsyncFrame) GetBackendUnconfirmedMask() uint64 {
if x != nil {
return x.BackendUnconfirmedMask
}
return 0
}
var File_room_downsync_frame_proto protoreflect.FileDescriptor
var file_room_downsync_frame_proto_rawDesc = []byte{
@ -1377,7 +1385,7 @@ var file_room_downsync_frame_proto_rawDesc = []byte{
0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65,
0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x22, 0x9a, 0x02, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73,
0x38, 0x01, 0x22, 0xd2, 0x02, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73,
0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x40, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79,
0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74,
@ -1389,14 +1397,18 @@ var file_room_downsync_frame_proto_rawDesc = []byte{
0x6f, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65,
0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x0c, 0x6d,
0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x1a, 0x52, 0x0a, 0x0c, 0x50,
0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x44, 0x6f, 0x77, 0x6e,
0x73, 0x79, 0x6e, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42,
0x13, 0x5a, 0x11, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x73, 0x72, 0x76, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x62,
0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65,
0x64, 0x4d, 0x61, 0x73, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x62, 0x61, 0x63,
0x6b, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d,
0x61, 0x73, 0x6b, 0x1a, 0x52, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6c,
0x61, 0x79, 0x65, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x13, 0x5a, 0x11, 0x62, 0x61, 0x74, 0x74, 0x6c,
0x65, 0x5f, 0x73, 0x72, 0x76, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@ -134,4 +134,5 @@ message RoomDownsyncFrame {
map<int32, PlayerDownsync> players = 2;
int64 countdownNanos = 3;
repeated MeleeBullet meleeBullets = 4; // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
uint64 backendUnconfirmedMask = 5; // Indexed by "joinIndex", same compression concern as stated in InputFrameDownsync
}

View File

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

View File

@ -96,7 +96,7 @@ cc.Class({
_interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName) {
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
// No "framesToRecover"
console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, playing new ${newAnimName} from the beginning: while the playing anim is ${playingAnimName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, to: ${JSON.stringify(rdfPlayer)}`);
//console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1);
} else {
const animationData = underlyingAnimationCtrl._animations[newAnimName];
@ -112,7 +112,7 @@ cc.Class({
_interruptPlayingAnimAndPlayNewAnimFrameAnim(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, playingAnimName) {
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
// No "framesToRecover"
console.warn(`#FrameAnim JoinIndex=${rdfPlayer.joinIndex}, playing new ${newAnimName} from the beginning: while the playing anim is ${playingAnimName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, to: ${JSON.stringify(rdfPlayer)}`);
//console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
this.animComp.play(newAnimName, 0);
return;
}

View File

@ -110,7 +110,7 @@ cc.Class({
while (0 < self.recentRenderCache.cnt && self.recentRenderCache.stFrameId < minToKeepRenderFrameId) {
self.recentRenderCache.pop();
}
const ret = self.recentRenderCache.setByFrameId(rdf, rdf.id);
const [ret, oldStFrameId, oldEdFrameId] = self.recentRenderCache.setByFrameId(rdf, rdf.id);
return ret;
},
@ -123,9 +123,12 @@ cc.Class({
while (0 < self.recentInputCache.cnt && self.recentInputCache.stFrameId < minToKeepInputFrameId) {
self.recentInputCache.pop();
}
const ret = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId);
if (-1 < self.lastAllConfirmedInputFrameId && self.recentInputCache.stFrameId > self.lastAllConfirmedInputFrameId) {
console.error("Invalid input cache dumped! lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId, ", recentRenderCache=", self._stringifyRecentRenderCache(false), ", recentInputCache=", self._stringifyRecentInputCache(false));
const [ret, oldStFrameId, oldEdFrameId] = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId);
if (window.RING_BUFF_NON_CONSECUTIVE_SET == ret) {
throw `Failed to dump input cache#1! inputFrameDownsync.inputFrameId=${inputFrameDownsync.inputFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
if (window.RING_BUFF_FAILED_TO_SET == ret) {
throw `Failed to dump input cache#2 (maybe recentInputCache too small)! inputFrameDownsync.inputFrameId=${inputFrameDownsync.inputFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
return ret;
},
@ -153,7 +156,7 @@ cc.Class({
null == self.ctrl ||
null == self.selfPlayerInfo
) {
return [null, null];
throw `noDelayInputFrameId=${inputFrameId} couldn't be generated: recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
const joinIndex = self.selfPlayerInfo.joinIndex;
@ -163,19 +166,24 @@ cc.Class({
// If "forceConfirmation" is active on backend, we shouldn't override the already downsynced "inputFrameDownsync"s.
const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId);
if (null != existingInputFrame && self._allConfirmed(existingInputFrame.confirmedList)) {
console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache and is all-confirmed: recentInputCache=${self._stringifyRecentInputCache(false)}`);
return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]];
}
const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice());
const currSelfInput = self.ctrl.getEncodedInput();
prefabbedInputList[(joinIndex - 1)] = currSelfInput;
const prefabbedInputFrameDownsync = {
const prefabbedInputFrameDownsync = window.pb.protos.InputFrameDownsync.create({
inputFrameId: inputFrameId,
inputList: prefabbedInputList,
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"
if (inputFrameId >= self.recentInputCache.edFrameId) {
throw `noDelayInputFrameId=${inputFrameId} seems not properly dumped #1: recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
return [previousSelfInput, currSelfInput];
},
@ -203,7 +211,7 @@ cc.Class({
for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) {
const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
if (null == inputFrameDownsync) {
console.error("sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=", i, ": latestLocalInputFrameId=", latestLocalInputFrameId, ", recentInputCache=", self._stringifyRecentInputCache(false));
console.error(`sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=i: latestLocalInputFrameId=${latestLocalInputFrameId}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
} else {
const inputFrameUpsync = {
inputFrameId: i,
@ -225,6 +233,9 @@ cc.Class({
}).finish();
window.sendSafely(reqData);
self.lastUpsyncInputFrameId = latestLocalInputFrameId;
if (self.lastUpsyncInputFrameId >= self.recentInputCache.edFrameId) {
throw `noDelayInputFrameId=${self.lastUpsyncInputFrameId} == latestLocalInputFrameId=${latestLocalInputFrameId} seems not properly dumped #2: recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
},
onEnable() {
@ -413,7 +424,9 @@ cc.Class({
/** Init required prefab ended. */
window.handleBattleColliderInfo = function(parsedBattleColliderInfo) {
// 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);
self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis;
const tiledMapIns = self.node.getComponent(cc.TiledMap);
@ -574,14 +587,18 @@ cc.Class({
onRoomDownsyncFrame(rdf) {
// This function is also applicable to "re-joining".
const self = window.mapIns;
if (rdf.id < self.lastAllConfirmedRenderFrameId) {
return window.RING_BUFF_FAILED_TO_SET;
if (!self.recentRenderCache) {
return;
}
if (ALL_BATTLE_STATES.IN_SETTLEMENT == self.battleState) {
return;
}
const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id);
const shouldForceDumping2 = (rdf.id > self.renderFrameId + self.renderFrameIdLagTolerance);
const dumpRenderCacheRet = self.dumpToRenderCache(rdf);
const dumpRenderCacheRet = (shouldForceDumping1 || shouldForceDumping2) ? self.dumpToRenderCache(rdf) : window.RING_BUFF_CONSECUTIVE_SET;
if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) {
console.error("Something is wrong while setting the RingBuffer by frameId!");
return 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)}`;
}
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet) {
/*
@ -592,13 +609,7 @@ cc.Class({
return dumpRenderCacheRet;
}
// The logic below applies to ( || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id) {
console.log('On battle started! renderFrameId=', rdf.id);
} else {
console.log('On battle resynced! renderFrameId=', rdf.id);
}
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
const players = rdf.players;
self._initPlayerRichInfoDict(players);
@ -612,11 +623,22 @@ cc.Class({
if (null == self.renderFrameId || self.renderFrameId <= rdf.id) {
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id) {
console.log('On battle started! renderFrameId=', rdf.id);
} else {
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)}`);
}
self.renderFrameId = rdf.id;
self.lastRenderFrameIdTriggeredAt = performance.now();
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
self.lastAllConfirmedRenderFrameId = rdf.id;
self.chaserRenderFrameId = rdf.id;
const candidateLastAllConfirmedInputFrame = self._convertToInputFrameId(rdf.id - 1, self.inputDelayFrames);
if (self.lastAllConfirmedInputFrame < candidateLastAllConfirmedInputFrame) {
self.lastAllConfirmedInputFrame = candidateLastAllConfirmedInputFrame;
}
const canvasNode = self.canvasNode;
self.ctrl = canvasNode.getComponent("TouchEventsManager");
@ -651,8 +673,10 @@ cc.Class({
onInputFrameDownsyncBatch(batch) {
const self = this;
if (ALL_BATTLE_STATES.IN_BATTLE != self.battleState
&& ALL_BATTLE_STATES.IN_SETTLEMENT != self.battleState) {
if (!self.recentInputCache) {
return;
}
if (ALL_BATTLE_STATES.IN_SETTLEMENT == self.battleState) {
return;
}
@ -663,6 +687,7 @@ cc.Class({
if (inputFrameDownsyncId < self.lastAllConfirmedInputFrameId) {
continue;
}
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId);
if (null != localInputFrame
&&
@ -672,7 +697,6 @@ cc.Class({
) {
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
}
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1;
self.dumpToInputCache(inputFrameDownsync);
@ -716,7 +740,7 @@ cc.Class({
logBattleStats() {
const self = this;
let s = [];
s.push(`Battle stats: renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}`);
s.push(`Battle stats: renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) {
const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
@ -761,7 +785,8 @@ cc.Class({
const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
newPlayerNode.setPosition(wx, wy);
playerScriptIns.mapNode = self.node;
const colliderWidth = playerDownsyncInfo.colliderRadius * 2, colliderHeight = playerDownsyncInfo.colliderRadius * 3;
const colliderWidth = playerDownsyncInfo.colliderRadius * 2,
colliderHeight = playerDownsyncInfo.colliderRadius * 3;
const [x0, y0] = self.virtualGridToPolygonColliderAnchorPos(vx, vy, colliderWidth, colliderHeight),
pts = [[0, 0], [colliderWidth, 0], [colliderWidth, colliderHeight], [0, colliderHeight]];
@ -783,7 +808,8 @@ cc.Class({
const self = this;
if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) {
const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt;
if (elapsedMillisSinceLastFrameIdTriggered < (self.rollbackEstimatedDtMillis)) {
if (elapsedMillisSinceLastFrameIdTriggered < self.tooFastDtIntervalMillis) {
// [WARNING] We should avoid a frontend ticking too fast to prevent cheating, as well as ticking too slow to cause a "resync avalanche" that impacts user experience!
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
return;
}
@ -822,17 +848,13 @@ cc.Class({
*/
// [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!
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
self.lastRenderFrameIdTriggeredAt = performance.now();
let t3 = performance.now();
} catch (err) {
console.error("Error during Map.update", err);
self.onBattleStopped(); // TODO: Popup to ask player to refresh browser
} finally {
// Update countdown
self.countdownNanos = self.battleDurationNanos - self.renderFrameId * self.rollbackEstimatedDtNanos;
if (self.countdownNanos <= 0) {
self.onBattleStopped(self.playerRichInfoDict);
return;
}
const countdownSeconds = parseInt(self.countdownNanos / 1000000000);
if (isNaN(countdownSeconds)) {
console.warn(`countdownSeconds is NaN for countdownNanos == ${self.countdownNanos}.`);
@ -840,8 +862,6 @@ cc.Class({
if (null != self.countdownLabel) {
self.countdownLabel.string = countdownSeconds;
}
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
self.lastRenderFrameIdTriggeredAt = performance.now();
}
}
},
@ -967,15 +987,21 @@ cc.Class({
playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed);
playerRichInfo.scriptIns.updateCharacterAnim(immediatePlayerInfo, prevRdfPlayer, false);
}
// Update countdown
self.countdownNanos = self.battleDurationNanos - self.renderFrameId * self.rollbackEstimatedDtNanos;
if (self.countdownNanos <= 0) {
self.onBattleStopped(self.playerRichInfoDict);
}
},
getCachedInputFrameDownsyncWithPrediction(inputFrameId) {
const self = this;
let inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId);
if (null != inputFrameDownsync && -1 != self.lastAllConfirmedInputFrameId && inputFrameId > self.lastAllConfirmedInputFrameId) {
const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId);
const inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId);
const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId);
if (null != inputFrameDownsync && null != lastAllConfirmedInputFrame && inputFrameId > self.lastAllConfirmedInputFrameId) {
for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) {
if (i == self.selfPlayerInfo.joinIndex - 1) continue;
if (i == (self.selfPlayerInfo.joinIndex - 1)) continue;
inputFrameDownsync.inputList[i] = (lastAllConfirmedInputFrame.inputList[i] & 15); // Don't predict attack input!
}
}
@ -1007,11 +1033,7 @@ cc.Class({
};
}
const toRet = {
id: currRenderFrame.id + 1,
players: nextRenderFramePlayers,
meleeBullets: []
};
const nextRenderFrameMeleeBullets = [];
const bulletPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
const effPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
@ -1104,7 +1126,7 @@ cc.Class({
collisionSysMap.delete(collisionBulletIndex);
}
if (removedBulletsAtCurrFrame.has(collisionBulletIndex)) continue;
toRet.meleeBullets.push(meleeBullet);
nextRenderFrameMeleeBullets.push(meleeBullet);
}
// Process player inputs
@ -1145,7 +1167,7 @@ cc.Class({
punch.offenderJoinIndex = joinIndex;
punch.offenderPlayerId = playerId;
punch.originatedRenderFrameId = currRenderFrame.id;
toRet.meleeBullets.push(punch);
nextRenderFrameMeleeBullets.push(punch);
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`);
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
@ -1198,7 +1220,11 @@ cc.Class({
}
return toRet;
return window.pb.protos.RoomDownsyncFrame.create({
id: currRenderFrame.id + 1,
players: nextRenderFramePlayers,
meleeBullets: nextRenderFrameMeleeBullets,
});
},
rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap, isChasing) {
@ -1206,31 +1232,24 @@ cc.Class({
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted.
*/
const self = this;
let prevLatestRdf = null;
let latestRdf = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame"
if (null == latestRdf) {
console.error(`Couldn't find renderFrameId=${renderFrameIdSt}, to rollback, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
return [prevLatestRdf, latestRdf];
}
let i = renderFrameIdSt,
prevLatestRdf = null,
latestRdf = null;
if (renderFrameIdSt >= renderFrameIdEd) {
return [prevLatestRdf, latestRdf];
}
for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) {
const currRenderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
if (null == currRenderFrame) {
do {
latestRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
if (null == latestRdf) {
console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`);
return [prevLatestRdf, latestRdf];
}
const j = self._convertToInputFrameId(i, self.inputDelayFrames);
const delayedInputFrame = self.getCachedInputFrameDownsyncWithPrediction(j);
if (null == delayedInputFrame) {
console.warn(`Failed to get cached delayedInputFrame for i=${i}, j=${j}, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`);
return [prevLatestRdf, latestRdf];
// Shouldn't happen!
throw `Failed to get cached delayedInputFrame for i=${i}, j=${j}, renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
}
prevLatestRdf = latestRdf;
latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap);
latestRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, prevLatestRdf, collisionSys, collisionSysMap);
if (
self._allConfirmed(delayedInputFrame.confirmedList)
&&
@ -1249,7 +1268,8 @@ cc.Class({
self.chaserRenderFrameId = latestRdf.id;
}
self.dumpToRenderCache(latestRdf);
}
++i;
} while (i < renderFrameIdEd);
return [prevLatestRdf, latestRdf];
},

View File

@ -13,6 +13,10 @@ var RingBuffer = function(capacity) {
};
RingBuffer.prototype.put = function(item) {
while (this.cnt >= this.n - 1) {
// Make room for the new element
this.pop();
}
this.eles[this.ed] = item
this.edFrameId++;
this.cnt++;
@ -61,40 +65,41 @@ RingBuffer.prototype.getArrIdxByOffset = function(offsetFromSt) {
};
RingBuffer.prototype.getByFrameId = function(frameId) {
if (frameId >= this.edFrameId) return null;
const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
return (null == arrIdx ? null : this.eles[arrIdx]);
};
// [WARNING] During a battle, frontend could receive non-consecutive frames (either renderFrame or inputFrame) due to resync, the buffer should handle these frames properly.
RingBuffer.prototype.setByFrameId = function(item, frameId) {
const oldStFrameId = this.stFrameId,
oldEdFrameId = this.edFrameId;
if (frameId < this.stFrameId) {
console.error("Invalid putByFrameId#1: stFrameId=", this.stFrameId, ", edFrameId=", this.edFrameId, ", incoming item=", item);
return window.RING_BUFF_FAILED_TO_SET;
return [window.RING_BUFF_FAILED_TO_SET, oldStFrameId, oldEdFrameId];
}
const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
if (null != arrIdx) {
this.eles[arrIdx] = item;
return window.RING_BUFF_CONSECUTIVE_SET;
// By now "this.stFrameId <= frameId"
if (this.edFrameId > frameId) {
const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
if (null != arrIdx) {
this.eles[arrIdx] = item;
return [window.RING_BUFF_CONSECUTIVE_SET, oldStFrameId, oldEdFrameId];
}
}
// When "null == arrIdx", should it still be deemed consecutive if "frameId == edFrameId" prior to the reset?
// By now "this.edFrameId <= frameId"
let ret = window.RING_BUFF_CONSECUTIVE_SET;
if (this.edFrameId < frameId) {
this.st = this.ed = 0;
this.stFrameId = this.edFrameId = frameId;
this.cnt = 0;
ret = window.RING_BUFF_NON_CONSECUTIVE_SET;
} else {
// this.edFrameId == frameId
this.put(item);
}
this.eles[this.ed] = item
this.edFrameId++;
this.cnt++;
this.ed++;
if (this.ed >= this.n) {
this.ed -= this.n; // Deliberately not using "%" operator for performance concern
}
return ret;
return [ret, oldStFrameId, oldEdFrameId];
};
module.exports = RingBuffer;

View File

@ -177,8 +177,6 @@ ${JSON.stringify(resp, null, 2)}`);
return;
}
const inputFrameIdConsecutive = (resp.inputFrameDownsyncBatch[0].inputFrameId == mapIns.lastAllConfirmedInputFrameId + 1);
const renderFrameIdConsecutive = (resp.rdf.id <= mapIns.renderFrameId + mapIns.renderFrameIdLagTolerance);
console.warn(`Got resync@localRenderFrameId=${mapIns.renderFrameId}, @lastAllConfirmedRenderFrameId=${mapIns.lastAllConfirmedRenderFrameId}, @lastAllConfirmedInputFrameId=${mapIns.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${mapIns.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}, inputFrameIdConsecutive=${inputFrameIdConsecutive}, renderFrameIdConsecutive=${renderFrameIdConsecutive}`);
// The following order of execution is important
mapIns.onRoomDownsyncFrame(resp.rdf);
mapIns.onInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch);

View File

@ -5082,6 +5082,7 @@ $root.protos = (function() {
* @property {Object.<string,protos.PlayerDownsync>|null} [players] RoomDownsyncFrame players
* @property {number|Long|null} [countdownNanos] RoomDownsyncFrame countdownNanos
* @property {Array.<protos.MeleeBullet>|null} [meleeBullets] RoomDownsyncFrame meleeBullets
* @property {number|Long|null} [backendUnconfirmedMask] RoomDownsyncFrame backendUnconfirmedMask
*/
/**
@ -5133,6 +5134,14 @@ $root.protos = (function() {
*/
RoomDownsyncFrame.prototype.meleeBullets = $util.emptyArray;
/**
* RoomDownsyncFrame backendUnconfirmedMask.
* @member {number|Long} backendUnconfirmedMask
* @memberof protos.RoomDownsyncFrame
* @instance
*/
RoomDownsyncFrame.prototype.backendUnconfirmedMask = $util.Long ? $util.Long.fromBits(0,0,true) : 0;
/**
* Creates a new RoomDownsyncFrame instance using the specified properties.
* @function create
@ -5169,6 +5178,8 @@ $root.protos = (function() {
if (message.meleeBullets != null && message.meleeBullets.length)
for (var i = 0; i < message.meleeBullets.length; ++i)
$root.protos.MeleeBullet.encode(message.meleeBullets[i], writer.uint32(/* id 4, wireType 2 =*/34).fork()).ldelim();
if (message.backendUnconfirmedMask != null && Object.hasOwnProperty.call(message, "backendUnconfirmedMask"))
writer.uint32(/* id 5, wireType 0 =*/40).uint64(message.backendUnconfirmedMask);
return writer;
};
@ -5240,6 +5251,10 @@ $root.protos = (function() {
message.meleeBullets.push($root.protos.MeleeBullet.decode(reader, reader.uint32()));
break;
}
case 5: {
message.backendUnconfirmedMask = reader.uint64();
break;
}
default:
reader.skipType(tag & 7);
break;
@ -5304,6 +5319,9 @@ $root.protos = (function() {
return "meleeBullets." + error;
}
}
if (message.backendUnconfirmedMask != null && message.hasOwnProperty("backendUnconfirmedMask"))
if (!$util.isInteger(message.backendUnconfirmedMask) && !(message.backendUnconfirmedMask && $util.isInteger(message.backendUnconfirmedMask.low) && $util.isInteger(message.backendUnconfirmedMask.high)))
return "backendUnconfirmedMask: integer|Long expected";
return null;
};
@ -5350,6 +5368,15 @@ $root.protos = (function() {
message.meleeBullets[i] = $root.protos.MeleeBullet.fromObject(object.meleeBullets[i]);
}
}
if (object.backendUnconfirmedMask != null)
if ($util.Long)
(message.backendUnconfirmedMask = $util.Long.fromValue(object.backendUnconfirmedMask)).unsigned = true;
else if (typeof object.backendUnconfirmedMask === "string")
message.backendUnconfirmedMask = parseInt(object.backendUnconfirmedMask, 10);
else if (typeof object.backendUnconfirmedMask === "number")
message.backendUnconfirmedMask = object.backendUnconfirmedMask;
else if (typeof object.backendUnconfirmedMask === "object")
message.backendUnconfirmedMask = new $util.LongBits(object.backendUnconfirmedMask.low >>> 0, object.backendUnconfirmedMask.high >>> 0).toNumber(true);
return message;
};
@ -5377,6 +5404,11 @@ $root.protos = (function() {
object.countdownNanos = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
} else
object.countdownNanos = options.longs === String ? "0" : 0;
if ($util.Long) {
var long = new $util.Long(0, 0, true);
object.backendUnconfirmedMask = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long;
} else
object.backendUnconfirmedMask = options.longs === String ? "0" : 0;
}
if (message.id != null && message.hasOwnProperty("id"))
object.id = message.id;
@ -5396,6 +5428,11 @@ $root.protos = (function() {
for (var j = 0; j < message.meleeBullets.length; ++j)
object.meleeBullets[j] = $root.protos.MeleeBullet.toObject(message.meleeBullets[j], options);
}
if (message.backendUnconfirmedMask != null && message.hasOwnProperty("backendUnconfirmedMask"))
if (typeof message.backendUnconfirmedMask === "number")
object.backendUnconfirmedMask = options.longs === String ? String(message.backendUnconfirmedMask) : message.backendUnconfirmedMask;
else
object.backendUnconfirmedMask = options.longs === String ? $util.Long.prototype.toString.call(message.backendUnconfirmedMask) : options.longs === Number ? new $util.LongBits(message.backendUnconfirmedMask.low >>> 0, message.backendUnconfirmedMask.high >>> 0).toNumber(true) : message.backendUnconfirmedMask;
return object;
};