Compare commits

..

3 Commits

Author SHA1 Message Date
genxium
5b9b3a0b55 Updated documentation. 2023-01-07 22:51:06 +08:00
genxium
24e980c17f Simplified backend input prediction. 2023-01-05 14:07:59 +08:00
genxium
ab6a04693d Enhanced backend input prediction alignment with the frontend counterpart. 2023-01-05 12:14:55 +08:00
5 changed files with 33 additions and 31 deletions

View File

@@ -46,9 +46,10 @@ type Player struct {
TutorialStage int `db:"tutorial_stage"`
// other in-battle info fields
LastSentInputFrameId int32
AckingFrameId int32
AckingInputFrameId int32
LastReceivedInputFrameId int32
LastSentInputFrameId int32
AckingFrameId int32
AckingInputFrameId int32
}
func ExistPlayerByName(name string) (bool, error) {

View File

@@ -128,7 +128,7 @@ type Room struct {
EffectivePlayerCount int32
DismissalWaitGroup sync.WaitGroup
InputsBuffer *battle.RingBuffer // Indices are STRICTLY consecutive
InputsBufferLock sync.Mutex // Guards [InputsBuffer, LatestPlayerUpsyncedInputFrameId, LastAllConfirmedInputFrameId, LastAllConfirmedInputList, LastAllConfirmedInputFrameIdWithChange]
InputsBufferLock sync.Mutex // Guards [InputsBuffer, LatestPlayerUpsyncedInputFrameId, LastAllConfirmedInputFrameId, LastAllConfirmedInputList, LastAllConfirmedInputFrameIdWithChange, LastIndividuallyConfirmedInputList, player.LastReceivedInputFrameId]
RenderFrameBuffer *battle.RingBuffer // Indices are STRICTLY consecutive
LatestPlayerUpsyncedInputFrameId int32
LastAllConfirmedInputFrameId int32
@@ -148,7 +148,8 @@ type Room struct {
TmxPointsMap StrToVec2DListMap
TmxPolygonsMap StrToPolygon2DListMap
rdfIdToActuallyUsedInput map[int32]*pb.InputFrameDownsync
rdfIdToActuallyUsedInput map[int32]*pb.InputFrameDownsync
LastIndividuallyConfirmedInputList []uint64
}
func (pR *Room) updateScore() {
@@ -172,6 +173,7 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
pPlayerFromDbInit.AckingFrameId = -1
pPlayerFromDbInit.AckingInputFrameId = -1
pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
pPlayerFromDbInit.LastReceivedInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
@@ -740,6 +742,7 @@ func (pR *Room) OnDismissed() {
pR.RenderFrameBuffer = battle.NewRingBuffer(pR.RenderCacheSize)
pR.InputsBuffer = battle.NewRingBuffer((pR.RenderCacheSize >> 1) + 1)
pR.rdfIdToActuallyUsedInput = make(map[int32]*pb.InputFrameDownsync)
pR.LastIndividuallyConfirmedInputList = make([]uint64, pR.Capacity)
pR.LatestPlayerUpsyncedInputFrameId = -1
pR.LastAllConfirmedInputFrameId = -1
@@ -1016,12 +1019,7 @@ func (pR *Room) sendSafely(roomDownsyncFrame *pb.RoomDownsyncFrame, toSendInputF
func (pR *Room) getOrPrefabInputFrameDownsync(inputFrameId int32) *battle.InputFrameDownsync {
/*
[WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked.
Kindly note that on backend the prefab is much simpler than its frontend counterpart, because frontend will upsync its latest command immediately if there's any change w.r.t. its own prev cmd, thus if no upsync received from a frontend,
- EITHER it's due to local lag and bad network,
- OR there's no change w.r.t. to its prev cmd.
*/
var currInputFrameDownsync *battle.InputFrameDownsync = nil
tmp1 := pR.InputsBuffer.GetByFrameId(inputFrameId) // Would be nil if "pR.InputsBuffer.EdFrameId <= inputFrameId", else if "pR.InputsBuffer.EdFrameId > inputFrameId" is already met, then by now we can just return "tmp1.(*InputFrameDownsync)"
if nil == tmp1 {
@@ -1033,18 +1031,26 @@ func (pR *Room) getOrPrefabInputFrameDownsync(inputFrameId int32) *battle.InputF
ConfirmedList: uint64(0),
}
j2 := j - 1
tmp2 := pR.InputsBuffer.GetByFrameId(j2)
if nil != tmp2 {
prevInputFrameDownsync := tmp2.(*battle.InputFrameDownsync)
for i, _ := range currInputFrameDownsync.InputList {
currInputFrameDownsync.InputList[i] = prevInputFrameDownsync.InputList[i]
}
}
/*
[WARNING] Don't reference "pR.InputsBuffer.GetByFrameId(j-1)" to prefab here!
Otherwise if an ActiveSlowTicker got a forced confirmation sequence like
```
inputFrame#42 {dx: -2} upsynced;
inputFrame#43-50 {dx: +2} ignored by [type#1 forceConfirmation];
inputFrame#51 {dx: +2} upsynced;
inputFrame#52-60 {dx: +2} ignored by [type#1 forceConfirmation];
inputFrame#61 {dx: +2} upsynced;
...there would be more [type#1 forceConfirmation]s for this ActiveSlowTicker if it doesn't catch up the upsync pace...
```
, the backend might've been prefabbing TOO QUICKLY and thus still replicating "inputFrame#42" by now for this ActiveSlowTicker, making its graphics inconsistent upon "[type#1 forceConfirmation] at inputFrame#52-60", i.e. as if always dragged to the left while having been controlled to the right for a few frames -- what's worse, the same graphical inconsistence could even impact later "[type#1 forceConfirmation]s" if this ActiveSlowTicker doesn't catch up with the upsync pace!
*/
for i, _ := range currInputFrameDownsync.InputList {
// [WARNING] The use of "InputsBufferLock" guarantees that by now "inputFrameId >= pR.InputsBuffer.EdFrameId >= pR.LatestPlayerUpsyncedInputFrameId", thus it's safe to use "pR.LastIndividuallyConfirmedInputList" for prediction.
// Don't predict "btnA & btnB"!
currInputFrameDownsync.InputList[i] = (currInputFrameDownsync.InputList[i] & uint64(15))
currInputFrameDownsync.InputList[i] = (pR.LastIndividuallyConfirmedInputList[i] & uint64(15))
}
pR.InputsBuffer.Put(currInputFrameDownsync)
@@ -1066,8 +1072,8 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#1: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
continue
}
if clientInputFrameId < pR.LastAllConfirmedInputFrameId {
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#2: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
if clientInputFrameId < player.LastReceivedInputFrameId {
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#2: roomId=%v, playerId=%v, clientInputFrameId=%v, playerLastReceivedInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, player.LastReceivedInputFrameId, pR.InputsBufferString(false)))
continue
}
if clientInputFrameId > pR.InputsBuffer.EdFrameId {
@@ -1079,6 +1085,9 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr
targetInputFrameDownsync.InputList[player.JoinIndex-1] = inputFrameUpsync.Encoded
targetInputFrameDownsync.ConfirmedList |= uint64(1 << uint32(player.JoinIndex-1))
player.LastReceivedInputFrameId = clientInputFrameId
pR.LastIndividuallyConfirmedInputList[player.JoinIndex-1] = inputFrameUpsync.Encoded
if clientInputFrameId > pR.LatestPlayerUpsyncedInputFrameId {
pR.LatestPlayerUpsyncedInputFrameId = clientInputFrameId
}

View File

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

View File

@@ -1082,14 +1082,6 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
}
const nextRdf = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, currRdf, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex);
if (3 == nextRdf.PlayersArr[0].ActiveSkillId && 3 != currRdf.PlayersArr[0].ActiveSkillId) {
console.log(`Started skill 3 at rdf.Id=${nextRdf.Id}`);
self.lastSkill3Started = nextRdf.Id;
}
if (3 != nextRdf.PlayersArr[0].ActiveSkillId && 3 == currRdf.PlayersArr[0].ActiveSkillId) {
console.log(`Stopped skill 3 at rdf.Id=${nextRdf.Id}, duration = ${nextRdf.Id-self.lastSkill3Started}`);
}
if (true == isChasing) {
// [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic!
self.chaserRenderFrameId = nextRdf.Id;

View File

@@ -1,4 +1,4 @@
GopherJs is supposed to be run by `go run`.
GopherJs is NOT supposed to be run by `go run` but `gopherjs <args>` instead.
If on-the-fly compilation is needed, run `gopherjs serve jsexport` and then visit `http://localhost:8080/jsexport.js` -- if 404 not found is responded, run `gopherjs build` to check syntax errors.