From af34a2890e141bf76db23f6ae6970fdbbd484cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20D=C3=BCrrenberger?= Date: Sat, 3 Oct 2026 12:40:17 +0200 Subject: [PATCH] Add readiness types and callbacks to sfSocketSelector --- include/CSFML/Network/SocketSelector.h | 138 +++++++++++++++++++++++-- src/CSFML/Network/SocketSelector.cpp | 117 ++++++++++++++++++--- test/CMakeLists.txt | 1 + test/Network/SocketSelector.test.cpp | 44 ++++++++ 4 files changed, 278 insertions(+), 22 deletions(-) create mode 100644 test/Network/SocketSelector.test.cpp diff --git a/include/CSFML/Network/SocketSelector.h b/include/CSFML/Network/SocketSelector.h index aa0fcf3..85d2b10 100644 --- a/include/CSFML/Network/SocketSelector.h +++ b/include/CSFML/Network/SocketSelector.h @@ -32,6 +32,31 @@ #include #include +#include +#include + + +//////////////////////////////////////////////////////////// +/// \brief Readiness types a socket selector can check for +/// +/// The values can be combined with a bitwise OR. +/// +//////////////////////////////////////////////////////////// +typedef enum +{ + sfSocketSelectorReceive = 1 << 0, ///< Check if sockets are ready to be received from + sfSocketSelectorSend = 1 << 1 ///< Check if sockets are ready to be sent to +} sfSocketSelectorReadiness; + +//////////////////////////////////////////////////////////// +/// \brief Callback that is called when a socket is ready +/// +/// \param readiness Readiness of the socket, a combination of sfSocketSelectorReadiness values +/// \param userData User data passed when adding the socket +/// +//////////////////////////////////////////////////////////// +typedef void (*sfSocketSelectorCallback)(uint32_t readiness, void* userData); + //////////////////////////////////////////////////////////// /// \brief Create a new selector @@ -66,13 +91,64 @@ CSFML_NETWORK_API void sfSocketSelector_destroy(const sfSocketSelector* selector /// so you have to make sure that the socket is not destroyed /// while it is stored in the selector. /// +/// The socket is checked for being ready to receive. +/// /// \param selector Socket selector object /// \param socket Pointer to the socket to add /// +/// \return True if the socket was successfully added +/// //////////////////////////////////////////////////////////// -CSFML_NETWORK_API void sfSocketSelector_addTcpListener(sfSocketSelector* selector, sfTcpListener* socket); -CSFML_NETWORK_API void sfSocketSelector_addTcpSocket(sfSocketSelector* selector, sfTcpSocket* socket); -CSFML_NETWORK_API void sfSocketSelector_addUdpSocket(sfSocketSelector* selector, sfUdpSocket* socket); +CSFML_NETWORK_API bool sfSocketSelector_addTcpListener(sfSocketSelector* selector, sfTcpListener* socket); +CSFML_NETWORK_API bool sfSocketSelector_addTcpSocket(sfSocketSelector* selector, sfTcpSocket* socket); +CSFML_NETWORK_API bool sfSocketSelector_addUdpSocket(sfSocketSelector* selector, sfUdpSocket* socket); + +//////////////////////////////////////////////////////////// +/// \brief Add a new socket to a socket selector with the readiness to check for and an optional callback +/// +/// This function keeps a weak pointer to the socket, +/// so you have to make sure that the socket is not destroyed +/// while it is stored in the selector. +/// +/// The readiness to wait for can be specified as a combination +/// of sfSocketSelectorReceive and sfSocketSelectorSend. +/// +/// If a callback is provided, it will be called with the +/// readiness of the socket when sfSocketSelector_dispatchReadyCallbacks +/// is called after sfSocketSelector_wait returned true. Using +/// callbacks scales better than checking every socket with +/// sfSocketSelector_isXxxReadyFor. +/// +/// Adding a socket that has already been added updates its +/// readiness and callback. +/// +/// \param selector Socket selector object +/// \param socket Pointer to the socket to add +/// \param readiness Readiness to wait for, combination of sfSocketSelectorReadiness values +/// \param callback Callback to call when the socket is ready, can be NULL +/// \param userData User data that will be passed to the callback +/// +/// \return True if the socket was successfully added +/// +//////////////////////////////////////////////////////////// +CSFML_NETWORK_API bool sfSocketSelector_addTcpListenerWithReadiness( + sfSocketSelector* selector, + sfTcpListener* socket, + uint32_t readiness, + sfSocketSelectorCallback callback, + void* userData); +CSFML_NETWORK_API bool sfSocketSelector_addTcpSocketWithReadiness( + sfSocketSelector* selector, + sfTcpSocket* socket, + uint32_t readiness, + sfSocketSelectorCallback callback, + void* userData); +CSFML_NETWORK_API bool sfSocketSelector_addUdpSocketWithReadiness( + sfSocketSelector* selector, + sfUdpSocket* socket, + uint32_t readiness, + sfSocketSelectorCallback callback, + void* userData); //////////////////////////////////////////////////////////// /// \brief Remove a socket from a socket selector @@ -83,10 +159,12 @@ CSFML_NETWORK_API void sfSocketSelector_addUdpSocket(sfSocketSelector* selector, /// \param selector Socket selector object /// \param socket Pointer to the socket to remove /// +/// \return True if the socket was successfully removed +/// //////////////////////////////////////////////////////////// -CSFML_NETWORK_API void sfSocketSelector_removeTcpListener(sfSocketSelector* selector, sfTcpListener* socket); -CSFML_NETWORK_API void sfSocketSelector_removeTcpSocket(sfSocketSelector* selector, sfTcpSocket* socket); -CSFML_NETWORK_API void sfSocketSelector_removeUdpSocket(sfSocketSelector* selector, sfUdpSocket* socket); +CSFML_NETWORK_API bool sfSocketSelector_removeTcpListener(sfSocketSelector* selector, sfTcpListener* socket); +CSFML_NETWORK_API bool sfSocketSelector_removeTcpSocket(sfSocketSelector* selector, sfTcpSocket* socket); +CSFML_NETWORK_API bool sfSocketSelector_removeUdpSocket(sfSocketSelector* selector, sfUdpSocket* socket); //////////////////////////////////////////////////////////// /// \brief Remove all the sockets stored in a selector @@ -101,11 +179,13 @@ CSFML_NETWORK_API void sfSocketSelector_removeUdpSocket(sfSocketSelector* select CSFML_NETWORK_API void sfSocketSelector_clear(sfSocketSelector* selector); //////////////////////////////////////////////////////////// -/// \brief Wait until one or more sockets are ready to receive +/// \brief Wait until one or more sockets are ready /// -/// This function returns as soon as at least one socket has -/// some data available to be received. To know which sockets are -/// ready, use the sfSocketSelector_isXxxReady functions. +/// This function returns as soon as at least one socket is +/// ready for the readiness it was added with (by default +/// having some data available to be received). To know which +/// sockets are ready, use the sfSocketSelector_isXxxReady +/// functions or sfSocketSelector_dispatchReadyCallbacks. /// If you use a timeout and no socket is ready before the timeout /// is over, the function returns false. /// @@ -136,3 +216,41 @@ CSFML_NETWORK_API bool sfSocketSelector_wait(sfSocketSelector* selector, sfTime CSFML_NETWORK_API bool sfSocketSelector_isTcpListenerReady(const sfSocketSelector* selector, sfTcpListener* socket); CSFML_NETWORK_API bool sfSocketSelector_isTcpSocketReady(const sfSocketSelector* selector, sfTcpSocket* socket); CSFML_NETWORK_API bool sfSocketSelector_isUdpSocketReady(const sfSocketSelector* selector, sfUdpSocket* socket); + +//////////////////////////////////////////////////////////// +/// \brief Test a socket to know if it is ready for the given readiness +/// +/// This function must be used after a call to +/// sfSocketSelector_wait, to know which sockets are ready. +/// +/// \param selector Socket selector object +/// \param socket Socket to test +/// \param readiness Readiness to check for, combination of sfSocketSelectorReadiness values +/// +/// \return true if the socket is ready, false otherwise +/// +//////////////////////////////////////////////////////////// +CSFML_NETWORK_API bool sfSocketSelector_isTcpListenerReadyFor(const sfSocketSelector* selector, + sfTcpListener* socket, + uint32_t readiness); +CSFML_NETWORK_API bool sfSocketSelector_isTcpSocketReadyFor(const sfSocketSelector* selector, + sfTcpSocket* socket, + uint32_t readiness); +CSFML_NETWORK_API bool sfSocketSelector_isUdpSocketReadyFor(const sfSocketSelector* selector, + sfUdpSocket* socket, + uint32_t readiness); + +//////////////////////////////////////////////////////////// +/// \brief Call the callbacks of all sockets that are ready +/// +/// After sfSocketSelector_wait returned true, at least one +/// socket is ready. Calling this function will call the +/// callbacks of all the sockets that became ready during +/// the wait. Calling this function multiple times after a +/// single call to sfSocketSelector_wait will run the callbacks +/// multiple times. +/// +/// \param selector Socket selector object +/// +//////////////////////////////////////////////////////////// +CSFML_NETWORK_API void sfSocketSelector_dispatchReadyCallbacks(sfSocketSelector* selector); diff --git a/src/CSFML/Network/SocketSelector.cpp b/src/CSFML/Network/SocketSelector.cpp index 9d4673d..1abc685 100644 --- a/src/CSFML/Network/SocketSelector.cpp +++ b/src/CSFML/Network/SocketSelector.cpp @@ -31,6 +31,22 @@ #include #include +#include + + +namespace +{ +//////////////////////////////////////////////////////////// +[[nodiscard]] std::function convertCallback(sfSocketSelectorCallback callback, + void* userData) +{ + if (!callback) + return {}; + + return [callback, userData](sf::SocketSelector::ReadinessType readiness) { callback(readiness, userData); }; +} +} // namespace + //////////////////////////////////////////////////////////// sfSocketSelector* sfSocketSelector_create() @@ -55,44 +71,86 @@ void sfSocketSelector_destroy(const sfSocketSelector* selector) //////////////////////////////////////////////////////////// -void sfSocketSelector_addTcpListener(sfSocketSelector* selector, sfTcpListener* socket) +bool sfSocketSelector_addTcpListener(sfSocketSelector* selector, sfTcpListener* socket) { assert(selector); assert(socket); - selector->add(*socket); + return selector->add(*socket); } -void sfSocketSelector_addTcpSocket(sfSocketSelector* selector, sfTcpSocket* socket) +bool sfSocketSelector_addTcpSocket(sfSocketSelector* selector, sfTcpSocket* socket) { assert(selector); assert(socket); - selector->add(*socket); + return selector->add(*socket); } -void sfSocketSelector_addUdpSocket(sfSocketSelector* selector, sfUdpSocket* socket) +bool sfSocketSelector_addUdpSocket(sfSocketSelector* selector, sfUdpSocket* socket) { assert(selector); assert(socket); - selector->add(*socket); + return selector->add(*socket); } //////////////////////////////////////////////////////////// -void sfSocketSelector_removeTcpListener(sfSocketSelector* selector, sfTcpListener* socket) +bool sfSocketSelector_addTcpListenerWithReadiness( + sfSocketSelector* selector, + sfTcpListener* socket, + uint32_t readiness, + sfSocketSelectorCallback callback, + void* userData) { assert(selector); assert(socket); - selector->remove(*socket); + return selector->add(*socket, readiness, convertCallback(callback, userData)); } -void sfSocketSelector_removeTcpSocket(sfSocketSelector* selector, sfTcpSocket* socket) + + +//////////////////////////////////////////////////////////// +bool sfSocketSelector_addTcpSocketWithReadiness( + sfSocketSelector* selector, + sfTcpSocket* socket, + uint32_t readiness, + sfSocketSelectorCallback callback, + void* userData) { assert(selector); assert(socket); - selector->remove(*socket); + return selector->add(*socket, readiness, convertCallback(callback, userData)); } -void sfSocketSelector_removeUdpSocket(sfSocketSelector* selector, sfUdpSocket* socket) + + +//////////////////////////////////////////////////////////// +bool sfSocketSelector_addUdpSocketWithReadiness( + sfSocketSelector* selector, + sfUdpSocket* socket, + uint32_t readiness, + sfSocketSelectorCallback callback, + void* userData) { assert(selector); assert(socket); - selector->remove(*socket); + return selector->add(*socket, readiness, convertCallback(callback, userData)); +} + + +//////////////////////////////////////////////////////////// +bool sfSocketSelector_removeTcpListener(sfSocketSelector* selector, sfTcpListener* socket) +{ + assert(selector); + assert(socket); + return selector->remove(*socket); +} +bool sfSocketSelector_removeTcpSocket(sfSocketSelector* selector, sfTcpSocket* socket) +{ + assert(selector); + assert(socket); + return selector->remove(*socket); +} +bool sfSocketSelector_removeUdpSocket(sfSocketSelector* selector, sfUdpSocket* socket) +{ + assert(selector); + assert(socket); + return selector->remove(*socket); } @@ -131,3 +189,38 @@ bool sfSocketSelector_isUdpSocketReady(const sfSocketSelector* selector, sfUdpSo assert(socket); return selector->isReady(*socket); } + + +//////////////////////////////////////////////////////////// +bool sfSocketSelector_isTcpListenerReadyFor(const sfSocketSelector* selector, sfTcpListener* socket, uint32_t readiness) +{ + assert(selector); + assert(socket); + return selector->isReady(*socket, readiness); +} + + +//////////////////////////////////////////////////////////// +bool sfSocketSelector_isTcpSocketReadyFor(const sfSocketSelector* selector, sfTcpSocket* socket, uint32_t readiness) +{ + assert(selector); + assert(socket); + return selector->isReady(*socket, readiness); +} + + +//////////////////////////////////////////////////////////// +bool sfSocketSelector_isUdpSocketReadyFor(const sfSocketSelector* selector, sfUdpSocket* socket, uint32_t readiness) +{ + assert(selector); + assert(socket); + return selector->isReady(*socket, readiness); +} + + +//////////////////////////////////////////////////////////// +void sfSocketSelector_dispatchReadyCallbacks(sfSocketSelector* selector) +{ + assert(selector); + selector->dispatchReadyCallbacks(); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c18fb00..927cda7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -70,6 +70,7 @@ add_executable(test-csfml-network Network/Ftp.test.cpp Network/Http.test.cpp Network/IpAddress.test.cpp + Network/SocketSelector.test.cpp Network/SocketStatus.test.cpp Network/TcpSocket.test.cpp ) diff --git a/test/Network/SocketSelector.test.cpp b/test/Network/SocketSelector.test.cpp new file mode 100644 index 0000000..03967c2 --- /dev/null +++ b/test/Network/SocketSelector.test.cpp @@ -0,0 +1,44 @@ +#include +#include +#include + +#include + +#include + +TEST_CASE("[Network] sfSocketSelector") +{ + SECTION("sfSocketSelectorReadiness") + { + STATIC_CHECK(sfSocketSelectorReceive == static_cast(sf::SocketSelector::Receive)); + STATIC_CHECK(sfSocketSelectorSend == static_cast(sf::SocketSelector::Send)); + } + + SECTION("Ready callbacks") + { + sfTcpListener* listener = sfTcpListener_create(); + REQUIRE(sfTcpListener_listen(listener, 0, sfIpAddress_LocalHost) == sfSocketDone); + + sfTcpSocket* client = sfTcpSocket_create(); + REQUIRE(sfTcpSocket_connect(client, sfIpAddress_LocalHost, sfTcpListener_getLocalPort(listener), sfSeconds(1)) == + sfSocketDone); + + sfSocketSelector* selector = sfSocketSelector_create(); + + uint32_t readiness = 0; + const auto callback = [](uint32_t ready, void* userData) { *static_cast(userData) |= ready; }; + CHECK(sfSocketSelector_addTcpListenerWithReadiness(selector, listener, sfSocketSelectorReceive, callback, &readiness)); + + REQUIRE(sfSocketSelector_wait(selector, sfSeconds(1))); + CHECK(sfSocketSelector_isTcpListenerReady(selector, listener)); + CHECK(sfSocketSelector_isTcpListenerReadyFor(selector, listener, sfSocketSelectorReceive)); + sfSocketSelector_dispatchReadyCallbacks(selector); + CHECK(readiness == sfSocketSelectorReceive); + + CHECK(sfSocketSelector_removeTcpListener(selector, listener)); + + sfSocketSelector_destroy(selector); + sfTcpSocket_destroy(client); + sfTcpListener_destroy(listener); + } +}