Updating the Netplay README to be true of the current implementation.

This commit is contained in:
Gregor Richards 2016-09-13 16:57:32 -04:00
parent 147d739197
commit c5fe0ec6be

View File

@ -1,4 +1,15 @@
How Netplay works (as documented by somebody who didn't write it):
This is RetroArch's Netplay code. RetroArch Netplay allows a second player to
be connected via the Internet, rather than local at the same computer. Netplay
in RetroArch is guaranteed* to work with perfect synchronization given a few
minor constraints:
(1) The core is deterministic,
(2) The only input devices the core interacts with are the joypad and analog sticks, and
(3) Both the core and the loaded content are identical on host and client.
Furthermore, if the core supports serialization (save states), Netplay allows
for latency and clock drift, providing both host and client with a smooth
experience.
Note that this documentation is all for (the poorly-named) “net” mode, which is
the normal mode, and not “spectator” mode, which has its own whole host of
@ -10,43 +21,61 @@ So long as both sides agree on which frame is which, it should be impossible
for them to become de-synced, since each input event always happens at the
correct frame.
Within the state buffers, there are three locations: self, other and read. Self
is where the emulator believes itself to be, which inevitably will be ahead of
what it's read from the peer. Other is where it was most recently in perfect
sync: i.e., other has been read and actioned. Read is where it's read up to,
which can be slightly ahead of other since it can't always immediately act upon
new data. In general, other ≤ read ≤ self. If read > self, logically it should
try to catch up, but that logic currently doesn't seem to exist (?)
In terms of the implementation, Netplay is in effect a state buffer
(implemented as a ring of buffers) and some pre- and post-frame behaviors.
In terms of the implementation, Netplay is in effect an input buffer and some
pre- and post-frame behaviors.
Within the state buffers, there are three locations: self, other and read. Each
refers to a frame, and a state buffer corresponding to that frame. The state
buffer contains the savestate for the frame, and the input from both the local
and remote players.
Pre-frame, it serializes the core's state, polls for input from the other side,
and if input has not been received for the other side up to the current frame
(which it shouldn't be), “simulates” the other side's input up to the current
frame. The simulation is simply assuming that the input state hasn't changed.
Each frame's local serialized state and simulated or real input goes into the
frame buffers. Frame buffers that are simulated are marked as such.
Self is where the emulator believes itself to be, which may be ahead or behind
of what it's read from the peer. Generally speaking, self progresses at 1 frame
per frame, except when the network stalls, described later.
Other is where it was most recently in perfect sync: i.e., other-1 is the last
frame from which both local and remote input have been actioned. As such, other
is always less than or equal to both self and read. Since the state buffer is a
ring, other is the first frame that it's unsafe to overwrite.
Read is where it's read up to, which can be slightly ahead of other since it
can't always immediately act upon new data.
In general, other ≤ read and other ≤ self. In all likelyhood, read ≤ self, but
it is both possible and supported for the remote host to get ahead of the local
host.
Pre-frame, Netplay serializes the core's state, polls for local input, and
polls for input from the other side. If the input from the other side is too
far behind, it stalls to allow the other side to catch up. To assure that this
stalling does not block the UI thread, it is implemented by rewinding the
thread every frame until data is ready.
If input has not been received for the other side up to the current frame (the
usual case), the remote input is simulated in a simplistic manner. Each
frame's local serialized state and simulated or real input goes into the frame
buffers.
During the frame of execution, when the core requests input, it receives the
input from the thread buffer, both local and real or simulated remote.
Post-frame, it checks whether it's read more than it's actioned, i.e. if read >
other. If so, it rewinds to other and runs the core in replay mode with the
real data up to read, then sets other = read.
other. If so, it rewinds to other (by loading the serialized state there) and
runs the core in replay mode with the real data up to read, then sets other =
read.
When in Netplay mode, the callback for receiving input is replaced by
input_state_net. It is the role of input_state_net to combine the true local
input (whether live or replay) with the remote input (whether true or
simulated).
In the previous implementation, there was seemingly nothing to prevent the self
frame from advancing past the other frame in the ring buffer, causing local
input from one time to be mixed with remote input from another. The new
implementation uses a not-at-all-better strategy of simply refusing local input
if it steps past remote input.
Some thoughts about "frame counts": The frame counters act like indexes into a
0-indexed array; i.e., they refer to the first unactioned frame. With
self_frame_count it's slightly more complicated, since there are two relevant
actions: Reading the data and emulating with the data. The frame count is only
incremented after the latter, but the nature of the emulation assures that the
current frame's input will always be read before it's actioned (or at least, we
should certainly hope so!)
0-indexed array; i.e., they refer to the first unactioned frame. So, when
read_frame_count is 23, we've read 23 frames, but the last frame we read is
frame 22. With self_frame_count it's slightly more complicated, since there are
two relevant actions: Reading the data and emulating with the data. The frame
count is only incremented after the latter, so there is a period of time during
which we've actually read self_frame_count+1 frames of local input.
* Guarantee not actually a guarantee.