From 71f2a1ecdf95f0152f30c0bf2b13ca13b628db4a Mon Sep 17 00:00:00 2001 From: genxium Date: Wed, 1 Mar 2023 07:03:49 +0800 Subject: [PATCH] Updated documentation. --- README.md | 5 +++++ battle_srv/models/room.go | 7 ++++--- frontend/assets/scenes/login.fire | 2 +- frontend/assets/scenes/offline_map.fire | 2 +- frontend/assets/scripts/Map.js | 3 +++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 70cfd88..badc94a 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ This project is a demo for a websocket-based rollback netcode inspired by [GGPO] ![Phone4g_battle_spedup](./charts/Phone4g_battle_spedup.gif) +**Since v1.0.12, smoothness in worst cases (e.g. turn-around on ground, in air and after dashing) is drastically improved due to update of prediction approach. The gifs and corresponding screenrecordings above are not updated because there's no big difference when network is good -- however, `input delay` is now set to `4 frames` -- while `input delay = 6 frames` was used in the screenrecordings -- and smoothness is even better now.** Key changes are listed below. +- [change#1](https://github.com/genxium/DelayNoMore/blob/de9f3c90902bc6da98359be996887a55964e011e/jsexport/battle/battle.go#L647) +- [change#2](https://github.com/genxium/DelayNoMore/blob/de9f3c90902bc6da98359be996887a55964e011e/frontend/assets/scripts/Map.js#L1451) +- [change#3](https://github.com/genxium/DelayNoMore/blob/de9f3c90902bc6da98359be996887a55964e011e/battle_srv/models/room.go#L1312) + As lots of feedbacks ask for a discussion on using UDP instead, I tried to summarize my personal opinion about it in [ConcerningEdgeCases](./ConcerningEdgeCases.md) -- **since v0.9.25, the project is actually equipped with UDP capabilities as follows**. - When using the so called `native apps` on `Android` and `Windows` (I'm working casually hard to support `iOS` next), the frontends will try to use UDP hole-punching w/ the help of backend as a registry. If UDP hole-punching is working, the rollback is often less than `turn-around frames to recover` and thus not noticeable, being much better than using websocket alone. This video shows how the UDP holepunched p2p performs for [Phone-Wifi v.s. PC-Wifi (viewed by PC side)](https://pan.baidu.com/s/1K6704bJKlrSBTVqGcXhajA?pwd=l7ok). - If UDP hole-punching is not working, e.g. for Symmetric NAT like in 4G/5G cellular network, the frontends will use backend as a UDP tunnel (or relay, whatever you like to call it). This video shows how the UDP tunnel performs for [Phone-4G v.s. PC-Wifi (merged view@v0.9.34, excellent synchronization)](https://pan.baidu.com/s/1yeIrN5TSf6_av_8-N3vdVg?pwd=7tzw). diff --git a/battle_srv/models/room.go b/battle_srv/models/room.go index 2ab7ed3..3d2443b 100644 --- a/battle_srv/models/room.go +++ b/battle_srv/models/room.go @@ -1201,7 +1201,8 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr } if clientInputFrameId < player.LastConsecutiveRecvInputFrameId { // [WARNING] It's important for correctness that we use "player.LastConsecutiveRecvInputFrameId" instead of "pR.LastIndividuallyConfirmedInputFrameId[player.JoinIndex-1]" here! - Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#2: roomId=%v, playerId=%v, clientInputFrameId=%v, playerLastConsecutiveRecvInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, player.LastConsecutiveRecvInputFrameId, pR.InputsBufferString(false))) + //Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#2: roomId=%v, playerId=%v, clientInputFrameId=%v, playerLastConsecutiveRecvInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, player.LastConsecutiveRecvInputFrameId, pR.InputsBufferString(false))) + Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#2: roomId=%v, playerId=%v, clientInputFrameId=%v, playerLastConsecutiveRecvInputFrameId=%v", pR.Id, playerId, clientInputFrameId, player.LastConsecutiveRecvInputFrameId)) continue } if clientInputFrameId > pR.InputsBuffer.EdFrameId { @@ -1259,7 +1260,7 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr shouldBreakConfirmation = true // Could be an `ACTIVE SLOW TICKER` here, but no action needed for now break } - Logger.Debug(fmt.Sprintf("markConfirmationIfApplicable for roomId=%v, skipping UNCONFIRMED BUT INACTIVE player(id:%v, joinIndex:%v) while checking inputFrameId=[%v, %v): InputsBuffer=%v", pR.Id, player.Id, player.JoinIndex, inputFrameId1, pR.InputsBuffer.EdFrameId, pR.InputsBufferString(false))) + //Logger.Debug(fmt.Sprintf("markConfirmationIfApplicable for roomId=%v, skipping UNCONFIRMED BUT INACTIVE player(id:%v, joinIndex:%v) while checking inputFrameId=[%v, %v): InputsBuffer=%v", pR.Id, player.Id, player.JoinIndex, inputFrameId1, pR.InputsBuffer.EdFrameId, pR.InputsBufferString(false))) } } @@ -1391,7 +1392,7 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende } } - battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame.Id, pR.Space, pR.CollisionSysMap, pR.SpaceOffsetX, pR.SpaceOffsetY, pR.CharacterConfigsArr, pR.RenderFrameBuffer, pR.collisionHolder, pR.effPushbacks, pR.hardPushbackNormsArr, pR.jumpedOrNotList, pR.dynamicRectangleColliders, pR.LastIndividuallyConfirmedInputFrameId, pR.LastIndividuallyConfirmedInputList, false, MAGIC_JOIN_INDEX_INVALID) // "allowUpdateInputFrameInPlaceUponDynamics" is instead used when "forceConfirmationIfApplicable" + battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame.Id, pR.Space, pR.CollisionSysMap, pR.SpaceOffsetX, pR.SpaceOffsetY, pR.CharacterConfigsArr, pR.RenderFrameBuffer, pR.collisionHolder, pR.effPushbacks, pR.hardPushbackNormsArr, pR.jumpedOrNotList, pR.dynamicRectangleColliders, pR.LastIndividuallyConfirmedInputFrameId, pR.LastIndividuallyConfirmedInputList, false, MAGIC_JOIN_INDEX_INVALID) // "allowUpdateInputFrameInPlaceUponDynamics" is instead used in "forceConfirmationIfApplicable" pR.CurDynamicsRenderFrameId++ } } diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index 3a227ba..ac65035 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -461,7 +461,7 @@ "array": [ 0, 0, - 209.57814771583418, + 210.43877906529718, 0, 0, 0, diff --git a/frontend/assets/scenes/offline_map.fire b/frontend/assets/scenes/offline_map.fire index 113795d..4dd19fa 100644 --- a/frontend/assets/scenes/offline_map.fire +++ b/frontend/assets/scenes/offline_map.fire @@ -518,7 +518,7 @@ "array": [ 0, 0, - 217.5376534000385, + 210.43877906529718, 0, 0, 0, diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index 7fbdedd..a790bf7 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -1444,6 +1444,9 @@ othersForcedDownsyncRenderFrame=${self._stringifyGopkgRdfForFrameDataLogging(oth const ii = gopkgs.ConvertToFirstUsedRenderFrameId(j); if (ii < i) { /* + [WARNING] + If we don't rollback at this spot, when the mutated "delayedInputFrame.inputList" a.k.a. "inputFrame#j" matches the later downsynced version, rollback WOULDN'T be triggered for the incorrectly rendered "renderFrame#(ii+1)", and it would STAY IN HISTORY FOREVER -- as the history becomes incorrect, EVERY LATEST renderFrame since "inputFrame#j" was mutated would be ALWAYS incorrectly rendering too! + The backend counterpart doesn't need this rollback because 1. Backend only applies all-confirmed inputFrames to calc dynamics. 2. Backend applies an all-confirmed inputFrame to all applicable render frames at once.