diff --git a/network/netplay/README b/network/netplay/README index 9617cfd8f7..f343692546 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -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 diff --git a/network/netplay/netplay_buf.c b/network/netplay/netplay_buf.c index 2af28dbd2a..a08413f110 100644 --- a/network/netplay/netplay_buf.c +++ b/network/netplay/netplay_buf.c @@ -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; diff --git a/network/netplay/netplay_discovery.c b/network/netplay/netplay_discovery.c index 34780a6c17..c6244503ee 100644 --- a/network/netplay/netplay_discovery.c +++ b/network/netplay/netplay_discovery.c @@ -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); diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index dbce355939..1cea5a9bb8 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -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(); diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index 910508a0a1..cc714db5da 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -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; \ diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index 956a9a7d4d..a7fa861f3c 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -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); diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index df5602bb46..b60a414e85 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -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 * diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 05ac467dfc..ac98b9c491 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -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<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); + } + } + } } } }