mirror of
https://github.com/libretro/RetroArch
synced 2025-03-29 22:20:21 +00:00
Start implementing player flipping.
This commit is contained in:
parent
99f622de0e
commit
d551d93ce4
301
netplay.c
301
netplay.c
@ -54,6 +54,9 @@ static bool netplay_can_poll(netplay_t *handle);
|
||||
static const struct snes_callbacks* netplay_callbacks(netplay_t *handle);
|
||||
static void netplay_set_spectate_input(netplay_t *handle, int16_t input);
|
||||
|
||||
static bool netplay_send_cmd(netplay_t *handle, uint32_t cmd, const void *data, size_t size);
|
||||
static bool netplay_get_cmd(netplay_t *handle);
|
||||
|
||||
#ifdef _WIN32
|
||||
// Woohoo, Winsock has headers from the STONE AGE! :D
|
||||
#define close(x) closesocket(x)
|
||||
@ -83,12 +86,16 @@ struct delta_frame
|
||||
#define UDP_FRAME_PACKETS 16
|
||||
#define MAX_SPECTATORS 16
|
||||
|
||||
#define NETPLAY_CMD_ACK 0
|
||||
#define NETPLAY_CMD_NAK 1
|
||||
#define NETPLAY_CMD_FLIP_PLAYERS 2
|
||||
|
||||
struct netplay
|
||||
{
|
||||
struct snes_callbacks cbs;
|
||||
int fd; // TCP connection for state sending, etc. Could perhaps be used for messaging later on. :)
|
||||
int fd; // TCP connection for state sending, etc. Also used for commands.
|
||||
int udp_fd; // UDP connection for game state updates.
|
||||
unsigned port; // Which port is governed by netplay?
|
||||
unsigned port; // Which port is governed by netplay (other player)?
|
||||
bool has_connection;
|
||||
|
||||
struct delta_frame *buffer;
|
||||
@ -101,7 +108,7 @@ struct netplay
|
||||
|
||||
size_t state_size;
|
||||
|
||||
size_t is_replay; // Are we replaying old frames?
|
||||
bool is_replay; // Are we replaying old frames?
|
||||
bool can_poll; // We don't want to poll several times on a frame.
|
||||
|
||||
uint32_t packet_buffer[UDP_FRAME_PACKETS * 2]; // To compat UDP packet loss we also send old data along with the packets.
|
||||
@ -121,8 +128,48 @@ struct netplay
|
||||
uint16_t *spectate_input;
|
||||
size_t spectate_input_ptr;
|
||||
size_t spectate_input_size;
|
||||
|
||||
// Player flipping
|
||||
// Flipping state. If ptr >= flip_frame, we apply the flip.
|
||||
// If not, we apply the opposite, effectively creating a trigger point.
|
||||
// To avoid collition we need to make sure our client/host is synced up well after flip_frame
|
||||
// before allowing another flip.
|
||||
bool flip;
|
||||
uint32_t flip_frame;
|
||||
};
|
||||
|
||||
static bool send_all(int fd, const void *data_, size_t size)
|
||||
{
|
||||
const uint8_t *data = (const uint8_t*)data_;
|
||||
while (size)
|
||||
{
|
||||
ssize_t ret = send(fd, CONST_CAST data, size, 0);
|
||||
if (ret <= 0)
|
||||
return false;
|
||||
|
||||
data += ret;
|
||||
size -= ret;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool recv_all(int fd, void *data_, size_t size)
|
||||
{
|
||||
uint8_t *data = (uint8_t*)data_;
|
||||
while (size)
|
||||
{
|
||||
ssize_t ret = recv(fd, NONCONST_CAST data, size, 0);
|
||||
if (ret <= 0)
|
||||
return false;
|
||||
|
||||
data += ret;
|
||||
size -= ret;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void warn_hangup(void)
|
||||
{
|
||||
SSNES_WARN("Netplay has disconnected. Will continue without connection ...\n");
|
||||
@ -391,22 +438,17 @@ static uint32_t implementation_magic_value(void)
|
||||
static bool send_info(netplay_t *handle)
|
||||
{
|
||||
uint32_t header[3] = { htonl(g_extern.cart_crc), htonl(implementation_magic_value()), htonl(psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM)) };
|
||||
if (send(handle->fd, CONST_CAST header, sizeof(header), 0) != sizeof(header))
|
||||
if (!send_all(handle->fd, header, sizeof(header)))
|
||||
return false;
|
||||
|
||||
// Get SRAM data from Player 1 :)
|
||||
uint8_t *sram = psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
unsigned sram_size = psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
while (sram_size > 0)
|
||||
|
||||
if (!recv_all(handle->fd, sram, sram_size))
|
||||
{
|
||||
ssize_t ret = recv(handle->fd, NONCONST_CAST sram, sram_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to receive SRAM data from host.\n");
|
||||
return false;
|
||||
}
|
||||
sram += ret;
|
||||
sram_size -= ret;
|
||||
SSNES_ERR("Failed to receive SRAM data from host.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -415,7 +457,7 @@ static bool send_info(netplay_t *handle)
|
||||
static bool get_info(netplay_t *handle)
|
||||
{
|
||||
uint32_t header[3];
|
||||
if (recv(handle->fd, NONCONST_CAST header, sizeof(header), 0) != sizeof(header))
|
||||
if (!recv_all(handle->fd, header, sizeof(header)))
|
||||
{
|
||||
SSNES_ERR("Failed to receive header from client.\n");
|
||||
return false;
|
||||
@ -439,16 +481,10 @@ static bool get_info(netplay_t *handle)
|
||||
// Send SRAM data to our Player 2 :)
|
||||
const uint8_t *sram = psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
unsigned sram_size = psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
while (sram_size)
|
||||
if (!send_all(handle->fd, sram, sram_size))
|
||||
{
|
||||
ssize_t ret = send(handle->fd, CONST_CAST sram, sram_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to send SRAM data to client.\n");
|
||||
return false;
|
||||
}
|
||||
sram += ret;
|
||||
sram_size -= ret;
|
||||
SSNES_ERR("Failed to send SRAM data to client.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -457,7 +493,7 @@ static bool get_info(netplay_t *handle)
|
||||
static bool get_info_spectate(netplay_t *handle)
|
||||
{
|
||||
uint32_t header[4];
|
||||
if (recv(handle->fd, NONCONST_CAST header, sizeof(header), 0) != (ssize_t)sizeof(header))
|
||||
if (recv_all(handle->fd, header, sizeof(header)))
|
||||
{
|
||||
SSNES_ERR("Cannot get header from host!\n");
|
||||
return false;
|
||||
@ -476,18 +512,12 @@ static bool get_info_spectate(netplay_t *handle)
|
||||
|
||||
size_t size = save_state_size;
|
||||
uint8_t *tmp_buf = buf;
|
||||
while (size)
|
||||
{
|
||||
ssize_t ret = recv(handle->fd, NONCONST_CAST tmp_buf, size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to receive save state from host!\n");
|
||||
free(tmp_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
size -= ret;
|
||||
tmp_buf += ret;
|
||||
if (!recv_all(handle->fd, tmp_buf, size))
|
||||
{
|
||||
SSNES_ERR("Failed to receive save state from host!\n");
|
||||
free(tmp_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = true;
|
||||
@ -630,9 +660,9 @@ static int poll_input(netplay_t *handle, bool block)
|
||||
if (select(max_fd, &fds, NULL, NULL, &tmp_tv) < 0)
|
||||
return -1;
|
||||
|
||||
// Somewhat hacky, but we aren't using the TCP connection for anything useful atm.
|
||||
// Will probably add some proper messaging system here later.
|
||||
if (FD_ISSET(handle->fd, &fds))
|
||||
// Somewhat hacky,
|
||||
// but we aren't using the TCP connection for anything useful atm.
|
||||
if (FD_ISSET(handle->fd, &fds) && !netplay_get_cmd(handle))
|
||||
return -1;
|
||||
|
||||
if (FD_ISSET(handle->udp_fd, &fds))
|
||||
@ -666,14 +696,16 @@ static bool get_self_input_state(netplay_t *handle)
|
||||
if (handle->frame_count > 0) // First frame we always give zero input since relying on input from first frame screws up when we use -F 0.
|
||||
{
|
||||
snes_input_state_t cb = handle->cbs.state_cb;
|
||||
for (unsigned i = 0; i <= 11; i++)
|
||||
for (unsigned i = 0; i < SSNES_FIRST_META_KEY; i++)
|
||||
{
|
||||
int16_t tmp = cb(g_settings.input.netplay_client_swap_input ? 0 : !handle->port, SNES_DEVICE_JOYPAD, 0, i);
|
||||
int16_t tmp = cb(g_settings.input.netplay_client_swap_input ? 0 : !handle->port,
|
||||
SNES_DEVICE_JOYPAD, 0, i);
|
||||
state |= tmp ? 1 << i : 0;
|
||||
}
|
||||
}
|
||||
|
||||
memmove(handle->packet_buffer, handle->packet_buffer + 2, sizeof (handle->packet_buffer) - 2 * sizeof(uint32_t));
|
||||
memmove(handle->packet_buffer, handle->packet_buffer + 2,
|
||||
sizeof (handle->packet_buffer) - 2 * sizeof(uint32_t));
|
||||
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2] = htonl(handle->frame_count);
|
||||
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2 + 1] = htonl(state);
|
||||
|
||||
@ -797,15 +829,147 @@ static bool netplay_poll(netplay_t *handle)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool netplay_send_cmd(netplay_t *handle, uint32_t cmd, const void *data, size_t size)
|
||||
{
|
||||
cmd = (cmd << 16) | (size & 0xffff);
|
||||
cmd = htonl(cmd);
|
||||
|
||||
if (!send_all(handle->fd, &cmd, sizeof(cmd)))
|
||||
return false;
|
||||
|
||||
if (!send_all(handle->fd, data, size))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool netplay_cmd_ack(netplay_t *handle)
|
||||
{
|
||||
uint32_t cmd = htonl(NETPLAY_CMD_ACK);
|
||||
return send_all(handle->fd, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static bool netplay_cmd_nak(netplay_t *handle)
|
||||
{
|
||||
uint32_t cmd = htonl(NETPLAY_CMD_NAK);
|
||||
return send_all(handle->fd, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static bool netplay_get_response(netplay_t *handle)
|
||||
{
|
||||
uint32_t response;
|
||||
if (!recv_all(handle->fd, &response, sizeof(response)))
|
||||
return false;
|
||||
|
||||
return ntohl(response) == NETPLAY_CMD_ACK;
|
||||
}
|
||||
|
||||
static bool netplay_get_cmd(netplay_t *handle)
|
||||
{
|
||||
uint32_t cmd;
|
||||
if (!recv_all(handle->fd, &cmd, sizeof(cmd)))
|
||||
return false;
|
||||
|
||||
cmd = ntohl(cmd);
|
||||
|
||||
size_t cmd_size = cmd & 0xffff;
|
||||
cmd = cmd >> 16;
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case NETPLAY_CMD_FLIP_PLAYERS:
|
||||
{
|
||||
if (cmd_size != sizeof(uint32_t))
|
||||
{
|
||||
SSNES_ERR("CMD_FLIP_PLAYERS has unexpected command size!\n");
|
||||
return netplay_cmd_nak(handle);
|
||||
}
|
||||
|
||||
uint32_t flip_frame;
|
||||
if (!recv_all(handle->fd, &flip_frame, sizeof(flip_frame)))
|
||||
{
|
||||
SSNES_ERR("Failed to receive CMD_FLIP_PLAYERS argument!\n");
|
||||
return netplay_cmd_nak(handle);
|
||||
}
|
||||
|
||||
flip_frame = ntohl(flip_frame);
|
||||
if (flip_frame < handle->flip_frame)
|
||||
{
|
||||
SSNES_ERR("Host asked us to flip players in the past. Not possible ...\n");
|
||||
return netplay_cmd_nak(handle);
|
||||
}
|
||||
|
||||
handle->flip ^= true;
|
||||
handle->flip_frame = flip_frame;
|
||||
|
||||
return netplay_cmd_ack(handle);
|
||||
}
|
||||
|
||||
default:
|
||||
SSNES_ERR("Unknown netplay command received!\n");
|
||||
return netplay_cmd_nak(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void netplay_flip_players(netplay_t *handle)
|
||||
{
|
||||
uint32_t flip_frame = handle->self_ptr + 2 * UDP_FRAME_PACKETS;
|
||||
uint32_t flip_frame_net = htonl(flip_frame);
|
||||
const char *msg = NULL;
|
||||
|
||||
if (handle->spectate)
|
||||
{
|
||||
msg = "Cannot flip players in spectate mode!";
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (handle->port == 0)
|
||||
{
|
||||
msg = "Cannot flip players if you're not the host!";
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Make sure both clients are definitely synced up.
|
||||
if (handle->self_ptr < handle->flip_frame + 2 * UDP_FRAME_PACKETS)
|
||||
{
|
||||
msg = "Cannot flip players yet! Wait a second or two before attempting flip.";
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (netplay_send_cmd(handle, NETPLAY_CMD_FLIP_PLAYERS, &flip_frame_net, sizeof(flip_frame_net))
|
||||
&& netplay_get_response(handle))
|
||||
{
|
||||
// Queue up a flip well enough in the future.
|
||||
handle->flip ^= true;
|
||||
handle->flip_frame = flip_frame;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = "Failed to flip players!";
|
||||
goto error;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
SSNES_WARN("%s\n", msg);
|
||||
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
|
||||
}
|
||||
|
||||
static bool netplay_flip_port(netplay_t *handle, bool port, size_t ptr)
|
||||
{
|
||||
if (handle->flip_frame == 0)
|
||||
return port;
|
||||
|
||||
return port ^ handle->flip ^ (ptr < handle->flip_frame);
|
||||
}
|
||||
|
||||
int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
uint16_t input_state = 0;
|
||||
size_t ptr = 0;
|
||||
size_t ptr = handle->is_replay ? handle->tmp_ptr : PREV_PTR(handle->self_ptr);
|
||||
|
||||
if (handle->is_replay)
|
||||
ptr = handle->tmp_ptr;
|
||||
else
|
||||
ptr = PREV_PTR(handle->self_ptr);
|
||||
port = netplay_flip_port(handle, port, ptr);
|
||||
|
||||
if ((port ? 1 : 0) == handle->port)
|
||||
{
|
||||
@ -897,7 +1061,7 @@ int16_t input_state_spectate(bool port, unsigned device, unsigned index, unsigne
|
||||
static int16_t netplay_get_spectate_input(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
int16_t inp;
|
||||
if (recv(handle->fd, NONCONST_CAST &inp, sizeof(inp), 0) == (ssize_t)sizeof(inp))
|
||||
if (recv_all(handle->fd, NONCONST_CAST &inp, sizeof(inp)))
|
||||
return swap_if_big16(inp);
|
||||
else
|
||||
{
|
||||
@ -970,19 +1134,12 @@ static void netplay_pre_frame_spectate(netplay_t *handle)
|
||||
setsockopt(new_fd, SOL_SOCKET, SO_SNDBUF, CONST_CAST &bufsize, sizeof(int));
|
||||
|
||||
const uint8_t *tmp_header = header;
|
||||
while (header_size)
|
||||
if (!send_all(new_fd, tmp_header, header_size))
|
||||
{
|
||||
ssize_t ret = send(new_fd, CONST_CAST tmp_header, header_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to send header to client!\n");
|
||||
close(new_fd);
|
||||
free(header);
|
||||
return;
|
||||
}
|
||||
|
||||
header_size -= ret;
|
||||
tmp_header += ret;
|
||||
SSNES_ERR("Failed to send header to client!\n");
|
||||
close(new_fd);
|
||||
free(header);
|
||||
return;
|
||||
}
|
||||
|
||||
free(header);
|
||||
@ -1053,26 +1210,18 @@ static void netplay_post_frame_spectate(netplay_t *handle)
|
||||
if (handle->spectate_fds[i] == -1)
|
||||
continue;
|
||||
|
||||
size_t send_size = handle->spectate_input_ptr * sizeof(int16_t);
|
||||
const uint8_t *tmp_buf = (const uint8_t*)handle->spectate_input;
|
||||
while (send_size)
|
||||
if (!send_all(handle->spectate_fds[i],
|
||||
handle->spectate_input, handle->spectate_input_ptr * sizeof(int16_t)))
|
||||
{
|
||||
ssize_t ret = send(handle->spectate_fds[i], CONST_CAST tmp_buf, send_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_LOG("Client (#%u) disconnected ...\n", i);
|
||||
SSNES_LOG("Client (#%u) disconnected ...\n", i);
|
||||
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "Client (#%u) disconnected!", i);
|
||||
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "Client (#%u) disconnected!", i);
|
||||
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
|
||||
|
||||
close(handle->spectate_fds[i]);
|
||||
handle->spectate_fds[i] = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
tmp_buf += ret;
|
||||
send_size -= ret;
|
||||
close(handle->spectate_fds[i]);
|
||||
handle->spectate_fds[i] = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,9 +41,14 @@ struct snes_callbacks
|
||||
};
|
||||
|
||||
// Creates a new netplay handle. A NULL host means we're hosting (player 1). :)
|
||||
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb, bool spectate);
|
||||
netplay_t *netplay_new(const char *server,
|
||||
uint16_t port, unsigned frames,
|
||||
const struct snes_callbacks *cb, bool spectate);
|
||||
void netplay_free(netplay_t *handle);
|
||||
|
||||
// On regular netplay, flip who controls player 1 and 2.
|
||||
void netplay_flip_players(netplay_t *handle);
|
||||
|
||||
// Call this before running snes_run()
|
||||
void netplay_pre_frame(netplay_t *handle);
|
||||
// Call this after running snes_run()
|
||||
|
Loading…
x
Reference in New Issue
Block a user