Merge pull request #4691 from GregorR/netplay-slave-mode

Netplay slave mode
This commit is contained in:
Twinaphex 2017-02-26 21:49:21 +01:00 committed by GitHub
commit fdbdcfee65
16 changed files with 367 additions and 52 deletions

View File

@ -821,6 +821,12 @@ static const bool netplay_public_announce = true;
/* Start netplay in spectator mode */ /* Start netplay in spectator mode */
static const bool netplay_start_as_spectator = false; 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 */ /* Netplay without savestates/rewind */
static const bool netplay_stateless_mode = false; static const bool netplay_stateless_mode = false;

View File

@ -730,6 +730,8 @@ static struct config_bool_setting *populate_settings_bool(settings_t *settings,
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
SETTING_BOOL("netplay_public_announce", &settings->netplay.public_announce, true, netplay_public_announce, false); 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_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_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); SETTING_BOOL("netplay_client_swap_input", &settings->netplay.swap_input, true, netplay_client_swap_input, false);
#endif #endif

View File

@ -407,6 +407,8 @@ typedef struct settings
char server[255]; char server[255];
unsigned port; unsigned port;
bool start_as_spectator; bool start_as_spectator;
bool allow_slaves;
bool require_slaves;
bool stateless_mode; bool stateless_mode;
int check_frames; int check_frames;
unsigned input_latency_frames_min; unsigned input_latency_frames_min;

View File

@ -599,6 +599,8 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_ENABLE_CLIENT,
"menu_netplay_enable_client") "menu_netplay_enable_client")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_ENABLE_HOST, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_ENABLE_HOST,
"menu_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, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_IP_ADDRESS,
"netplay_ip_address") "netplay_ip_address")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_MODE, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_MODE,
@ -613,6 +615,8 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SETTINGS,
"menu_netplay_settings") "menu_netplay_settings")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_PUBLIC_ANNOUNCE, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_PUBLIC_ANNOUNCE,
"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, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD,
"netplay_spectate_password") "netplay_spectate_password")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE,

View File

@ -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" "on start. It's always possible to change mode \n"
"later."); "later.");
break; 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: case MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE:
snprintf(s, len, snprintf(s, len,
"Whether to run netplay in a mode not requiring\n" "Whether to run netplay in a mode not requiring\n"

View File

@ -961,6 +961,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_NEAREST,
"Nearest") "Nearest")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY, MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY,
"Netplay") "Netplay")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_ALLOW_SLAVES,
"Allow Slave-Mode Clients")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_CHECK_FRAMES, MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_CHECK_FRAMES,
"Netplay Check Frames") "Netplay Check Frames")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_INPUT_LATENCY_FRAMES_MIN, 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") "Server Password")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_PUBLIC_ANNOUNCE, MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_PUBLIC_ANNOUNCE,
"Publicly Announce Netplay") "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, MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SETTINGS,
"Netplay settings") "Netplay settings")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_START_AS_SPECTATOR, MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_START_AS_SPECTATOR,
@ -2638,6 +2642,14 @@ MSG_HASH(
MENU_ENUM_SUBLABEL_NETPLAY_START_AS_SPECTATOR, MENU_ENUM_SUBLABEL_NETPLAY_START_AS_SPECTATOR,
"Whether to start netplay in spectator mode." "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( MSG_HASH(
MENU_ENUM_SUBLABEL_NETPLAY_STATELESS_MODE, 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." "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."

View File

@ -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_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_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_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_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_check_frames, MENU_ENUM_SUBLABEL_NETPLAY_CHECK_FRAMES)
default_sublabel_macro(action_bind_sublabel_netplay_nat_traversal, MENU_ENUM_SUBLABEL_NETPLAY_NAT_TRAVERSAL) 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: case MENU_ENUM_LABEL_NETPLAY_START_AS_SPECTATOR:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_start_as_spectator); BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_start_as_spectator);
break; 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: case MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_stateless_mode); BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_stateless_mode);
break; break;

View File

@ -4854,6 +4854,14 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data)
MENU_ENUM_LABEL_NETPLAY_START_AS_SPECTATOR, MENU_ENUM_LABEL_NETPLAY_START_AS_SPECTATOR,
PARSE_ONLY_BOOL, false) != -1) PARSE_ONLY_BOOL, false) != -1)
count++; 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, if (menu_displaylist_parse_settings_enum(menu, info,
MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE, MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE,
PARSE_ONLY_BOOL, false) != -1) PARSE_ONLY_BOOL, false) != -1)

View File

@ -5674,6 +5674,7 @@ static bool setting_append_list(
general_write_handler, general_write_handler,
general_read_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_ALLOW_INPUT);
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
CONFIG_UINT( CONFIG_UINT(
list, list_info, list, list_info,
@ -5688,6 +5689,7 @@ static bool setting_append_list(
general_read_handler); general_read_handler);
menu_settings_list_current_add_range(list, list_info, 0, 65535, 1, true, true); 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_ALLOW_INPUT);
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
CONFIG_STRING( CONFIG_STRING(
list, list_info, list, list_info,
@ -5732,6 +5734,38 @@ static bool setting_append_list(
general_read_handler, general_read_handler,
SD_FLAG_NONE); 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( CONFIG_BOOL(
list, list_info, list, list_info,
&settings->netplay.stateless_mode, &settings->netplay.stateless_mode,
@ -5746,6 +5780,7 @@ static bool setting_append_list(
general_write_handler, general_write_handler,
general_read_handler, general_read_handler,
SD_FLAG_NONE); SD_FLAG_NONE);
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
CONFIG_INT( CONFIG_INT(
list, list_info, list, list_info,
@ -5773,7 +5808,6 @@ static bool setting_append_list(
general_write_handler, general_write_handler,
general_read_handler); general_read_handler);
menu_settings_list_current_add_range(list, list_info, 0, 15, 1, true, true); 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( CONFIG_INT(
list, list_info, list, list_info,
@ -5787,14 +5821,13 @@ static bool setting_append_list(
general_write_handler, general_write_handler,
general_read_handler); general_read_handler);
menu_settings_list_current_add_range(list, list_info, 0, 15, 1, true, true); 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( CONFIG_BOOL(
list, list_info, list, list_info,
&settings->netplay.nat_traversal, &settings->netplay.nat_traversal,
MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL, MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL,
MENU_ENUM_LABEL_VALUE_NETPLAY_NAT_TRAVERSAL, MENU_ENUM_LABEL_VALUE_NETPLAY_NAT_TRAVERSAL,
false, true,
MENU_ENUM_LABEL_VALUE_OFF, MENU_ENUM_LABEL_VALUE_OFF,
MENU_ENUM_LABEL_VALUE_ON, MENU_ENUM_LABEL_VALUE_ON,
&group_info, &group_info,
@ -5803,6 +5836,7 @@ static bool setting_append_list(
general_write_handler, general_write_handler,
general_read_handler, general_read_handler,
SD_FLAG_NONE); SD_FLAG_NONE);
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
CONFIG_BOOL( CONFIG_BOOL(
list, list_info, list, list_info,
@ -5818,6 +5852,7 @@ static bool setting_append_list(
general_write_handler, general_write_handler,
general_read_handler, general_read_handler,
SD_FLAG_NONE); SD_FLAG_NONE);
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
END_SUB_GROUP(list, list_info, parent_group); END_SUB_GROUP(list, list_info, parent_group);

View File

@ -1020,6 +1020,8 @@ enum msg_hash_enums
MENU_LABEL(NETPLAY_DELAY_FRAMES), MENU_LABEL(NETPLAY_DELAY_FRAMES),
MENU_LABEL(NETPLAY_PUBLIC_ANNOUNCE), MENU_LABEL(NETPLAY_PUBLIC_ANNOUNCE),
MENU_LABEL(NETPLAY_START_AS_SPECTATOR), MENU_LABEL(NETPLAY_START_AS_SPECTATOR),
MENU_LABEL(NETPLAY_ALLOW_SLAVES),
MENU_LABEL(NETPLAY_REQUIRE_SLAVES),
MENU_LABEL(NETPLAY_STATELESS_MODE), MENU_LABEL(NETPLAY_STATELESS_MODE),
MENU_LABEL(NETPLAY_CHECK_FRAMES), MENU_LABEL(NETPLAY_CHECK_FRAMES),
MENU_LABEL(NETPLAY_INPUT_LATENCY_FRAMES_MIN), MENU_LABEL(NETPLAY_INPUT_LATENCY_FRAMES_MIN),

View File

@ -234,16 +234,22 @@ Description:
itself to be in spectator mode and send no further input. itself to be in spectator mode and send no further input.
Command: PLAY Command: PLAY
Payload: None Payload:
{
reserved: 31 bits
as slave?: 1 bit
}
Description: Description:
Request to enter player mode. The client must wait for a MODE command 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 Command: MODE
Payload: Payload:
{ {
frame number: uint32 frame number: uint32
reserved: 14 bits reserved: 13 bits
slave: 1 bit
playing: 1 bit playing: 1 bit
you: 1 bit you: 1 bit
player number: uint16 player number: uint16

View File

@ -162,8 +162,7 @@ bool netplay_send(struct socket_buffer *sbuf, int sockfd, const void *buf,
} }
/* Flush what we can immediately */ return true;
return netplay_send_flush(sbuf, sockfd, false);
} }
/** /**

View File

@ -220,6 +220,10 @@ static bool netplay_poll(void)
/* Simulate the input if we don't have real input */ /* Simulate the input if we don't have real input */
netplay_simulate_input(netplay_data, netplay_data->run_ptr, false); 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); netplay_update_unread_ptr(netplay_data);
/* Figure out how many frames of input latency we should be using to hide /* Figure out how many frames of input latency we should be using to hide
@ -299,6 +303,11 @@ static bool netplay_poll(void)
break; 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: case NETPLAY_STALL_INPUT_LATENCY:
/* Just let it recalculate momentarily */ /* Just let it recalculate momentarily */
netplay_data->stall = NETPLAY_STALL_NONE; netplay_data->stall = NETPLAY_STALL_NONE;
@ -331,7 +340,7 @@ static bool netplay_poll(void)
/* If we're not stalled, consider stalling */ /* If we're not stalled, consider stalling */
if (!netplay_data->stall) 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 && if (netplay_data->self_mode == NETPLAY_CONNECTION_PLAYING &&
netplay_data->connected_players && netplay_data->connected_players &&
netplay_data->run_frame_count + netplay_data->input_latency_frames > netplay_data->self_frame_count) 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 */ /* If we're stalling, consider disconnection */
@ -943,7 +962,8 @@ static void netplay_toggle_play_spectate(netplay_t *netplay)
char msg[512]; char msg[512];
const char *dmsg = NULL; const char *dmsg = NULL;
payload[0] = htonl(netplay->self_frame_count); 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 */ /* Mark us as no longer playing */
payload[1] = htonl(netplay->self_player); payload[1] = htonl(netplay->self_player);
@ -980,7 +1000,8 @@ static void netplay_toggle_play_spectate(netplay_t *netplay)
{ {
uint32_t cmd; 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 */ /* Switch to spectator mode immediately */
netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; netplay->self_mode = NETPLAY_CONNECTION_SPECTATING;

View File

@ -24,6 +24,7 @@
#include "netplay_private.h" #include "netplay_private.h"
#include "../../configuration.h"
#include "../../runloop.h" #include "../../runloop.h"
#include "../../tasks/tasks_internal.h" #include "../../tasks/tasks_internal.h"
@ -129,9 +130,11 @@ void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection)
else else
{ {
/* Remove this player */ /* Remove this player */
if (connection->mode == NETPLAY_CONNECTION_PLAYING) if (connection->mode == NETPLAY_CONNECTION_PLAYING ||
connection->mode == NETPLAY_CONNECTION_SLAVE)
{ {
netplay->connected_players &= ~(1<<connection->player); netplay->connected_players &= ~(1<<connection->player);
netplay->connected_slaves &= ~(1<<connection->player);
/* FIXME: Duplication */ /* FIXME: Duplication */
if (netplay->is_server) if (netplay->is_server)
@ -240,11 +243,12 @@ bool netplay_send_cur_input(netplay_t *netplay,
} }
/* Send our own data */ /* 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, if (!send_input_frame(netplay, connection, NULL,
netplay->self_frame_count, 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)) dframe->self_state))
return false; return false;
} }
@ -364,12 +368,18 @@ bool netplay_cmd_mode(netplay_t *netplay,
enum rarch_netplay_connection_mode mode) enum rarch_netplay_connection_mode mode)
{ {
uint32_t cmd; uint32_t cmd;
uint32_t payloadBuf, *payload = NULL;
switch (mode) switch (mode)
{ {
case NETPLAY_CONNECTION_SPECTATING: case NETPLAY_CONNECTION_SPECTATING:
cmd = NETPLAY_CMD_SPECTATE; cmd = NETPLAY_CMD_SPECTATE;
break; break;
case NETPLAY_CONNECTION_SLAVE:
payload = &payloadBuf;
payloadBuf = htonl(NETPLAY_CMD_PLAY_BIT_SLAVE);
/* Intentional fallthrough */
case NETPLAY_CONNECTION_PLAYING: case NETPLAY_CONNECTION_PLAYING:
cmd = NETPLAY_CMD_PLAY; cmd = NETPLAY_CMD_PLAY;
break; break;
@ -377,7 +387,8 @@ bool netplay_cmd_mode(netplay_t *netplay,
default: default:
return false; 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) if (netplay->is_server)
{ {
/* Ignore the claimed player #, must be this client */ /* 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"); RARCH_ERR("Netplay input from non-participating player.\n");
return netplay_cmd_nak(netplay, connection); return netplay_cmd_nak(netplay, connection);
@ -478,16 +490,20 @@ static bool netplay_get_cmd(netplay_t *netplay,
return netplay_cmd_nak(netplay, connection); 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 */ if (buffer[0] < netplay->read_frame_count[player])
break; {
} /* We already had this, so ignore the new transmission */
else if (buffer[0] > netplay->read_frame_count[player]) break;
{ }
/* Out of order = out of luck */ else if (buffer[0] > netplay->read_frame_count[player])
RARCH_ERR("Netplay input out of order.\n"); {
return netplay_cmd_nak(netplay, connection); /* Out of order = out of luck */
RARCH_ERR("Netplay input out of order.\n");
return netplay_cmd_nak(netplay, connection);
}
} }
/* The data's good! */ /* The data's good! */
@ -500,15 +516,22 @@ static bool netplay_get_cmd(netplay_t *netplay,
memcpy(dframe->real_input_state[player], buffer + 2, memcpy(dframe->real_input_state[player], buffer + 2,
WORDS_PER_INPUT*sizeof(uint32_t)); WORDS_PER_INPUT*sizeof(uint32_t));
dframe->have_real[player] = true; 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*/ netplay->read_ptr[player] = NEXT_PTR(netplay->read_ptr[player]);
if (dframe->frame <= netplay->self_frame_count) netplay->read_frame_count[player]++;
send_input_frame(netplay, NULL, connection, buffer[0],
player, dframe->real_input_state[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 */ /* 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); 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 */ /* The frame we haven't received is their end frame */
payload[0] = htonl(netplay->read_frame_count[connection->player]); 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 */ /* Mark them as not playing anymore */
connection->mode = NETPLAY_CONNECTION_SPECTATING; connection->mode = NETPLAY_CONNECTION_SPECTATING;
netplay->connected_players &= ~(1<<connection->player); netplay->connected_players &= ~(1<<connection->player);
netplay->connected_slaves &= ~(1<<connection->player);
/* Tell everyone */ /* Tell everyone */
payload[1] = htonl(connection->player); payload[1] = htonl(connection->player);
@ -639,6 +664,34 @@ static bool netplay_get_cmd(netplay_t *netplay,
{ {
uint32_t payload[2]; uint32_t payload[2];
uint32_t player = 0; 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); payload[0] = htonl(netplay->self_frame_count + 1);
if (!netplay->is_server) if (!netplay->is_server)
@ -671,15 +724,21 @@ static bool netplay_get_cmd(netplay_t *netplay,
break; break;
} }
if (connection->mode != NETPLAY_CONNECTION_PLAYING) if (connection->mode != NETPLAY_CONNECTION_PLAYING &&
connection->mode != NETPLAY_CONNECTION_SLAVE)
{ {
/* Mark them as playing */ /* Mark them as playing */
connection->mode = NETPLAY_CONNECTION_PLAYING; connection->mode = slave ? NETPLAY_CONNECTION_SLAVE :
NETPLAY_CONNECTION_PLAYING;
connection->player = player; connection->player = player;
netplay->connected_players |= 1<<player; netplay->connected_players |= 1<<player;
if (slave)
netplay->connected_slaves |= 1<<player;
/* Tell everyone */ /* Tell everyone */
payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING | connection->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)); netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
/* Announce it */ /* Announce it */
@ -692,7 +751,10 @@ static bool netplay_get_cmd(netplay_t *netplay,
/* Tell the player even if they were confused */ /* Tell the player even if they were confused */
payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING | 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)); netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
/* And expect their data */ /* And expect their data */
@ -764,7 +826,21 @@ static bool netplay_get_cmd(netplay_t *netplay,
return netplay_cmd_nak(netplay, connection); 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<<player);
netplay->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; netplay->self_player = player;
/* Fix up current frame info */ /* Fix up current frame info */
@ -836,6 +912,9 @@ static bool netplay_get_cmd(netplay_t *netplay,
return netplay_cmd_nak(netplay, connection); return netplay_cmd_nak(netplay, connection);
} }
/* Unmark ourself, in case we were in slave mode */
netplay->connected_players &= ~(1<<player);
/* Announce it */ /* Announce it */
strlcpy(msg, "You have left the game", sizeof(msg)); strlcpy(msg, "You have left the game", sizeof(msg));
RARCH_LOG("%s\n", msg); RARCH_LOG("%s\n", msg);
@ -1049,7 +1128,8 @@ static bool netplay_get_cmd(netplay_t *netplay,
} }
/* Only players may load states */ /* Only players may load states */
if (connection->mode != NETPLAY_CONNECTION_PLAYING) if (connection->mode != NETPLAY_CONNECTION_PLAYING &&
connection->mode != NETPLAY_CONNECTION_SLAVE)
{ {
RARCH_ERR("Netplay state load from a spectator.\n"); RARCH_ERR("Netplay state load from a spectator.\n");
return netplay_cmd_nak(netplay, connection); return netplay_cmd_nak(netplay, connection);
@ -1212,7 +1292,7 @@ static bool netplay_get_cmd(netplay_t *netplay,
} }
nick[sizeof(nick)-1] = '\0'; 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) if (connection->mode != NETPLAY_CONNECTION_PLAYING)
break; break;
@ -1367,6 +1447,49 @@ int netplay_poll_net_input(netplay_t *netplay, bool block)
return 0; 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 * netplay_flip_port
* *

View File

@ -42,6 +42,7 @@
#define MAX_SERVER_STALL_TIME_USEC (5*1000*1000) #define MAX_SERVER_STALL_TIME_USEC (5*1000*1000)
#define MAX_CLIENT_STALL_TIME_USEC (10*1000*1000) #define MAX_CLIENT_STALL_TIME_USEC (10*1000*1000)
#define CATCH_UP_CHECK_TIME_USEC (500*1000)
#define MAX_RETRIES 16 #define MAX_RETRIES 16
#define RETRY_MS 500 #define RETRY_MS 500
@ -176,6 +177,8 @@ enum netplay_cmd
#define NETPLAY_CMD_INPUT_BIT_SERVER (1U<<31) #define NETPLAY_CMD_INPUT_BIT_SERVER (1U<<31)
#define NETPLAY_CMD_SYNC_BIT_PAUSED (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_PLAYING (1U<<17)
#define NETPLAY_CMD_MODE_BIT_YOU (1U<<16) #define NETPLAY_CMD_MODE_BIT_YOU (1U<<16)
@ -206,15 +209,28 @@ enum rarch_netplay_connection_mode
/* Ready: */ /* Ready: */
NETPLAY_CONNECTION_CONNECTED, /* Modes above this are connected */ NETPLAY_CONNECTION_CONNECTED, /* Modes above this are connected */
NETPLAY_CONNECTION_SPECTATING, /* Spectator mode */ NETPLAY_CONNECTION_SPECTATING, /* Spectator mode */
NETPLAY_CONNECTION_SLAVE, /* Playing in slave mode */
NETPLAY_CONNECTION_PLAYING /* Normal ready state */ NETPLAY_CONNECTION_PLAYING /* Normal ready state */
}; };
enum rarch_netplay_stall_reason enum rarch_netplay_stall_reason
{ {
NETPLAY_STALL_NONE = 0, NETPLAY_STALL_NONE = 0,
/* We're so far ahead that we can't read more data without overflowing the
* buffer */
NETPLAY_STALL_RUNNING_FAST, 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, NETPLAY_STALL_INPUT_LATENCY,
/* The server asked us to stall */
NETPLAY_STALL_SERVER_REQUESTED, NETPLAY_STALL_SERVER_REQUESTED,
/* We have no connection and must have one to proceed */
NETPLAY_STALL_NO_CONNECTION NETPLAY_STALL_NO_CONNECTION
}; };
@ -331,10 +347,13 @@ struct netplay
size_t connections_size; size_t connections_size;
struct netplay_connection one_connection; /* Client only */ struct netplay_connection one_connection; /* Client only */
/* Bitmap of players with controllers (whether local or remote) (low bit is /* Bitmap of players with controllers (low bit is player 1) */
* player 1) */
uint32_t connected_players; 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 */ /* Maximum player number */
uint32_t player_max; uint32_t player_max;
@ -461,6 +480,12 @@ struct netplay
/* Opposite of stalling, should we be catching up? */ /* Opposite of stalling, should we be catching up? */
bool catch_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 */ /* Frequency with which to check CRCs */
int check_frames; int check_frames;
@ -758,6 +783,13 @@ bool netplay_cmd_stall(netplay_t *netplay,
*/ */
int netplay_poll_net_input(netplay_t *netplay, bool block); 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 * netplay_flip_port
* *

View File

@ -55,6 +55,7 @@ void netplay_update_unread_ptr(netplay_t *netplay)
for (player = 0; player < MAX_USERS; player++) for (player = 0; player < MAX_USERS; player++)
{ {
if (!(netplay->connected_players & (1<<player))) continue; if (!(netplay->connected_players & (1<<player))) continue;
if ((netplay->connected_slaves & (1<<player))) continue;
if (netplay->read_frame_count[player] < new_unread_frame_count) if (netplay->read_frame_count[player] < new_unread_frame_count)
{ {
new_unread_ptr = netplay->read_ptr[player]; 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; new_unread_frame_count = netplay->server_frame_count;
} }
netplay->unread_ptr = new_unread_ptr; if (new_unread_frame_count != (uint32_t) -1)
netplay->unread_frame_count = new_unread_frame_count; {
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) if (netplay->catch_up)
{ {
/* Are we caught 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; netplay->catch_up = false;
input_driver_unset_nonblock_state(); input_driver_unset_nonblock_state();
@ -556,17 +565,44 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled)
} }
else if (!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? */ retro_time_t cur_time = cpu_features_get_time_usec();
netplay->catch_up = true; uint32_t cur_behind = lo_frame_count - netplay->self_frame_count;
input_driver_set_nonblock_state();
driver_set_nonblock_state(); /* 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; size_t i;
netplay->catch_up_time = 0;
/* We're falling behind some clients but not others, so request that /* We're falling behind some clients but not others, so request that
* clients ahead of us stall */ * clients ahead of us stall */
@ -580,7 +616,7 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled)
player = connection->player; player = connection->player;
/* Are they ahead? */ /* 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 */ /* Tell them to stall */
if (connection->stall_frame + NETPLAY_MAX_REQ_STALL_FREQUENCY < 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;
} }