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 */
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;

View File

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

View File

@ -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;

View File

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

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

View File

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

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_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;

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

View File

@ -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);

View File

@ -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),

View File

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

View File

@ -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;
}
/**

View File

@ -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;

View File

@ -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<<connection->player);
netplay->connected_slaves &= ~(1<<connection->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<<connection->player);
netplay->connected_slaves &= ~(1<<connection->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<<player;
if (slave)
netplay->connected_slaves |= 1<<player;
/* 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));
/* 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<<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;
/* 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<<player);
/* Announce it */
strlcpy(msg, "You have left the game", sizeof(msg));
RARCH_LOG("%s\n", msg);
@ -1049,7 +1128,8 @@ static bool netplay_get_cmd(netplay_t *netplay,
}
/* 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");
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
*

View File

@ -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
*

View File

@ -55,6 +55,7 @@ void netplay_update_unread_ptr(netplay_t *netplay)
for (player = 0; player < MAX_USERS; player++)
{
if (!(netplay->connected_players & (1<<player))) continue;
if ((netplay->connected_slaves & (1<<player))) continue;
if (netplay->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;
}