mirror of
https://github.com/libretro/RetroArch
synced 2025-02-28 12:40:23 +00:00
(Netplay) Show client slowdown information (#14272)
This commit is contained in:
parent
e6f3a387b2
commit
8105688a99
@ -11731,6 +11731,10 @@ MSG_HASH(
|
||||
MSG_NETPLAY_CHAT_SUPPORTED,
|
||||
"Chat Supported"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_SLOWDOWNS_CAUSED,
|
||||
"Slowdowns Caused"
|
||||
)
|
||||
|
||||
MSG_HASH(
|
||||
MSG_AUDIO_VOLUME,
|
||||
|
@ -1620,12 +1620,17 @@ static int action_bind_sublabel_netplay_kick_client(file_list_t *list,
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s: %s",
|
||||
snprintf(buf, sizeof(buf), "%s: %s\n",
|
||||
msg_hash_to_str(MSG_NETPLAY_CHAT_SUPPORTED),
|
||||
msg_hash_to_str((client->protocol >= 6) ?
|
||||
MENU_ENUM_LABEL_VALUE_YES : MENU_ENUM_LABEL_VALUE_NO));
|
||||
strlcat(s, buf, len);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s: %lu",
|
||||
msg_hash_to_str(MSG_NETPLAY_SLOWDOWNS_CAUSED),
|
||||
(unsigned long)client->slowdowns);
|
||||
strlcat(s, buf, len);
|
||||
|
||||
if (client->ping >= 0)
|
||||
{
|
||||
snprintf(buf, sizeof(buf), "\nPing: %u ms", (unsigned)client->ping);
|
||||
|
@ -226,6 +226,7 @@ enum msg_hash_enums
|
||||
MSG_NETPLAY_STATUS_SPECTATING,
|
||||
MSG_NETPLAY_CLIENT_DEVICES,
|
||||
MSG_NETPLAY_CHAT_SUPPORTED,
|
||||
MSG_NETPLAY_SLOWDOWNS_CAUSED,
|
||||
MSG_RESAMPLER_QUALITY_LOWEST,
|
||||
MSG_RESAMPLER_QUALITY_LOWER,
|
||||
MSG_RESAMPLER_QUALITY_NORMAL,
|
||||
|
@ -154,6 +154,7 @@ typedef struct netplay_client_info
|
||||
{
|
||||
uint32_t protocol;
|
||||
uint32_t devices;
|
||||
uint32_t slowdowns;
|
||||
int32_t ping;
|
||||
int id;
|
||||
enum rarch_netplay_connection_mode mode;
|
||||
|
@ -7793,225 +7793,238 @@ static bool get_self_input_state(
|
||||
static bool netplay_poll(netplay_t *netplay, bool block_libretro_input)
|
||||
{
|
||||
size_t i;
|
||||
int res;
|
||||
uint32_t client;
|
||||
|
||||
if (!get_self_input_state(block_libretro_input, netplay))
|
||||
goto catastrophe;
|
||||
|
||||
/* If we're not connected, we're done */
|
||||
/* If we're not connected, we're done. */
|
||||
if (netplay->self_mode == NETPLAY_CONNECTION_NONE)
|
||||
return true;
|
||||
|
||||
/* Read Netplay input, block if we're configured to stall for input every
|
||||
* frame */
|
||||
netplay_update_unread_ptr(netplay);
|
||||
if (netplay->stateless_mode &&
|
||||
(netplay->connected_players>1) &&
|
||||
netplay->unread_frame_count <= netplay->run_frame_count)
|
||||
res = netplay_poll_net_input(netplay, true);
|
||||
else
|
||||
res = netplay_poll_net_input(netplay, false);
|
||||
if (res == -1)
|
||||
goto catastrophe;
|
||||
|
||||
/* Resolve and/or simulate the input if we don't have real input */
|
||||
/* Read netplay input,
|
||||
block if we're configured to stall for input every frame. */
|
||||
{
|
||||
bool block = netplay->stateless_mode && netplay->connected_players > 1 &&
|
||||
netplay->unread_frame_count <= netplay->run_frame_count;
|
||||
|
||||
if (netplay_poll_net_input(netplay, block) == -1)
|
||||
goto catastrophe;
|
||||
}
|
||||
|
||||
/* Resolve and/or simulate the input if we don't have real input. */
|
||||
netplay_resolve_input(netplay, netplay->run_ptr, false);
|
||||
|
||||
/* Handle any slaves */
|
||||
/* Handle slaves. */
|
||||
if (netplay->is_server && netplay->connected_slaves)
|
||||
netplay_handle_slaves(netplay);
|
||||
|
||||
netplay_update_unread_ptr(netplay);
|
||||
|
||||
/* Figure out how many frames of input latency we should be using to hide
|
||||
* network latency */
|
||||
if (netplay->frame_run_time_avg || netplay->stateless_mode)
|
||||
/* Figure out how many frames of input latency we should be using to
|
||||
hide network latency. */
|
||||
if (netplay->stateless_mode)
|
||||
{
|
||||
int input_latency_frames_min = (int)netplay->input_latency_frames_min;
|
||||
int input_latency_frames_max = (int)netplay->input_latency_frames_max;
|
||||
|
||||
/* In stateless mode, we adjust up if we're "close"
|
||||
and down if we have a lot of slack. */
|
||||
if (netplay->input_latency_frames < input_latency_frames_min ||
|
||||
(netplay->unread_frame_count == (netplay->run_frame_count + 1) &&
|
||||
netplay->input_latency_frames < input_latency_frames_max))
|
||||
netplay->input_latency_frames++;
|
||||
else if (netplay->input_latency_frames > input_latency_frames_max ||
|
||||
(netplay->unread_frame_count > (netplay->run_frame_count + 2) &&
|
||||
netplay->input_latency_frames > input_latency_frames_min))
|
||||
netplay->input_latency_frames--;
|
||||
}
|
||||
else if (netplay->frame_run_time_avg)
|
||||
{
|
||||
/* FIXME: Using fixed 60fps for this calculation */
|
||||
unsigned frames_per_frame = netplay->frame_run_time_avg ?
|
||||
(16666 / netplay->frame_run_time_avg) :
|
||||
0;
|
||||
unsigned frames_ahead = (netplay->run_frame_count > netplay->unread_frame_count) ?
|
||||
(netplay->run_frame_count - netplay->unread_frame_count) :
|
||||
0;
|
||||
int input_latency_frames_min = netplay->input_latency_frames_min;
|
||||
int input_latency_frames_max = netplay->input_latency_frames_max;
|
||||
(unsigned)(16666 / netplay->frame_run_time_avg) : 0;
|
||||
unsigned frames_ahead =
|
||||
(netplay->run_frame_count > netplay->unread_frame_count) ?
|
||||
(unsigned)(netplay->run_frame_count - netplay->unread_frame_count)
|
||||
: 0;
|
||||
int input_latency_frames_min = (int)netplay->input_latency_frames_min;
|
||||
int input_latency_frames_max = (int)netplay->input_latency_frames_max;
|
||||
|
||||
/* Assume we need a couple frames worth of time to actually run the
|
||||
* current frame */
|
||||
/* Assume we need a couple frames worth of time
|
||||
to actually run the current frame. */
|
||||
if (frames_per_frame > 2)
|
||||
frames_per_frame -= 2;
|
||||
else
|
||||
frames_per_frame = 0;
|
||||
frames_per_frame = 0;
|
||||
|
||||
/* Shall we adjust our latency? */
|
||||
if (netplay->stateless_mode)
|
||||
{
|
||||
/* In stateless mode, we adjust up if we're "close" and down if we
|
||||
* have a lot of slack */
|
||||
if (netplay->input_latency_frames < input_latency_frames_min ||
|
||||
(netplay->unread_frame_count == netplay->run_frame_count + 1 &&
|
||||
netplay->input_latency_frames < input_latency_frames_max))
|
||||
netplay->input_latency_frames++;
|
||||
else if (netplay->input_latency_frames > input_latency_frames_max ||
|
||||
(netplay->unread_frame_count > netplay->run_frame_count + 2 &&
|
||||
netplay->input_latency_frames > input_latency_frames_min))
|
||||
netplay->input_latency_frames--;
|
||||
}
|
||||
else if (netplay->input_latency_frames < input_latency_frames_min ||
|
||||
(frames_per_frame < frames_ahead &&
|
||||
netplay->input_latency_frames < input_latency_frames_max))
|
||||
{
|
||||
/* We can't hide this much network latency with replay, so hide some
|
||||
* with input latency */
|
||||
/* We can't hide this much network latency with replay,
|
||||
so hide some with input latency. */
|
||||
if (netplay->input_latency_frames < input_latency_frames_min ||
|
||||
(frames_per_frame < frames_ahead &&
|
||||
netplay->input_latency_frames < input_latency_frames_max))
|
||||
netplay->input_latency_frames++;
|
||||
}
|
||||
/* We don't need this much latency (any more). */
|
||||
else if (netplay->input_latency_frames > input_latency_frames_max ||
|
||||
(frames_per_frame > frames_ahead + 2 &&
|
||||
netplay->input_latency_frames > input_latency_frames_min))
|
||||
{
|
||||
/* We don't need this much latency (any more) */
|
||||
(frames_per_frame > (frames_ahead + 2) &&
|
||||
netplay->input_latency_frames > input_latency_frames_min))
|
||||
netplay->input_latency_frames--;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we're stalled, consider unstalling */
|
||||
/* If we're stalled, consider unstalling. */
|
||||
switch (netplay->stall)
|
||||
{
|
||||
case NETPLAY_STALL_RUNNING_FAST:
|
||||
if (netplay->unread_frame_count + NETPLAY_MAX_STALL_FRAMES - 2
|
||||
> netplay->self_frame_count)
|
||||
if ((netplay->unread_frame_count + NETPLAY_MAX_STALL_FRAMES - 2) >
|
||||
netplay->self_frame_count)
|
||||
{
|
||||
netplay->stall = NETPLAY_STALL_NONE;
|
||||
struct netplay_connection *connection;
|
||||
|
||||
for (i = 0; i < netplay->connections_size; i++)
|
||||
{
|
||||
struct netplay_connection *connection = &netplay->connections[i];
|
||||
if (connection->active && connection->stall)
|
||||
connection = &netplay->connections[i];
|
||||
if (connection->active)
|
||||
connection->stall = NETPLAY_STALL_NONE;
|
||||
}
|
||||
|
||||
netplay->stall = NETPLAY_STALL_NONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case NETPLAY_STALL_SPECTATOR_WAIT:
|
||||
if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING || netplay->unread_frame_count > netplay->self_frame_count)
|
||||
if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING ||
|
||||
netplay->unread_frame_count > netplay->self_frame_count)
|
||||
netplay->stall = NETPLAY_STALL_NONE;
|
||||
break;
|
||||
|
||||
case NETPLAY_STALL_INPUT_LATENCY:
|
||||
/* Just let it recalculate momentarily */
|
||||
/* Just let it recalculate momentarily. */
|
||||
netplay->stall = NETPLAY_STALL_NONE;
|
||||
break;
|
||||
|
||||
case NETPLAY_STALL_SERVER_REQUESTED:
|
||||
/* See if the stall is done */
|
||||
if (netplay->connections[0].stall_frame == 0)
|
||||
{
|
||||
/* Stop stalling! */
|
||||
netplay->connections[0].stall = NETPLAY_STALL_NONE;
|
||||
netplay->stall = NETPLAY_STALL_NONE;
|
||||
struct netplay_connection *connection = &netplay->connections[0];
|
||||
|
||||
/* See if the stall is done. */
|
||||
if (!connection->stall_frame)
|
||||
{
|
||||
/* Stop stalling! */
|
||||
connection->stall = NETPLAY_STALL_NONE;
|
||||
netplay->stall = NETPLAY_STALL_NONE;
|
||||
}
|
||||
else
|
||||
connection->stall_frame--;
|
||||
}
|
||||
else
|
||||
netplay->connections[0].stall_frame--;
|
||||
break;
|
||||
case NETPLAY_STALL_NO_CONNECTION:
|
||||
/* We certainly haven't fixed this */
|
||||
/* We certainly haven't fixed this. */
|
||||
break;
|
||||
default: /* not stalling */
|
||||
default:
|
||||
/* Not stalling. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* If we're not stalled, consider stalling */
|
||||
if (!netplay->stall)
|
||||
/* If we're not stalled, consider stalling. */
|
||||
if (netplay->stall == NETPLAY_STALL_NONE)
|
||||
{
|
||||
/* Have we not read enough latency frames? */
|
||||
if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING &&
|
||||
netplay->connected_players &&
|
||||
netplay->run_frame_count + netplay->input_latency_frames > netplay->self_frame_count)
|
||||
switch (netplay->self_mode)
|
||||
{
|
||||
netplay->stall = NETPLAY_STALL_INPUT_LATENCY;
|
||||
netplay->stall_time = 0;
|
||||
case NETPLAY_CONNECTION_SPECTATING:
|
||||
case NETPLAY_CONNECTION_SLAVE:
|
||||
/* If we're a spectator, are we ahead at all? */
|
||||
if (!netplay->is_server &&
|
||||
netplay->unread_frame_count <= netplay->self_frame_count)
|
||||
{
|
||||
netplay->stall = NETPLAY_STALL_SPECTATOR_WAIT;
|
||||
netplay->stall_time = cpu_features_get_time_usec();
|
||||
}
|
||||
break;
|
||||
case NETPLAY_CONNECTION_PLAYING:
|
||||
/* Have we not read enough latency frames? */
|
||||
if (netplay->connected_players &&
|
||||
(netplay->run_frame_count + netplay->input_latency_frames) >
|
||||
netplay->self_frame_count)
|
||||
{
|
||||
netplay->stall = NETPLAY_STALL_INPUT_LATENCY;
|
||||
netplay->stall_time = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Are we too far ahead? */
|
||||
if (netplay->unread_frame_count + NETPLAY_MAX_STALL_FRAMES
|
||||
<= netplay->self_frame_count)
|
||||
if (netplay->stall == NETPLAY_STALL_NONE &&
|
||||
netplay->self_frame_count > NETPLAY_MAX_STALL_FRAMES)
|
||||
{
|
||||
netplay->stall = NETPLAY_STALL_RUNNING_FAST;
|
||||
netplay->stall_time = cpu_features_get_time_usec();
|
||||
uint32_t min_frame_count = netplay->self_frame_count -
|
||||
NETPLAY_MAX_STALL_FRAMES;
|
||||
|
||||
/* Figure out who to blame */
|
||||
if (netplay->is_server)
|
||||
if (netplay->unread_frame_count <= min_frame_count)
|
||||
{
|
||||
for (client = 1; client < MAX_CLIENTS; client++)
|
||||
netplay->stall = NETPLAY_STALL_RUNNING_FAST;
|
||||
netplay->stall_time = cpu_features_get_time_usec();
|
||||
|
||||
/* Figure out who to blame. */
|
||||
if (netplay->is_server)
|
||||
{
|
||||
struct netplay_connection *connection;
|
||||
if (!(netplay->connected_players & (1 << client)))
|
||||
continue;
|
||||
if (netplay->read_frame_count[client] > netplay->unread_frame_count)
|
||||
continue;
|
||||
connection = &netplay->connections[client-1];
|
||||
if (connection->active &&
|
||||
connection->mode == NETPLAY_CONNECTION_PLAYING)
|
||||
|
||||
for (i = 0; i < netplay->connections_size; i++)
|
||||
{
|
||||
connection->stall = NETPLAY_STALL_RUNNING_FAST;
|
||||
connection->stall_time = netplay->stall_time;
|
||||
connection = &netplay->connections[i];
|
||||
if (!connection->active ||
|
||||
connection->mode != NETPLAY_CONNECTION_PLAYING)
|
||||
continue;
|
||||
if (netplay->read_frame_count[i + 1] < min_frame_count)
|
||||
{
|
||||
connection->stall = NETPLAY_STALL_RUNNING_FAST;
|
||||
connection->stall_slow++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* If we're a spectator, are we ahead at all? */
|
||||
if (!netplay->is_server &&
|
||||
(netplay->self_mode == NETPLAY_CONNECTION_SPECTATING ||
|
||||
netplay->self_mode == NETPLAY_CONNECTION_SLAVE) &&
|
||||
netplay->unread_frame_count <= netplay->self_frame_count)
|
||||
{
|
||||
netplay->stall = NETPLAY_STALL_SPECTATOR_WAIT;
|
||||
netplay->stall_time = cpu_features_get_time_usec();
|
||||
}
|
||||
}
|
||||
|
||||
/* If we're stalling, consider disconnection */
|
||||
if (netplay->stall && netplay->stall_time)
|
||||
/* If we're stalling, consider disconnection. */
|
||||
if (netplay->stall != NETPLAY_STALL_NONE && netplay->stall_time)
|
||||
{
|
||||
retro_time_t now = cpu_features_get_time_usec();
|
||||
|
||||
/* Don't stall out while they're paused */
|
||||
if (netplay->remote_paused)
|
||||
netplay->stall_time = now;
|
||||
else if (now - netplay->stall_time >=
|
||||
(netplay->is_server ? MAX_SERVER_STALL_TIME_USEC :
|
||||
MAX_CLIENT_STALL_TIME_USEC))
|
||||
if (!netplay->remote_paused)
|
||||
{
|
||||
/* Stalled out! */
|
||||
retro_time_t delta = now - netplay->stall_time;
|
||||
|
||||
if (netplay->is_server)
|
||||
{
|
||||
bool fixed = false;
|
||||
for (i = 0; i < netplay->connections_size; i++)
|
||||
if (delta >= MAX_SERVER_STALL_TIME_USEC)
|
||||
{
|
||||
struct netplay_connection *connection = &netplay->connections[i];
|
||||
if (connection->active &&
|
||||
connection->mode == NETPLAY_CONNECTION_PLAYING &&
|
||||
connection->stall)
|
||||
{
|
||||
netplay_hangup(netplay, connection);
|
||||
fixed = true;
|
||||
}
|
||||
}
|
||||
/* Stalled out! */
|
||||
struct netplay_connection *connection;
|
||||
|
||||
for (i = 0; i < netplay->connections_size; i++)
|
||||
{
|
||||
connection = &netplay->connections[i];
|
||||
if (!connection->active ||
|
||||
connection->mode != NETPLAY_CONNECTION_PLAYING)
|
||||
continue;
|
||||
if (connection->stall != NETPLAY_STALL_NONE)
|
||||
netplay_hangup(netplay, connection);
|
||||
}
|
||||
|
||||
if (fixed)
|
||||
{
|
||||
/* Not stalled now :) */
|
||||
netplay->stall = NETPLAY_STALL_NONE;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
goto catastrophe;
|
||||
return false;
|
||||
{
|
||||
if (delta >= MAX_CLIENT_STALL_TIME_USEC)
|
||||
/* Stalled out! */
|
||||
goto catastrophe;
|
||||
}
|
||||
}
|
||||
else
|
||||
/* Don't stall out while they're paused. */
|
||||
netplay->stall_time = now;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -8019,6 +8032,7 @@ static bool netplay_poll(netplay_t *netplay, bool block_libretro_input)
|
||||
catastrophe:
|
||||
for (i = 0; i < netplay->connections_size; i++)
|
||||
netplay_hangup(netplay, &netplay->connections[i]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -8818,11 +8832,12 @@ static size_t retrieve_client_info(netplay_t *netplay, netplay_client_info_t *bu
|
||||
{
|
||||
netplay_client_info_t *info = &buf[j++];
|
||||
|
||||
info->id = (int)i;
|
||||
info->protocol = connection->netplay_protocol;
|
||||
info->mode = connection->mode;
|
||||
info->ping = connection->ping;
|
||||
info->devices = netplay->client_devices[i + 1];
|
||||
info->id = (int)i;
|
||||
info->protocol = connection->netplay_protocol;
|
||||
info->mode = connection->mode;
|
||||
info->ping = connection->ping;
|
||||
info->slowdowns = connection->stall_slow;
|
||||
info->devices = netplay->client_devices[i + 1];
|
||||
strlcpy(info->name, connection->nick, sizeof(info->name));
|
||||
}
|
||||
}
|
||||
|
@ -365,9 +365,6 @@ typedef struct netplay_address
|
||||
/* Each connection gets a connection struct */
|
||||
struct netplay_connection
|
||||
{
|
||||
/* Is this connection stalling? */
|
||||
retro_time_t stall_time;
|
||||
|
||||
/* Timer used to estimate a connection's latency */
|
||||
retro_time_t ping_timer;
|
||||
|
||||
@ -399,6 +396,10 @@ struct netplay_connection
|
||||
* For the client: How many frames of stall do we have left? */
|
||||
uint32_t stall_frame;
|
||||
|
||||
/* How many times has this connection caused a stall because it's running
|
||||
too slow? */
|
||||
uint32_t stall_slow;
|
||||
|
||||
/* What latency is this connection running on?
|
||||
* Network latency has limited precision as we estimate it
|
||||
* once every pre-frame. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user