From b595e79a4f6c5531c4385b92f8da2057dcb85a7d Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Tue, 18 Jul 2023 19:44:50 -0400
Subject: [PATCH] First shot at an SDL3_net API and implementation! WIP!
---
CMakeLists.txt | 288 +---------
README.md | 19 +
README.txt | 26 -
SDL_net.c | 1381 ++++++++++++++++++++++++++++++++++++++++++++++++
SDL_net.h | 1103 ++------------------------------------
5 files changed, 1456 insertions(+), 1361 deletions(-)
create mode 100644 README.md
delete mode 100644 README.txt
create mode 100644 SDL_net.c
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 17dfb9f..012731d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,288 +1,4 @@
cmake_minimum_required(VERSION 3.16)
+project(SDL3_net)
+add_library(SDL3_net SHARED SDL_net.c)
-list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
-
-# See release_checklist.md
-set(MAJOR_VERSION 2)
-set(MINOR_VERSION 3)
-set(MICRO_VERSION 0)
-set(SDL_REQUIRED_VERSION 2.0.4)
-
-include(PrivateSdlFunctions)
-sdl_calculate_derived_version_variables()
-
-if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
- message(FATAL_ERROR "Prevented in-tree built. Please create a build directory outside of the SDL_net source code and call cmake from there")
-endif()
-
-project(SDL2_net
- LANGUAGES C
- VERSION "${FULL_VERSION}"
-)
-
-message(STATUS "Configuring ${PROJECT_NAME} ${PROJECT_VERSION}")
-
-if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
- set(SDL2NET_ROOTPROJECT ON)
-else()
- set(SDL2NET_ROOTPROJECT OFF)
-endif()
-
-if(TARGET SDL2::SDL2test)
- set(SDL2test_FOUND ON)
-else()
- find_package(SDL2test)
-endif()
-
-set(SDLNET_PKGCONFIG_PRIVATE_LIBS)
-
-# Set defaults preventing destination file conflicts
-set(SDL2NET_DEBUG_POSTFIX "d"
- CACHE STRING "Name suffix for debug builds")
-mark_as_advanced(SDL2NET_DEBUG_POSTFIX)
-
-set(sdl2net_install_enableable ON)
-if ((TARGET SDL2 OR TARGET SDL2-static) AND SDL2_DISABLE_INSTALL)
- # Cannot install SDL2_net when SDL2 is built in same built, and is not installed.
- set(sdl2net_install_enableable OFF)
-endif()
-
-include(CMakeDependentOption)
-include(CMakePackageConfigHelpers)
-include(GNUInstallDirs)
-
-option(CMAKE_POSITION_INDEPENDENT_CODE "Build static libraries with -fPIC" ON)
-option(BUILD_SHARED_LIBS "Build the library as a shared library" ON)
-
-cmake_dependent_option(SDL2NET_INSTALL "Enable SDL2_net install target" ${SDL2NET_ROOTPROJECT} ${sdl2net_install_enableable} OFF)
-
-cmake_dependent_option(SDL2NET_SAMPLES "Build SDL2_net samples" ${SDL2NET_ROOTPROJECT} "${SDL2test_FOUND}" OFF)
-
-if(SDL2NET_SAMPLES)
- find_package(SDL2test REQUIRED)
-endif()
-
-# Save BUILD_SHARED_LIBS variable
-set(SDL2NET_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
-
-if(SDL2NET_BUILD_SHARED_LIBS)
- set(sdl2_net_export_name SDL2_net)
- set(sdl2_net_install_name_infix shared)
- set(sdl2_target_name SDL2::SDL2)
-else()
- set(sdl2_net_export_name SDL2_net-static)
- set(sdl2_net_install_name_infix static)
- set(sdl2_target_name SDL2::SDL2-static)
-endif()
-
-sdl_find_sdl2(${sdl2_target_name} ${SDL_REQUIRED_VERSION})
-
-add_library(SDL2_net
- SDLnet.c
- SDLnetselect.c
- SDLnetTCP.c
- SDLnetUDP.c
-)
-add_library(SDL2_net::${sdl2_net_export_name} ALIAS SDL2_net)
-target_include_directories(SDL2_net PUBLIC
- "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
- "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/SDL2>"
-)
-target_compile_definitions(SDL2_net PRIVATE
- BUILD_SDL
- SDL_BUILD_MAJOR_VERSION=${MAJOR_VERSION}
- SDL_BUILD_MINOR_VERSION=${MINOR_VERSION}
- SDL_BUILD_MICRO_VERSION=${MICRO_VERSION}
-)
-target_link_libraries(SDL2_net PRIVATE $<BUILD_INTERFACE:${sdl2_target_name}>)
-if(WIN32)
- if (MSVC)
- target_compile_options(SDL2_net PRIVATE /W3 /wd4244)
- endif()
- target_compile_definitions(SDL2_net PRIVATE _WINSOCK_DEPRECATED_NO_WARNINGS)
- target_link_libraries(SDL2_net PRIVATE ws2_32 iphlpapi)
- list(APPEND SDLNET_PKGCONFIG_PRIVATE_LIBS -lws2_32 -liphlpapi)
- if(SDL2NET_BUILD_SHARED_LIBS)
- target_sources(SDL2_net PRIVATE version.rc)
- endif()
-endif()
-set_target_properties(SDL2_net PROPERTIES
- DEFINE_SYMBOL DLL_EXPORT
- EXPORT_NAME ${sdl2_net_export_name}
- C_VISIBILITY_PRESET "hidden"
-)
-if(NOT ANDROID)
- set_target_properties(SDL2_net PROPERTIES
- DEBUG_POSTFIX "${SDL2NET_DEBUG_POSTFIX}"
- )
- if(APPLE)
- # the SOVERSION property corresponds to the compatibility version and VERSION corresponds to the current version
- # https://cmake.org/cmake/help/latest/prop_tgt/SOVERSION.html#mach-o-versions
- set_target_properties(SDL2_net PROPERTIES
- SOVERSION "${DYLIB_COMPATIBILITY_VERSION}"
- VERSION "${DYLIB_CURRENT_VERSION}"
- )
- else()
- set_target_properties(SDL2_net PROPERTIES
- SOVERSION "${LT_MAJOR}"
- VERSION "${LT_VERSION}"
- )
- endif()
-endif()
-if(SDL2NET_BUILD_SHARED_LIBS AND (APPLE OR (UNIX AND NOT ANDROID)))
- add_custom_command(TARGET SDL2_net POST_BUILD
- COMMAND "${CMAKE_COMMAND}" -E create_symlink "$<TARGET_SONAME_FILE_NAME:SDL2_net>" "libSDL2_net$<$<CONFIG:Debug>:${SDL2NET_DEBUG_POSTFIX}>$<TARGET_FILE_SUFFIX:SDL2_net>"
- WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
- )
-endif()
-if(SDL2NET_BUILD_SHARED_LIBS)
- if(WIN32 OR OS2)
- set_target_properties(SDL2_net PROPERTIES
- PREFIX ""
- )
- endif()
- if(OS2)
- # FIXME: OS/2 Makefile has a different LIBNAME
- set_target_properties(SDL2_net PROPERTIES
- OUTPUT_NAME "SDL2net"
- )
- elseif(UNIX AND NOT ANDROID)
- set_target_properties(SDL2_net PROPERTIES
- OUTPUT_NAME "SDL2_net-${LT_RELEASE}"
- )
- endif()
-else()
- if(MSVC OR (WATCOM AND (WIN32 OR OS2)))
- set_target_properties(SDL2_net PROPERTIES
- OUTPUT_NAME "SDL2_net-static"
- )
- endif()
-endif()
-
-# Use `Compatible Interface Properties` to ensure a shared SDL2_net is built with a shared SDL2
-if(SDL2NET_BUILD_SHARED_LIBS)
- set_target_properties(SDL2_net PROPERTIES
- INTERFACE_SDL2_SHARED ${SDL2NET_BUILD_SHARED_LIBS}
- COMPATIBLE_INTERFACE_BOOL SDL2_SHARED
- )
-endif()
-
-if(SDL2NET_BUILD_SHARED_LIBS)
- sdl_target_link_options_no_undefined(SDL2_net)
-endif()
-
-if(SDL2NET_INSTALL)
- install(
- TARGETS SDL2_net
- EXPORT SDL2NetExports
- ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT devel
- LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT library
- RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT library
- )
- install(FILES
- "${CMAKE_CURRENT_SOURCE_DIR}/SDL_net.h"
- DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/SDL2" COMPONENT devel
- )
-
- if(WIN32 AND NOT MINGW)
- set(SDLNET_INSTALL_CMAKEDIR_DEFAULT "cmake")
- else()
- set(SDLNET_INSTALL_CMAKEDIR_DEFAULT "${CMAKE_INSTALL_LIBDIR}/cmake/SDL2_net")
- endif()
- set(SDLNET_INSTALL_CMAKEDIR "${SDLNET_INSTALL_CMAKEDIR_DEFAULT}" CACHE STRING "Location where to install SDL2_netConfig.cmake")
-
- configure_package_config_file(SDL2_netConfig.cmake.in SDL2_netConfig.cmake
- INSTALL_DESTINATION "${SDLNET_INSTALL_CMAKEDIR}"
- )
- write_basic_package_version_file("${PROJECT_BINARY_DIR}/SDL2_netConfigVersion.cmake"
- VERSION ${FULL_VERSION}
- COMPATIBILITY AnyNewerVersion
- )
- install(
- FILES
- "${CMAKE_CURRENT_BINARY_DIR}/SDL2_netConfig.cmake"
- "${CMAKE_CURRENT_BINARY_DIR}/SDL2_netConfigVersion.cmake"
- DESTINATION "${SDLNET_INSTALL_CMAKEDIR}"
- COMPONENT devel
- )
- install(EXPORT SDL2NetExports
- FILE SDL2_net-${sdl2_net_install_name_infix}-targets.cmake
- NAMESPACE SDL2_net::
- DESTINATION "${SDLNET_INSTALL_CMAKEDIR}"
- COMPONENT devel
- )
-
- if(SDL2NET_BUILD_SHARED_LIBS)
- set(ENABLE_SHARED_TRUE "")
- set(ENABLE_SHARED_FALSE "#")
- set(ENABLE_STATIC_TRUE "#")
- set(ENABLE_STATIC_FALSE "")
- else()
- set(ENABLE_SHARED_TRUE "#")
- set(ENABLE_SHARED_FALSE "")
- set(ENABLE_STATIC_TRUE "")
- set(ENABLE_STATIC_FALSE "#")
- endif()
-
- set(prefix "${CMAKE_INSTALL_PREFIX}")
- set(exec_prefix "\${prefix}")
- set(libdir "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
- set(includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
- set(PACKAGE "${PROJECT_NAME}")
- set(VERSION "${FULL_VERSION}")
- set(SDL_VERSION "${SDL_REQUIRED_VERSION}")
- string(JOIN " " PC_REQUIRES ${PC_REQUIRES})
- string(JOIN " " PC_LIBS ${PC_LIBS})
- string(JOIN " " INETLIB ${SDLNET_PKGCONFIG_PRIVATE_LIBS})
- configure_file("${PROJECT_SOURCE_DIR}/SDL2_net.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/SDL2_net.pc.intermediate" @ONLY)
- file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/SDL2_net-$<CONFIG>.pc" INPUT "${CMAKE_CURRENT_BINARY_DIR}/SDL2_net.pc.intermediate")
-
- set(PC_DESTDIR)
- if(CMAKE_SYSTEM_NAME MATCHES FreeBSD)
- # FreeBSD uses ${PREFIX}/libdata/pkgconfig
- set(PC_DESTDIR "libdata/pkgconfig")
- else()
- set(PC_DESTDIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
- endif()
- # Always install SDL2_net.pc: libraries might be different between config modes
- install(CODE "
- # FIXME: use file(COPY_FILE) if minimum CMake version >= 3.21
- execute_process(COMMAND \"\${CMAKE_COMMAND}\" -E copy_if_different
- \"${CMAKE_CURRENT_BINARY_DIR}/SDL2_net-$<CONFIG>.pc\"
- \"${CMAKE_CURRENT_BINARY_DIR}/SDL2_net.pc\")
- file(INSTALL DESTINATION \"\${CMAKE_INSTALL_PREFIX}/${PC_DESTDIR}\"
- TYPE FILE
- FILES \"${CMAKE_CURRENT_BINARY_DIR}/SDL2_net.pc\")" COMPONENT devel)
-
- if(SDL2NET_BUILD_SHARED_LIBS AND (APPLE OR (UNIX AND NOT ANDROID)))
- install(
- FILES
- "${PROJECT_BINARY_DIR}/libSDL2_net$<$<CONFIG:Debug>:${SDL2NET_DEBUG_POSTFIX}>$<TARGET_FILE_SUFFIX:SDL2_net>"
- DESTINATION "${CMAKE_INSTALL_LIBDIR}"
- COMPONENT devel
- )
- endif()
-
- install(FILES "LICENSE.txt"
- DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/licenses/${PROJECT_NAME}"
- COMPONENT library
- )
-endif()
-
-if(SDL2NET_SAMPLES)
- find_package(SDL2main)
-
- add_executable(showinterfaces showinterfaces.c)
- target_compile_definitions(showinterfaces PRIVATE SDL_MAIN_HANDLED)
- target_link_libraries(showinterfaces PRIVATE SDL2_net::${sdl2_net_export_name} ${sdl2_target_name})
-
- add_executable(chat chat.c chat.h)
- if(TARGET SDL2::SDL2main)
- target_link_libraries(chat PRIVATE SDL2::SDL2main)
- endif()
- target_link_libraries(chat PRIVATE SDL2_net::${sdl2_net_export_name} SDL2::SDL2test ${sdl2_target_name})
-
- add_executable(chatd chatd.c)
- target_compile_definitions(chatd PRIVATE SDL_MAIN_HANDLED)
- target_link_libraries(chatd PRIVATE SDL2_net::${sdl2_net_export_name} ${sdl2_target_name})
-endif()
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4ce286a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+# SDL_net 3.0
+
+The latest version of this library is available from GitHub:
+
+https://github.com/libsdl-org/SDL_net/releases
+
+This is a portable network library for use with SDL. It's goal is to
+simplify the use of the usual socket interfaces and use SDL infrastructure
+to handle some portability things (such as threading and reporting
+errors).
+
+It is available under the zlib license, found in the file LICENSE.txt.
+The API can be found in the file SDL_net.h and online at https://wiki.libsdl.org/SDL3_net
+This library supports most platforms that offer both SDL3 and networking.
+
+This is a work in progress!
+
+Enjoy!
+
diff --git a/README.txt b/README.txt
deleted file mode 100644
index d5e7f0a..0000000
--- a/README.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-
-SDL_net 2.0
-
-The latest version of this library is available from GitHub:
-https://github.com/libsdl-org/SDL_net/releases
-
-This is an example portable network library for use with SDL.
-It is available under the zlib license, found in the file LICENSE.txt.
-The API can be found in the file SDL_net.h and online at https://wiki.libsdl.org/SDL_net
-This library supports UNIX, Windows, MacOS Classic, MacOS X,
-BeOS and QNX.
-
-The demo program is a chat client and server.
-
-The chat client connects to the server via TCP, registering itself.
-The server sends back a list of connected clients, and keeps the
-client updated with the status of other clients.
-Every line of text from a client is sent via UDP to every other client.
-
-Note that this isn't necessarily how you would want to write a chat
-program, but it demonstrates how to use the basic features of the
-network library.
-
-Enjoy!
- -Sam Lantinga and Roy Wood
-
diff --git a/SDL_net.c b/SDL_net.c
new file mode 100644
index 0000000..4710892
--- /dev/null
+++ b/SDL_net.c
@@ -0,0 +1,1381 @@
+#include "SDL_net.h"
+
+#ifdef __WINDOWS__
+#define WIN32_LEAN_AND_MEAN 1
+#include <windows.h>
+#include <winsock.h>
+typedef SOCKET Socket;
+typedef int SockLen;
+typedef SOCKADDR_STORAGE AddressStorage;
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <poll.h>
+#include <unistd.h>
+#include <fcntl.h>
+#define INVALID_SOCKET -1
+#define SOCKET_ERROR -1
+typedef int Socket;
+typedef socklen_t SockLen;
+typedef struct sockaddr_storage AddressStorage;
+#endif
+
+const SDL_version *SDLNet_Linked_Version(void)
+{
+ static const SDL_version linked_version = {
+ SDL_NET_MAJOR_VERSION, SDL_NET_MINOR_VERSION, SDL_NET_PATCHLEVEL
+ };
+ return &linked_version;
+}
+
+struct SDLNet_Address
+{
+ char *hostname;
+ char *human_readable;
+ char *errstr;
+ SDL_AtomicInt refcount;
+ SDL_AtomicInt status; // 0==in progress, 1==resolved, 2==error
+ struct addrinfo *ainfo;
+ SDLNet_Address *resolver_next; // a linked list for the resolution job queue.
+};
+
+#define MIN_RESOLVER_THREADS 2
+#define MAX_RESOLVER_THREADS 10
+
+static SDLNet_Address *resolver_queue = NULL;
+static SDL_Thread *resolver_threads[MAX_RESOLVER_THREADS];
+static SDL_Mutex *resolver_lock = NULL;
+static SDL_Condition *resolver_condition = NULL;
+static SDL_AtomicInt resolver_shutdown;
+static SDL_AtomicInt resolver_num_threads;
+static SDL_AtomicInt resolver_num_requests;
+static SDL_AtomicInt resolver_percent_loss;
+
+// I really want an SDL_random(). :(
+static int random_seed = 0;
+static int RandomNumber(void)
+{
+ // this is POSIX.1-2001's potentially bad suggestion, but we're not exactly doing cryptography here.
+ random_seed = random_seed * 1103515245 + 12345;
+ return (int) ((unsigned int) (random_seed / 65536) % 32768);
+}
+
+// between lo and hi (inclusive; it can return lo or hi itself, too!).
+static int RandomNumberBetween(const int lo, const int hi)
+{
+ return (RandomNumber() % (hi + 1 - lo)) + lo;
+}
+
+// !!! FIXME: replace strerror.
+// !!! FIXME: replace errno.
+static int LastSocketError(void)
+{
+#ifdef __WINDOWS__
+ return WSAGetLastError();
+#else
+ return errno;
+#endif
+}
+
+static int CloseSocketHandle(Socket handle)
+{
+#ifdef __WINDOWS__
+ return closesocket(handle);
+#else
+ return close(handle);
+#endif
+}
+
+// this blocks!
+static int ResolveAddress(SDLNet_Address *addr)
+{
+ SDL_assert(addr != NULL); // we control all this, so this shouldn't happen.
+ struct addrinfo *ainfo = NULL;
+ int rc;
+
+//SDL_Log("getaddrinfo '%s'", addr->hostname);
+ rc = getaddrinfo(addr->hostname, NULL, NULL, &ainfo);
+//SDL_Log("rc=%d", rc);
+ if (rc != 0) {
+ addr->errstr = SDL_strdup((rc == EAI_SYSTEM) ? strerror(errno) : gai_strerror(rc));
+ return -1; // error
+ } else if (ainfo == NULL) {
+ addr->errstr = SDL_strdup("Unknown error (query succeeded but result was NULL!)");
+ return -1;
+ }
+
+ char buf[128];
+ rc = getnameinfo(ainfo->ai_addr, ainfo->ai_addrlen, buf, sizeof (buf), NULL, 0, NI_NUMERICHOST);
+ if (rc != 0) {
+ addr->errstr = SDL_strdup((rc == EAI_SYSTEM) ? strerror(errno) : gai_strerror(rc));
+ freeaddrinfo(ainfo);
+ return -1; // error
+ }
+
+ addr->human_readable = SDL_strdup(buf);
+ addr->ainfo = ainfo;
+ return 1; // success (zero means "still in progress").
+}
+
+static int SDLCALL ResolverThread(void *data)
+{
+ const int threadnum = (int) ((size_t) data);
+ //SDL_Log("ResolverThread #%d starting up!", threadnum);
+
+ SDL_LockMutex(resolver_lock);
+
+ while (!SDL_AtomicGet(&resolver_shutdown)) {
+ SDLNet_Address *addr = SDL_AtomicGetPtr((void **) &resolver_queue);
+ if (!addr) {
+ if (SDL_AtomicGet(&resolver_num_threads) > MIN_RESOLVER_THREADS) { // nothing pending and too many threads waiting in reserve? Quit.
+ SDL_DetachThread(resolver_threads[threadnum]); // detach ourselves so no one has to wait on us.
+ SDL_AtomicSetPtr((void **) &resolver_threads[threadnum], NULL);
+ break; // we quit. They'll spawn new threads if necessary.
+ }
+
+ // Block until there's something to do.
+ SDL_WaitCondition(resolver_condition, resolver_lock); // surrenders the lock, sleeps until alerted, then relocks.
+ continue; // check for shutdown and new work again!
+ }
+
+ SDL_AtomicSetPtr((void **) &resolver_queue, addr->resolver_next); // take this task off the list, then release the lock so others can work.
+ SDL_UnlockMutex(resolver_lock);
+
+ //SDL_Log("ResolverThread #%d got new task ('%s')", threadnum, addr->hostname);
+
+ const int simulated_loss = SDL_AtomicGet(&resolver_percent_loss);
+
+ if (simulated_loss && (RandomNumberBetween(0, 100) > simulated_loss)) {
+ // won the percent_loss lottery? Delay resolving this address between 250 and 7000 milliseconds
+ SDL_Delay(RandomNumberBetween(250, 2000 + (50 * simulated_loss)));
+ }
+
+ int outcome;
+ if (!simulated_loss || (RandomNumberBetween(0, 100) > simulated_loss)) {
+ outcome = ResolveAddress(addr);
+ } else {
+ outcome = -1;
+ addr->errstr = SDL_strdup("simulated failure");
+ }
+
+ SDL_AtomicSet(&addr->status, outcome);
+ //SDL_Log("ResolverThread #%d finished current task (%s, '%s' => '%s')", threadnum, (outcome < 0) ? "failure" : "success", addr->hostname, (outcome < 0) ? addr->errstr : addr->human_readable);
+
+ SDLNet_UnrefAddress(addr); // we're done with it, but others might still own it.
+
+ SDL_AtomicAdd(&resolver_num_requests, -1);
+
+ // okay, we're done with this task, grab the lock so we can see what's next.
+ SDL_LockMutex(resolver_lock);
+ SDL_BroadcastCondition(resolver_condition); // wake up anything waiting on results, and also give all resolver threads a chance to see if they are still needed.
+ }
+
+ SDL_AtomicAdd(&resolver_num_threads, -1);
+ SDL_UnlockMutex(resolver_lock); // we're quitting, let go of the lock.
+
+ //SDL_Log("ResolverThread #%d ending!", threadnum);
+ return 0;
+}
+
+static SDL_Thread *SpinResolverThread(const int num)
+{
+ char name[16];
+ SDL_snprintf(name, sizeof (name), "SDLNetRslv%d", num);
+ SDL_assert(resolver_threads[num] == NULL);
+ SDL_AtomicAdd(&resolver_num_threads, 1);
+ resolver_threads[num] = SDL_CreateThreadWithStackSize(ResolverThread, name, 64 * 1024, (void *) ((size_t) num));
+ if (!resolver_threads[num]) {
+ SDL_AtomicAdd(&resolver_num_threads, -1);
+ }
+ return resolver_threads[num];
+}
+
+static void DestroyAddress(SDLNet_Address *addr)
+{
+ if (addr) {
+ if (addr->ainfo) {
+ freeaddrinfo(addr->ainfo);
+ }
+ SDL_free(addr->hostname);
+ SDL_free(addr->human_readable);
+ SDL_free(addr->errstr);
+ SDL_free(addr);
+ }
+}
+
+int SDLNet_Init(void)
+{
+ #ifdef __WINDOWS__
+ WSADATA data;
+ if (WSAStartup(MAKEWORD(1, 1), &data) != 0) {
+ return SDL_SetError("WSAStartup() failed");
+ }
+ #else
+ signal(SIGPIPE, SIG_IGN);
+ #endif
+
+ SDL_zeroa(resolver_threads);
+ SDL_AtomicSet(&resolver_shutdown, 0);
+ SDL_AtomicSet(&resolver_num_threads, 0);
+ SDL_AtomicSet(&resolver_num_requests, 0);
+ SDL_AtomicSet(&resolver_percent_loss, 0);
+ resolver_queue = NULL;
+
+ resolver_lock = SDL_CreateMutex();
+ if (!resolver_lock) {
+ goto failed;
+ }
+
+ resolver_condition = SDL_CreateCondition();
+ if (!resolver_condition) {
+ goto failed;
+ }
+
+ for (int i = 0; i < MIN_RESOLVER_THREADS; i++) {
+ if (!SpinResolverThread(i)) {
+ goto failed;
+ }
+ }
+
+ random_seed = (int) ((unsigned int) (SDL_GetPerformanceCounter() & 0xFFFFFFFF));
+
+ return 0; // good to go.
+
+failed:
+ char *origerrstr = SDL_strdup(SDL_GetError());
+
+ SDLNet_Quit();
+
+ if (origerrstr) {
+ SDL_SetError("%s", origerrstr);
+ SDL_free(origerrstr);
+ }
+
+ return -1;
+}
+
+void SDLNet_Quit(void)
+{
+ if (resolver_lock && resolver_condition) {
+ SDL_LockMutex(resolver_lock);
+ SDL_AtomicSet(&resolver_shutdown, 1);
+ for (int i = 0; i < SDL_arraysize(resolver_threads); i++) {
+ if (resolver_threads[i]) {
+ SDL_BroadcastCondition(resolver_condition);
+ SDL_UnlockMutex(resolver_lock);
+ SDL_WaitThread(resolver_threads[i], NULL);
+ SDL_LockMutex(resolver_lock);
+ resolver_threads[i] = NULL;
+ }
+ }
+ SDL_UnlockMutex(resolver_lock);
+ }
+
+ SDL_AtomicSet(&resolver_shutdown, 0);
+ SDL_AtomicSet(&resolver_num_threads, 0);
+ SDL_AtomicSet(&resolver_num_requests, 0);
+ SDL_AtomicSet(&resolver_percent_loss, 0);
+
+ if (resolver_condition) {
+ SDL_DestroyCondition(resolver_condition);
+ resolver_condition = NULL;
+ }
+
+ if (resolver_lock) {
+ SDL_DestroyMutex(resolver_lock);
+ resolver_lock = NULL;
+ }
+
+ resolver_queue = NULL;
+
+ #ifdef __WINDOWS__
+ WSACleanup();
+ #endif
+}
+
+SDLNet_Address *SDLNet_ResolveHostname(const char *host)
+{
+ SDLNet_Address *addr = SDL_calloc(1, sizeof (SDLNet_Address));
+ if (!addr) {
+ SDL_OutOfMemory();
+ return NULL;
+ }
+
+ addr->hostname = SDL_strdup(host);
+ if (!addr->hostname) {
+ SDL_free(addr);
+ SDL_OutOfMemory();
+ return NULL;
+ }
+
+ SDL_AtomicSet(&addr->refcount, 2); // one for creation, one for the resolver thread to unref when done.
+
+ SDL_LockMutex(resolver_lock);
+
+ // !!! FIXME: this should append to the list, not prepend; as is, new requests will make existing pending requests take longer to start processing.
+ SDL_AtomicSetPtr((void **) &addr->resolver_next, SDL_AtomicGetPtr((void **) &resolver_queue));
+ SDL_AtomicSetPtr((void **) &resolver_queue, addr);
+
+ const int num_threads = SDL_AtomicGet(&resolver_num_threads);
+ const int num_requests = SDL_AtomicAdd(&resolver_num_requests, 1) + 1;
+ //SDL_Log("num_threads=%d, num_requests=%d", num_threads, num_requests);
+ if ((num_requests >= num_threads) && (num_threads < MAX_RESOLVER_THREADS)) { // all threads are busy? Maybe spawn a new one.
+ // if this didn't actually spin one up, it is what it is...the existing threads will eventually get there.
+ for (int i = 0; i < SDL_arraysize(resolver_threads); i++) {
+ if (!resolver_threads[i]) {
+ SpinResolverThread(i);
+ break;
+ }
+ }
+ }
+
+ SDL_SignalCondition(resolver_condition);
+ SDL_UnlockMutex(resolver_lock);
+
+ return addr;
+}
+
+int SDLNet_WaitForResolution(SDLNet_Address *addr)
+{
+ if (!addr) {
+ return SDL_InvalidParamError("address"); // obviously nothing to wait for.
+ }
+
+ // we _could_ use a different lock for this, but this is Good Enough.
+ SDL_LockMutex(resolver_lock);
+ while (SDL_AtomicGet(&addr->status) == 0) {
+ SDL_WaitCondition(resolver_condition, resolver_lock);
+ }
+ SDL_UnlockMutex(resolver_lock);
+
+ return SDL_AtomicGet(&addr->status);
+}
+
+int SDLNet_GetAddressStatus(SDLNet_Address *addr)
+{
+ if (!addr) {
+ return SDL_InvalidParamError("address");
+ }
+ const int retval = SDL_AtomicGet(&addr->status);
+ if (retval == -1) {
+ SDL_SetError("%s", (const char *) SDL_AtomicGetPtr((void **) &addr->errstr));
+ }
+ return retval;
+}
+
+const char *SDLNet_GetAddressString(SDLNet_Address *addr)
+{
+ return addr ? (const char *) SDL_AtomicGetPtr((void **) &addr->human_readable) : NULL;
+}
+
+void SDLNet_RefAddress(SDLNet_Address *addr)
+{
+ if (addr) {
+ SDL_AtomicIncRef(&addr->refcount);
+ }
+}
+
+void SDLNet_UnrefAddress(SDLNet_Address *addr)
+{
+ if (addr && SDL_AtomicDecRef(&addr->refcount)) {
+ DestroyAddress(addr);
+ }
+}
+
+void SDLNet_SimulateAddressResolutionLoss(int percent_loss)
+{
+ percent_loss = SDL_min(100, percent_loss);
+ percent_loss = SDL_max(0, percent_loss);
+ SDL_AtomicSet(&resolver_percent_loss, percent_loss);
+}
+
+SDLNet_Address **SDLNet_GetLocalAddresses(int *num_addresses)
+{
+ // !!! FIXME: write me!
+ return NULL;
+}
+
+void SDLNet_FreeLocalAddresses(SDLNet_Address **addresses)
+{
+ if (addresses) {
+ for (int i = 0; addresses[i] != NULL; i++) {
+ SDLNet_UnrefAddress(addresses[i]);
+ }
+ SDL_free(addresses);
+ }
+}
+
+static struct addrinfo *MakeAddrInfoWithPort(const SDLNet_Address *addr, const int socktype, const Uint16 port)
+{
+ const struct addrinfo *ainfo = addr ? addr->ainfo : NULL;
+ SDL_assert(!addr || ainfo);
+
+ // we need to set up a sockaddr with the port in it for connect(), etc, which is kind of a pain, since we
+ // want to keep things generic and also not set up a port at resolve time.
+ struct addrinfo hints;
+ SDL_zero(hints);
+ hints.ai_family = ainfo ? ainfo->ai_family : AF_UNSPEC;
+ hints.ai_socktype = socktype;
+ hints.ai_protocol = ainfo ? ainfo->ai_protocol : 0;
+ hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | (!ainfo ? AI_PASSIVE : 0);
+
+ char service[16];
+ SDL_snprintf(service, sizeof (service), "%d", (int) port);
+
+ struct addrinfo *addrwithport = NULL;
+ int rc = getaddrinfo(addr ? addr->human_readable : NULL, service, &hints, &addrwithport);
+ if (rc != 0) {
+ SDL_SetError("Failed to prepare address with port: %s", (rc == EAI_SYSTEM) ? strerror(errno) : gai_strerror(rc));
+ return NULL;
+ }
+
+ return addrwithport;
+}
+
+
+struct SDLNet_StreamSocket
+{
+ SDLNet_Address *addr;
+ Uint16 port;
+ Socket handle;
+ int status;
+ Uint8 *pending_output_buffer;
+ int pending_output_len;
+ int pending_output_allocation;
+ int percent_loss;
+ Uint64 simulated_failure_until;
+};
+
+static int MakeSocketNonblocking(Socket handle)
+{
+ #ifdef __WINDOWS__
+ DWORD one = 1;
+ return ioctlsocket(handle, FIONBIO, &one);
+ #else
+ return fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
+ #endif
+}
+
+static SDL_bool WouldBlock(const int err)
+{
+ #ifdef __WINDOWS__
+ return (err == WSAEWOULDBLOCK) ? SDL_TRUE : SDL_FALSE;
+ #else
+ return ((err == EWOULDBLOCK) || (err == EAGAIN) || (err == EINPROGRESS)) ? SDL_TRUE : SDL_FALSE;
+ #endif
+}
+
+SDLNet_StreamSocket *SDLNet_CreateClient(SDLNet_Address *addr, Uint16 port)
+{
+ if (addr == NULL) {
+ SDL_InvalidParamError("address");
+ return NULL;
+ } else if (SDL_AtomicGet(&addr->status) != 1) {
+ SDL_SetError("Address is not resolved");
+ return NULL;
+ }
+
+ SDLNet_StreamSocket *sock = (SDLNet_StreamSocket *) SDL_calloc(1, sizeof (SDLNet_StreamSocket));
+ if (!sock) {
+ SDL_OutOfMemory();
+ return NULL;
+ }
+
+ sock->addr = addr;
+ sock->port = port;
+
+ // we need to set up a sockaddr with the port in it for connect(), which is kind of a pain, since we
+ // want to keep things generic and also not set up a port at resolve time.
+ struct addrinfo *addrwithport = MakeAddrInfoWithPort(addr, SOCK_STREAM, port);
+ if (!addrwithport) {
+ SDL_free(sock);
+ return NULL;
+ }
+
+ sock->handle = socket(addrwithport->ai_family, addrwithport->ai_socktype, addrwithport->ai_protocol);
+ if (sock->handle == INVALID_SOCKET) {
+ SDL_SetError("Failed to create socket: %s", strerror(errno));
+ freeaddrinfo(addrwithport);
+ SDL_free(sock);
+ return NULL;
+ }
+
+ if (MakeSocketNonblocking(sock->handle) < 0) {
+ CloseSocketHandle(sock->handle);
+ freeaddrinfo(addrwithport);
+ SDL_free(sock);
+ SDL_SetError("Failed to make new socket non-blocking");
+ return NULL;
+ }
+
+ const int rc = connect(sock->handle, addrwithport->ai_addr, addrwithport->ai_addrlen);
+
+ freeaddrinfo(addrwithport);
+
+ if (rc == SOCKET_ERROR) {
+ const int err = LastSocketError();
+ if (!WouldBlock(err)) {
+ SDL_SetError("Connection failed at startup: %s", strerror(err));
+ CloseSocketHandle(sock->handle);
+ SDL_free(sock);
+ return NULL;
+ }
+ }
+
+ SDLNet_RefAddress(addr);
+ return sock;
+}
+
+static int CheckClientConnection(SDLNet_StreamSocket *sock, int timeoutms)
+{
+ if (!sock) {
+ return SDL_InvalidParamError("sock");
+ } else if (sock->status == 0) { // still pending?
+ /*!!! FIXME: add this later? if (sock->simulated_failure_ticks) {
+ if (SDL_GetTicks() >= sock->simulated_failure_ticks) {
+ sock->status = SDL_SetError("simulated failure");
+ }
+ } else*/ {
+ struct pollfd pfd;
+ SDL_zero(pfd);
+ pfd.fd = sock->handle;
+ pfd.events = POLLOUT;
+ if (poll(&pfd, 1, timeoutms) == SOCKET_ERROR) {
+ sock->status = SDL_SetError("Failed to poll socket: %s", strerror(errno));
+ } else if ((pfd.revents & (POLLERR|POLLHUP|POLLNVAL)) != 0) {
+ int err = 0;
+ SockLen errsize = sizeof (err);
+ getsockopt(sock->handle, SOL_SOCKET, SO_ERROR, &err, &errsize);
+ sock->status = SDL_SetError("Socket failed to connect: %s", strerror(err));
+ } else if (pfd.revents & POLLOUT) {
+ sock->status = 1; // good to go!
+ }
+ }
+ }
+ return sock->status;
+}
+
+int SDLNet_WaitForConnection(SDLNet_StreamSocket *sock)
+{
+ return CheckClientConnection(sock, -1); // infinite wait
+}
+
+int SDLNet_GetConnectionStatus(SDLNet_StreamSocket *sock)
+{
+ return CheckClientConnection(sock, 0);
+}
+
+
+struct SDLNet_Server
+{
+ SDLNet_Address *addr; // bound to this address (NULL for any).
+ Uint16 port;
+ Socket handle;
+};
+
+SDLNet_Server *SDLNet_CreateServer(SDLNet_Address *addr, Uint16 port)
+{
+ if (addr && SDL_AtomicGet(&addr->status) != 1) {
+ SDL_SetError("Address is not resolved"); // strictly speaking, this should be a local interface, but a resolved address can fail later.
+ return NULL;
+
(Patch may be truncated, please check the link at the top of this post.)