Integrating SDL3_net Library with CMake on Windows & Ubuntu (Client, SDL_Renderer) Интеграция C++-библиотеки SDL3_net с CMake под Windows и Ubuntu (Клиент, SDL_Renderer)

A step-by-step guide to configuring and integrating the SDL3_net satellite library with SDL_Renderer to build a graphic client in your CMake projects on Windows and Ubuntu. Пошаговое руководство по настройке и интеграции вспомогательной библиотеки SDL3_net с SDL_Renderer для создания графического клиента в проектах CMake под Windows и Ubuntu.

Step 1: Create Project Directory Шаг 1: Создание директории проекта

Create a workspace folder and a new project directory for your client project: Создайте рабочую папку и новую директорию проекта для вашего клиента:

Windows (Command Prompt / PowerShell)

mkdir C:\projects\sdl3-net-client-test
cd C:\projects\sdl3-net-client-test

Ubuntu (Terminal)

mkdir -p ~/projects/sdl3-net-client-test
cd ~/projects/sdl3-net-client-test

Step 2: Create Source Files (main.cpp & CMakeLists.txt) Шаг 2: Создание исходных файлов (main.cpp и CMakeLists.txt)

You can create these files using Notepad / Sublime Text on Windows, or nano in Ubuntu terminal: Вы можете создать эти файлы через Блокнот / Sublime Text в Windows или с помощью nano в терминале Ubuntu:

Windows (Terminal / Notepad)

notepad main.cpp
notepad CMakeLists.txt

Ubuntu (Terminal / Nano)

nano main.cpp
nano CMakeLists.txt
Quick Nano Commands (Ubuntu):Быстрые команды Nano (Ubuntu): Paste code with Right Mouse Button (RMB) → Press Ctrl + O and Enter to save → Press Ctrl + X to exit. Вставьте код правой кнопкой мыши (ПКМ) → Нажмите Ctrl + O и Enter для сохранения → Нажмите Ctrl + X для выхода.

1A. File Contents for main.cpp (Classic Loop) 1A. Содержимое файла main.cpp (Классический цикл)

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_net/SDL_net.h>

int main(int argc, char* argv[]) {
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("SDL Initialization failed: %s", SDL_GetError());
        return -1;
    }

    if (!NET_Init()) {
        SDL_Log("NET_Init failed: %s", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    SDL_Window* window = SDL_CreateWindow("SDL3_net Client (Classic Loop)", 800, 600, SDL_WINDOW_RESIZABLE);
    if (!window) {
        SDL_Log("CreateWindow failed: %s", SDL_GetError());
        NET_Quit();
        SDL_Quit();
        return -1;
    }

    SDL_Renderer* renderer = SDL_CreateRenderer(window, NULL);
    if (!renderer) {
        SDL_Log("CreateRenderer failed: %s", SDL_GetError());
        SDL_DestroyWindow(window);
        NET_Quit();
        SDL_Quit();
        return -1;
    }

    const char* host = (argc > 1) ? argv[1] : "127.0.0.1";
    const Uint16 port = 12345;

    NET_Address* address = NET_ResolveHostname(host);
    NET_StreamSocket* socket = nullptr;

    if (address) {
        if (NET_WaitUntilResolved(address, 5000) >= 0) {
            socket = NET_CreateClient(address, port, 0);
        } else {
            SDL_Log("Failed to resolve address %s: %s", host, SDL_GetError());
        }
        NET_UnrefAddress(address);
    }

    if (!socket) {
        SDL_Log("Failed to create client socket: %s", SDL_GetError());
    } else {
        SDL_Log("Connecting to server %s:%u...", host, port);
    }

    bool running = true;
    bool connected = false;
    bool message_sent = false;
    SDL_Event event;

    while (running) {
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_EVENT_QUIT) {
                running = false;
            }
        }

        if (socket) {
            if (!connected) {
                int result = NET_GetConnectionStatus(socket);
                if (result == 1) {
                    connected = true;
                    SDL_Log("Connected to server successfully!");
                } else if (result < 0) {
                    SDL_Log("Connection failed: %s", SDL_GetError());
                    NET_DestroyStreamSocket(socket);
                    socket = nullptr;
                }
            }

            if (connected) {
                if (!message_sent) {
                    const char* msg = "Hello from SDL3_net Client!\n";
                    NET_WriteToStreamSocket(socket, msg, (int)SDL_strlen(msg));
                    message_sent = true;
                }

                char buffer[512];
                int bytes_read = NET_ReadFromStreamSocket(socket, buffer, sizeof(buffer) - 1);
                if (bytes_read > 0) {
                    buffer[bytes_read] = '\0';
                    SDL_Log("Received response: %s", buffer);
                } else if (bytes_read < 0) {
                    SDL_Log("Server disconnected or socket error.");
                    NET_DestroyStreamSocket(socket);
                    socket = nullptr;
                    connected = false;
                }
            }
        }

        if (connected) {
            SDL_SetRenderDrawColor(renderer, 0, 128, 0, 255);
        } else {
            SDL_SetRenderDrawColor(renderer, 128, 0, 0, 255);
        }
        SDL_RenderClear(renderer);
        SDL_RenderPresent(renderer);

        SDL_Delay(16);
    }

    if (socket) {
        NET_DestroyStreamSocket(socket);
    }
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    NET_Quit();
    SDL_Quit();

    return 0;
}

1B. File Contents for main.cpp (SDL3 Callbacks API) 1B. Содержимое файла main.cpp (SDL3 Callbacks API)

#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_net/SDL_net.h>

struct AppState {
    SDL_Window* window = nullptr;
    SDL_Renderer* renderer = nullptr;
    NET_StreamSocket* socket = nullptr;
    bool connected = false;
    bool message_sent = false;
};

SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("SDL Initialization failed: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!NET_Init()) {
        SDL_Log("NET_Init failed: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    AppState* state = new AppState();
    *appstate = state;

    state->window = SDL_CreateWindow("SDL3_net Client (Callbacks API)", 800, 600, SDL_WINDOW_RESIZABLE);
    if (!state->window) {
        SDL_Log("CreateWindow failed: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    state->renderer = SDL_CreateRenderer(state->window, NULL);
    if (!state->renderer) {
        SDL_Log("CreateRenderer failed: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    const char* host = (argc > 1) ? argv[1] : "127.0.0.1";
    const Uint16 port = 12345;

    NET_Address* address = NET_ResolveHostname(host);

    if (address) {
        if (NET_WaitUntilResolved(address, 5000) >= 0) {
            state->socket = NET_CreateClient(address, port, 0);
        } else {
            SDL_Log("Failed to resolve address %s: %s", host, SDL_GetError());
        }
        NET_UnrefAddress(address);
    }

    if (!state->socket) {
        SDL_Log("Failed to create client socket: %s", SDL_GetError());
    } else {
        SDL_Log("Connecting to server %s:%u...", host, port);
    }

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
    if (event->type == SDL_EVENT_QUIT) {
        return SDL_APP_SUCCESS;
    }
    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void* appstate) {
    auto* state = static_cast<AppState*>(appstate);

    if (state->socket) {
        if (!state->connected) {
            int result = NET_GetConnectionStatus(state->socket);
            if (result == 1) {
                state->connected = true;
                SDL_Log("Connected to server successfully!");
            } else if (result < 0) {
                SDL_Log("Connection failed: %s", SDL_GetError());
                NET_DestroyStreamSocket(state->socket);
                state->socket = nullptr;
            }
        }

        if (state->connected) {
            if (!state->message_sent) {
                const char* msg = "Hello from SDL3_net Client!\n";
                NET_WriteToStreamSocket(state->socket, msg, (int)SDL_strlen(msg));
                state->message_sent = true;
            }

            char buffer[512];
            int bytes_read = NET_ReadFromStreamSocket(state->socket, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                SDL_Log("Received response: %s", buffer);
            } else if (bytes_read < 0) {
                SDL_Log("Server disconnected or socket error.");
                NET_DestroyStreamSocket(state->socket);
                state->socket = nullptr;
                state->connected = false;
            }
        }
    }

    if (state->connected) {
        SDL_SetRenderDrawColor(state->renderer, 0, 128, 0, 255);
    } else {
        SDL_SetRenderDrawColor(state->renderer, 128, 0, 0, 255);
    }
    SDL_RenderClear(state->renderer);
    SDL_RenderPresent(state->renderer);

    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void* appstate, SDL_AppResult result) {
    if (appstate) {
        auto* state = static_cast<AppState*>(appstate);

        if (state->socket) {
            NET_DestroyStreamSocket(state->socket);
        }
        if (state->renderer) {
            SDL_DestroyRenderer(state->renderer);
        }
        if (state->window) {
            SDL_DestroyWindow(state->window);
        }
        delete state;
    }
    NET_Quit();
    SDL_Quit();
}
SDL3 Note: Classic Loop vs. Callbacks API Заметка SDL3: Классический цикл vs. Callbacks API

Option 1A uses the traditional main() loop. Option 1B uses the new SDL3 recommended Callbacks API (via SDL_MAIN_USE_CALLBACKS). Note that when using SDL_MAIN_USE_CALLBACKS, headers like <SDL3_net/SDL_net.h> should be included after setting up application definitions. Вариант 1A использует традиционный цикл main(). Вариант 1B использует новый рекомендуемый в SDL3 Callbacks API (через SDL_MAIN_USE_CALLBACKS). Обратите внимание: при использовании SDL_MAIN_USE_CALLBACKS подключение заголовков, таких как <SDL3_net/SDL_net.h>, следует выполнять после установки мета-макросов.

2. File Contents for CMakeLists.txt 2. Содержимое файла CMakeLists.txt

cmake_minimum_required(VERSION 3.21)

# Default to Debug if CMAKE_BUILD_TYPE is not set via command line
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Debug")
endif()

project(sdl3_net_client_test CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# -----------------------------------------------------------------------------
# Locate SDL3 and SDL3_net Packages via Custom Paths
# -----------------------------------------------------------------------------
if(WIN32)
    set(SDL3_DIR "C:/libs/sdl3-3.4.16-mingw/lib/cmake/SDL3")
    set(SDL3_net_DIR "C:/libs/sdl3-net-3.2.0-mingw/lib/cmake/SDL3_net")
else()
    set(SDL3_DIR "$ENV{HOME}/libs/sdl3-3.4.16-ubuntu/lib/cmake/SDL3")
    set(SDL3_net_DIR "$ENV{HOME}/libs/sdl3-net-3.2.0-ubuntu/lib/cmake/SDL3_net")
endif()

find_package(SDL3 REQUIRED CONFIG)
find_package(SDL3_net REQUIRED CONFIG)

# -----------------------------------------------------------------------------
# Target Definition
# -----------------------------------------------------------------------------
set(PROJECT_SOURCES
    main.cpp
)

add_executable(app ${PROJECT_SOURCES})

# Link SDL3 and SDL3_net statically (automatically brings in include dirs and
# socket system libraries like ws2_32 / iphlpapi on Windows)
target_link_libraries(app PRIVATE SDL3::SDL3-static SDL3_net::SDL3_net-static)

# -----------------------------------------------------------------------------
# Platform-Specific Compiler & Linker Flags
# -----------------------------------------------------------------------------
if(WIN32)
    target_compile_options(app PRIVATE -Os)
    target_link_options(app PRIVATE -static -s -static-libgcc)

    if(CMAKE_BUILD_TYPE STREQUAL "Debug")
        target_link_options(app PRIVATE -mconsole)
    else()
        target_link_options(app PRIVATE -mwindows)
    endif()
endif()

Step 3: Create Build & Execution Scripts Шаг 3: Создание скриптов сборки и запуска

Windows (MinGW / Batch Scripts)

config-exe.bat

cmake -G Ninja -S . -B dist/win

build-exe.bat

cmake --build dist/win

run-exe.bat

@echo off
.\dist\win\app.exe %1

Run the scripts sequentially in Command Prompt or PowerShell (pass your server IP address as an argument, or omit it to default to localhost): Последовательно запустите скрипты в командной строке или PowerShell (передайте IP-адрес сервера в качестве аргумента или опустите его для использования localhost по умолчанию):

config-exe.bat
build-exe.bat
run-exe.bat 192.0.2.1

Ubuntu (Bash Scripts)

config-bin.sh

#!/usr/bin/env bash
set -e
cmake -G Ninja -S . -B dist/linux

build-bin.sh

#!/usr/bin/env bash
set -e
cmake --build dist/linux

run-bin.sh

#!/usr/bin/env bash
set -e
./dist/linux/app "$1"

Grant execute permissions on Ubuntu before running: Предоставьте права на выполнение в Ubuntu перед запуском:

chmod +x config-bin.sh build-bin.sh run-bin.sh
./config-bin.sh
./build-bin.sh
./run-bin.sh 192.0.2.1

Step 4: Expected Output Шаг 4: Ожидаемый результат

Upon running the client, a window with a red background will appear while attempting to connect to the server: После запуска клиента появится окно с красным фоном во время попытки подключения к серверу:

INFO: Connecting to server 192.0.2.1:12345...

When successfully connected, the window background will turn green and the console will log: При успешном подключении фон окна станет зеленым, а в консоли отобразится:

INFO: Connected to server successfully!
INFO: Received response: Hello from SDL3_net Server!

Support My Work Поддержать проект

If these tutorials helped you, consider buying me a coffee! Если эти туториалы вам помогли, вы можете поддержать автора.

Sberbank (Russia only) Сбербанк (только для РФ)

Sberbank SBP QR Code

Direct transfer via phone number (Russia only) Перевод по номеру телефона (только для РФ)

+7 (917) 212-29-59

USDT TRC20

USDT TRC20 QR Code

Support via Cryptocurrency Поддержка криптовалютой

TMtY1YifNf6FKvgeFmqKGQR4NStKr3csGp