RetroArch/network/netplay
2016-12-18 19:28:43 -05:00
..
netplay_buf.c Cleaning up netplay headers. 2016-12-18 19:28:43 -05:00
netplay_delta.c Cleaning up netplay headers. 2016-12-18 19:28:43 -05:00
netplay_discovery.c Cleaning up netplay headers. 2016-12-18 19:28:43 -05:00
netplay_discovery.h Netplay discovery LAN scanning task (incomplete) 2016-12-02 18:56:29 -05:00
netplay_frontend.c Goodbye delay_frames! stateless_mode is the new delay_frames=0 2016-12-18 19:28:43 -05:00
netplay_handshake.c Updated error messages and fixed some stall-related disconnected-client 2016-12-18 19:28:43 -05:00
netplay_init.c Server needs double the buffer frames since two connected clients could 2016-12-18 19:28:43 -05:00
netplay_io.c Updated error messages and fixed some stall-related disconnected-client 2016-12-18 19:28:43 -05:00
netplay_private.h Goodbye delay_frames! stateless_mode is the new delay_frames=0 2016-12-18 19:28:43 -05:00
netplay_sync.c Goodbye delay_frames! stateless_mode is the new delay_frames=0 2016-12-18 19:28:43 -05:00
netplay.h New sync system 2016-12-18 19:28:43 -05:00
README Documenting (somewhat) the new sync system. 2016-12-18 19:28:43 -05:00

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, 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.
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.

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 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
per frame, but both fast-forwarding and stalling occur and are 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.

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 ≤ 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 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
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 state 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 and self > other. If so, it first checks whether its simulated remote
data was correct. If it was, it simply moves other up. If not, it rewinds to
other (by loading the serialized state there) and runs the core in replay mode
with the real data up to the least of self and read, then sets other to that.
To avoid latency building up, if the input from the network is too far ahead
(i.e., unread is too far ahead of self), the frame limiter is momentarily
disabled to catch up. Note that since network latency is expected, the normal
case is the opposite: unread is behind self.

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).

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. 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.

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.


Netplay's command format

Netplay commands consist of a 32-bit command identifier, followed by a 32-bit
payload size, both in network byte order, followed by a payload. The command
identifiers are listed in netplay.h. The commands are described below. Unless
specified otherwise, all payload values are in network byte order.

Command: ACK
Payload: None
Description:
    Acknowledgement. Not used.

Command: NAK
Payload: None
Description:
    Negative Acknowledgement. If received, the connection is terminated. Sent
    whenever a command is malformed or otherwise not understood.

Command: DISCONNECT
Payload: None
Description:
    Gracefully disconnect. Not used.

Command: INPUT
Payload:
    {
       frame number: uint32
       is server data: 1 bit
       player: 31 bits
       joypad input: uint32
       analog 1 input: uint32
       analog 2 input: uint32
    }
Description:
    Input state for each frame. Netplay must send an INPUT command for every
    frame in order to function at all. Client's player value is ignored. Server
    indicates which frames are its own input data because INPUT is a
    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:
    {
       nickname: char[32]
    }
Description:
    Send nickname. Mandatory handshake command.

Command: PASSWORD
Payload:
    {
       password hash: char[64]
    }
Description:
    Send hashed password to server. Mandatory handshake command for clients if
    the server demands a password.

Command: SYNC
Payload:
    {
       frame number: uint32
       connected players: uint32
       flip frame: uint32
       controller devices: uint32[16]
       sram: variable
    }
Description:
    Initial state synchronization. Mandatory handshake command from server to
    client only. Sent after receiving client's NICK. Connected players is a
    bitmap with the lowest bit being player 0. Flip frame is 0 if players
    aren't flipped. Controller devices are the devices plugged into each
    controller port, and 16 is really MAX_USERS.

Command: SPECTATE
Payload: None
Description:
    Request to enter spectate mode. The client should immediately consider
    itself to be in spectator mode and send no further input.

Command: PLAY
Payload: None
Description:
    Request to enter player mode. The client must wait for a MODE command
    before sending input.

Command: MODE
Payload:
    {
       frame number: uint32
       reserved: 14 bits
       playing: 1 bit
       you: 1 bit
       player number: uint16
    }
Description:
    Inform of a connection mode change (possibly of the receiving client). Only
    server-to-client. Frame number is the first frame in which player data is
    expected, or the first frame in which player data is not expected. In the
    case of new players the frame number must be later than the last frame of
    the server's own input that has been sent, and in the case of leaving
    players the frame number must be later than the last frame of the relevant
    player's input that has been transmitted.

Command: MODE_REFUSED
Payload:
    {
       reason: uint32
    }
Description:
    Inform a client that its request to change modes has been refused.

Command: CRC
Payload:
    {
       frame number: uint32
       hash: uint32
    }
Description:
    Informs the peer of the correct CRC hash for the specified frame. If the
    receiver's hash doesn't match, they should send a REQUEST_SAVESTATE
    command.

Command: REQUEST_SAVESTATE
Payload: None
Description:
    Requests that the peer send a savestate.

Command: LOAD_SAVESTATE
Payload:
    {
       frame number: uint32
       uncompressed size: uint32
       serialized save state: blob (variable size)
    }
Description:
    Cause the other side to load a savestate, notionally one which the sending
    side has also loaded. If both sides support zlib compression, the
    serialized state is zlib compressed. Otherwise it is uncompressed.

Command: PAUSE
Payload: None
    Indicates that the core is paused. The receiving peer should also pause.

Command: RESUME
Payload: None
    Indicates that the core is no longer paused.

Command: CHEATS
Unused

Command: FLIP_PLAYERS
Payload:
    {
       frame number: uint32
    }
Description:
    Flip players at the requested frame.

Command: CFG
Unused

Command: CFG_ACK
Unused