Enhance netpacket interface

- Enable core host to refuse connecting new players to limit the number of connected players
- Enable a core to flush outgoing packets and read incoming packets without waiting for the next frame (can be used for lower latency or blocking reads)
This commit is contained in:
Bernhard Schelling 2023-06-26 20:13:41 +09:00 committed by LibretroAdmin
parent 2546ac6bbc
commit e8283363fb
2 changed files with 91 additions and 51 deletions

View File

@ -1811,26 +1811,26 @@ enum retro_mod
#define RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE 76 #define RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE 76
/* const struct retro_netpacket_callback * -- /* const struct retro_netpacket_callback * --
* When set, a core gets control over network packets sent and * When set, a core gains control over network packets sent and
* received during a multiplayer session. This can be used to emulate * received during a multiplayer session. This can be used to
* multiplayer games that were originally played on 2 or more separate * emulate multiplayer games that were originally played on two
* consoles or computers connected together. * or more separate consoles or computers connected together.
* *
* The frontend will take care of connecting players together. * The frontend will take care of connecting players together,
* The core only needs to send the actual data as needed for the * and the core only needs to send the actual data as needed for
* emulation while handshake and connection management happens in * the emulation, while handshake and connection management happen
* the background. * in the background.
* *
* When 2 or more players are connected and this interface has been * When two or more players are connected and this interface has
* set, time manipulation features (pausing, slow motion, fast forward, * been set, time manipulation features (such as pausing, slow motion,
* rewinding, save state loading, etc.) are disabled to not interrupt * fast forward, rewinding, save state loading, etc.) are disabled to
* communication. * avoid interrupting communication.
* *
* Should be set in either retro_init or retro_load_game, but not both. * Should be set in either retro_init or retro_load_game, but not both.
* *
* When not set, a frontend may use state serialization based * When not set, a frontend may use state serialization-based
* multiplayer where a deterministic core supporting multiple * multiplayer, where a deterministic core supporting multiple
* input devices does not need to do anything on its own. * input devices does not need to take any action on its own.
*/ */
/* VFS functionality */ /* VFS functionality */
@ -3065,16 +3065,23 @@ struct retro_disk_control_ext_callback
#define RETRO_NETPACKET_UNSEQUENCED (1 << 1) /* Packet will not be sequenced with other packets and may arrive out of order. Cannot be set on reliable packets. */ #define RETRO_NETPACKET_UNSEQUENCED (1 << 1) /* Packet will not be sequenced with other packets and may arrive out of order. Cannot be set on reliable packets. */
/* Used by the core to send a packet to one or more connected players. /* Used by the core to send a packet to one or more connected players.
* A single packet sent via this interface can contain up to 64kb of data. * A single packet sent via this interface can contain up to 64 KB of data.
* *
* The broadcast flag can be set to true to send to multiple connected clients. * The broadcast flag can be set to true to send to multiple connected clients.
* On a broadcast, the client_id argument indicates 1 client NOT to send the * In a broadcast, the client_id argument indicates 1 client NOT to send the
* packet to (pass 0xFFFF to send to everyone). Otherwise, the client_id * packet to (pass 0xFFFF to send to everyone). Otherwise, the client_id
* argument indicates a single client to send the packet to. * argument indicates a single client to send the packet to.
* *
* A frontend must support sending of reliable packets (RETRO_NETPACKET_RELIABLE). * A frontend must support sending reliable packets (RETRO_NETPACKET_RELIABLE).
* Unreliable packets might not be supported by the frontend but the flags can * Unreliable packets might not be supported by the frontend, but the flags can
* still be specified, reliable transmission will be used instead. * still be specified. Reliable transmission will be used instead.
*
* If this function is called passing NULL for buf, it will instead flush all
* previously buffered outgoing packets and instantly read any incoming packets.
* During such a call, retro_netpacket_receive_t and retro_netpacket_stop_t can
* be called. The core can perform this in a loop to do a blocking read, i.e.,
* wait for incoming data, but needs to handle stop getting called and also
* give up after a short while to avoid freezing on a connection problem.
* *
* This function is not guaranteed to be thread-safe and must be called during * This function is not guaranteed to be thread-safe and must be called during
* retro_run or any of the netpacket callbacks passed with this interface. * retro_run or any of the netpacket callbacks passed with this interface.
@ -3088,14 +3095,14 @@ typedef void (RETRO_CALLCONV *retro_netpacket_send_t)(int flags, const void* buf
* If client_id is > 0 the local player is a client connected to a host and * If client_id is > 0 the local player is a client connected to a host and
* at this point is already fully connected to the host. * at this point is already fully connected to the host.
* *
* The core will have to store the retro_netpacket_send_t function pointer * The core must store the retro_netpacket_send_t function pointer provided
* passed here and use it whenever it wants to send a packet. That send * here and use it whenever it wants to send a packet. This function pointer
* function pointer is valid until the frontend calls retro_netpacket_stop_t. * remains valid until the frontend calls retro_netpacket_stop_t.
*/ */
typedef void (RETRO_CALLCONV *retro_netpacket_start_t)(uint16_t client_id, retro_netpacket_send_t send_fn); typedef void (RETRO_CALLCONV *retro_netpacket_start_t)(uint16_t client_id, retro_netpacket_send_t send_fn);
/* Called by the frontend when a new packet arrives which has been sent from /* Called by the frontend when a new packet arrives which has been sent from
* another peer with retro_netpacket_send_t. The client_id argument indicates * another player with retro_netpacket_send_t. The client_id argument indicates
* who has sent the packet. * who has sent the packet.
*/ */
typedef void (RETRO_CALLCONV *retro_netpacket_receive_t)(const void* buf, size_t len, uint16_t client_id); typedef void (RETRO_CALLCONV *retro_netpacket_receive_t)(const void* buf, size_t len, uint16_t client_id);
@ -3114,8 +3121,10 @@ typedef void (RETRO_CALLCONV *retro_netpacket_poll_t)(void);
/* Called by the frontend when a new player connects to the hosted session. /* Called by the frontend when a new player connects to the hosted session.
* This is only called on the host side, not for clients connected to the host. * This is only called on the host side, not for clients connected to the host.
* If this function returns false, the newly connected player gets dropped.
* This can be used for example to limit the number of players.
*/ */
typedef void (RETRO_CALLCONV *retro_netpacket_connected_t)(uint16_t client_id); typedef bool (RETRO_CALLCONV *retro_netpacket_connected_t)(uint16_t client_id);
/* Called by the frontend when a player leaves or disconnects from the hosted session. /* Called by the frontend when a player leaves or disconnects from the hosted session.
* This is only called on the host side, not for clients connected to the host. * This is only called on the host side, not for clients connected to the host.

View File

@ -4451,7 +4451,7 @@ static uint8_t netplay_settings_share_mode(
static void announce_play_spectate(netplay_t *netplay, static void announce_play_spectate(netplay_t *netplay,
const char *nick, const char *nick,
enum rarch_netplay_connection_mode mode, uint32_t devices, enum rarch_netplay_connection_mode mode, uint32_t devices,
int32_t ping) int32_t ping, uint32_t client_num)
{ {
char msg[512]; char msg[512];
const char *dmsg = NULL; const char *dmsg = NULL;
@ -4478,16 +4478,23 @@ static void announce_play_spectate(netplay_t *netplay,
uint32_t one_device = (uint32_t) -1; uint32_t one_device = (uint32_t) -1;
char *pdevice_str = NULL; char *pdevice_str = NULL;
for (device = 0; device < MAX_INPUT_DEVICES; device++) if (netplay->modus == NETPLAY_MODUS_CORE_PACKET_INTERFACE)
{ {
if (!(devices & (1<<device))) one_device = client_num;
continue; }
if (one_device == (uint32_t) -1) else
one_device = device; {
else for (device = 0; device < MAX_INPUT_DEVICES; device++)
{ {
one_device = (uint32_t) -1; if (!(devices & (1<<device)))
break; continue;
if (one_device == (uint32_t) -1)
one_device = device;
else
{
one_device = (uint32_t) -1;
break;
}
} }
} }
@ -4598,7 +4605,7 @@ static void handle_play_spectate(netplay_t *netplay,
NETPLAY_CMD_MODE, &payload, sizeof(payload)); NETPLAY_CMD_MODE, &payload, sizeof(payload));
announce_play_spectate(netplay, connection->nick, announce_play_spectate(netplay, connection->nick,
NETPLAY_CONNECTION_SPECTATING, 0, -1); NETPLAY_CONNECTION_SPECTATING, 0, -1, client_num);
} }
else else
{ {
@ -4609,7 +4616,7 @@ static void handle_play_spectate(netplay_t *netplay,
netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; netplay->self_mode = NETPLAY_CONNECTION_SPECTATING;
announce_play_spectate(netplay, NULL, announce_play_spectate(netplay, NULL,
NETPLAY_CONNECTION_SPECTATING, 0, -1); NETPLAY_CONNECTION_SPECTATING, 0, -1, client_num);
/* It was the server, so tell everyone else */ /* It was the server, so tell everyone else */
netplay_send_raw_cmd_all(netplay, NULL, netplay_send_raw_cmd_all(netplay, NULL,
@ -4643,7 +4650,12 @@ static void handle_play_spectate(netplay_t *netplay,
share_mode &= ~NETPLAY_SHARE_NO_PREFERENCE; share_mode &= ~NETPLAY_SHARE_NO_PREFERENCE;
} }
if (devices) if (netplay->modus == NETPLAY_MODUS_CORE_PACKET_INTERFACE)
{
/* no device needs to be assigned with netpacket interface */
devices = 0;
}
else if (devices)
{ {
/* Make sure the devices are available and/or shareable */ /* Make sure the devices are available and/or shareable */
for (i = 0; i < MAX_INPUT_DEVICES; i++) for (i = 0; i < MAX_INPUT_DEVICES; i++)
@ -4800,17 +4812,27 @@ static void handle_play_spectate(netplay_t *netplay,
payload.mode = htonl(mode | NETPLAY_CMD_MODE_BIT_YOU); payload.mode = htonl(mode | NETPLAY_CMD_MODE_BIT_YOU);
if (networking_driver_st.core_netpacket_interface
&& networking_driver_st.core_netpacket_interface->connected
&& !networking_driver_st.core_netpacket_interface->connected(
(uint16_t)(connection - netplay->connections + 1)))
{
/* core wants us to drop this new client */
connection->mode = NETPLAY_CONNECTION_CONNECTED;
uint32_t reason = htonl(
NETPLAY_CMD_MODE_REFUSED_REASON_OTHER);
netplay_send_raw_cmd(netplay, connection,
NETPLAY_CMD_MODE_REFUSED, &reason, sizeof(reason));
netplay_hangup(netplay, connection);
return;
}
/* Tell the player */ /* Tell the player */
netplay_send_raw_cmd(netplay, connection, netplay_send_raw_cmd(netplay, connection,
NETPLAY_CMD_MODE, &payload, sizeof(payload)); NETPLAY_CMD_MODE, &payload, sizeof(payload));
announce_play_spectate(netplay, connection->nick, announce_play_spectate(netplay, connection->nick,
connection->mode, devices, connection->ping); connection->mode, devices, connection->ping, client_num);
if (networking_driver_st.core_netpacket_interface
&& networking_driver_st.core_netpacket_interface->connected)
networking_driver_st.core_netpacket_interface->connected
((uint16_t)(connection - netplay->connections + 1));
} }
else else
{ {
@ -4828,7 +4850,7 @@ static void handle_play_spectate(netplay_t *netplay,
netplay->self_mode = NETPLAY_CONNECTION_PLAYING; netplay->self_mode = NETPLAY_CONNECTION_PLAYING;
announce_play_spectate(netplay, NULL, announce_play_spectate(netplay, NULL,
netplay->self_mode, devices, -1); netplay->self_mode, devices, -1, client_num);
} }
payload.mode = htonl(mode); payload.mode = htonl(mode);
@ -5632,7 +5654,7 @@ static bool netplay_get_cmd(netplay_t *netplay,
/* Announce it */ /* Announce it */
announce_play_spectate(netplay, NULL, netplay->self_mode, devices, announce_play_spectate(netplay, NULL, netplay->self_mode, devices,
connection->ping); connection->ping, client_num);
#ifdef DEBUG_NETPLAY_STEPS #ifdef DEBUG_NETPLAY_STEPS
RARCH_LOG("[Netplay] Received mode change self->%X\n", devices); RARCH_LOG("[Netplay] Received mode change self->%X\n", devices);
@ -5677,7 +5699,7 @@ static bool netplay_get_cmd(netplay_t *netplay,
netplay->read_frame_count[client_num] = netplay->server_frame_count; netplay->read_frame_count[client_num] = netplay->server_frame_count;
/* Announce it */ /* Announce it */
announce_play_spectate(netplay, nick, NETPLAY_CONNECTION_PLAYING, devices, -1); announce_play_spectate(netplay, nick, NETPLAY_CONNECTION_PLAYING, devices, -1, client_num);
#ifdef DEBUG_NETPLAY_STEPS #ifdef DEBUG_NETPLAY_STEPS
RARCH_LOG("[Netplay] Received mode change %u->%u\n", client_num, devices); RARCH_LOG("[Netplay] Received mode change %u->%u\n", client_num, devices);
@ -5692,7 +5714,7 @@ static bool netplay_get_cmd(netplay_t *netplay,
netplay->device_clients[device] &= ~(1<<client_num); netplay->device_clients[device] &= ~(1<<client_num);
/* Announce it */ /* Announce it */
announce_play_spectate(netplay, nick, NETPLAY_CONNECTION_SPECTATING, 0, -1); announce_play_spectate(netplay, nick, NETPLAY_CONNECTION_SPECTATING, 0, -1, client_num);
#ifdef DEBUG_NETPLAY_STEPS #ifdef DEBUG_NETPLAY_STEPS
RARCH_LOG("[Netplay] Received mode change %u->spectator\n", client_num); RARCH_LOG("[Netplay] Received mode change %u->spectator\n", client_num);
@ -5970,7 +5992,7 @@ static bool netplay_get_cmd(netplay_t *netplay,
} }
case NETPLAY_CMD_RESET: case NETPLAY_CMD_RESET:
{ {DBP_ASSERT_MODUS(NETPLAY_MODUS_INPUT_FRAME_SYNC)
uint32_t i; uint32_t i;
uint32_t frame; uint32_t frame;
size_t reset_ptr; size_t reset_ptr;
@ -7418,7 +7440,8 @@ static void netplay_toggle_play_spectate(netplay_t *netplay)
if (!netplay->is_server) if (!netplay->is_server)
{ {
int i; int i;
uint32_t client_mask = ~(1 << netplay->self_client_num); uint32_t client_num = netplay->self_client_num;
uint32_t client_mask = ~(1 << client_num);
netplay->connected_players &= client_mask; netplay->connected_players &= client_mask;
@ -7430,7 +7453,7 @@ static void netplay_toggle_play_spectate(netplay_t *netplay)
netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; netplay->self_mode = NETPLAY_CONNECTION_SPECTATING;
announce_play_spectate(netplay, NULL, announce_play_spectate(netplay, NULL,
NETPLAY_CONNECTION_SPECTATING, 0, -1); NETPLAY_CONNECTION_SPECTATING, 0, -1, client_num);
} }
netplay_cmd_mode(netplay, NETPLAY_CONNECTION_SPECTATING); netplay_cmd_mode(netplay, NETPLAY_CONNECTION_SPECTATING);
@ -9335,7 +9358,7 @@ bool netplay_decode_hostname(const char *hostname,
* @buf : packet data pointer * @buf : packet data pointer
* @len : packet data size * @len : packet data size
* @pkt_client_id : source id if host sending to client, otherwise recipient id or excepted id if broadcast * @pkt_client_id : source id if host sending to client, otherwise recipient id or excepted id if broadcast
* @broadcast : pass as true from client if host should relay this to everyone else * @broadcast : pass true on client if host should relay this to everyone else
* *
* Send a netpacket command to a connected peer. * Send a netpacket command to a connected peer.
*/ */
@ -9373,6 +9396,14 @@ static void RETRO_CALLCONV netplay_netpacket_send_cb(int flags,
netplay_t *netplay = net_st->data; netplay_t *netplay = net_st->data;
if (!netplay) return; if (!netplay) return;
if (buf == NULL)
{
/* With NULL this function instead flushes packets and checks incoming */
netplay_send_flush_all(netplay, NULL);
input_poll_net(netplay);
return;
}
if (!netplay->is_server) if (!netplay->is_server)
{ {
/* client always sends packet to host, host will relay it if needed */ /* client always sends packet to host, host will relay it if needed */