# How SDL2-Net Integration Enables Multiplayer in Lighthouse: A Deep Dive into the Network Architecture

> Explore how Lighthouse integrates SDL2-net for threaded TCP networking. Discover how this enables multiplayer features and can be conditionally compiled to exclude networking when not needed.

- Repository: [Harbour Masters/Lighthouse](https://github.com/HarbourMasters/Lighthouse)
- Tags: deep-dive
- Published: 2026-08-04

---

**Lighthouse uses SDL2-net to implement a lightweight, threaded TCP networking layer that is conditionally compiled via the `USE_NETWORKING` macro, allowing multiplayer features to be fully excluded from builds when not required.**

The HarbourMasters/Lighthouse project—a reimplementation of the classic game engine—integrates SDL2-net to power its multiplayer capabilities. This article examines how the engine initializes SDL2-net, manages TCP sockets through a dedicated `Network` class, and handles packet framing with JSON messaging. All networking code resides in `src/port/Network/` and is guarded by preprocessor directives to maintain binary flexibility.

## Engine Initialization and Shutdown

The engine lifecycle for SDL2-net is managed in [`src/port/Engine.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/Engine.cpp). During startup, the engine calls `SDLNet_Init()` to allocate the networking subsystem. On shutdown, `SDLNet_Quit()` ensures proper resource cleanup.

This initialization happens only when `USE_NETWORKING` is defined at compile time. Without this flag, the entire networking stack is excluded from the build, producing smaller binaries for single-player-only deployments.

## The Network Class: SDL2-Net Wrapper

The core abstraction layer lives in [`src/port/Network/Network.h`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/Network/Network.h) and [`src/port/Network/Network.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/Network/Network.cpp). This class wraps SDL2-net's TCP socket API into a manageable interface with the following responsibilities:

- **Host resolution**: Uses `SDLNet_ResolveHost()` to convert server addresses to network-ready structures
- **Socket creation**: Opens TCP connections via `SDLNet_TCP_Open()`
- **Thread management**: Spawns and controls a dedicated receive thread
- **State tracking**: Maintains `isEnabled` and `isConnected` flags for lifecycle control

### Connection Establishment

When `Enable(host, port)` is called, the Network class resolves the server address and opens a TCP socket. The method signature follows this pattern:

```cpp
void Enable(const char* host, int port);

```

This spawns the receive thread that runs continuously while both `isEnabled` and `isConnected` remain true.

## Threaded Receiving Without Blocking

The `Network::ReceiveFromServer()` method implements non-blocking I/O to prevent frame drops in the main game loop. The implementation uses SDL2-net's socket sets:

```cpp
// Pseudo-structure of the receive loop
SDLNet_SocketSet set = SDLNet_AllocSocketSet(1);
SDLNet_TCP_AddSocket(set, tcpSocket);

while (isEnabled && isConnected) {
    if (SDLNet_CheckSockets(set, timeout) > 0) {
        // Data available - read without blocking main thread
        int received = SDLNet_TCP_Recv(tcpSocket, buffer, bufferSize);
        // Process into receivedData buffer...
    }
}

```

**Key SDL2-net functions used:**
- `SDLNet_AllocSocketSet()` — Creates a set for polling multiple sockets
- `SDLNet_CheckSockets()` — Non-blocking check for readable sockets
- `SDLNet_TCP_Recv()` — Retrieves incoming byte data

## Packet Framing for Message Boundaries

Raw TCP streams lack inherent message boundaries. Lighthouse solves this with **null-terminated packet framing**:

- Outgoing packets: All messages terminate with `'\0'`
- Incoming processing: Bytes accumulate in `receivedData`, split on the delimiter, and complete payloads forward to `OnIncomingJson`

This simple framing strategy guarantees that JSON objects arrive intact regardless of TCP segmentation or buffering behavior.

## Virtual Callbacks for Game-Specific Logic

The `Network` class defines five virtual methods that subclasses override to implement gameplay logic:

| Callback | Purpose |
|----------|---------|
| `OnIncomingData` | Raw byte array handling (rarely used directly) |
| `OnIncomingJson` | Parsed JSON message dispatch |
| `OnConnected` | Connection established handshake |
| `OnDisconnected` | Cleanup and reconnection logic |
| `ProcessOutgoingPackets` | Custom send-queue processing |

Subclasses like game-specific network handlers implement protocol logic by overriding these methods. The `src/port/Network/Anchor/` directory contains packet definitions—[`PlayerUpdate.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/PlayerUpdate.cpp), [`FightUpdate.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/FightUpdate.cpp), and others—that serialize game state into JSON for transmission.

### Example: Handling Remote Player Movement

```cpp
class GameNetwork : public Network {
public:
    void OnIncomingJson(nlohmann::json payload) override {
        if (payload["type"] == "player_move") {
            ApplyRemotePlayerMove(payload);
        }
    }
};

```

## Sending Data to Remote Servers

The Network class provides two methods for outbound communication:

**`SendDataToRemote(const std::string& data)`**
- Direct wrapper around `SDLNet_TCP_Send()`
- Transmits raw string data including null terminator

**`SendJsonToRemote(const nlohmann::json& payload)`**
- Serializes JSON via `payload.dump()`
- Forwards to `SendDataToRemote` with framing applied

```cpp
// Example: Transmitting player state update
nlohmann::json update;
update["type"] = "player_move";
update["playerId"] = 3;
update["position"] = { "x", 12.5, "y", 8.0 };

net->SendJsonToRemote(update);

```

## Conditional Compilation with USE_NETWORKING

Every SDL2-net call is wrapped in `#ifdef USE_NETWORKING` blocks. This design:

- Eliminates SDL2-net dependency for single-player builds
- Reduces binary size when networking is unused
- Simplizes porting to platforms without network support

The UI entry point in [`src/port/UI/LighthouseMenuNetwork.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/UI/LighthouseMenuNetwork.cpp) provides the player-facing interface, but all underlying calls compile out cleanly when the flag is absent.

## Summary

- SDL2-net integration in Lighthouse centers on a `Network` class in `src/port/Network/` that wraps TCP socket operations
- Non-blocking receive threads use `SDLNet_CheckSockets()` to avoid main loop stalls
- Null-terminated framing ensures reliable JSON message boundaries over TCP streams
- Virtual callbacks decouple network transport from game-specific protocol handlers
- The `USE_NETWORKING` macro enables complete compile-time exclusion of multiplayer code

## Frequently Asked Questions

### What SDL2-net functions does Lighthouse use for multiplayer?

Lighthouse uses `SDLNet_Init()`, `SDLNet_Quit()`, `SDLNet_ResolveHost()`, `SDLNet_TCP_Open()`, `SDLNet_AllocSocketSet()`, `SDLNet_CheckSockets()`, `SDLNet_TCP_Recv()`, and `SDLNet_TCP_Send()` according to the source in [`src/port/Network/Network.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/Network/Network.cpp).

### How does Lighthouse prevent networking from blocking the game?

The engine uses a dedicated receive thread with `SDLNet_CheckSockets()` for non-blocking polling. This thread runs independently of the main game loop, checking for available data without halting rendering or input processing.

### Can Lighthouse be built without multiplayer support?

Yes. All SDL2-net code is guarded by `#ifdef USE_NETWORKING`. Building without this flag excludes the entire networking subsystem, producing smaller binaries with no SDL2-net dependency.

### Where is the multiplayer protocol defined in the source?

Game-specific message types are defined in `src/port/Network/Anchor/`, with files like [`PlayerUpdate.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/PlayerUpdate.cpp) and [`FightUpdate.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/FightUpdate.cpp) handling serialization. The base framing and transport logic resides in [`src/port/Network/Network.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/Network/Network.cpp).