mirror of
https://github.com/genxium/DelayNoMore
synced 2025-01-13 14:31:36 +00:00
Minor updates.
This commit is contained in:
parent
54d6e52498
commit
9d9bea21ef
@ -28,7 +28,7 @@ const (
|
||||
|
||||
DOWNSYNC_MSG_ACT_HB_REQ = int32(1)
|
||||
DOWNSYNC_MSG_ACT_INPUT_BATCH = int32(2)
|
||||
DOWNSYNC_MSG_ACT_ROOM_FRAME = int32(3)
|
||||
DOWNSYNC_MSG_ACT_BATTLE_STOPPED = int32(3)
|
||||
DOWNSYNC_MSG_ACT_FORCED_RESYNC = int32(4)
|
||||
|
||||
DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = int32(-1)
|
||||
@ -334,6 +334,10 @@ func (pR *Room) ConvertToInputFrameId(renderFrameId int32, inputDelayFrames int3
|
||||
return ((renderFrameId - inputDelayFrames) >> pR.InputScaleFrames)
|
||||
}
|
||||
|
||||
func (pR *Room) ConvertToGeneratingRenderFrameId(inputFrameId int32) int32 {
|
||||
return (inputFrameId << pR.InputScaleFrames)
|
||||
}
|
||||
|
||||
func (pR *Room) ConvertToFirstUsedRenderFrameId(inputFrameId int32, inputDelayFrames int32) int32 {
|
||||
return ((inputFrameId << pR.InputScaleFrames) + inputDelayFrames)
|
||||
}
|
||||
@ -349,6 +353,10 @@ func (pR *Room) EncodeUpsyncCmd(upsyncCmd *pb.InputFrameUpsync) uint64 {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (pR *Room) RenderFrameBufferString() string {
|
||||
return fmt.Sprintf("{renderFrameId: %d, stRenderFrameId: %d, edRenderFrameId: %d, lastAllConfirmedRenderFrameId: %d}", pR.RenderFrameId, pR.RenderFrameBuffer.StFrameId, pR.RenderFrameBuffer.EdFrameId, pR.CurDynamicsRenderFrameId)
|
||||
}
|
||||
|
||||
func (pR *Room) AllPlayerInputsBufferString(allDetails bool) string {
|
||||
if allDetails {
|
||||
// Appending of the array of strings can be very SLOW due to on-demand heap allocation! Use this printing with caution.
|
||||
@ -366,7 +374,7 @@ func (pR *Room) AllPlayerInputsBufferString(allDetails bool) string {
|
||||
s = append(s, fmt.Sprintf("{inputFrameId: %v, inputList: %v, confirmedList: %v}", f.InputFrameId, f.InputList, f.ConfirmedList))
|
||||
}
|
||||
|
||||
return strings.Join(s, "\n")
|
||||
return strings.Join(s, "; ")
|
||||
} else {
|
||||
return fmt.Sprintf("{renderFrameId: %d, stInputFrameId: %d, edInputFrameId: %d, lastAllConfirmedInputFrameIdWithChange: %d, lastAllConfirmedInputFrameId: %d}", pR.RenderFrameId, pR.AllPlayerInputsBuffer.StFrameId, pR.AllPlayerInputsBuffer.EdFrameId, pR.LastAllConfirmedInputFrameIdWithChange, pR.LastAllConfirmedInputFrameId)
|
||||
}
|
||||
@ -443,7 +451,7 @@ func (pR *Room) StartBattle() {
|
||||
dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt
|
||||
}
|
||||
|
||||
lastAllConfirmedInputFrameIdWithChange := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameIdWithChange))
|
||||
upperToSendInputFrameId := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameId))
|
||||
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 disconnected, because it could jam the channel and cause significant delay upon "battle recovery for reconnected player".
|
||||
@ -463,46 +471,65 @@ func (pR *Room) StartBattle() {
|
||||
}
|
||||
|
||||
// [WARNING] EDGE CASE HERE: Upon initialization, all of "lastAllConfirmedInputFrameId", "lastAllConfirmedInputFrameIdWithChange" and "anchorInputFrameId" are "-1", thus "candidateToSendInputFrameId" starts with "0", however "inputFrameId: 0" might not have been all confirmed!
|
||||
debugSendingInputFrameId := int32(-1)
|
||||
|
||||
for candidateToSendInputFrameId <= lastAllConfirmedInputFrameIdWithChange {
|
||||
for candidateToSendInputFrameId <= upperToSendInputFrameId {
|
||||
tmp := pR.AllPlayerInputsBuffer.GetByFrameId(candidateToSendInputFrameId)
|
||||
if nil == tmp {
|
||||
panic(fmt.Sprintf("Required inputFrameId=%v for roomId=%v, playerId=%v doesn't exist! AllPlayerInputsBuffer=%v", candidateToSendInputFrameId, pR.Id, playerId, pR.AllPlayerInputsBufferString(false)))
|
||||
}
|
||||
f := tmp.(*pb.InputFrameDownsync)
|
||||
if pR.inputFrameIdDebuggable(candidateToSendInputFrameId) {
|
||||
debugSendingInputFrameId = candidateToSendInputFrameId
|
||||
Logger.Debug("inputFrame lifecycle#3[sending]:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("playerAckingInputFrameId", player.AckingInputFrameId), zap.Any("inputFrameId", candidateToSendInputFrameId), zap.Any("inputFrameId-doublecheck", f.InputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString(false)), zap.Any("ConfirmedList", f.ConfirmedList))
|
||||
}
|
||||
toSendInputFrames = append(toSendInputFrames, f)
|
||||
candidateToSendInputFrameId++
|
||||
}
|
||||
|
||||
if 0 >= len(toSendInputFrames) {
|
||||
// [WARNING] When sending DOWNSYNC_MSG_ACT_FORCED_RESYNC, there MUST BE accompanying "toSendInputFrames" for calculating "refRenderFrameId"!
|
||||
continue
|
||||
}
|
||||
|
||||
indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1)
|
||||
var joinMask uint64 = (1 << indiceInJoinIndexBooleanArr)
|
||||
if 0 < (unconfirmedMask & joinMask) {
|
||||
refRenderFrameId := pR.CurDynamicsRenderFrameId
|
||||
/*
|
||||
[WARNING]
|
||||
"refRenderFrameId" MUST BE CAPPED somehow by "candidateToSendInputFrameId-1", 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".
|
||||
|
||||
If "NstDelayFrames" becomes larger, "pR.RenderFrameId - refRenderFrameId" possibly becomes larger because the force confirmation is delayed more.
|
||||
|
||||
Hence even upon resync, it's still possible that "refRenderFrameId < frontend.chaserRenderFrameId".
|
||||
*/
|
||||
refRenderFrameId := pR.ConvertToGeneratingRenderFrameId(upperToSendInputFrameId) + (1 << pR.InputScaleFrames) - 1
|
||||
// [WARNING] The following inequalities are seldom true, but just to avoid that in good network condition the frontend resyncs itself to a "too advanced frontend.renderFrameId", and then starts upsyncing "too advanced inputFrameId".
|
||||
if refRenderFrameId > pR.RenderFrameId {
|
||||
// [WARNING] To avoid that in good network condition the frontend resyncs itself to a "too advanced frontend.renderFrameId", and then starts upsyncing "too advanced inputFrameId".
|
||||
refRenderFrameId = pR.RenderFrameId
|
||||
}
|
||||
refRenderFrame := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId).(*pb.RoomDownsyncFrame)
|
||||
if refRenderFrameId > pR.CurDynamicsRenderFrameId {
|
||||
refRenderFrameId = pR.CurDynamicsRenderFrameId
|
||||
}
|
||||
tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId)
|
||||
if nil == tmp {
|
||||
panic(fmt.Sprintf("Required refRenderFrameId=%v for roomId=%v, playerId=%v, candidateToSendInputFrameId=%v doesn't exist! AllPlayerInputsBuffer=%v, RenderFrameBuffer=%v", refRenderFrameId, pR.Id, playerId, candidateToSendInputFrameId, pR.AllPlayerInputsBufferString(false), pR.RenderFrameBufferString()))
|
||||
}
|
||||
refRenderFrame := tmp.(*pb.RoomDownsyncFrame)
|
||||
pR.sendSafely(refRenderFrame, toSendInputFrames, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId)
|
||||
} else {
|
||||
if 0 >= len(toSendInputFrames) {
|
||||
continue
|
||||
}
|
||||
pR.sendSafely(nil, toSendInputFrames, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId)
|
||||
}
|
||||
if -1 != debugSendingInputFrameId {
|
||||
Logger.Info("inputFrame lifecycle#4[sent]:", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("playerAckingInputFrameId", player.AckingInputFrameId), zap.Any("inputFrameId", debugSendingInputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString(false)))
|
||||
}
|
||||
atomic.StoreInt32(&(pR.Players[playerId].LastSentInputFrameId), candidateToSendInputFrameId-1)
|
||||
}
|
||||
}
|
||||
|
||||
for 0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < pR.CurDynamicsRenderFrameId {
|
||||
renderFrameBuffLowerBound := pR.ConvertToGeneratingRenderFrameId(upperToSendInputFrameId) + (1 << pR.InputScaleFrames) - 1
|
||||
if renderFrameBuffLowerBound > pR.RenderFrameId {
|
||||
renderFrameBuffLowerBound = pR.RenderFrameId
|
||||
}
|
||||
if renderFrameBuffLowerBound > pR.CurDynamicsRenderFrameId {
|
||||
renderFrameBuffLowerBound = pR.CurDynamicsRenderFrameId
|
||||
}
|
||||
for 0 < pR.RenderFrameBuffer.Cnt && pR.RenderFrameBuffer.StFrameId < renderFrameBuffLowerBound {
|
||||
_ = pR.RenderFrameBuffer.Pop()
|
||||
}
|
||||
|
||||
@ -511,7 +538,7 @@ func (pR *Room) StartBattle() {
|
||||
f := pR.AllPlayerInputsBuffer.Pop().(*pb.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
|
||||
Logger.Debug("inputFrame lifecycle#5[popped]:", zap.Any("roomId", pR.Id), zap.Any("inputFrameId", f.InputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString(false)))
|
||||
Logger.Debug("inputFrame lifecycle#4[popped]:", zap.Any("roomId", pR.Id), zap.Any("inputFrameId", f.InputFrameId), zap.Any("AllPlayerInputsBuffer", pR.AllPlayerInputsBufferString(false)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -586,12 +613,12 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
|
||||
// In Golang 1.12, there's no "compare-and-swap primitive" on a custom struct (or it's pointer, unless it's an unsafe pointer https://pkg.go.dev/sync/atomic@go1.12#CompareAndSwapPointer). Although CAS on custom struct is possible in Golang 1.19 https://pkg.go.dev/sync/atomic@go1.19.1#Value.CompareAndSwap, using a single word is still faster whenever possible.
|
||||
|
||||
// [WARNING] No need to use CAS for updating "inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr]", the upsync from frontend takes top priority.
|
||||
atomic.StoreUint64(&inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr], encodedInput);
|
||||
atomic.StoreUint64(&inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr], encodedInput)
|
||||
|
||||
newConfirmedList := (oldConfirmedList | joinMask)
|
||||
if swapped := atomic.CompareAndSwapUint64(&(inputFrameDownsync.ConfirmedList), oldConfirmedList, newConfirmedList); !swapped {
|
||||
// [WARNING] Upon this error, the actual input has already been updated, which is an expected result if it caused by the force confirmation from "battleMainLoop".
|
||||
Logger.Warn(fmt.Sprintf("Failed confirm CAS: roomId=%v, playerId=%v, clientInputFrameId=%v", pR.Id, playerId, clientInputFrameId))
|
||||
Logger.Warn(fmt.Sprintf("Failed confirm CAS, might've been forced to all-confirmed: roomId=%v, playerId=%v, clientInputFrameId=%v", pR.Id, playerId, clientInputFrameId))
|
||||
return
|
||||
}
|
||||
|
||||
@ -606,7 +633,11 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) {
|
||||
func (pR *Room) onInputFrameDownsyncAllConfirmed(inputFrameDownsync *pb.InputFrameDownsync, playerId int32) {
|
||||
inputFrameId := inputFrameDownsync.InputFrameId
|
||||
if -1 == pR.LastAllConfirmedInputFrameIdWithChange || false == pR.equalInputLists(inputFrameDownsync.InputList, pR.LastAllConfirmedInputList) {
|
||||
if -1 == playerId {
|
||||
Logger.Info(fmt.Sprintf("Key inputFrame change: roomId=%v, newInputFrameId=%v, lastInputFrameId=%v, newInputList=%v, lastInputList=%v, AllPlayerInputsBuffer=%v", pR.Id, inputFrameId, pR.LastAllConfirmedInputFrameId, inputFrameDownsync.InputList, pR.LastAllConfirmedInputList, pR.AllPlayerInputsBufferString(false)))
|
||||
} else {
|
||||
Logger.Info(fmt.Sprintf("Key inputFrame change: roomId=%v, playerId=%v, newInputFrameId=%v, lastInputFrameId=%v, newInputList=%v, lastInputList=%v, AllPlayerInputsBuffer=%v", pR.Id, playerId, inputFrameId, pR.LastAllConfirmedInputFrameId, inputFrameDownsync.InputList, pR.LastAllConfirmedInputList, pR.AllPlayerInputsBufferString(false)))
|
||||
}
|
||||
atomic.StoreInt32(&(pR.LastAllConfirmedInputFrameIdWithChange), inputFrameId)
|
||||
}
|
||||
atomic.StoreInt32(&(pR.LastAllConfirmedInputFrameId), inputFrameId) // [WARNING] It's IMPORTANT that "pR.LastAllConfirmedInputFrameId" is NOT NECESSARILY CONSECUTIVE, i.e. if one of the players disconnects and reconnects within a considerable amount of frame delays!
|
||||
@ -614,12 +645,10 @@ func (pR *Room) onInputFrameDownsyncAllConfirmed(inputFrameDownsync *pb.InputFra
|
||||
// To avoid potential misuse of pointers
|
||||
pR.LastAllConfirmedInputList[i] = v
|
||||
}
|
||||
if pR.inputFrameIdDebuggable(inputFrameId) {
|
||||
if -1 == playerId {
|
||||
Logger.Info(fmt.Sprintf("inputFrame lifecycle#2[forced-allconfirmed]: roomId=%v, inputFrameId=%v, lastAllConfirmedInputFrameId=%v, AllPlayerInputsBuffer=%v", pR.Id, inputFrameId, pR.LastAllConfirmedInputFrameId, pR.AllPlayerInputsBufferString(false)))
|
||||
Logger.Debug(fmt.Sprintf("inputFrame lifecycle#2[forced-allconfirmed]: roomId=%v, AllPlayerInputsBuffer=%v", pR.Id, pR.AllPlayerInputsBufferString(false)))
|
||||
} else {
|
||||
Logger.Info(fmt.Sprintf("inputFrame lifecycle#2[allconfirmed]: roomId=%v, playerId=%v, inputFrameId=%v, lastAllConfirmedInputFrameId=%v, AllPlayerInputsBuffer=%v", pR.Id, playerId, inputFrameId, pR.LastAllConfirmedInputFrameId, pR.AllPlayerInputsBufferString(false)))
|
||||
}
|
||||
Logger.Info(fmt.Sprintf("inputFrame lifecycle#2[allconfirmed]: roomId=%v, playerId=%v, AllPlayerInputsBuffer=%v", pR.Id, playerId, pR.AllPlayerInputsBufferString(false)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -648,7 +677,7 @@ func (pR *Room) StopBattleForSettlement() {
|
||||
Players: toPbPlayers(pR.Players),
|
||||
CountdownNanos: -1, // TODO: Replace this magic constant!
|
||||
}
|
||||
pR.sendSafely(&assembledFrame, nil, DOWNSYNC_MSG_ACT_ROOM_FRAME, playerId)
|
||||
pR.sendSafely(&assembledFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_STOPPED, playerId)
|
||||
}
|
||||
// Note that `pR.onBattleStoppedForSettlement` will be called by `battleMainLoop`.
|
||||
}
|
||||
@ -1000,7 +1029,7 @@ func (pR *Room) sendSafely(roomDownsyncFrame *pb.RoomDownsyncFrame, toSendFrames
|
||||
}
|
||||
|
||||
if err := pR.PlayerDownsyncSessionDict[playerId].WriteMessage(websocket.BinaryMessage, theBytes); nil != err {
|
||||
panic(fmt.Sprintf("Error sending downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount))
|
||||
panic(fmt.Sprintf("Error sending downsync message: roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v, err=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount, err))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1087,21 +1116,22 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
|
||||
}
|
||||
|
||||
Logger.Debug(fmt.Sprintf("Applying inputFrame dynamics: roomId=%v, room.RenderFrameId=%v, fromRenderFrameId=%v, toRenderFrameId=%v", pR.Id, pR.RenderFrameId, fromRenderFrameId, toRenderFrameId))
|
||||
|
||||
totPlayerCnt := uint32(pR.Capacity)
|
||||
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
|
||||
|
||||
for collisionSysRenderFrameId := fromRenderFrameId; collisionSysRenderFrameId < toRenderFrameId; collisionSysRenderFrameId++ {
|
||||
delayedInputFrameId := pR.ConvertToInputFrameId(collisionSysRenderFrameId, pR.InputDelayFrames)
|
||||
if 0 <= delayedInputFrameId {
|
||||
if delayedInputFrameId > pR.LastAllConfirmedInputFrameId {
|
||||
panic(fmt.Sprintf("delayedInputFrameId=%v is not yet all-confirmed for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! AllPlayerInputsBuffer=%v", delayedInputFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, collisionSysRenderFrameId, pR.AllPlayerInputsBufferString(false)))
|
||||
}
|
||||
tmp := pR.AllPlayerInputsBuffer.GetByFrameId(delayedInputFrameId)
|
||||
if nil == tmp {
|
||||
panic(fmt.Sprintf("delayedInputFrameId=%v doesn't exist for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! AllPlayerInputsBuffer=%v", delayedInputFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, collisionSysRenderFrameId, pR.AllPlayerInputsBufferString(false)))
|
||||
}
|
||||
delayedInputFrame := tmp.(*pb.InputFrameDownsync)
|
||||
if swapped := atomic.CompareAndSwapUint64(&(delayedInputFrame.ConfirmedList), allConfirmedMask, allConfirmedMask); !swapped {
|
||||
panic(fmt.Sprintf("delayedInputFrameId=%v is not yet all-confirmed for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! AllPlayerInputsBuffer=%v", delayedInputFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, collisionSysRenderFrameId, pR.AllPlayerInputsBufferString(false)))
|
||||
}
|
||||
// [WARNING] It's possible that by now "allConfirmedMask != delayedInputFrame.ConfirmedList && delayedInputFrameId <= pR.LastAllConfirmedInputFrameId", we trust "pR.LastAllConfirmedInputFrameId" as the TOP AUTHORITY.
|
||||
atomic.StoreUint64(&(delayedInputFrame.ConfirmedList), allConfirmedMask)
|
||||
|
||||
inputList := delayedInputFrame.InputList
|
||||
// Ordered by joinIndex to guarantee determinism
|
||||
|
@ -440,7 +440,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
209.63606633899616,
|
||||
216.6425019058577,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
@ -103,47 +103,30 @@ cc.Class({
|
||||
return (0 == inputFrameId % 10);
|
||||
},
|
||||
|
||||
_dumpToRenderCache: function(roomDownsyncFrame) {
|
||||
dumpToRenderCache: function(roomDownsyncFrame) {
|
||||
const self = this;
|
||||
const minToKeepRenderFrameId = self.lastAllConfirmedRenderFrameId;
|
||||
while (0 < self.recentRenderCache.cnt && self.recentRenderCache.stFrameId < minToKeepRenderFrameId) {
|
||||
self.recentRenderCache.pop();
|
||||
}
|
||||
if (self.recentRenderCache.stFrameId < minToKeepRenderFrameId) {
|
||||
console.warn("Weird dumping of RENDER frame: self.renderFrame=", self.renderFrame, ", self.recentInputCache=", self._stringifyRecentInputCache(false), ", self.recentRenderCache=", self._stringifyRecentRenderCache(false), ", self.lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", self.lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId);
|
||||
}
|
||||
const existing = self.recentRenderCache.getByFrameId(roomDownsyncFrame.id);
|
||||
if (null != existing) {
|
||||
existing.players = roomDownsyncFrame.players;
|
||||
existing.sentAt = roomDownsyncFrame.sentAt;
|
||||
existing.countdownNanos = roomDownsyncFrame.countdownNanos;
|
||||
existing.treasures = roomDownsyncFrame.treasures;
|
||||
existing.bullets = roomDownsyncFrame.bullets;
|
||||
existing.speedShoes = roomDownsyncFrame.speedShoes;
|
||||
existing.guardTowers = roomDownsyncFrame.guardTowers;
|
||||
existing.playerMetas = roomDownsyncFrame.playerMetas;
|
||||
} else {
|
||||
self.recentRenderCache.put(roomDownsyncFrame);
|
||||
}
|
||||
const ret = self.recentRenderCache.setByFrameId(roomDownsyncFrame, roomDownsyncFrame.id);
|
||||
return ret;
|
||||
},
|
||||
|
||||
_dumpToInputCache: function(inputFrameDownsync) {
|
||||
dumpToInputCache: function(inputFrameDownsync) {
|
||||
const self = this;
|
||||
let minToKeepInputFrameId = self._convertToInputFrameId(self.lastAllConfirmedRenderFrameId, self.inputDelayFrames); // [WARNING] This could be different from "self.lastAllConfirmedInputFrameId". We'd like to keep the corresponding inputFrame 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;
|
||||
if (minToKeepInputFrameId > self.lastAllConfirmedInputFrameId) {
|
||||
minToKeepInputFrameId = self.lastAllConfirmedInputFrameId;
|
||||
}
|
||||
while (0 < self.recentInputCache.cnt && self.recentInputCache.stFrameId < minToKeepInputFrameId) {
|
||||
self.recentInputCache.pop();
|
||||
}
|
||||
if (self.recentInputCache.stFrameId < minToKeepInputFrameId) {
|
||||
console.warn("Weird dumping of INPUT frame: self.renderFrame=", self.renderFrame, ", self.recentInputCache=", self._stringifyRecentInputCache(false), ", self.recentRenderCache=", self._stringifyRecentRenderCache(false), ", self.lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", self.lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId);
|
||||
}
|
||||
const existing = self.recentInputCache.getByFrameId(inputFrameDownsync.inputFrameId);
|
||||
if (null != existing) {
|
||||
existing.inputList = inputFrameDownsync.inputList;
|
||||
existing.confirmedList = inputFrameDownsync.confirmedList;
|
||||
} else {
|
||||
self.recentInputCache.put(inputFrameDownsync);
|
||||
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));
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
|
||||
_convertToInputFrameId(renderFrameId, inputDelayFrames) {
|
||||
@ -183,7 +166,7 @@ cc.Class({
|
||||
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.dumpToInputCache(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
|
||||
|
||||
const previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]);
|
||||
return [previousSelfInput, discreteDir.encodedIdx];
|
||||
@ -201,14 +184,19 @@ cc.Class({
|
||||
return shouldUpsyncForEarlyAllConfirmedOnBackend || (prevSelfInput != currSelfInput);
|
||||
},
|
||||
|
||||
sendInputFrameUpsyncBatch(inputFrameId) {
|
||||
// [WARNING] Why not just send the latest input? Because different player would have a different "inputFrameId" of changing its last input, and that could make the server not recognizing any "all-confirmed inputFrame"!
|
||||
sendInputFrameUpsyncBatch(latestLocalInputFrameId) {
|
||||
// [WARNING] Why not just send the latest input? Because different player would have a different "latestLocalInputFrameId" of changing its last input, and that could make the server not recognizing any "all-confirmed inputFrame"!
|
||||
const self = this;
|
||||
let inputFrameUpsyncBatch = [];
|
||||
for (let i = self.lastUpsyncInputFrameId+1; i <= inputFrameId; ++i) {
|
||||
let batchInputFrameIdSt = self.lastUpsyncInputFrameId + 1;
|
||||
if (batchInputFrameIdSt < self.recentInputCache.stFrameId) {
|
||||
// Upon resync, "self.lastUpsyncInputFrameId" might not have been updated properly.
|
||||
batchInputFrameIdSt = self.recentInputCache.stFrameId;
|
||||
}
|
||||
for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) {
|
||||
const inputFrameDownsync = self.recentInputCache.getByFrameId(i);
|
||||
if (null == inputFrameDownsync) {
|
||||
console.warn("sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=", i, "; recentInputCache=", self._stringifyRecentInputCache(false));
|
||||
console.error("sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=", i, ": latestLocalInputFrameId=", latestLocalInputFrameId, ", recentInputCache=", self._stringifyRecentInputCache(false));
|
||||
} else {
|
||||
const inputFrameUpsync = {
|
||||
inputFrameId: i,
|
||||
@ -227,7 +215,7 @@ cc.Class({
|
||||
inputFrameUpsyncBatch: inputFrameUpsyncBatch,
|
||||
}).finish();
|
||||
window.sendSafely(reqData);
|
||||
self.lastUpsyncInputFrameId = inputFrameId;
|
||||
self.lastUpsyncInputFrameId = latestLocalInputFrameId;
|
||||
},
|
||||
|
||||
onEnable() {
|
||||
@ -244,12 +232,6 @@ cc.Class({
|
||||
if (null == self.battleState || ALL_BATTLE_STATES.WAITING == self.battleState) {
|
||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
||||
}
|
||||
if (null != window.handleRoomDownsyncFrame) {
|
||||
window.handleRoomDownsyncFrame = null;
|
||||
}
|
||||
if (null != window.handleInputFrameDownsyncBatch) {
|
||||
window.handleInputFrameDownsyncBatch = null;
|
||||
}
|
||||
if (null != window.handleBattleColliderInfo) {
|
||||
window.handleBattleColliderInfo = null;
|
||||
}
|
||||
@ -317,7 +299,7 @@ cc.Class({
|
||||
self.lastAllConfirmedRenderFrameId = -1;
|
||||
self.lastAllConfirmedInputFrameId = -1;
|
||||
self.lastUpsyncInputFrameId = -1;
|
||||
self.chaserRenderFrameId = -1; // at any moment, "lastAllConfirmedRenderFrameId <= chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "handleInputFrameDownsyncBatch"
|
||||
self.chaserRenderFrameId = -1; // at any moment, "lastAllConfirmedRenderFrameId <= chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "onInputFrameDownsyncBatch"
|
||||
|
||||
self.recentRenderCache = new RingBuffer(1024);
|
||||
|
||||
@ -382,9 +364,7 @@ cc.Class({
|
||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
||||
window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ );
|
||||
};
|
||||
resultPanelScriptIns.onCloseDelegate = () => {
|
||||
|
||||
};
|
||||
resultPanelScriptIns.onCloseDelegate = () => {};
|
||||
|
||||
self.gameRuleNode = cc.instantiate(self.gameRulePrefab);
|
||||
self.gameRuleNode.width = self.canvasNode.width;
|
||||
@ -464,7 +444,8 @@ cc.Class({
|
||||
let barrierIdCounter = 0;
|
||||
const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node);
|
||||
for (let boundaryObj of boundaryObjs.barriers) {
|
||||
const x0 = boundaryObj[0].x, y0 = boundaryObj[0].y;
|
||||
const x0 = boundaryObj[0].x,
|
||||
y0 = boundaryObj[0].y;
|
||||
let pts = [];
|
||||
// TODO: Simplify this redundant coordinate conversion within "extractBoundaryObjects", but since this routine is only called once per battle, not urgent.
|
||||
for (let i = 0; i < boundaryObj.length; ++i) {
|
||||
@ -512,90 +493,6 @@ cc.Class({
|
||||
self.hideGameRuleNode();
|
||||
self.transitToState(ALL_MAP_STATES.WAITING);
|
||||
self._inputControlEnabled = false;
|
||||
|
||||
let findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
|
||||
window.handlePlayerAdded = function(rdf) {
|
||||
// Update the "finding player" GUI and show it if not previously present
|
||||
if (!self.findingPlayerNode.parent) {
|
||||
self.showPopupInCanvas(self.findingPlayerNode);
|
||||
}
|
||||
findingPlayerScriptIns.updatePlayersInfo(rdf.playerMetas);
|
||||
};
|
||||
|
||||
window.handleRoomDownsyncFrame = function(rdf) {
|
||||
const frameId = rdf.id;
|
||||
// Right upon establishment of the "PersistentSessionClient", we should receive an initial signal "BattleColliderInfo" earlier than any "RoomDownsyncFrame" containing "PlayerMeta" data.
|
||||
switch (frameId) {
|
||||
case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_READY_TO_START:
|
||||
self.onBattleReadyToStart(rdf.playerMetas, false);
|
||||
return;
|
||||
case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START:
|
||||
self.onBattleStartedOrResynced(rdf);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Inject a NetworkDoctor as introduced in https://app.yinxiang.com/shard/s61/nl/13267014/5c575124-01db-419b-9c02-ec81f78c6ddc/.
|
||||
};
|
||||
|
||||
window.handleInputFrameDownsyncBatch = function(batch) {
|
||||
if (ALL_BATTLE_STATES.IN_BATTLE != self.battleState
|
||||
&& ALL_BATTLE_STATES.IN_SETTLEMENT != self.battleState) {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log("Received inputFrameDownsyncBatch=", batch, ", now correspondingLastLocalInputFrame=", self.recentInputCache.getByFrameId(batch[batch.length-1].inputFrameId));
|
||||
let firstPredictedYetIncorrectInputFrameId = null;
|
||||
let firstPredictedYetIncorrectInputFrameJoinIndex = null;
|
||||
for (let k in batch) {
|
||||
const inputFrameDownsync = batch[k];
|
||||
const inputFrameDownsyncId = inputFrameDownsync.inputFrameId;
|
||||
const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId);
|
||||
if (null == localInputFrame) {
|
||||
console.warn("handleInputFrameDownsyncBatch: recentInputCache is NOT having inputFrameDownsyncId=", inputFrameDownsyncId, "; now recentInputCache=", self._stringifyRecentInputCache(false));
|
||||
} else {
|
||||
if (null == firstPredictedYetIncorrectInputFrameId) {
|
||||
for (let i in localInputFrame.inputList) {
|
||||
if (localInputFrame.inputList[i] != inputFrameDownsync.inputList[i]) {
|
||||
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
|
||||
firstPredictedYetIncorrectInputFrameJoinIndex = (parseInt(i)+1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
|
||||
self._dumpToInputCache(inputFrameDownsync);
|
||||
}
|
||||
|
||||
if (null != firstPredictedYetIncorrectInputFrameId) {
|
||||
const inputFrameId1 = firstPredictedYetIncorrectInputFrameId;
|
||||
const renderFrameId1 = self._convertToFirstUsedRenderFrameId(inputFrameId1, self.inputDelayFrames); // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId"
|
||||
if (renderFrameId1 < self.renderFrameId) {
|
||||
/*
|
||||
A typical case is as follows.
|
||||
--------------------------------------------------------
|
||||
[self.lastAllConfirmedRenderFrameId] : 22
|
||||
|
||||
<renderFrameId1> : 36
|
||||
|
||||
|
||||
<self.chaserRenderFrameId> : 62
|
||||
|
||||
[self.renderFrameId] : 64
|
||||
--------------------------------------------------------
|
||||
*/
|
||||
if (renderFrameId1 < self.chaserRenderFrameId) {
|
||||
// The actual rollback-and-chase would later be executed in update(dt).
|
||||
console.warn("Mismatched input detected, resetting chaserRenderFrameId: inputFrameId1:", inputFrameId1, ", renderFrameId1:", renderFrameId1, ", chaserRenderFrameId before reset: ", self.chaserRenderFrameId);
|
||||
self.chaserRenderFrameId = renderFrameId1;
|
||||
} else {
|
||||
// Deliberately left blank, chasing is ongoing.
|
||||
}
|
||||
} else {
|
||||
// No need to rollback when "renderFrameId1 == self.renderFrameId", because the "corresponding delayedInputFrame for renderFrameId2" is NOT YET EXECUTED BY NOW, it just went through "++self.renderFrameId" in "update(dt)" and javascript-runtime is mostly single-threaded in our programmable range.
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// The player is now viewing "self.gameRuleNode" with button(s) to start an actual battle. -- YFLu
|
||||
@ -652,9 +549,26 @@ cc.Class({
|
||||
|
||||
onBattleStartedOrResynced(rdf) {
|
||||
// This function is also applicable to "re-joining".
|
||||
console.log('On battle started or resynced! renderFrameId=', rdf.id);
|
||||
const self = window.mapIns;
|
||||
const dumpRenderCacheRet = self.dumpToRenderCache(rdf);
|
||||
if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) {
|
||||
console.error("Something is wrong while setting the RingBuffer by frameId!");
|
||||
return dumpRenderCacheRet;
|
||||
}
|
||||
if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START < rdf.id && window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet) {
|
||||
if (rdf.id < self.chaserRenderFrameId) {
|
||||
// This "rdf.id = backend.refRenderFrameId" could be small, see comments around "room.go".
|
||||
self.chaserRenderFrameId = rdf.id;
|
||||
}
|
||||
// In this case, we'll also got proper all-confirmed inputFrames for advancing the renderFrames in the coming "update(dt)"
|
||||
return dumpRenderCacheRet;
|
||||
}
|
||||
|
||||
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
|
||||
console.log('On battle started or resynced! renderFrameId=', rdf.id);
|
||||
|
||||
self.renderFrameId = rdf.id;
|
||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||
self.lastAllConfirmedRenderFrameId = rdf.id;
|
||||
self.chaserRenderFrameId = rdf.id;
|
||||
|
||||
@ -682,13 +596,85 @@ cc.Class({
|
||||
self.countdownToBeginGameNode.parent.removeChild(self.countdownToBeginGameNode);
|
||||
}
|
||||
self.transitToState(ALL_MAP_STATES.VISUAL);
|
||||
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
|
||||
self.applyRoomDownsyncFrameDynamics(rdf);
|
||||
self._dumpToRenderCache(rdf);
|
||||
self.battleState = ALL_BATTLE_STATES.IN_BATTLE; // Starts the increment of "self.renderFrameId" in "self.update(dt)"
|
||||
self.lastRenderFrameIdTriggeredAt = performance.now();
|
||||
if (null != window.boundRoomId) {
|
||||
self.boundRoomIdLabel.string = window.boundRoomId;
|
||||
|
||||
return dumpRenderCacheRet;
|
||||
},
|
||||
|
||||
equalInputLists(lhs, rhs) {
|
||||
if (null == lhs || null == rhs) return false;
|
||||
if (lhs.length != rhs.length) return false;
|
||||
for (let i in lhs) {
|
||||
if (lhs[i] == rhs[i]) continue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
onInputFrameDownsyncBatch(batch, dumpRenderCacheRet /* second param is default to null */ ) {
|
||||
const self = this;
|
||||
if (ALL_BATTLE_STATES.IN_BATTLE != self.battleState
|
||||
&& ALL_BATTLE_STATES.IN_SETTLEMENT != self.battleState) {
|
||||
return;
|
||||
}
|
||||
|
||||
let firstPredictedYetIncorrectInputFrameId = null;
|
||||
for (let k in batch) {
|
||||
const inputFrameDownsync = batch[k];
|
||||
const inputFrameDownsyncId = inputFrameDownsync.inputFrameId;
|
||||
if (window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet) {
|
||||
// Deliberately left blank, in this case "chaserRenderFrameId" is already reset to proper value.
|
||||
} else {
|
||||
const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId);
|
||||
if (null == localInputFrame) {
|
||||
console.warn("localInputFrame not existing: recentInputCache is NOT having inputFrameDownsyncId=", inputFrameDownsyncId, "; now recentInputCache=", self._stringifyRecentInputCache(false));
|
||||
} else if (null == firstPredictedYetIncorrectInputFrameId && !self.equalInputLists(localInputFrame.inputList, inputFrameDownsync.inputList)) {
|
||||
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
|
||||
}
|
||||
}
|
||||
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
|
||||
self.dumpToInputCache(inputFrameDownsync);
|
||||
}
|
||||
|
||||
if (null != firstPredictedYetIncorrectInputFrameId) {
|
||||
const inputFrameId1 = firstPredictedYetIncorrectInputFrameId;
|
||||
const renderFrameId1 = self._convertToFirstUsedRenderFrameId(inputFrameId1, self.inputDelayFrames); // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId"
|
||||
if (renderFrameId1 < self.renderFrameId) {
|
||||
/*
|
||||
A typical case is as follows.
|
||||
--------------------------------------------------------
|
||||
[self.lastAllConfirmedRenderFrameId] : 22
|
||||
|
||||
<renderFrameId1> : 36
|
||||
|
||||
|
||||
<self.chaserRenderFrameId> : 62
|
||||
|
||||
[self.renderFrameId] : 64
|
||||
--------------------------------------------------------
|
||||
*/
|
||||
if (renderFrameId1 < self.chaserRenderFrameId) {
|
||||
// The actual rollback-and-chase would later be executed in update(dt).
|
||||
console.warn("Mismatched input detected, resetting chaserRenderFrameId: inputFrameId1:", inputFrameId1, ", renderFrameId1:", renderFrameId1, ", chaserRenderFrameId before reset: ", self.chaserRenderFrameId);
|
||||
self.chaserRenderFrameId = renderFrameId1;
|
||||
} else {
|
||||
// Deliberately left blank, chasing is ongoing.
|
||||
}
|
||||
} else {
|
||||
// No need to rollback when "renderFrameId1 == self.renderFrameId", because the "corresponding delayedInputFrame for renderFrameId2" is NOT YET EXECUTED BY NOW, it just went through "++self.renderFrameId" in "update(dt)" and javascript-runtime is mostly single-threaded in our programmable range.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onPlayerAdded(rdf) {
|
||||
const self = this;
|
||||
// Update the "finding player" GUI and show it if not previously present
|
||||
if (!self.findingPlayerNode.parent) {
|
||||
self.showPopupInCanvas(self.findingPlayerNode);
|
||||
}
|
||||
let findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer");
|
||||
findingPlayerScriptIns.updatePlayersInfo(rdf.playerMetas);
|
||||
},
|
||||
|
||||
logBattleStats() {
|
||||
@ -706,6 +692,9 @@ cc.Class({
|
||||
|
||||
onBattleStopped() {
|
||||
const self = this;
|
||||
if (ALL_BATTLE_STATES.IN_BATTLE != self.battleState) {
|
||||
return;
|
||||
}
|
||||
self.countdownNanos = null;
|
||||
self.logBattleStats();
|
||||
if (self.musicEffectManagerScriptIns) {
|
||||
@ -741,7 +730,10 @@ cc.Class({
|
||||
|
||||
newPlayerNode.active = true;
|
||||
const playerScriptIns = newPlayerNode.getComponent("SelfPlayer");
|
||||
playerScriptIns.scheduleNewDirection({dx: 0, dy: 0}, true);
|
||||
playerScriptIns.scheduleNewDirection({
|
||||
dx: 0,
|
||||
dy: 0
|
||||
}, true);
|
||||
|
||||
return [newPlayerNode, playerScriptIns];
|
||||
},
|
||||
@ -756,7 +748,8 @@ cc.Class({
|
||||
}
|
||||
try {
|
||||
let st = performance.now();
|
||||
let prevSelfInput = null, currSelfInput = null;
|
||||
let prevSelfInput = null,
|
||||
currSelfInput = null;
|
||||
const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here
|
||||
if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) {
|
||||
const prevAndCurrInputs = self._generateInputFrameUpsync(noDelayInputFrameId);
|
||||
@ -774,7 +767,8 @@ cc.Class({
|
||||
// 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;
|
||||
let nextChaserRenderFrameId = (prevChaserRenderFrameId + self.maxChasingRenderFramesPerUpdate);
|
||||
if (nextChaserRenderFrameId > self.renderFrameId) nextChaserRenderFrameId = self.renderFrameId;
|
||||
if (nextChaserRenderFrameId > self.renderFrameId)
|
||||
nextChaserRenderFrameId = self.renderFrameId;
|
||||
self.rollbackAndChase(prevChaserRenderFrameId, nextChaserRenderFrameId, self.chaserCollisionSys, self.chaserCollisionSysMap);
|
||||
self.chaserRenderFrameId = nextChaserRenderFrameId; // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
|
||||
let t2 = performance.now();
|
||||
@ -827,7 +821,9 @@ cc.Class({
|
||||
NetworkUtils.ajax({
|
||||
url: backendAddress.PROTOCOL + '://' + backendAddress.HOST + ':' + backendAddress.PORT + constants.ROUTE_PATH.API + constants.ROUTE_PATH.PLAYER + constants.ROUTE_PATH.VERSION + constants.ROUTE_PATH.INT_AUTH_TOKEN + constants.ROUTE_PATH.LOGOUT,
|
||||
type: "POST",
|
||||
data: { intAuthToken: selfPlayerInfo.intAuthToken },
|
||||
data: {
|
||||
intAuthToken: selfPlayerInfo.intAuthToken
|
||||
},
|
||||
success: function(res) {
|
||||
if (res.ret != constants.RET_CODE.OK) {
|
||||
console.log("Logout failed: ", res);
|
||||
@ -949,7 +945,7 @@ cc.Class({
|
||||
self.lastAllConfirmedRenderFrameId = rdf.id;
|
||||
self.chaserRenderFrameId = rdf.id; // it must be true that "chaserRenderFrameId >= lastAllConfirmedRenderFrameId"
|
||||
}
|
||||
self._dumpToRenderCache(rdf);
|
||||
self.dumpToRenderCache(rdf);
|
||||
return rdf;
|
||||
},
|
||||
|
||||
@ -986,7 +982,7 @@ cc.Class({
|
||||
const self = this;
|
||||
const renderFrameSt = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame"
|
||||
if (null == renderFrameSt) {
|
||||
console.error("Couldn't find renderFrameId=", renderFrameIdSt, " to rollback, recentRenderCache=", self._stringifyRecentRenderCache(false));
|
||||
console.error("Couldn't find renderFrameId=", renderFrameIdSt, " to rollback, lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId, ", recentRenderCache=", self._stringifyRecentRenderCache(false), ", recentInputCache=", self._stringifyRecentInputCache(false));
|
||||
}
|
||||
/*
|
||||
Reset "position" of players in "collisionSys" according to "renderFrameSt". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics.
|
||||
@ -1008,7 +1004,11 @@ cc.Class({
|
||||
for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) {
|
||||
const renderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"
|
||||
const j = self._convertToInputFrameId(i, self.inputDelayFrames);
|
||||
const inputList = self.getCachedInputFrameDownsyncWithPrediction(j).inputList;
|
||||
const inputFrameDownsync = self.getCachedInputFrameDownsyncWithPrediction(j);
|
||||
if (null == inputFrameDownsync) {
|
||||
console.error("Failed to get cached inputFrameDownsync for renderFrameId=", i, ", inputFrameId=", j, "lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId, ", recentRenderCache=", self._stringifyRecentRenderCache(false), ", recentInputCache=", self._stringifyRecentInputCache(false));
|
||||
}
|
||||
const inputList = inputFrameDownsync.inputList;
|
||||
for (let j in self.playerRichInfoArr) {
|
||||
const joinIndex = parseInt(j) + 1;
|
||||
const playerId = self.playerRichInfoArr[j].id;
|
||||
@ -1020,11 +1020,6 @@ cc.Class({
|
||||
const baseChange = player.speed * self.rollbackEstimatedDt * decodedInput.speedFactor;
|
||||
playerCollider.x += baseChange * decodedInput.dx;
|
||||
playerCollider.y += baseChange * decodedInput.dy;
|
||||
/*
|
||||
if (0 < encodedInput) {
|
||||
console.log("playerId=", playerId, "@renderFrameId=", i, ", delayedInputFrameId=", j, ", baseChange=", baseChange, ": x=", playerCollider.x, ", y=", playerCollider.y);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
collisionSys.update();
|
||||
|
@ -1,3 +1,7 @@
|
||||
window.RING_BUFF_CONSECUTIVE_SET = 0;
|
||||
window.RING_BUFF_NON_CONSECUTIVE_SET = 1;
|
||||
window.RING_BUFF_FAILED_TO_SET = 2;
|
||||
|
||||
var RingBuffer = function(capacity) {
|
||||
this.ed = 0; // write index, open index
|
||||
this.st = 0; // read index, closed index
|
||||
@ -32,15 +36,15 @@ RingBuffer.prototype.pop = function() {
|
||||
return item;
|
||||
};
|
||||
|
||||
RingBuffer.prototype.getByOffset = function(offsetFromSt) {
|
||||
if (0 == this.cnt) {
|
||||
RingBuffer.prototype.getArrIdxByOffset = function(offsetFromSt) {
|
||||
if (0 > offsetFromSt || 0 == this.cnt) {
|
||||
return null;
|
||||
}
|
||||
let arrIdx = this.st + offsetFromSt;
|
||||
if (this.st < this.ed) {
|
||||
// case#1: 0...st...ed...n-1
|
||||
if (this.st <= arrIdx && arrIdx < this.ed) {
|
||||
return this.eles[arrIdx];
|
||||
return arrIdx;
|
||||
}
|
||||
} else {
|
||||
// if this.st >= this.sd
|
||||
@ -49,7 +53,7 @@ RingBuffer.prototype.getByOffset = function(offsetFromSt) {
|
||||
arrIdx -= this.n
|
||||
}
|
||||
if (arrIdx >= this.st || arrIdx < this.ed) {
|
||||
return this.eles[arrIdx];
|
||||
return arrIdx;
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +61,40 @@ RingBuffer.prototype.getByOffset = function(offsetFromSt) {
|
||||
};
|
||||
|
||||
RingBuffer.prototype.getByFrameId = function(frameId) {
|
||||
return this.getByOffset(frameId - this.stFrameId);
|
||||
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) {
|
||||
if (frameId < this.stFrameId) {
|
||||
console.error("Invalid putByFrameId#1: stFrameId=", stFrameId, ", edFrameId=", edFrameId, ", incoming item=", item);
|
||||
return window.RING_BUFF_FAILED_TO_SET;
|
||||
}
|
||||
const arrIdx = this.getArrIdxByOffset(frameId - this.stFrameId);
|
||||
if (null != arrIdx) {
|
||||
this.eles[arrIdx] = item;
|
||||
return window.RING_BUFF_CONSECUTIVE_SET;
|
||||
}
|
||||
|
||||
// When "null == arrIdx", should it still be deemed consecutive if "frameId == edFrameId" prior to the reset?
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
module.exports = RingBuffer;
|
||||
|
@ -1,3 +1,5 @@
|
||||
const RingBuffer = require('./RingBuffer');
|
||||
|
||||
window.UPSYNC_MSG_ACT_HB_PING = 1;
|
||||
window.UPSYNC_MSG_ACT_PLAYER_CMD = 2;
|
||||
window.UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK = 3;
|
||||
@ -8,7 +10,7 @@ window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START = -1;
|
||||
window.DOWNSYNC_MSG_ACT_BATTLE_START = 0;
|
||||
window.DOWNSYNC_MSG_ACT_HB_REQ = 1;
|
||||
window.DOWNSYNC_MSG_ACT_INPUT_BATCH = 2;
|
||||
window.DOWNSYNC_MSG_ACT_ROOM_FRAME = 3;
|
||||
window.DOWNSYNC_MSG_ACT_BATTLE_STOPPED = 3;
|
||||
window.DOWNSYNC_MSG_ACT_FORCED_RESYNC = 4;
|
||||
|
||||
|
||||
@ -160,30 +162,28 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) {
|
||||
window.handleHbRequirements(resp); // 获取boundRoomId并存储到localStorage
|
||||
break;
|
||||
case window.DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED:
|
||||
window.handlePlayerAdded(resp.rdf);
|
||||
mapIns.onPlayerAdded(resp.rdf);
|
||||
break;
|
||||
case window.DOWNSYNC_MSG_ACT_PLAYER_READDED_AND_ACKED:
|
||||
// Deliberately left blank for now
|
||||
break;
|
||||
case window.DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START:
|
||||
mapIns.onBattleReadyToStart(resp.rdf.playerMetas, false);
|
||||
break;
|
||||
case window.DOWNSYNC_MSG_ACT_BATTLE_START:
|
||||
case window.DOWNSYNC_MSG_ACT_ROOM_FRAME:
|
||||
if (window.handleRoomDownsyncFrame) {
|
||||
window.handleRoomDownsyncFrame(resp.rdf);
|
||||
}
|
||||
mapIns.onBattleStartedOrResynced(resp.rdf);
|
||||
break;
|
||||
case window.DOWNSYNC_MSG_ACT_BATTLE_STOPPED:
|
||||
mapIns.onBattleStopped();
|
||||
break;
|
||||
case window.DOWNSYNC_MSG_ACT_INPUT_BATCH:
|
||||
if (window.handleInputFrameDownsyncBatch) {
|
||||
window.handleInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch);
|
||||
}
|
||||
mapIns.onInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch);
|
||||
break;
|
||||
case window.DOWNSYNC_MSG_ACT_FORCED_RESYNC:
|
||||
if (window.handleInputFrameDownsyncBatch && window.handleRoomDownsyncFrame) {
|
||||
console.warn("Got forced resync:", JSON.stringify(resp), " @localRenderFrameId=", mapIns.renderFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false));
|
||||
// The following order of execution is important, because "handleInputFrameDownsyncBatch" is only available when state is IN_BATTLE
|
||||
window.handleRoomDownsyncFrame(resp.rdf);
|
||||
window.handleInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch);
|
||||
}
|
||||
// The following order of execution is important, because "onInputFrameDownsyncBatch" is only available when state is IN_BATTLE
|
||||
const dumpRenderCacheRet = mapIns.onBattleStartedOrResynced(resp.rdf);
|
||||
mapIns.onInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch, dumpRenderCacheRet);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
Loading…
x
Reference in New Issue
Block a user