|
|
|
@@ -2,12 +2,17 @@ package models
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
. "dnmshared"
|
|
|
|
|
"encoding/xml"
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
|
"github.com/solarlune/resolv"
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"math"
|
|
|
|
|
"math/rand"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
. "server/common"
|
|
|
|
|
"server/common/utils"
|
|
|
|
|
pb "server/pb_output"
|
|
|
|
@@ -15,11 +20,6 @@ import (
|
|
|
|
|
"sync"
|
|
|
|
|
"sync/atomic"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"encoding/xml"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
@@ -188,7 +188,7 @@ type Room struct {
|
|
|
|
|
StageTileH int32
|
|
|
|
|
RawBattleStrToVec2DListMap StrToVec2DListMap
|
|
|
|
|
RawBattleStrToPolygon2DListMap StrToPolygon2DListMap
|
|
|
|
|
BackendDynamicsEnabled bool
|
|
|
|
|
BackendDynamicsEnabled bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
@@ -459,13 +459,13 @@ func (pR *Room) StartBattle() {
|
|
|
|
|
pR.prefabInputFrameDownsync(noDelayInputFrameId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Force setting all-confirmed of buffered inputFrames periodically
|
|
|
|
|
unconfirmedMask := uint64(0)
|
|
|
|
|
if pR.BackendDynamicsEnabled {
|
|
|
|
|
unconfirmedMask = pR.forceConfirmationIfApplicable()
|
|
|
|
|
} else {
|
|
|
|
|
pR.markConfirmationIfApplicable()
|
|
|
|
|
}
|
|
|
|
|
if pR.BackendDynamicsEnabled {
|
|
|
|
|
// Force setting all-confirmed of buffered inputFrames periodically
|
|
|
|
|
unconfirmedMask = pR.forceConfirmationIfApplicable()
|
|
|
|
|
} else {
|
|
|
|
|
pR.markConfirmationIfApplicable()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
upperToSendInputFrameId := atomic.LoadInt32(&(pR.LastAllConfirmedInputFrameId))
|
|
|
|
|
/*
|
|
|
|
@@ -474,29 +474,29 @@ func (pR *Room) StartBattle() {
|
|
|
|
|
|
|
|
|
|
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".
|
|
|
|
|
Upon resync, it's still possible that "refRenderFrameId < frontend.chaserRenderFrameId" -- and this is allowed.
|
|
|
|
|
*/
|
|
|
|
|
refRenderFrameId := pR.ConvertToGeneratingRenderFrameId(upperToSendInputFrameId) + (1 << pR.InputScaleFrames) - 1
|
|
|
|
|
if refRenderFrameId > pR.RenderFrameId {
|
|
|
|
|
refRenderFrameId = pR.RenderFrameId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dynamicsDuration := int64(0)
|
|
|
|
|
if pR.BackendDynamicsEnabled {
|
|
|
|
|
if 0 <= pR.LastAllConfirmedInputFrameId {
|
|
|
|
|
dynamicsStartedAt := utils.UnixtimeNano()
|
|
|
|
|
// Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId"
|
|
|
|
|
nextDynamicsRenderFrameId := pR.ConvertToLastUsedRenderFrameId(pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames)
|
|
|
|
|
Logger.Debug(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, LastAllConfirmedInputFrameId=%v, InputDelayFrames=%v, nextDynamicsRenderFrameId=%v", pR.Id, pR.RenderFrameId, pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames, nextDynamicsRenderFrameId))
|
|
|
|
|
pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId)
|
|
|
|
|
dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt
|
|
|
|
|
}
|
|
|
|
|
dynamicsDuration := int64(0)
|
|
|
|
|
if pR.BackendDynamicsEnabled {
|
|
|
|
|
if 0 <= pR.LastAllConfirmedInputFrameId {
|
|
|
|
|
dynamicsStartedAt := utils.UnixtimeNano()
|
|
|
|
|
// Apply "all-confirmed inputFrames" to move forward "pR.CurDynamicsRenderFrameId"
|
|
|
|
|
nextDynamicsRenderFrameId := pR.ConvertToLastUsedRenderFrameId(pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames)
|
|
|
|
|
Logger.Debug(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, LastAllConfirmedInputFrameId=%v, InputDelayFrames=%v, nextDynamicsRenderFrameId=%v", pR.Id, pR.RenderFrameId, pR.LastAllConfirmedInputFrameId, pR.InputDelayFrames, nextDynamicsRenderFrameId))
|
|
|
|
|
pR.applyInputFrameDownsyncDynamics(pR.CurDynamicsRenderFrameId, nextDynamicsRenderFrameId)
|
|
|
|
|
dynamicsDuration = utils.UnixtimeNano() - dynamicsStartedAt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [WARNING] The following inequality 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.CurDynamicsRenderFrameId {
|
|
|
|
|
refRenderFrameId = pR.CurDynamicsRenderFrameId
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// [WARNING] The following inequality 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.CurDynamicsRenderFrameId {
|
|
|
|
|
refRenderFrameId = pR.CurDynamicsRenderFrameId
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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".
|
|
|
|
@@ -539,7 +539,7 @@ func (pR *Room) StartBattle() {
|
|
|
|
|
// [WARNING] When sending DOWNSYNC_MSG_ACT_FORCED_RESYNC, there MUST BE accompanying "toSendInputFrames" for calculating "refRenderFrameId"!
|
|
|
|
|
|
|
|
|
|
if MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED == player.LastSentInputFrameId {
|
|
|
|
|
Logger.Warn(fmt.Sprintf("Not sending due to empty toSendInputFrames: roomId=%v, playerId=%v, refRenderFrameId=%v, candidateToSendInputFrameId=%v, upperToSendInputFrameId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, refRenderFrameId, candidateToSendInputFrameId, upperToSendInputFrameId, player.LastSentInputFrameId, player.AckingInputFrameId))
|
|
|
|
|
Logger.Warn(fmt.Sprintf("Not sending due to empty toSendInputFrames: roomId=%v, playerId=%v, refRenderFrameId=%v, upperToSendInputFrameId=%v, lastSentInputFrameId=%v, playerAckingInputFrameId=%v", pR.Id, playerId, refRenderFrameId, upperToSendInputFrameId, player.LastSentInputFrameId, player.AckingInputFrameId))
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
@@ -567,6 +567,17 @@ func (pR *Room) StartBattle() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toApplyInputFrameId := pR.ConvertToInputFrameId(refRenderFrameId, pR.InputDelayFrames)
|
|
|
|
|
if false == pR.BackendDynamicsEnabled {
|
|
|
|
|
// When "false == pR.BackendDynamicsEnabled", the variable "refRenderFrameId" is not well defined
|
|
|
|
|
minLastSentInputFrameId := int32(math.MaxInt32)
|
|
|
|
|
for _, player := range pR.Players {
|
|
|
|
|
if player.LastSentInputFrameId >= minLastSentInputFrameId {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
minLastSentInputFrameId = player.LastSentInputFrameId
|
|
|
|
|
}
|
|
|
|
|
toApplyInputFrameId = minLastSentInputFrameId
|
|
|
|
|
}
|
|
|
|
|
for pR.InputsBuffer.N < pR.InputsBuffer.Cnt || (0 < pR.InputsBuffer.Cnt && pR.InputsBuffer.StFrameId < toApplyInputFrameId) {
|
|
|
|
|
f := pR.InputsBuffer.Pop().(*pb.InputFrameDownsync)
|
|
|
|
|
if pR.inputFrameIdDebuggable(f.InputFrameId) {
|
|
|
|
@@ -810,7 +821,7 @@ func (pR *Room) OnDismissed() {
|
|
|
|
|
pR.InputFrameUpsyncDelayTolerance = 2
|
|
|
|
|
pR.MaxChasingRenderFramesPerUpdate = 10
|
|
|
|
|
|
|
|
|
|
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
|
|
|
|
|
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
|
|
|
|
|
|
|
|
|
|
pR.ChooseStage()
|
|
|
|
|
pR.EffectivePlayerCount = 0
|
|
|
|
@@ -1080,40 +1091,40 @@ func (pR *Room) prefabInputFrameDownsync(inputFrameId int32) *pb.InputFrameDowns
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (pR *Room) markConfirmationIfApplicable() {
|
|
|
|
|
inputFrameId1 := pR.LastAllConfirmedInputFrameId+1
|
|
|
|
|
gap := int32(4) // This value is hardcoded and doesn't need be much bigger, because the backend side is supposed to never lag when "false == BackendDynamicsEnabled".
|
|
|
|
|
inputFrameId2 := inputFrameId1+gap
|
|
|
|
|
if inputFrameId2 > pR.InputsBuffer.EdFrameId {
|
|
|
|
|
inputFrameId2 = pR.InputsBuffer.EdFrameId
|
|
|
|
|
}
|
|
|
|
|
inputFrameId1 := pR.LastAllConfirmedInputFrameId + 1
|
|
|
|
|
gap := int32(4) // This value is hardcoded and doesn't need be much bigger, because the backend side is supposed to never lag when "false == BackendDynamicsEnabled".
|
|
|
|
|
inputFrameId2 := inputFrameId1 + gap
|
|
|
|
|
if inputFrameId2 > pR.InputsBuffer.EdFrameId {
|
|
|
|
|
inputFrameId2 = pR.InputsBuffer.EdFrameId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
totPlayerCnt := uint32(pR.Capacity)
|
|
|
|
|
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
|
|
|
|
|
for inputFrameId := inputFrameId1; inputFrameId < inputFrameId2; inputFrameId++ {
|
|
|
|
|
tmp := pR.InputsBuffer.GetByFrameId(inputFrameId)
|
|
|
|
|
if nil == tmp {
|
|
|
|
|
panic(fmt.Sprintf("inputFrameId=%v doesn't exist for roomId=%v, this is abnormal because the server should prefab inputFrameDownsync in a most advanced pace, check the prefab logic! InputsBuffer=%v", inputFrameId, pR.Id, pR.InputsBufferString(false)))
|
|
|
|
|
}
|
|
|
|
|
inputFrameDownsync := tmp.(*pb.InputFrameDownsync)
|
|
|
|
|
for _, player := range pR.Players {
|
|
|
|
|
bufIndex := pR.toDiscreteInputsBufferIndex(inputFrameId, player.JoinIndex)
|
|
|
|
|
tmp, loaded := pR.DiscreteInputsBuffer.LoadAndDelete(bufIndex) // It's safe to "LoadAndDelete" here because the "inputFrameUpsync" of this player is already remembered by the corresponding "inputFrameDown".
|
|
|
|
|
if !loaded {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
inputFrameUpsync := tmp.(*pb.InputFrameUpsync)
|
|
|
|
|
indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1)
|
|
|
|
|
inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr] = pR.EncodeUpsyncCmd(inputFrameUpsync)
|
|
|
|
|
inputFrameDownsync.ConfirmedList |= (1 << indiceInJoinIndexBooleanArr)
|
|
|
|
|
}
|
|
|
|
|
totPlayerCnt := uint32(pR.Capacity)
|
|
|
|
|
allConfirmedMask := uint64((1 << totPlayerCnt) - 1)
|
|
|
|
|
for inputFrameId := inputFrameId1; inputFrameId < inputFrameId2; inputFrameId++ {
|
|
|
|
|
tmp := pR.InputsBuffer.GetByFrameId(inputFrameId)
|
|
|
|
|
if nil == tmp {
|
|
|
|
|
panic(fmt.Sprintf("inputFrameId=%v doesn't exist for roomId=%v, this is abnormal because the server should prefab inputFrameDownsync in a most advanced pace, check the prefab logic! InputsBuffer=%v", inputFrameId, pR.Id, pR.InputsBufferString(false)))
|
|
|
|
|
}
|
|
|
|
|
inputFrameDownsync := tmp.(*pb.InputFrameDownsync)
|
|
|
|
|
for _, player := range pR.Players {
|
|
|
|
|
bufIndex := pR.toDiscreteInputsBufferIndex(inputFrameId, player.JoinIndex)
|
|
|
|
|
tmp, loaded := pR.DiscreteInputsBuffer.LoadAndDelete(bufIndex) // It's safe to "LoadAndDelete" here because the "inputFrameUpsync" of this player is already remembered by the corresponding "inputFrameDown".
|
|
|
|
|
if !loaded {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
inputFrameUpsync := tmp.(*pb.InputFrameUpsync)
|
|
|
|
|
indiceInJoinIndexBooleanArr := uint32(player.JoinIndex - 1)
|
|
|
|
|
inputFrameDownsync.InputList[indiceInJoinIndexBooleanArr] = pR.EncodeUpsyncCmd(inputFrameUpsync)
|
|
|
|
|
inputFrameDownsync.ConfirmedList |= (1 << indiceInJoinIndexBooleanArr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Force confirmation of "inputFrame2"
|
|
|
|
|
if allConfirmedMask == inputFrameDownsync.ConfirmedList {
|
|
|
|
|
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
|
|
|
|
|
} else {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Force confirmation of "inputFrame2"
|
|
|
|
|
if allConfirmedMask == inputFrameDownsync.ConfirmedList {
|
|
|
|
|
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
|
|
|
|
|
} else {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (pR *Room) forceConfirmationIfApplicable() uint64 {
|
|
|
|
|