mirror of
https://github.com/libretro/RetroArch
synced 2025-03-30 07:20:36 +00:00
Initial implementation of Netplay master/slave mode.
This commit is contained in:
parent
bd9e2e5fc7
commit
e70ee045bf
@ -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
|
||||
|
@ -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
|
||||
@ -331,7 +335,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)
|
||||
@ -943,7 +947,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 +985,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;
|
||||
|
@ -129,9 +129,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,7 +242,8 @@ 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,
|
||||
@ -364,12 +367,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 +386,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 +470,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 +489,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 +515,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 +627,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 +636,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 +663,27 @@ static bool netplay_get_cmd(netplay_t *netplay,
|
||||
{
|
||||
uint32_t payload[2];
|
||||
uint32_t player = 0;
|
||||
bool slave = false;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
payload[0] = htonl(netplay->self_frame_count + 1);
|
||||
|
||||
if (!netplay->is_server)
|
||||
@ -671,15 +716,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 +743,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 +818,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 +904,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 +1120,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 +1284,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 +1439,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
|
||||
*
|
||||
|
@ -176,6 +176,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,6 +208,7 @@ 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 */
|
||||
};
|
||||
|
||||
@ -331,10 +334,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;
|
||||
|
||||
@ -758,6 +764,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
|
||||
*
|
||||
|
@ -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];
|
||||
|
Loading…
x
Reference in New Issue
Block a user