mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-26 03:39:00 +00:00
Updated README for UDP discussion.
This commit is contained in:
parent
e6a4295773
commit
e9119530f1
@ -47,3 +47,23 @@ renderFrameId | toApplyInputFrameId
|
||||
..., ..., ..., 368 | 90
|
||||
369, 370, 371, 372 | 91
|
||||
373, 374, 375, ... | 92
|
||||
|
||||
# Would using UDP instead of TCP yield better synchronization performance?
|
||||
Yes, but with non-trivial efforts.
|
||||
|
||||
## Neat advantage using UDP
|
||||
Let's check an actual use case. As soon as an inputFrame becomes all-confirmed, the server should downsync it to all active players -- and upon reception loss of the packet containing this "all-confirmed downsync inputFrame" to a certain player, the server MUST retransmit another packet containing the same inputFrame to that player.
|
||||
|
||||
To apply UDP on this use case, additional `ack & retransmission mechanism` would be required, which is a moderately difficult task -- don't just pick a 3rd party lib using TCP flow-control alike `sliding window mechanism`, e.g. [RUDP](https://www.geeksforgeeks.org/reliable-user-datagram-protocol-rudp/)! Here's why.
|
||||
|
||||
Assume that the server is downsyncing `sequence of packets[#1, #2, #3, #4, #5, #6, #7, #8, #9, #10]`, when using TCP we get the advantage that each active player is guaranteed to receive that same sequence in the same order -- however in a bad, lossy network when `packet#2` got lost several times for a certain player whose reception window size is just 5, it has to wait for the arrival of `packet#2` at `[_, #3, #4, #5, #6]`, thus unable to process `[#7, #8, #9, #10]` which could contain `unpredictable inputFrame` while `#2` being `correct prediction` for that player.
|
||||
|
||||
That's so neat but still an advantage for using UDP! Yet if the TCP flow-control alike `sliding window mechanism` is employed on UDP, such advantage'd be compromised.
|
||||
|
||||
To summarize, if UDP is used we need
|
||||
- an `ack & retransmission mechanism` built on top of it to guarantee reception of critical packets for active players, and
|
||||
- reception order is not necessary to be reserved (mimic [markConfirmationIfApplicable](https://github.com/genxium/DelayNoMore/blob/v0.9.14/battle_srv/models/room.go#L1085) to maintain `lastAllConfirmedInputFrameId`), but
|
||||
- TCP flow-control alike `sliding window mechanism` should be avoided to gain advantage over TCP.
|
||||
|
||||
## Additional hassles to care about using UDP
|
||||
When using UDP, it's also necessary to verify authorization of each incoming packet, e.g. by simple time limited symmetric key, due to being connectionless.
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Preface
|
||||
|
||||
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md).
|
||||
This project is a demo for a websocket-based rollback netcode inspired by [GGPO](https://github.com/pond3r/ggpo/blob/master/doc/README.md). 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) -- not necessarily correct but that's indeed a question to face :)
|
||||
|
||||
The following video is recorded over INTERNET using an input delay of 4 frames and it feels SMOOTH when playing! Please also checkout [this demo video](https://pan.baidu.com/s/1ML6hNupaPHPJRd5rcTvQvw?pwd=8ruc) to see how this demo carries out a full 60fps synchronization with the help of _batched input upsync/downsync_ for satisfying network I/O performance.
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -464,7 +464,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
211.36523796872766,
|
||||
210.4441731196186,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
@ -106,6 +106,7 @@ cc.Class({
|
||||
this.cachedBtnDownLevel = 0;
|
||||
this.cachedBtnLeftLevel = 0;
|
||||
this.cachedBtnRightLevel = 0;
|
||||
|
||||
this.cachedBtnALevel = 0;
|
||||
this.cachedBtnBLevel = 0;
|
||||
|
||||
@ -167,11 +168,11 @@ cc.Class({
|
||||
evt.target.runAction(cc.scaleTo(0.1, 0.3));
|
||||
});
|
||||
self.btnA.on(cc.Node.EventType.TOUCH_END, function(evt) {
|
||||
self.cachedBtnALevel = 0;
|
||||
//self.cachedBtnALevel = 0;
|
||||
evt.target.runAction(cc.scaleTo(0.1, 1.0));
|
||||
});
|
||||
self.btnA.on(cc.Node.EventType.TOUCH_CANCEL, function(evt) {
|
||||
self.cachedBtnALevel = 0;
|
||||
//self.cachedBtnALevel = 0;
|
||||
evt.target.runAction(cc.scaleTo(0.1, 1.0));
|
||||
});
|
||||
}
|
||||
@ -182,11 +183,11 @@ cc.Class({
|
||||
evt.target.runAction(cc.scaleTo(0.1, 0.3));
|
||||
});
|
||||
self.btnB.on(cc.Node.EventType.TOUCH_END, function(evt) {
|
||||
self.cachedBtnBLevel = 0;
|
||||
//self.cachedBtnBLevel = 0;
|
||||
evt.target.runAction(cc.scaleTo(0.1, 1.0));
|
||||
});
|
||||
self.btnB.on(cc.Node.EventType.TOUCH_CANCEL, function(evt) {
|
||||
self.cachedBtnBLevel = 0;
|
||||
//self.cachedBtnBLevel = 0;
|
||||
evt.target.runAction(cc.scaleTo(0.1, 1.0));
|
||||
});
|
||||
}
|
||||
@ -247,12 +248,14 @@ cc.Class({
|
||||
case cc.macro.KEY.d:
|
||||
self.cachedBtnRightLevel = 0;
|
||||
break;
|
||||
/*
|
||||
case cc.macro.KEY.h:
|
||||
self.cachedBtnALevel = 0;
|
||||
break;
|
||||
case cc.macro.KEY.j:
|
||||
self.cachedBtnBLevel = 0;
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -464,6 +467,9 @@ cc.Class({
|
||||
const discretizedDir = this.discretizeDirection(this.stickhead.x, this.stickhead.y, this.joyStickEps).encodedIdx; // There're only 9 dirs, thus using only the lower 4-bits
|
||||
const btnALevel = (this.cachedBtnALevel << 4);
|
||||
const btnBLevel = (this.cachedBtnBLevel << 5);
|
||||
|
||||
this.cachedBtnALevel = 0;
|
||||
this.cachedBtnBLevel = 0;
|
||||
return (btnBLevel + btnALevel + discretizedDir);
|
||||
},
|
||||
|
||||
|
@ -23,7 +23,7 @@ const (
|
||||
GRAVITY_Y = -int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO) // makes all "playerCollider.Y" a multiple of 0.5 in all cases
|
||||
|
||||
INPUT_DELAY_FRAMES = int32(4) // in the count of render frames
|
||||
INPUT_SCALE_FRAMES = uint32(2) // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames)
|
||||
INPUT_SCALE_FRAMES = uint32(3) // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames)
|
||||
NST_DELAY_FRAMES = int32(16) // network-single-trip delay in the count of render frames, proposed to be (InputDelayFrames >> 1) because we expect a round-trip delay to be exactly "InputDelayFrames"
|
||||
|
||||
SP_ATK_LOOKUP_FRAMES = int32(5)
|
||||
|
Loading…
Reference in New Issue
Block a user