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
|
..., ..., ..., 368 | 90
|
||||||
369, 370, 371, 372 | 91
|
369, 370, 371, 372 | 91
|
||||||
373, 374, 375, ... | 92
|
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
|
# 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.
|
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": [
|
"array": [
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
211.36523796872766,
|
210.4441731196186,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -106,6 +106,7 @@ cc.Class({
|
|||||||
this.cachedBtnDownLevel = 0;
|
this.cachedBtnDownLevel = 0;
|
||||||
this.cachedBtnLeftLevel = 0;
|
this.cachedBtnLeftLevel = 0;
|
||||||
this.cachedBtnRightLevel = 0;
|
this.cachedBtnRightLevel = 0;
|
||||||
|
|
||||||
this.cachedBtnALevel = 0;
|
this.cachedBtnALevel = 0;
|
||||||
this.cachedBtnBLevel = 0;
|
this.cachedBtnBLevel = 0;
|
||||||
|
|
||||||
@ -167,11 +168,11 @@ cc.Class({
|
|||||||
evt.target.runAction(cc.scaleTo(0.1, 0.3));
|
evt.target.runAction(cc.scaleTo(0.1, 0.3));
|
||||||
});
|
});
|
||||||
self.btnA.on(cc.Node.EventType.TOUCH_END, function(evt) {
|
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));
|
evt.target.runAction(cc.scaleTo(0.1, 1.0));
|
||||||
});
|
});
|
||||||
self.btnA.on(cc.Node.EventType.TOUCH_CANCEL, function(evt) {
|
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));
|
evt.target.runAction(cc.scaleTo(0.1, 1.0));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -182,11 +183,11 @@ cc.Class({
|
|||||||
evt.target.runAction(cc.scaleTo(0.1, 0.3));
|
evt.target.runAction(cc.scaleTo(0.1, 0.3));
|
||||||
});
|
});
|
||||||
self.btnB.on(cc.Node.EventType.TOUCH_END, function(evt) {
|
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));
|
evt.target.runAction(cc.scaleTo(0.1, 1.0));
|
||||||
});
|
});
|
||||||
self.btnB.on(cc.Node.EventType.TOUCH_CANCEL, function(evt) {
|
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));
|
evt.target.runAction(cc.scaleTo(0.1, 1.0));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -247,12 +248,14 @@ cc.Class({
|
|||||||
case cc.macro.KEY.d:
|
case cc.macro.KEY.d:
|
||||||
self.cachedBtnRightLevel = 0;
|
self.cachedBtnRightLevel = 0;
|
||||||
break;
|
break;
|
||||||
|
/*
|
||||||
case cc.macro.KEY.h:
|
case cc.macro.KEY.h:
|
||||||
self.cachedBtnALevel = 0;
|
self.cachedBtnALevel = 0;
|
||||||
break;
|
break;
|
||||||
case cc.macro.KEY.j:
|
case cc.macro.KEY.j:
|
||||||
self.cachedBtnBLevel = 0;
|
self.cachedBtnBLevel = 0;
|
||||||
break;
|
break;
|
||||||
|
*/
|
||||||
default:
|
default:
|
||||||
break;
|
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 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 btnALevel = (this.cachedBtnALevel << 4);
|
||||||
const btnBLevel = (this.cachedBtnBLevel << 5);
|
const btnBLevel = (this.cachedBtnBLevel << 5);
|
||||||
|
|
||||||
|
this.cachedBtnALevel = 0;
|
||||||
|
this.cachedBtnBLevel = 0;
|
||||||
return (btnBLevel + btnALevel + discretizedDir);
|
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
|
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_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"
|
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)
|
SP_ATK_LOOKUP_FRAMES = int32(5)
|
||||||
|
Loading…
Reference in New Issue
Block a user