diff --git a/config.def.h b/config.def.h index 7b03437332..fe46c2aab8 100644 --- a/config.def.h +++ b/config.def.h @@ -821,6 +821,12 @@ static const bool netplay_public_announce = true; /* Start netplay in spectator mode */ static const bool netplay_start_as_spectator = false; +/* Allow connections in slave mode */ +static const bool netplay_allow_slaves = true; + +/* Require connections only in slave mode */ +static const bool netplay_require_slaves = false; + /* Netplay without savestates/rewind */ static const bool netplay_stateless_mode = false; diff --git a/configuration.c b/configuration.c index f501ff7325..ca578dc273 100644 --- a/configuration.c +++ b/configuration.c @@ -730,6 +730,8 @@ static struct config_bool_setting *populate_settings_bool(settings_t *settings, #ifdef HAVE_NETWORKING SETTING_BOOL("netplay_public_announce", &settings->netplay.public_announce, true, netplay_public_announce, false); SETTING_BOOL("netplay_start_as_spectator", &settings->netplay.start_as_spectator, false, netplay_start_as_spectator, false); + SETTING_BOOL("netplay_allow_slaves", &settings->netplay.allow_slaves, true, netplay_allow_slaves, false); + SETTING_BOOL("netplay_require_slaves", &settings->netplay.require_slaves, true, netplay_require_slaves, false); SETTING_BOOL("netplay_stateless_mode", &settings->netplay.stateless_mode, false, netplay_stateless_mode, false); SETTING_BOOL("netplay_client_swap_input", &settings->netplay.swap_input, true, netplay_client_swap_input, false); #endif diff --git a/configuration.h b/configuration.h index 3b6c51306f..941ff9f427 100644 --- a/configuration.h +++ b/configuration.h @@ -407,6 +407,8 @@ typedef struct settings char server[255]; unsigned port; bool start_as_spectator; + bool allow_slaves; + bool require_slaves; bool stateless_mode; int check_frames; unsigned input_latency_frames_min; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 8772de1888..8a0be73aa7 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -599,6 +599,8 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_ENABLE_CLIENT, "menu_netplay_enable_client") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_ENABLE_HOST, "menu_netplay_enable_host") +MSG_HASH(MENU_ENUM_LABEL_NETPLAY_ALLOW_SLAVES, + "netplay_allow_slaves") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_IP_ADDRESS, "netplay_ip_address") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_MODE, @@ -613,6 +615,8 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SETTINGS, "menu_netplay_settings") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_PUBLIC_ANNOUNCE, "netplay_public_announce") +MSG_HASH(MENU_ENUM_LABEL_NETPLAY_REQUIRE_SLAVES, + "netplay_require_slaves") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD, "netplay_spectate_password") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE, diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index 4dafc524fc..4decd24c25 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -1540,6 +1540,21 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) { "on start. It's always possible to change mode \n" "later."); break; + case MENU_ENUM_LABEL_NETPLAY_ALLOW_SLAVES: + snprintf(s, len, + "Whether to allow connections in slave mode. \n" + " \n" + "Slave-mode clients require very little processing \n" + "power on either side, but will suffer \n" + "significantly from network latency."); + break; + case MENU_ENUM_LABEL_NETPLAY_REQUIRE_SLAVES: + snprintf(s, len, + "Whether to disallow connections not in slave mode. \n" + " \n" + "Not recommended except for very fast networks \n" + "with very weak machines. \n"); + break; case MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE: snprintf(s, len, "Whether to run netplay in a mode not requiring\n" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 7ca3391f13..9a3adf36e2 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -961,6 +961,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_NEAREST, "Nearest") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY, "Netplay") +MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_ALLOW_SLAVES, + "Allow Slave-Mode Clients") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_CHECK_FRAMES, "Netplay Check Frames") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_INPUT_LATENCY_FRAMES_MIN, @@ -991,6 +993,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD, "Server Password") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_PUBLIC_ANNOUNCE, "Publicly Announce Netplay") +MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_REQUIRE_SLAVES, + "Disallow Non-Slave-Mode Clients") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SETTINGS, "Netplay settings") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_START_AS_SPECTATOR, @@ -2638,6 +2642,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_NETPLAY_START_AS_SPECTATOR, "Whether to start netplay in spectator mode." ) +MSG_HASH( + MENU_ENUM_SUBLABEL_NETPLAY_ALLOW_SLAVES, + "Whether to allow connections in slave mode. Slave-mode clients require very little processing power on either side, but will suffer significantly from network latency." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_NETPLAY_REQUIRE_SLAVES, + "Whether to disallow connections not in slave mode. Not recommended except for very fast networks with very weak machines." + ) MSG_HASH( MENU_ENUM_SUBLABEL_NETPLAY_STATELESS_MODE, "Whether to run netplay in a mode not requiring save states. If set to true, a very fast network is required, but no rewinding is performed, so there will be no netplay jitter." diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 161ff63e57..ee18c7a642 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -190,6 +190,8 @@ default_sublabel_macro(action_bind_sublabel_netplay_tcp_udp_port, MENU_ default_sublabel_macro(action_bind_sublabel_netplay_password, MENU_ENUM_SUBLABEL_NETPLAY_PASSWORD) default_sublabel_macro(action_bind_sublabel_netplay_spectate_password, MENU_ENUM_SUBLABEL_NETPLAY_SPECTATE_PASSWORD) default_sublabel_macro(action_bind_sublabel_netplay_start_as_spectator, MENU_ENUM_SUBLABEL_NETPLAY_START_AS_SPECTATOR) +default_sublabel_macro(action_bind_sublabel_netplay_allow_slaves, MENU_ENUM_SUBLABEL_NETPLAY_ALLOW_SLAVES) +default_sublabel_macro(action_bind_sublabel_netplay_require_slaves, MENU_ENUM_SUBLABEL_NETPLAY_REQUIRE_SLAVES) default_sublabel_macro(action_bind_sublabel_netplay_stateless_mode, MENU_ENUM_SUBLABEL_NETPLAY_STATELESS_MODE) default_sublabel_macro(action_bind_sublabel_netplay_check_frames, MENU_ENUM_SUBLABEL_NETPLAY_CHECK_FRAMES) default_sublabel_macro(action_bind_sublabel_netplay_nat_traversal, MENU_ENUM_SUBLABEL_NETPLAY_NAT_TRAVERSAL) @@ -750,6 +752,12 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_NETPLAY_START_AS_SPECTATOR: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_start_as_spectator); break; + case MENU_ENUM_LABEL_NETPLAY_ALLOW_SLAVES: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_allow_slaves); + break; + case MENU_ENUM_LABEL_NETPLAY_REQUIRE_SLAVES: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_require_slaves); + break; case MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_stateless_mode); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 29d20e35da..7d14e50c8a 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4854,6 +4854,14 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) MENU_ENUM_LABEL_NETPLAY_START_AS_SPECTATOR, PARSE_ONLY_BOOL, false) != -1) count++; + if (menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_NETPLAY_ALLOW_SLAVES, + PARSE_ONLY_BOOL, false) != -1) + count++; + if (menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_NETPLAY_REQUIRE_SLAVES, + PARSE_ONLY_BOOL, false) != -1) + count++; if (menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE, PARSE_ONLY_BOOL, false) != -1) diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 206339be19..ee0a74eb87 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -5674,6 +5674,7 @@ static bool setting_append_list( general_write_handler, general_read_handler); settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); CONFIG_UINT( list, list_info, @@ -5688,6 +5689,7 @@ static bool setting_append_list( general_read_handler); menu_settings_list_current_add_range(list, list_info, 0, 65535, 1, true, true); settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); CONFIG_STRING( list, list_info, @@ -5732,6 +5734,38 @@ static bool setting_append_list( general_read_handler, SD_FLAG_NONE); + CONFIG_BOOL( + list, list_info, + &settings->netplay.allow_slaves, + MENU_ENUM_LABEL_NETPLAY_ALLOW_SLAVES, + MENU_ENUM_LABEL_VALUE_NETPLAY_ALLOW_SLAVES, + true, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); + + CONFIG_BOOL( + list, list_info, + &settings->netplay.require_slaves, + MENU_ENUM_LABEL_NETPLAY_REQUIRE_SLAVES, + MENU_ENUM_LABEL_VALUE_NETPLAY_REQUIRE_SLAVES, + false, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); + CONFIG_BOOL( list, list_info, &settings->netplay.stateless_mode, @@ -5746,6 +5780,7 @@ static bool setting_append_list( general_write_handler, general_read_handler, SD_FLAG_NONE); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); CONFIG_INT( list, list_info, @@ -5773,7 +5808,6 @@ static bool setting_append_list( general_write_handler, general_read_handler); menu_settings_list_current_add_range(list, list_info, 0, 15, 1, true, true); - settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); CONFIG_INT( list, list_info, @@ -5787,14 +5821,13 @@ static bool setting_append_list( general_write_handler, general_read_handler); menu_settings_list_current_add_range(list, list_info, 0, 15, 1, true, true); - settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); CONFIG_BOOL( list, list_info, &settings->netplay.nat_traversal, MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL, MENU_ENUM_LABEL_VALUE_NETPLAY_NAT_TRAVERSAL, - false, + true, MENU_ENUM_LABEL_VALUE_OFF, MENU_ENUM_LABEL_VALUE_ON, &group_info, @@ -5803,6 +5836,7 @@ static bool setting_append_list( general_write_handler, general_read_handler, SD_FLAG_NONE); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); CONFIG_BOOL( list, list_info, @@ -5818,6 +5852,7 @@ static bool setting_append_list( general_write_handler, general_read_handler, SD_FLAG_NONE); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); END_SUB_GROUP(list, list_info, parent_group); diff --git a/msg_hash.h b/msg_hash.h index 52b421b503..d38c6789ea 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1020,6 +1020,8 @@ enum msg_hash_enums MENU_LABEL(NETPLAY_DELAY_FRAMES), MENU_LABEL(NETPLAY_PUBLIC_ANNOUNCE), MENU_LABEL(NETPLAY_START_AS_SPECTATOR), + MENU_LABEL(NETPLAY_ALLOW_SLAVES), + MENU_LABEL(NETPLAY_REQUIRE_SLAVES), MENU_LABEL(NETPLAY_STATELESS_MODE), MENU_LABEL(NETPLAY_CHECK_FRAMES), MENU_LABEL(NETPLAY_INPUT_LATENCY_FRAMES_MIN), 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_buf.c b/network/netplay/netplay_buf.c index 900c9ef904..695b68c67a 100644 --- a/network/netplay/netplay_buf.c +++ b/network/netplay/netplay_buf.c @@ -162,8 +162,7 @@ bool netplay_send(struct socket_buffer *sbuf, int sockfd, const void *buf, } - /* Flush what we can immediately */ - return netplay_send_flush(sbuf, sockfd, false); + return true; } /** diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 2c3bf332d4..bafe2503a3 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 @@ -299,6 +303,11 @@ static bool netplay_poll(void) break; } + case NETPLAY_STALL_SPECTATOR_WAIT: + if (netplay_data->unread_frame_count > netplay_data->self_frame_count) + netplay_data->stall = NETPLAY_STALL_NONE; + break; + case NETPLAY_STALL_INPUT_LATENCY: /* Just let it recalculate momentarily */ netplay_data->stall = NETPLAY_STALL_NONE; @@ -331,7 +340,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) @@ -370,6 +379,16 @@ static bool netplay_poll(void) } } + + /* If we're a spectator, are we ahead at all? */ + if (!netplay_data->is_server && + (netplay_data->self_mode == NETPLAY_CONNECTION_SPECTATING || + netplay_data->self_mode == NETPLAY_CONNECTION_SLAVE) && + netplay_data->unread_frame_count <= netplay_data->self_frame_count) + { + netplay_data->stall = NETPLAY_STALL_SPECTATOR_WAIT; + netplay_data->stall_time = cpu_features_get_time_usec(); + } } /* If we're stalling, consider disconnection */ @@ -943,7 +962,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 +1000,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 390ab535a1..cfef6f415e 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -24,6 +24,7 @@ #include "netplay_private.h" +#include "../../configuration.h" #include "../../runloop.h" #include "../../tasks/tasks_internal.h" @@ -129,9 +130,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,11 +243,12 @@ 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, - (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0) | netplay->self_player, + (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0) | netplay->self_player, dframe->self_state)) return false; } @@ -364,12 +368,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 +387,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 +471,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 +490,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 +516,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 +628,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 +637,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 +664,34 @@ static bool netplay_get_cmd(netplay_t *netplay, { uint32_t payload[2]; uint32_t player = 0; + bool slave = false; + settings_t *settings = config_get_ptr(); + + /* 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); + } + + /* Check if their slave mode request corresponds with what we allow */ + if (settings->netplay.require_slaves) + slave = true; + else if (!settings->netplay.allow_slaves) + slave = false; + payload[0] = htonl(netplay->self_frame_count + 1); if (!netplay->is_server) @@ -671,15 +724,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 +751,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 +826,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 +912,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 +1292,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 +1447,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..fc4a7bd751 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -42,6 +42,7 @@ #define MAX_SERVER_STALL_TIME_USEC (5*1000*1000) #define MAX_CLIENT_STALL_TIME_USEC (10*1000*1000) +#define CATCH_UP_CHECK_TIME_USEC (500*1000) #define MAX_RETRIES 16 #define RETRY_MS 500 @@ -176,6 +177,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,15 +209,28 @@ 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 */ }; enum rarch_netplay_stall_reason { NETPLAY_STALL_NONE = 0, + + /* We're so far ahead that we can't read more data without overflowing the + * buffer */ NETPLAY_STALL_RUNNING_FAST, + + /* We're in spectator or slave mode and are running ahead at all */ + NETPLAY_STALL_SPECTATOR_WAIT, + + /* Our actual execution is catching up with latency-adjusted input frames */ NETPLAY_STALL_INPUT_LATENCY, + + /* The server asked us to stall */ NETPLAY_STALL_SERVER_REQUESTED, + + /* We have no connection and must have one to proceed */ NETPLAY_STALL_NO_CONNECTION }; @@ -331,10 +347,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; @@ -461,6 +480,12 @@ struct netplay /* Opposite of stalling, should we be catching up? */ bool catch_up; + /* When did we start falling behind? */ + retro_time_t catch_up_time; + + /* How far behind did we fall? */ + uint32_t catch_up_behind; + /* Frequency with which to check CRCs */ int check_frames; @@ -758,6 +783,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..cf624c81de 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]; @@ -68,8 +69,16 @@ void netplay_update_unread_ptr(netplay_t *netplay) new_unread_frame_count = netplay->server_frame_count; } - netplay->unread_ptr = new_unread_ptr; - netplay->unread_frame_count = new_unread_frame_count; + if (new_unread_frame_count != (uint32_t) -1) + { + netplay->unread_ptr = new_unread_ptr; + netplay->unread_frame_count = new_unread_frame_count; + } + else + { + netplay->unread_ptr = netplay->self_ptr; + netplay->unread_frame_count = netplay->self_frame_count; + } } } @@ -546,7 +555,7 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled) if (netplay->catch_up) { /* Are we caught up? */ - if (netplay->self_frame_count >= lo_frame_count) + if (netplay->self_frame_count + 1 >= lo_frame_count) { netplay->catch_up = false; input_driver_unset_nonblock_state(); @@ -556,17 +565,44 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled) } else if (!stalled) { - if (netplay->self_frame_count + 2 < lo_frame_count) + if (netplay->self_frame_count + 3 < lo_frame_count) { - /* Are we falling behind? */ - netplay->catch_up = true; - input_driver_set_nonblock_state(); - driver_set_nonblock_state(); + retro_time_t cur_time = cpu_features_get_time_usec(); + uint32_t cur_behind = lo_frame_count - netplay->self_frame_count; + + /* We're behind, but we'll only try to catch up if we're actually + * falling behind, i.e. if we're more behind after some time */ + if (netplay->catch_up_time == 0) + { + /* Record our current time to check for catch-up later */ + netplay->catch_up_time = cur_time; + netplay->catch_up_behind = cur_behind; + + } + else if (cur_time - netplay->catch_up_time > CATCH_UP_CHECK_TIME_USEC) + { + /* Time to check how far behind we are */ + if (netplay->catch_up_behind <= cur_behind) + { + /* We're definitely falling behind! */ + netplay->catch_up = true; + netplay->catch_up_time = 0; + input_driver_set_nonblock_state(); + driver_set_nonblock_state(); + } + else + { + /* Check again in another period */ + netplay->catch_up_time = cur_time; + netplay->catch_up_behind = cur_behind; + } + } } - else if (netplay->self_frame_count + 2 < hi_frame_count) + else if (netplay->self_frame_count + 3 < hi_frame_count) { size_t i; + netplay->catch_up_time = 0; /* We're falling behind some clients but not others, so request that * clients ahead of us stall */ @@ -580,7 +616,7 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled) player = connection->player; /* Are they ahead? */ - if (netplay->self_frame_count + 2 < netplay->read_frame_count[player]) + if (netplay->self_frame_count + 3 < netplay->read_frame_count[player]) { /* Tell them to stall */ if (connection->stall_frame + NETPLAY_MAX_REQ_STALL_FREQUENCY < @@ -594,5 +630,9 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled) } } } + else + netplay->catch_up_time = 0; } + else + netplay->catch_up_time = 0; }