mirror of
https://github.com/libretro/RetroArch
synced 2025-04-10 15:45:19 +00:00
Some initial work on UDP networking. Still borked.
This commit is contained in:
parent
143079efbe
commit
97f2503794
237
netplay.c
237
netplay.c
@ -20,6 +20,7 @@
|
|||||||
#include "dynamic.h"
|
#include "dynamic.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define _WIN32_WINNT 0x0501
|
#define _WIN32_WINNT 0x0501
|
||||||
@ -58,10 +59,13 @@ struct delta_frame
|
|||||||
bool used_real;
|
bool used_real;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define UDP_FRAME_PACKETS 16
|
||||||
|
|
||||||
struct netplay
|
struct netplay
|
||||||
{
|
{
|
||||||
struct snes_callbacks cbs;
|
struct snes_callbacks cbs;
|
||||||
int fd;
|
int fd; // TCP connection for state sending, etc. Could perhaps be used for messaging later on. :)
|
||||||
|
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?
|
||||||
bool has_connection;
|
bool has_connection;
|
||||||
|
|
||||||
@ -76,7 +80,15 @@ struct netplay
|
|||||||
size_t state_size;
|
size_t state_size;
|
||||||
|
|
||||||
size_t is_replay; // Are we replaying old frames?
|
size_t is_replay; // Are we replaying old frames?
|
||||||
bool can_poll;
|
bool can_poll; // We don't want to poll several times on a frame.
|
||||||
|
|
||||||
|
struct timeval last_tv;
|
||||||
|
uint32_t packet_buffer[UDP_FRAME_PACKETS * 2]; // To compat UDP packet loss we also send old data along with the packets.
|
||||||
|
uint32_t frame_count;
|
||||||
|
uint32_t read_frame_count;
|
||||||
|
struct addrinfo *addr;
|
||||||
|
struct sockaddr_storage their_addr;
|
||||||
|
bool has_client_addr;
|
||||||
};
|
};
|
||||||
|
|
||||||
void input_poll_net(void)
|
void input_poll_net(void)
|
||||||
@ -108,19 +120,8 @@ int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id)
|
|||||||
return netplay_callbacks(g_extern.netplay)->state_cb(port, device, index, id);
|
return netplay_callbacks(g_extern.netplay)->state_cb(port, device, index, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool init_socket(netplay_t *handle, const char *server, uint16_t port)
|
static bool init_tcp_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
|
||||||
WSADATA wsaData;
|
|
||||||
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
|
|
||||||
{
|
|
||||||
WSACleanup();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
signal(SIGPIPE, SIG_IGN); // Do not like SIGPIPE killing our app :(
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct addrinfo hints, *res = NULL;
|
struct addrinfo hints, *res = NULL;
|
||||||
memset(&hints, 0, sizeof(hints));
|
memset(&hints, 0, sizeof(hints));
|
||||||
#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf.
|
#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf.
|
||||||
@ -156,8 +157,7 @@ static bool init_socket(netplay_t *handle, const char *server, uint16_t port)
|
|||||||
{
|
{
|
||||||
SSNES_ERR("Failed to connect to server.\n");
|
SSNES_ERR("Failed to connect to server.\n");
|
||||||
close(handle->fd);
|
close(handle->fd);
|
||||||
if (res)
|
freeaddrinfo(res);
|
||||||
freeaddrinfo(res);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,8 +170,7 @@ static bool init_socket(netplay_t *handle, const char *server, uint16_t port)
|
|||||||
{
|
{
|
||||||
SSNES_ERR("Failed to bind socket.\n");
|
SSNES_ERR("Failed to bind socket.\n");
|
||||||
close(handle->fd);
|
close(handle->fd);
|
||||||
if (res)
|
freeaddrinfo(res);
|
||||||
freeaddrinfo(res);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int new_fd = accept(handle->fd, NULL, NULL);
|
int new_fd = accept(handle->fd, NULL, NULL);
|
||||||
@ -179,21 +178,85 @@ static bool init_socket(netplay_t *handle, const char *server, uint16_t port)
|
|||||||
{
|
{
|
||||||
SSNES_ERR("Failed to accept socket.\n");
|
SSNES_ERR("Failed to accept socket.\n");
|
||||||
close(handle->fd);
|
close(handle->fd);
|
||||||
if (res)
|
freeaddrinfo(res);
|
||||||
freeaddrinfo(res);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
close(handle->fd);
|
close(handle->fd);
|
||||||
handle->fd = new_fd;
|
handle->fd = new_fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res)
|
freeaddrinfo(res);
|
||||||
freeaddrinfo(res);
|
|
||||||
|
|
||||||
// No nagle for you!
|
return true;
|
||||||
const int nodelay = 1;
|
}
|
||||||
setsockopt(handle->fd, SOL_SOCKET, TCP_NODELAY, CONST_CAST &nodelay, sizeof(int));
|
|
||||||
|
|
||||||
|
static bool init_udp_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||||
|
{
|
||||||
|
struct addrinfo hints;
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf.
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
#else
|
||||||
|
hints.ai_family = AF_UNSPEC;
|
||||||
|
#endif
|
||||||
|
hints.ai_socktype = SOCK_DGRAM;
|
||||||
|
if (!server)
|
||||||
|
hints.ai_flags = AI_PASSIVE;
|
||||||
|
|
||||||
|
char port_buf[16];
|
||||||
|
snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port);
|
||||||
|
if (getaddrinfo(server, port_buf, &hints, &handle->addr) < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!handle->addr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
handle->udp_fd = socket(handle->addr->ai_family, handle->addr->ai_socktype, handle->addr->ai_protocol);
|
||||||
|
if (handle->udp_fd < 0)
|
||||||
|
{
|
||||||
|
SSNES_ERR("Failed to init socket...\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!server)
|
||||||
|
{
|
||||||
|
// Note sure if we have to do this for UDP, but hey :)
|
||||||
|
int yes = 1;
|
||||||
|
setsockopt(handle->udp_fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int));
|
||||||
|
|
||||||
|
if (bind(handle->udp_fd, handle->addr->ai_addr, handle->addr->ai_addrlen) < 0)
|
||||||
|
{
|
||||||
|
SSNES_ERR("Failed to bind socket.\n");
|
||||||
|
close(handle->udp_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
freeaddrinfo(handle->addr);
|
||||||
|
handle->addr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just get some initial value.
|
||||||
|
gettimeofday(&handle->last_tv, NULL);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool init_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSADATA wsaData;
|
||||||
|
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
|
||||||
|
{
|
||||||
|
WSACleanup();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
signal(SIGPIPE, SIG_IGN); // Do not like SIGPIPE killing our app :(
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!init_tcp_socket(handle, server, port))
|
||||||
|
return false;
|
||||||
|
if (!init_udp_socket(handle, server, port))
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +336,10 @@ static void init_buffers(netplay_t *handle)
|
|||||||
handle->buffer = calloc(handle->buffer_size, sizeof(*handle->buffer));
|
handle->buffer = calloc(handle->buffer_size, sizeof(*handle->buffer));
|
||||||
handle->state_size = psnes_serialize_size();
|
handle->state_size = psnes_serialize_size();
|
||||||
for (unsigned i = 0; i < handle->buffer_size; i++)
|
for (unsigned i = 0; i < handle->buffer_size; i++)
|
||||||
|
{
|
||||||
handle->buffer[i].state = malloc(handle->state_size);
|
handle->buffer[i].state = malloc(handle->state_size);
|
||||||
|
handle->buffer[i].is_simulated = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb)
|
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb)
|
||||||
@ -311,8 +377,11 @@ netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
handle->buffer_size = frames + 1;
|
handle->buffer_size = frames + 1;
|
||||||
|
|
||||||
init_buffers(handle);
|
init_buffers(handle);
|
||||||
handle->has_connection = true;
|
handle->has_connection = true;
|
||||||
|
|
||||||
|
memset(handle->packet_buffer, 0xFF, sizeof(handle->packet_buffer));
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,23 +392,27 @@ bool netplay_is_alive(netplay_t *handle)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool poll_input(netplay_t *handle, bool block)
|
static int poll_input(netplay_t *handle, bool block)
|
||||||
{
|
{
|
||||||
fd_set fds;
|
fd_set fds;
|
||||||
FD_ZERO(&fds);
|
FD_ZERO(&fds);
|
||||||
FD_SET(handle->fd, &fds);
|
FD_SET(handle->udp_fd, &fds);
|
||||||
|
|
||||||
struct timeval tv = {
|
struct timeval tv = {
|
||||||
.tv_sec = 0,
|
.tv_sec = block ? 5 : 0,
|
||||||
.tv_usec = 0
|
.tv_usec = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
if (select(handle->fd + 1, &fds, NULL, NULL, block ? NULL : &tv) < 0)
|
if (select(handle->udp_fd + 1, &fds, NULL, NULL, &tv) < 0)
|
||||||
return false;
|
return -1;
|
||||||
|
|
||||||
if (FD_ISSET(handle->fd, &fds))
|
if (block && !FD_ISSET(handle->udp_fd, &fds))
|
||||||
return true;
|
return -1;
|
||||||
return false;
|
|
||||||
|
if (FD_ISSET(handle->udp_fd, &fds))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab our own input state and send this over the network.
|
// Grab our own input state and send this over the network.
|
||||||
@ -347,7 +420,7 @@ static bool get_self_input_state(netplay_t *handle)
|
|||||||
{
|
{
|
||||||
struct delta_frame *ptr = &handle->buffer[handle->self_ptr];
|
struct delta_frame *ptr = &handle->buffer[handle->self_ptr];
|
||||||
|
|
||||||
uint16_t state = 0;
|
uint32_t state = 0;
|
||||||
snes_input_state_t cb = handle->cbs.state_cb;
|
snes_input_state_t cb = handle->cbs.state_cb;
|
||||||
for (int i = 0; i <= 11; i++)
|
for (int i = 0; i <= 11; i++)
|
||||||
{
|
{
|
||||||
@ -355,13 +428,27 @@ static bool get_self_input_state(netplay_t *handle)
|
|||||||
state |= tmp ? 1 << i : 0;
|
state |= tmp ? 1 << i : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t send_state = htons(state);
|
memmove(handle->packet_buffer, handle->packet_buffer + 2, sizeof (handle->packet_buffer) - 2 * sizeof(uint32_t));
|
||||||
if (send(handle->fd, CONST_CAST &send_state, sizeof(send_state), 0) != sizeof(send_state))
|
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2] = htonl(handle->frame_count);
|
||||||
|
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2 + 1] = htonl(state);
|
||||||
|
|
||||||
|
const struct sockaddr *addr = NULL;
|
||||||
|
if (handle->addr)
|
||||||
|
addr = handle->addr->ai_addr;
|
||||||
|
else if (handle->has_client_addr)
|
||||||
|
addr = (const struct sockaddr*)&handle->their_addr;
|
||||||
|
|
||||||
|
if (addr)
|
||||||
{
|
{
|
||||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
fprintf(stderr, "Sending a packet! :D\n");
|
||||||
handle->has_connection = false;
|
if (sendto(handle->udp_fd, CONST_CAST handle->packet_buffer, sizeof(handle->packet_buffer), 0, addr, sizeof(struct sockaddr)) != sizeof(handle->packet_buffer))
|
||||||
return false;
|
{
|
||||||
|
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||||
|
handle->has_connection = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr->self_state = state;
|
ptr->self_state = state;
|
||||||
handle->self_ptr = NEXT_PTR(handle->self_ptr);
|
handle->self_ptr = NEXT_PTR(handle->self_ptr);
|
||||||
return true;
|
return true;
|
||||||
@ -377,6 +464,42 @@ static void simulate_input(netplay_t *handle)
|
|||||||
handle->buffer[ptr].is_simulated = true;
|
handle->buffer[ptr].is_simulated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void parse_packet(netplay_t *handle, uint32_t *buffer, unsigned size)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < size * 2; i++)
|
||||||
|
buffer[i] = ntohl(buffer[i]);
|
||||||
|
|
||||||
|
const uint32_t *tmp = buffer + (size - 1) * 2;
|
||||||
|
for (; tmp != buffer; tmp -= 2)
|
||||||
|
{
|
||||||
|
uint32_t frame = tmp[0];
|
||||||
|
uint32_t state = tmp[1];
|
||||||
|
|
||||||
|
if (frame < handle->frame_count && frame >= handle->read_frame_count)
|
||||||
|
{
|
||||||
|
size_t ptr = (handle->read_ptr + frame - handle->read_frame_count) % handle->buffer_size;
|
||||||
|
handle->buffer[ptr].is_simulated = false;
|
||||||
|
handle->buffer[ptr].real_input_state = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!handle->buffer[handle->read_ptr].is_simulated && handle->read_ptr != handle->self_ptr)
|
||||||
|
{
|
||||||
|
handle->read_ptr = NEXT_PTR(handle->read_ptr);
|
||||||
|
handle->read_frame_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool receive_data(netplay_t *handle, uint32_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
socklen_t addrlen = sizeof(handle->their_addr);
|
||||||
|
if (recvfrom(handle->udp_fd, NONCONST_CAST buffer, sizeof(buffer), 0, (struct sockaddr*)&handle->their_addr, &addrlen) != sizeof(buffer))
|
||||||
|
return false;
|
||||||
|
handle->has_client_addr = true;
|
||||||
|
fprintf(stderr, "Received some data!\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Poll network to see if we have anything new. If our network buffer is full, we simply have to block for new input data.
|
// Poll network to see if we have anything new. If our network buffer is full, we simply have to block for new input data.
|
||||||
bool netplay_poll(netplay_t *handle)
|
bool netplay_poll(netplay_t *handle)
|
||||||
{
|
{
|
||||||
@ -388,24 +511,37 @@ bool netplay_poll(netplay_t *handle)
|
|||||||
if (!get_self_input_state(handle))
|
if (!get_self_input_state(handle))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// We skip reading the first frame so the host has a change to grab our host info so we don't block forever :')
|
||||||
|
if (handle->frame_count == 0)
|
||||||
|
{
|
||||||
|
simulate_input(handle);
|
||||||
|
handle->buffer[PREV_PTR(handle->self_ptr)].used_real = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// We might have reached the end of the buffer, where we simply have to block.
|
// We might have reached the end of the buffer, where we simply have to block.
|
||||||
if (poll_input(handle, handle->other_ptr == NEXT_PTR(handle->self_ptr)))
|
int res = poll_input(handle, handle->other_ptr == NEXT_PTR(handle->self_ptr));
|
||||||
|
if (res == -1)
|
||||||
|
{
|
||||||
|
handle->has_connection = false;
|
||||||
|
SSNES_WARN("Netplay connection timed out. Will continue without netplay.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == 1)
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
struct delta_frame *ptr = &handle->buffer[handle->read_ptr];
|
uint32_t buffer[UDP_FRAME_PACKETS * 2];
|
||||||
if (recv(handle->fd, NONCONST_CAST &ptr->real_input_state, sizeof(ptr->real_input_state), 0) != sizeof(ptr->real_input_state))
|
if (!receive_data(handle, buffer, sizeof(buffer)))
|
||||||
{
|
{
|
||||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||||
handle->has_connection = false;
|
handle->has_connection = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ptr->real_input_state = ntohs(ptr->real_input_state);
|
parse_packet(handle, buffer, UDP_FRAME_PACKETS);
|
||||||
ptr->is_simulated = false;
|
|
||||||
|
|
||||||
handle->read_ptr = NEXT_PTR(handle->read_ptr);
|
} while ((handle->read_ptr != handle->self_ptr) && poll_input(handle, false) == 1);
|
||||||
|
|
||||||
} while ((handle->read_ptr != handle->self_ptr) && poll_input(handle, false));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -415,7 +551,6 @@ bool netplay_poll(netplay_t *handle)
|
|||||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handle->read_ptr != handle->self_ptr)
|
if (handle->read_ptr != handle->self_ptr)
|
||||||
@ -458,11 +593,14 @@ int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsig
|
|||||||
void netplay_free(netplay_t *handle)
|
void netplay_free(netplay_t *handle)
|
||||||
{
|
{
|
||||||
close(handle->fd);
|
close(handle->fd);
|
||||||
|
close(handle->udp_fd);
|
||||||
|
|
||||||
for (unsigned i = 0; i < handle->buffer_size; i++)
|
for (unsigned i = 0; i < handle->buffer_size; i++)
|
||||||
free(handle->buffer[i].state);
|
free(handle->buffer[i].state);
|
||||||
|
|
||||||
free(handle->buffer);
|
free(handle->buffer);
|
||||||
|
if (handle->addr)
|
||||||
|
freeaddrinfo(handle->addr);
|
||||||
free(handle);
|
free(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,6 +623,8 @@ void netplay_pre_frame(netplay_t *handle)
|
|||||||
// Here we check if we have new input and replay from recorded input.
|
// Here we check if we have new input and replay from recorded input.
|
||||||
void netplay_post_frame(netplay_t *handle)
|
void netplay_post_frame(netplay_t *handle)
|
||||||
{
|
{
|
||||||
|
handle->frame_count++;
|
||||||
|
|
||||||
// Nothing to do...
|
// Nothing to do...
|
||||||
if (handle->other_ptr == handle->read_ptr)
|
if (handle->other_ptr == handle->read_ptr)
|
||||||
return;
|
return;
|
||||||
@ -513,6 +653,7 @@ void netplay_post_frame(netplay_t *handle)
|
|||||||
handle->other_ptr = handle->read_ptr;
|
handle->other_ptr = handle->read_ptr;
|
||||||
handle->is_replay = false;
|
handle->is_replay = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user