mirror of
https://github.com/libretro/RetroArch
synced 2025-03-28 19:20:35 +00:00
Updating Netplay README to be more true
This commit is contained in:
parent
f6f9905ae3
commit
3d7f1f6575
@ -9,11 +9,7 @@ minor constraints:
|
||||
|
||||
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
|
||||
problems.
|
||||
experience, as well as the option of more than two players.
|
||||
|
||||
Netplay in RetroArch works by expecting input to come delayed from the network,
|
||||
then rewinding and re-playing with the delayed input to get a consistent state.
|
||||
@ -24,10 +20,10 @@ correct frame.
|
||||
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.
|
||||
|
||||
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.
|
||||
Within the state buffers, there are three locations: self, other and unread.
|
||||
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.
|
||||
|
||||
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
|
||||
@ -38,18 +34,31 @@ 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.
|
||||
Unread is the first frame at which not all players' data has been read, 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 likelihood, read ≤ self, but
|
||||
it is both possible and supported for the remote host to get ahead of the local
|
||||
host.
|
||||
In general, other ≤ unread and other ≤ self. In all likelihood, unread ≤ self,
|
||||
but it is both possible and supported for the remote host to get ahead of the
|
||||
local host.
|
||||
|
||||
The server has a slightly more complicated job as it can handle multiple
|
||||
clients, however it is not vastly more complicated: For each connection which
|
||||
is playing (i.e., has a controller), it maintains a per-player unread frame,
|
||||
and the global unread frame is the earliest of each player unread frame. The
|
||||
server forwards input data: When input data is received from an earlier frame
|
||||
than the server's current frame, it forwards it immediately. Otherwise, it
|
||||
forwards it when the frame is reached. i.e., during frame n, the server may
|
||||
send its own and any number of other players' data for frame n, but will never
|
||||
send frame n+1. This is because the server's clock is the arbiter of all
|
||||
synchronization-related events, such as flipping players, players joining and
|
||||
parting, and saving/loading states.
|
||||
|
||||
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 similarly to pausing,
|
||||
rather than by blocking on the socket.
|
||||
polls for input from the network. If the input from the network is too far
|
||||
behind (i.e., unread is too far behind self), it stalls to allow the other side
|
||||
to catch up. To assure that this stalling does not block the UI thread, it is
|
||||
implemented similarly to pausing, rather than by blocking on the socket.
|
||||
|
||||
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
|
||||
@ -78,6 +87,17 @@ 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.
|
||||
|
||||
Clients may come and go, and may start or stop playing even as they're
|
||||
connected. A client that is not playing is said to be “spectating”: It receives
|
||||
all the same data but sends none. A client may switch from spectating to
|
||||
playing by sending the appropriate request, at which point it is allotted a
|
||||
player number (see the SPECTATE, PLAY and MODE commands below).
|
||||
|
||||
The server may also be in spectator mode, but as the server never sends data
|
||||
early (i.e., it only forwards data on the frame it's reached), it must also
|
||||
inform all clients of its own current frame even if it has no input. The
|
||||
NOINPUT command is provided for that purpose.
|
||||
|
||||
|
||||
* Guarantee not actually a guarantee.
|
||||
|
||||
@ -122,6 +142,15 @@ Description:
|
||||
synchronization point: No synchronization events from the given frame may
|
||||
arrive after the server's input for the frame.
|
||||
|
||||
Command: NOINPUT
|
||||
Payload:
|
||||
{
|
||||
frame number: uint32
|
||||
}
|
||||
Description:
|
||||
Sent by the server to indicate a frame has passed when the server is not
|
||||
otherwise sending data.
|
||||
|
||||
Command: NICK
|
||||
Payload:
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user