Reverse catch-up, i.e., server-demanded stalling

Previously, if two clients were connected to the same server and one of
them was ahead of the server, the only way to rectify that situation was
for the client to get so far ahead that it stalled, as the server could
only catch up with an ahead client if all clients were ahead. That's
unrealistic. This gives the server the alternate option of demanding
that a client stall. This keeps things nicely in line even with >2
players.
This commit is contained in:
Gregor Richards 2016-12-24 15:25:03 -05:00
parent 958a028b0a
commit cd281d5757
8 changed files with 152 additions and 38 deletions

View File

@ -304,6 +304,14 @@ Payload: None
Description:
Indicates that the core is no longer paused.
Command: STALL
Payload:
{
frames: uint32
}
Description:
Request that a client stall for the given number of frames.
Command: CHEATS
Unused

View File

@ -322,7 +322,7 @@ ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf,
if (block)
{
sbuf->start = sbuf->read;
if (recvd < len)
if (recvd < 0 || recvd < (ssize_t) len)
{
if (!socket_receive_all_blocking(sockfd, (unsigned char *) buf + recvd, len - recvd))
return -1;

View File

@ -142,7 +142,7 @@ bool netplay_discovery_driver_ctl(enum rarch_netplay_discovery_ctl_state state,
/* And send it off */
if (sendto(lan_ad_client_fd, (const char *) &ad_packet_buffer,
2*sizeof(uint32_t), 0, addr->ai_addr, addr->ai_addrlen) <
2*sizeof(uint32_t))
(ssize_t) (2*sizeof(uint32_t)))
RARCH_WARN("Failed to send netplay discovery response.\n");
freeaddrinfo_retro(addr);

View File

@ -150,26 +150,6 @@ static bool get_self_input_state(netplay_t *netplay)
return true;
}
static uint32_t netplay_max_ahead(netplay_t *netplay)
{
uint32_t max_ahead;
/* Figure out how many frames we're allowed to be ahead: Ideally we need to be
* able to run our entire stall worth of frames in one real frame. In
* practice, we'll allow a couple jitter frames. (FIXME: hard coded
* as three 60FPS frames) */
if (netplay_data->frame_run_time_avg)
max_ahead = 50000 / netplay_data->frame_run_time_avg;
else
max_ahead = NETPLAY_MAX_STALL_FRAMES;
if (max_ahead > NETPLAY_MAX_STALL_FRAMES)
max_ahead = NETPLAY_MAX_STALL_FRAMES;
if (max_ahead < 2)
max_ahead = 2;
return max_ahead;
}
/**
* netplay_poll:
* @netplay : pointer to netplay object
@ -217,9 +197,8 @@ static bool netplay_poll(void)
{
case NETPLAY_STALL_RUNNING_FAST:
{
uint32_t max_ahead = netplay_max_ahead(netplay_data);
netplay_update_unread_ptr(netplay_data);
if (netplay_data->unread_frame_count + max_ahead - 2
if (netplay_data->unread_frame_count + NETPLAY_MAX_STALL_FRAMES - 2
> netplay_data->self_frame_count)
{
netplay_data->stall = NETPLAY_STALL_NONE;
@ -233,17 +212,31 @@ static bool netplay_poll(void)
break;
}
case NETPLAY_STALL_SERVER_REQUESTED:
{
/* See if the stall is done */
if (netplay_data->connections[0].stall_frame == 0)
{
/* Stop stalling! */
netplay_data->connections[0].stall = NETPLAY_STALL_NONE;
netplay_data->stall = NETPLAY_STALL_NONE;
}
else
{
netplay_data->connections[0].stall_frame--;
}
break;
}
case NETPLAY_STALL_NO_CONNECTION:
/* We certainly haven't fixed this */
break;
default: /* not stalling */
{
uint32_t max_ahead = netplay_max_ahead(netplay_data);
/* Are we too far ahead? */
netplay_update_unread_ptr(netplay_data);
if (netplay_data->unread_frame_count + max_ahead
if (netplay_data->unread_frame_count + NETPLAY_MAX_STALL_FRAMES
<= netplay_data->self_frame_count)
{
netplay_data->stall = NETPLAY_STALL_RUNNING_FAST;
@ -276,7 +269,7 @@ static bool netplay_poll(void)
}
/* If we're stalling, consider disconnection */
if (netplay_data->stall)
if (netplay_data->stall && netplay_data->stall_time)
{
retro_time_t now = cpu_features_get_time_usec();

View File

@ -257,7 +257,7 @@ struct info_buf_s
#define RECV(buf, sz) \
recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), (sz), false); \
if (recvd >= 0 && recvd < (sz)) \
if (recvd >= 0 && recvd < (ssize_t) (sz)) \
{ \
netplay_recv_reset(&connection->recv_packet_buffer); \
return true; \

View File

@ -379,11 +379,24 @@ bool netplay_cmd_mode(netplay_t *netplay,
return netplay_send_raw_cmd(netplay, connection, cmd, NULL, 0);
}
/**
* netplay_cmd_stall
*
* Send a stall command.
*/
bool netplay_cmd_stall(netplay_t *netplay,
struct netplay_connection *connection,
uint32_t frames)
{
frames = htonl(frames);
return netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_STALL, &frames, sizeof(frames));
}
#undef RECV
#define RECV(buf, sz) \
recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), \
(sz), false); \
if (recvd >= 0 && recvd < (sz)) goto shrt; \
if (recvd >= 0 && recvd < (ssize_t) (sz)) goto shrt; \
else if (recvd < 0)
static bool netplay_get_cmd(netplay_t *netplay,
@ -1199,6 +1212,42 @@ static bool netplay_get_cmd(netplay_t *netplay,
remote_unpaused(netplay, connection);
break;
case NETPLAY_CMD_STALL:
{
uint32_t frames;
if (cmd_size != sizeof(uint32_t))
{
RARCH_ERR("NETPLAY_CMD_STALL with incorrect payload size.\n");
return netplay_cmd_nak(netplay, connection);
}
RECV(&frames, sizeof(frames))
{
RARCH_ERR("Failed to receive NETPLAY_CMD_STALL payload.\n");
return netplay_cmd_nak(netplay, connection);
}
frames = ntohl(frames);
if (frames > NETPLAY_MAX_REQ_STALL_TIME)
frames = NETPLAY_MAX_REQ_STALL_TIME;
if (netplay->is_server)
{
/* Only servers can request a stall! */
RARCH_ERR("Netplay client requested a stall?\n");
return netplay_cmd_nak(netplay, connection);
}
/* We can only stall for one reason at a time */
if (!netplay->stall)
{
connection->stall = netplay->stall = NETPLAY_STALL_SERVER_REQUESTED;
netplay->stall_time = 0;
connection->stall_frame = frames;
}
break;
}
default:
RARCH_ERR("%s.\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED));
return netplay_cmd_nak(netplay, connection);

View File

@ -47,6 +47,8 @@
#define NETPLAY_MAX_STALL_FRAMES 60
#define NETPLAY_FRAME_RUN_TIME_WINDOW 120
#define NETPLAY_MAX_REQ_STALL_TIME 60
#define NETPLAY_MAX_REQ_STALL_FREQUENCY 120
#define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1)
#define NEXT_PTR(x) ((x + 1) % netplay->buffer_size)
@ -148,8 +150,11 @@ enum netplay_cmd
/* Resumes the game, takes no arguments */
NETPLAY_CMD_RESUME = 0x0044,
/* Request that a client stall because it's running fast */
NETPLAY_CMD_STALL = 0x0045,
/* Sends over cheats enabled on client (unsupported) */
NETPLAY_CMD_CHEATS = 0x0045,
NETPLAY_CMD_CHEATS = 0x0046,
/* Misc. commands */
@ -205,6 +210,7 @@ enum rarch_netplay_stall_reason
{
NETPLAY_STALL_NONE = 0,
NETPLAY_STALL_RUNNING_FAST,
NETPLAY_STALL_SERVER_REQUESTED,
NETPLAY_STALL_NO_CONNECTION
};
@ -273,7 +279,7 @@ struct netplay_connection
enum rarch_netplay_connection_mode mode;
/* Player # of connected player */
int player;
uint32_t player;
/* What compression does this peer support? */
uint32_t compression_supported;
@ -284,6 +290,10 @@ struct netplay_connection
/* Is this connection stalling? */
enum rarch_netplay_stall_reason stall;
retro_time_t stall_time;
/* For the server: When was the last time we requested this client to stall?
* For the client: How many frames of stall do we have left? */
uint32_t stall_frame;
};
/* Compression transcoder */
@ -717,6 +727,15 @@ bool netplay_cmd_mode(netplay_t *netplay,
struct netplay_connection *connection,
enum rarch_netplay_connection_mode mode);
/**
* netplay_cmd_stall
*
* Send a stall command.
*/
bool netplay_cmd_stall(netplay_t *netplay,
struct netplay_connection *connection,
uint32_t frames);
/**
* netplay_poll_net_input
*

View File

@ -352,7 +352,7 @@ process:
*/
void netplay_sync_post_frame(netplay_t *netplay, bool stalled)
{
uint32_t cmp_frame_count;
uint32_t lo_frame_count, hi_frame_count;
/* Unless we're stalling, we've just finished running a frame */
if (!stalled)
@ -498,15 +498,29 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled)
}
if (netplay->is_server)
cmp_frame_count = netplay->unread_frame_count;
{
uint32_t player;
lo_frame_count = hi_frame_count = netplay->unread_frame_count;
/* Look for players that are ahead of us */
for (player = 0; player < MAX_USERS; player++)
{
if (!(netplay->connected_players & (1<<player))) continue;
if (netplay->read_frame_count[player] > hi_frame_count)
hi_frame_count = netplay->read_frame_count[player];
}
}
else
cmp_frame_count = netplay->server_frame_count;
{
lo_frame_count = hi_frame_count = netplay->server_frame_count;
}
/* If we're behind, try to catch up */
if (netplay->catch_up)
{
/* Are we caught up? */
if (netplay->self_frame_count >= cmp_frame_count)
if (netplay->self_frame_count >= lo_frame_count)
{
netplay->catch_up = false;
input_driver_unset_nonblock_state();
@ -516,12 +530,43 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled)
}
else if (!stalled)
{
/* Are we falling behind? */
if (netplay->self_frame_count < cmp_frame_count - 2)
if (netplay->self_frame_count + 2 < lo_frame_count)
{
/* Are we falling behind? */
netplay->catch_up = true;
input_driver_set_nonblock_state();
driver_ctl(RARCH_DRIVER_CTL_SET_NONBLOCK_STATE, NULL);
}
else if (netplay->self_frame_count + 2 < hi_frame_count)
{
size_t i;
/* We're falling behind some clients but not others, so request that
* clients ahead of us stall */
for (i = 0; i < netplay->connections_size; i++)
{
struct netplay_connection *connection = &netplay->connections[i];
int player;
if (!connection->active ||
connection->mode != NETPLAY_CONNECTION_PLAYING)
continue;
player = connection->player;
/* Are they ahead? */
if (netplay->self_frame_count + 2 < netplay->read_frame_count[player])
{
/* Tell them to stall */
if (connection->stall_frame + NETPLAY_MAX_REQ_STALL_FREQUENCY <
netplay->self_frame_count)
{
connection->stall_frame = netplay->self_frame_count;
netplay_cmd_stall(netplay, connection,
netplay->read_frame_count[player] -
netplay->self_frame_count + 1);
}
}
}
}
}
}