Start implementing player flipping.

This commit is contained in:
Themaister 2012-01-21 14:00:11 +01:00
parent 99f622de0e
commit d551d93ce4
2 changed files with 231 additions and 77 deletions

301
netplay.c
View File

@ -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;
}
}

View File

@ -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()