From e8283363fbe9dbdcda0716c5ce57c91eb5dd9378 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Mon, 26 Jun 2023 20:13:41 +0900 Subject: [PATCH] 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) --- libretro-common/include/libretro.h | 59 ++++++++++++--------- network/netplay/netplay_frontend.c | 83 ++++++++++++++++++++---------- 2 files changed, 91 insertions(+), 51 deletions(-) diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index 15ad558147..8d6a1664a4 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -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 */ @@ -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. @@ -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); @@ -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. diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 333141f40f..2ebb47aef5 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -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; @@ -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<nick, - NETPLAY_CONNECTION_SPECTATING, 0, -1); + NETPLAY_CONNECTION_SPECTATING, 0, -1, client_num); } else { @@ -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, @@ -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++) @@ -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 { @@ -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); @@ -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); @@ -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); @@ -5692,7 +5714,7 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->device_clients[device] &= ~(1<spectator\n", client_num); @@ -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; @@ -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; @@ -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); @@ -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. */ @@ -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 */