Skip to content

Commit

Permalink
Enhance netpacket interface
Browse files Browse the repository at this point in the history
- 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)
  • Loading branch information
schellingb committed Jun 26, 2023
1 parent a4440ce commit 356f31b
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 51 deletions.
59 changes: 34 additions & 25 deletions libretro-common/include/libretro.h
Original file line number Diff line number Diff line change
Expand Up @@ -1811,26 +1811,26 @@ enum retro_mod

#define RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE 76
/* const struct retro_netpacket_callback * --
* When set, a core gets control over network packets sent and
* received during a multiplayer session. This can be used to emulate
* multiplayer games that were originally played on 2 or more separate
* consoles or computers connected together.
* When set, a core gains control over network packets sent and
* received during a multiplayer session. This can be used to
* emulate multiplayer games that were originally played on two
* or more separate consoles or computers connected together.
*
* The frontend will take care of connecting players together.
* The core only needs to send the actual data as needed for the
* emulation while handshake and connection management happens in
* the background.
* The frontend will take care of connecting players together,
* and the core only needs to send the actual data as needed for
* the emulation, while handshake and connection management happen
* in the background.
*
* When 2 or more players are connected and this interface has been
* set, time manipulation features (pausing, slow motion, fast forward,
* rewinding, save state loading, etc.) are disabled to not interrupt
* communication.
* When two or more players are connected and this interface has
* been set, time manipulation features (such as pausing, slow motion,
* fast forward, rewinding, save state loading, etc.) are disabled to
* avoid interrupting communication.
*
* Should be set in either retro_init or retro_load_game, but not both.
*
* When not set, a frontend may use state serialization based
* multiplayer where a deterministic core supporting multiple
* input devices does not need to do anything on its own.
* When not set, a frontend may use state serialization-based
* multiplayer, where a deterministic core supporting multiple
* input devices does not need to take any action on its own.
*/

/* VFS functionality */
Expand Down Expand Up @@ -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. */

/* 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.
* 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
* argument indicates a single client to send the packet to.
*
* A frontend must support sending of reliable packets (RETRO_NETPACKET_RELIABLE).
* Unreliable packets might not be supported by the frontend but the flags can
* still be specified, reliable transmission will be used instead.
* A frontend must support sending reliable packets (RETRO_NETPACKET_RELIABLE).
* Unreliable packets might not be supported by the frontend, but the flags can
* 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
* retro_run or any of the netpacket callbacks passed with this interface.
Expand All @@ -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
* at this point is already fully connected to the host.
*
* The core will have to store the retro_netpacket_send_t function pointer
* passed here and use it whenever it wants to send a packet. That send
* function pointer is valid until the frontend calls retro_netpacket_stop_t.
* The core must store the retro_netpacket_send_t function pointer provided
* here and use it whenever it wants to send a packet. This function pointer
* 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);

/* 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.
*/
typedef void (RETRO_CALLCONV *retro_netpacket_receive_t)(const void* buf, size_t len, uint16_t client_id);
Expand All @@ -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.
* 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.
* This is only called on the host side, not for clients connected to the host.
Expand Down
83 changes: 57 additions & 26 deletions network/netplay/netplay_frontend.c
Original file line number Diff line number Diff line change
Expand Up @@ -4451,7 +4451,7 @@ static uint8_t netplay_settings_share_mode(
static void announce_play_spectate(netplay_t *netplay,
const char *nick,
enum rarch_netplay_connection_mode mode, uint32_t devices,
int32_t ping)
int32_t ping, uint32_t client_num)
{
char msg[512];
const char *dmsg = NULL;
Expand All @@ -4478,16 +4478,23 @@ static void announce_play_spectate(netplay_t *netplay,
uint32_t one_device = (uint32_t) -1;
char *pdevice_str = NULL;

for (device = 0; device < MAX_INPUT_DEVICES; device++)
if (netplay->modus == NETPLAY_MODUS_CORE_PACKET_INTERFACE)
{
if (!(devices & (1<<device)))
continue;
if (one_device == (uint32_t) -1)
one_device = device;
else
one_device = client_num;
}
else
{
for (device = 0; device < MAX_INPUT_DEVICES; device++)
{
one_device = (uint32_t) -1;
break;
if (!(devices & (1<<device)))
continue;
if (one_device == (uint32_t) -1)
one_device = device;
else
{
one_device = (uint32_t) -1;
break;
}
}
}

Expand Down Expand Up @@ -4598,7 +4605,7 @@ static void handle_play_spectate(netplay_t *netplay,
NETPLAY_CMD_MODE, &payload, sizeof(payload));

announce_play_spectate(netplay, connection->nick,
NETPLAY_CONNECTION_SPECTATING, 0, -1);
NETPLAY_CONNECTION_SPECTATING, 0, -1, client_num);
}
else
{
Expand All @@ -4609,7 +4616,7 @@ static void handle_play_spectate(netplay_t *netplay,
netplay->self_mode = NETPLAY_CONNECTION_SPECTATING;

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 */
netplay_send_raw_cmd_all(netplay, NULL,
Expand Down Expand Up @@ -4643,7 +4650,12 @@ static void handle_play_spectate(netplay_t *netplay,
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 */
for (i = 0; i < MAX_INPUT_DEVICES; i++)
Expand Down Expand Up @@ -4800,17 +4812,27 @@ static void handle_play_spectate(netplay_t *netplay,

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 */
netplay_send_raw_cmd(netplay, connection,
NETPLAY_CMD_MODE, &payload, sizeof(payload));

announce_play_spectate(netplay, connection->nick,
connection->mode, devices, connection->ping);

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));
connection->mode, devices, connection->ping, client_num);
}
else
{
Expand All @@ -4828,7 +4850,7 @@ static void handle_play_spectate(netplay_t *netplay,
netplay->self_mode = NETPLAY_CONNECTION_PLAYING;

announce_play_spectate(netplay, NULL,
netplay->self_mode, devices, -1);
netplay->self_mode, devices, -1, client_num);
}

payload.mode = htonl(mode);
Expand Down Expand Up @@ -5632,7 +5654,7 @@ static bool netplay_get_cmd(netplay_t *netplay,

/* Announce it */
announce_play_spectate(netplay, NULL, netplay->self_mode, devices,
connection->ping);
connection->ping, client_num);

#ifdef DEBUG_NETPLAY_STEPS
RARCH_LOG("[Netplay] Received mode change self->%X\n", devices);
Expand Down Expand Up @@ -5677,7 +5699,7 @@ static bool netplay_get_cmd(netplay_t *netplay,
netplay->read_frame_count[client_num] = netplay->server_frame_count;

/* 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
RARCH_LOG("[Netplay] Received mode change %u->%u\n", client_num, devices);
Expand All @@ -5692,7 +5714,7 @@ static bool netplay_get_cmd(netplay_t *netplay,
netplay->device_clients[device] &= ~(1<<client_num);

/* 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
RARCH_LOG("[Netplay] Received mode change %u->spectator\n", client_num);
Expand Down Expand Up @@ -5970,7 +5992,7 @@ static bool netplay_get_cmd(netplay_t *netplay,
}

case NETPLAY_CMD_RESET:
{
{DBP_ASSERT_MODUS(NETPLAY_MODUS_INPUT_FRAME_SYNC)
uint32_t i;
uint32_t frame;
size_t reset_ptr;
Expand Down Expand Up @@ -7418,7 +7440,8 @@ static void netplay_toggle_play_spectate(netplay_t *netplay)
if (!netplay->is_server)
{
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;

Expand All @@ -7430,7 +7453,7 @@ static void netplay_toggle_play_spectate(netplay_t *netplay)
netplay->self_mode = NETPLAY_CONNECTION_SPECTATING;

announce_play_spectate(netplay, NULL,
NETPLAY_CONNECTION_SPECTATING, 0, -1);
NETPLAY_CONNECTION_SPECTATING, 0, -1, client_num);
}

netplay_cmd_mode(netplay, NETPLAY_CONNECTION_SPECTATING);
Expand Down Expand Up @@ -9335,7 +9358,7 @@ bool netplay_decode_hostname(const char *hostname,
* @buf : packet data pointer
* @len : packet data size
* @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.
*/
Expand Down Expand Up @@ -9373,6 +9396,14 @@ static void RETRO_CALLCONV netplay_netpacket_send_cb(int flags,
netplay_t *netplay = net_st->data;
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)
{
/* client always sends packet to host, host will relay it if needed */
Expand Down

0 comments on commit 356f31b

Please sign in to comment.