From e70ee045bf96b26e2702bbe76b4cf502a4d25b90 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 22 Feb 2017 20:34:17 -0500 Subject: [PATCH] Initial implementation of Netplay master/slave mode. --- network/netplay/README | 12 +- network/netplay/netplay_frontend.c | 12 +- network/netplay/netplay_io.c | 171 ++++++++++++++++++++++++----- network/netplay/netplay_private.h | 17 ++- network/netplay/netplay_sync.c | 1 + 5 files changed, 177 insertions(+), 36 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index 2f32dfd98f..095854627e 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -234,16 +234,22 @@ Description: itself to be in spectator mode and send no further input. Command: PLAY -Payload: None +Payload: + { + reserved: 31 bits + as slave?: 1 bit + } Description: Request to enter player mode. The client must wait for a MODE command - before sending input. + before sending input. Server may refuse or force slave connections, so the + request is not necessarily honored. Payload may be elided if zero. Command: MODE Payload: { frame number: uint32 - reserved: 14 bits + reserved: 13 bits + slave: 1 bit playing: 1 bit you: 1 bit player number: uint16 diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 02b95f21ee..6c3066f8a2 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -220,6 +220,10 @@ static bool netplay_poll(void) /* Simulate the input if we don't have real input */ netplay_simulate_input(netplay_data, netplay_data->run_ptr, false); + /* Handle any slaves */ + if (netplay_data->is_server && netplay_data->connected_slaves) + netplay_handle_slaves(netplay_data); + netplay_update_unread_ptr(netplay_data); /* Figure out how many frames of input latency we should be using to hide @@ -331,7 +335,7 @@ static bool netplay_poll(void) /* If we're not stalled, consider stalling */ if (!netplay_data->stall) { - /* Have we not reat enough latency frames? */ + /* Have we not read enough latency frames? */ if (netplay_data->self_mode == NETPLAY_CONNECTION_PLAYING && netplay_data->connected_players && netplay_data->run_frame_count + netplay_data->input_latency_frames > netplay_data->self_frame_count) @@ -943,7 +947,8 @@ static void netplay_toggle_play_spectate(netplay_t *netplay) char msg[512]; const char *dmsg = NULL; payload[0] = htonl(netplay->self_frame_count); - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING || + netplay->self_mode == NETPLAY_CONNECTION_SLAVE) { /* Mark us as no longer playing */ payload[1] = htonl(netplay->self_player); @@ -980,7 +985,8 @@ static void netplay_toggle_play_spectate(netplay_t *netplay) { uint32_t cmd; - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING || + netplay->self_mode == NETPLAY_CONNECTION_SLAVE) { /* Switch to spectator mode immediately */ netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index 20090e7b9a..68187931ba 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -129,9 +129,11 @@ void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection) else { /* Remove this player */ - if (connection->mode == NETPLAY_CONNECTION_PLAYING) + if (connection->mode == NETPLAY_CONNECTION_PLAYING || + connection->mode == NETPLAY_CONNECTION_SLAVE) { netplay->connected_players &= ~(1<player); + netplay->connected_slaves &= ~(1<player); /* FIXME: Duplication */ if (netplay->is_server) @@ -240,7 +242,8 @@ bool netplay_send_cur_input(netplay_t *netplay, } /* Send our own data */ - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING || + netplay->self_mode == NETPLAY_CONNECTION_SLAVE) { if (!send_input_frame(netplay, connection, NULL, netplay->self_frame_count, @@ -364,12 +367,18 @@ bool netplay_cmd_mode(netplay_t *netplay, enum rarch_netplay_connection_mode mode) { uint32_t cmd; + uint32_t payloadBuf, *payload = NULL; switch (mode) { case NETPLAY_CONNECTION_SPECTATING: cmd = NETPLAY_CMD_SPECTATE; break; + case NETPLAY_CONNECTION_SLAVE: + payload = &payloadBuf; + payloadBuf = htonl(NETPLAY_CMD_PLAY_BIT_SLAVE); + /* Intentional fallthrough */ + case NETPLAY_CONNECTION_PLAYING: cmd = NETPLAY_CMD_PLAY; break; @@ -377,7 +386,8 @@ bool netplay_cmd_mode(netplay_t *netplay, default: return false; } - return netplay_send_raw_cmd(netplay, connection, cmd, NULL, 0); + return netplay_send_raw_cmd(netplay, connection, cmd, payload, + payload ? sizeof(uint32_t) : 0); } /** @@ -460,7 +470,8 @@ static bool netplay_get_cmd(netplay_t *netplay, if (netplay->is_server) { /* Ignore the claimed player #, must be this client */ - if (connection->mode != NETPLAY_CONNECTION_PLAYING) + if (connection->mode != NETPLAY_CONNECTION_PLAYING && + connection->mode != NETPLAY_CONNECTION_SLAVE) { RARCH_ERR("Netplay input from non-participating player.\n"); return netplay_cmd_nak(netplay, connection); @@ -478,16 +489,20 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); } - if (buffer[0] < netplay->read_frame_count[player]) + /* Check the frame number only if they're not in slave mode */ + if (connection->mode == NETPLAY_CONNECTION_PLAYING) { - /* We already had this, so ignore the new transmission */ - break; - } - else if (buffer[0] > netplay->read_frame_count[player]) - { - /* Out of order = out of luck */ - RARCH_ERR("Netplay input out of order.\n"); - return netplay_cmd_nak(netplay, connection); + if (buffer[0] < netplay->read_frame_count[player]) + { + /* We already had this, so ignore the new transmission */ + break; + } + else if (buffer[0] > netplay->read_frame_count[player]) + { + /* Out of order = out of luck */ + RARCH_ERR("Netplay input out of order.\n"); + return netplay_cmd_nak(netplay, connection); + } } /* The data's good! */ @@ -500,15 +515,22 @@ static bool netplay_get_cmd(netplay_t *netplay, memcpy(dframe->real_input_state[player], buffer + 2, WORDS_PER_INPUT*sizeof(uint32_t)); dframe->have_real[player] = true; - netplay->read_ptr[player] = NEXT_PTR(netplay->read_ptr[player]); - netplay->read_frame_count[player]++; - if (netplay->is_server) + /* Slaves may go through several packets of data in the same frame + * if latency is choppy, so we advance and send their data after + * handling all network data this frame */ + if (connection->mode == NETPLAY_CONNECTION_PLAYING) { - /* Forward it on if it's past data*/ - if (dframe->frame <= netplay->self_frame_count) - send_input_frame(netplay, NULL, connection, buffer[0], - player, dframe->real_input_state[player]); + netplay->read_ptr[player] = NEXT_PTR(netplay->read_ptr[player]); + netplay->read_frame_count[player]++; + + if (netplay->is_server) + { + /* Forward it on if it's past data*/ + if (dframe->frame <= netplay->self_frame_count) + send_input_frame(netplay, NULL, connection, buffer[0], + player, dframe->real_input_state[player]); + } } /* If this was server data, advance our server pointer too */ @@ -605,7 +627,8 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); } - if (connection->mode == NETPLAY_CONNECTION_PLAYING) + if (connection->mode == NETPLAY_CONNECTION_PLAYING || + connection->mode == NETPLAY_CONNECTION_SLAVE) { /* The frame we haven't received is their end frame */ payload[0] = htonl(netplay->read_frame_count[connection->player]); @@ -613,6 +636,7 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Mark them as not playing anymore */ connection->mode = NETPLAY_CONNECTION_SPECTATING; netplay->connected_players &= ~(1<player); + netplay->connected_slaves &= ~(1<player); /* Tell everyone */ payload[1] = htonl(connection->player); @@ -639,6 +663,27 @@ static bool netplay_get_cmd(netplay_t *netplay, { uint32_t payload[2]; uint32_t player = 0; + bool slave = false; + + /* Check if they requested slave mode */ + if (cmd_size == sizeof(uint32_t)) + { + RECV(payload, sizeof(uint32_t)) + { + RARCH_ERR("Failed to receive NETPLAY_CMD_PLAY payload.\n"); + return netplay_cmd_nak(netplay, connection); + } + + payload[0] = ntohl(payload[0]); + if (payload[0] & NETPLAY_CMD_PLAY_BIT_SLAVE) + slave = true; + } + else if (cmd_size != 0) + { + RARCH_ERR("Invalid payload size for NETPLAY_CMD_PLAY.\n"); + return netplay_cmd_nak(netplay, connection); + } + payload[0] = htonl(netplay->self_frame_count + 1); if (!netplay->is_server) @@ -671,15 +716,21 @@ static bool netplay_get_cmd(netplay_t *netplay, break; } - if (connection->mode != NETPLAY_CONNECTION_PLAYING) + if (connection->mode != NETPLAY_CONNECTION_PLAYING && + connection->mode != NETPLAY_CONNECTION_SLAVE) { /* Mark them as playing */ - connection->mode = NETPLAY_CONNECTION_PLAYING; + connection->mode = slave ? NETPLAY_CONNECTION_SLAVE : + NETPLAY_CONNECTION_PLAYING; connection->player = player; netplay->connected_players |= 1<connected_slaves |= 1<player); + payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING | + (slave?NETPLAY_CMD_MODE_BIT_SLAVE:0) | + connection->player); netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); /* Announce it */ @@ -692,7 +743,10 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Tell the player even if they were confused */ payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING | - NETPLAY_CMD_MODE_BIT_YOU | connection->player); + ((connection->mode == NETPLAY_CONNECTION_SLAVE)? + NETPLAY_CMD_MODE_BIT_SLAVE:0) | + NETPLAY_CMD_MODE_BIT_YOU | + connection->player); netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); /* And expect their data */ @@ -764,7 +818,21 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); } - netplay->self_mode = NETPLAY_CONNECTION_PLAYING; + /* Our mode is based on whether we have the slave bit set */ + if (mode & NETPLAY_CMD_MODE_BIT_SLAVE) + { + netplay->self_mode = NETPLAY_CONNECTION_SLAVE; + + /* In slave mode we receive the data from the remote side, so + * we actually consider ourself a connected player */ + netplay->connected_players |= (1<read_ptr[player] = netplay->server_ptr; + netplay->read_frame_count[player] = netplay->server_frame_count; + } + else + { + netplay->self_mode = NETPLAY_CONNECTION_PLAYING; + } netplay->self_player = player; /* Fix up current frame info */ @@ -836,6 +904,9 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); } + /* Unmark ourself, in case we were in slave mode */ + netplay->connected_players &= ~(1<mode != NETPLAY_CONNECTION_PLAYING) + if (connection->mode != NETPLAY_CONNECTION_PLAYING && + connection->mode != NETPLAY_CONNECTION_SLAVE) { RARCH_ERR("Netplay state load from a spectator.\n"); return netplay_cmd_nak(netplay, connection); @@ -1212,7 +1284,7 @@ static bool netplay_get_cmd(netplay_t *netplay, } nick[sizeof(nick)-1] = '\0'; - /* We outright ignore pausing from spectators */ + /* We outright ignore pausing from spectators and slaves */ if (connection->mode != NETPLAY_CONNECTION_PLAYING) break; @@ -1367,6 +1439,49 @@ int netplay_poll_net_input(netplay_t *netplay, bool block) return 0; } +/** + * netplay_handle_slaves + * + * Handle any slave connections + */ +void netplay_handle_slaves(netplay_t *netplay) +{ + struct delta_frame *frame = &netplay->buffer[netplay->self_ptr]; + size_t i; + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (connection->active && + connection->mode == NETPLAY_CONNECTION_SLAVE) + { + int player = connection->player; + + /* This is a slave connection. First, should we do anything at all? If + * we've already "read" this data, then we can just ignore it */ + if (netplay->read_frame_count[player] > netplay->self_frame_count) + continue; + + /* Alright, we have to send something. Do we need to generate it first? */ + if (!frame->have_real[player]) + { + /* Copy the previous frame's data */ + memcpy(frame->real_input_state[player], + netplay->buffer[PREV_PTR(netplay->self_ptr)].real_input_state[player], + WORDS_PER_INPUT*sizeof(uint32_t)); + frame->have_real[player] = true; + } + + /* Send it along */ + send_input_frame(netplay, NULL, NULL, netplay->self_frame_count, + player, frame->real_input_state[player]); + + /* And mark it as "read" */ + netplay->read_ptr[player] = NEXT_PTR(netplay->self_ptr); + netplay->read_frame_count[player] = netplay->self_frame_count + 1; + } + } +} + /** * netplay_flip_port * diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index d76e193697..956a4e5147 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -176,6 +176,8 @@ enum netplay_cmd #define NETPLAY_CMD_INPUT_BIT_SERVER (1U<<31) #define NETPLAY_CMD_SYNC_BIT_PAUSED (1U<<31) +#define NETPLAY_CMD_PLAY_BIT_SLAVE (1U) +#define NETPLAY_CMD_MODE_BIT_SLAVE (1U<<18) #define NETPLAY_CMD_MODE_BIT_PLAYING (1U<<17) #define NETPLAY_CMD_MODE_BIT_YOU (1U<<16) @@ -206,6 +208,7 @@ enum rarch_netplay_connection_mode /* Ready: */ NETPLAY_CONNECTION_CONNECTED, /* Modes above this are connected */ NETPLAY_CONNECTION_SPECTATING, /* Spectator mode */ + NETPLAY_CONNECTION_SLAVE, /* Playing in slave mode */ NETPLAY_CONNECTION_PLAYING /* Normal ready state */ }; @@ -331,10 +334,13 @@ struct netplay size_t connections_size; struct netplay_connection one_connection; /* Client only */ - /* Bitmap of players with controllers (whether local or remote) (low bit is - * player 1) */ + /* Bitmap of players with controllers (low bit is player 1) */ uint32_t connected_players; + /* Bitmap of players playing in slave mode (should be a subset of + * connected_players) */ + uint32_t connected_slaves; + /* Maximum player number */ uint32_t player_max; @@ -758,6 +764,13 @@ bool netplay_cmd_stall(netplay_t *netplay, */ int netplay_poll_net_input(netplay_t *netplay, bool block); +/** + * netplay_handle_slaves + * + * Handle any slave connections + */ +void netplay_handle_slaves(netplay_t *netplay); + /** * netplay_flip_port * diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 02f949aa0c..8489a05013 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -55,6 +55,7 @@ void netplay_update_unread_ptr(netplay_t *netplay) for (player = 0; player < MAX_USERS; player++) { if (!(netplay->connected_players & (1<connected_slaves & (1<read_frame_count[player] < new_unread_frame_count) { new_unread_ptr = netplay->read_ptr[player];