Skip to main content

Overview

Atlas Engine provides lightweight TCP networking through the NetworkPipe class. While primarily designed for diagnostic tools like Tracer, it can be used for general-purpose TCP communication including multiplayer game networking.

NetworkPipe Architecture

The NetworkPipe implements a TCP client with:
  • Background connection management
  • Automatic reconnection
  • Asynchronous message receiving
  • Thread-safe message sending
  • Callback-based message handling
class NetworkPipe {
private:
    int port = 0;
    std::string serverAddress = "127.0.0.1";
    std::atomic<int> clientSocket{-1};
    std::thread recvThread;
    bool running = false;
    std::vector<std::string> messages;
    std::mutex messagesMutex;
    PipeCallback dispatcher;
};
Source: pipe.h:54-67

Creating a Connection

Basic Setup

#include <atlas/network/pipe.h>

// Create pipe instance
NetworkPipe pipe;

// Configure connection
pipe.setPort(5123);

// Register message handler
pipe.onReceive([](const std::string& msg) {
    std::cout << "Received: " << msg << std::endl;
});

// Start connection
pipe.start();

Callback-Based Receiving

Register a callback to handle incoming messages:
pipe.onReceive([](const std::string& message) {
    // Parse and handle message
    if (message.find("PLAYER_JOIN") != std::string::npos) {
        handlePlayerJoin(message);
    } else if (message.find("PLAYER_MOVE") != std::string::npos) {
        handlePlayerMove(message);
    }
});

Sending Messages

Simple Send

// Send raw string
pipe.send("Hello, server!\n");

// Send structured data
pipe.send("{\"type\":\"chat\",\"message\":\"Hello!\"}\n");

Batched Sending

void sendPlayerUpdate(const Player& player) {
    std::string msg = 
        "{\"type\":\"player_update\"," +
        "\"id\":" + std::to_string(player.id) + "," +
        "\"x\":" + std::to_string(player.x) + "," +
        "\"y\":" + std::to_string(player.y) + "}\n";
    
    pipe.send(msg);
}

Message History

Retrieve all received messages:
// Get snapshot of all messages
std::vector<std::string> messages = pipe.getMessages();

for (const auto& msg : messages) {
    processMessage(msg);
}
Messages are stored thread-safely using a mutex (pipe.h:63-64).

Connection Lifecycle

Starting

// Starts background threads
pipe.start();

// Connection loop runs in background
// Automatically attempts reconnection on disconnect
The start method initiates:
  1. Connection loop thread (pipe.h:68)
  2. Receive loop thread (pipe.h:69)

Stopping

// Graceful shutdown
pipe.stop();

// Stops background threads and closes socket
The destructor automatically calls stop() (pipe.h:75).

Connection Management

The NetworkPipe handles connection automatically:
void connectLoop() {
    while (running) {
        if (clientSocket == -1) {
            // Attempt connection
            attemptConnection();
        }
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}
Features:
  • Automatic reconnection on disconnect
  • Configurable retry interval
  • Non-blocking connection attempts

Tracer Integration

The Tracer diagnostic system uses NetworkPipe internally:
class TracerServices {
public:
    std::shared_ptr<NetworkPipe> tracerPipe;
    
    void startTracing(int port) {
        tracerPipe = std::make_shared<NetworkPipe>();
        tracerPipe->setPort(port);
        tracerPipe->start();
    }
    
    bool isOk() const { 
        return tracerPipe != nullptr; 
    }
};
Source: log.h:32-50

Real-World Examples

Multiplayer Client

class MultiplayerClient {
private:
    NetworkPipe connection;
    std::map<int, Player> players;
    
public:
    void connect(const std::string& serverIP, int port) {
        connection.setPort(port);
        
        connection.onReceive([this](const std::string& msg) {
            handleServerMessage(msg);
        });
        
        connection.start();
    }
    
    void handleServerMessage(const std::string& msg) {
        // Parse JSON message
        if (msg.find("player_joined") != std::string::npos) {
            // Add new player
            Player newPlayer = parsePlayer(msg);
            players[newPlayer.id] = newPlayer;
        }
        else if (msg.find("player_update") != std::string::npos) {
            // Update player position
            Player update = parsePlayer(msg);
            if (players.find(update.id) != players.end()) {
                players[update.id].x = update.x;
                players[update.id].y = update.y;
            }
        }
    }
    
    void sendPosition(float x, float y) {
        std::string msg = 
            "{\"type\":\"position\"," +
            "\"x\":" + std::to_string(x) + "," +
            "\"y\":" + std::to_string(y) + "}\n";
        connection.send(msg);
    }
    
    void disconnect() {
        connection.send("{\"type\":\"disconnect\"}\n");
        connection.stop();
    }
};

Chat System

class ChatClient {
private:
    NetworkPipe pipe;
    std::vector<std::string> chatHistory;
    
public:
    void connect(int port) {
        pipe.setPort(port);
        
        pipe.onReceive([this](const std::string& msg) {
            chatHistory.push_back(msg);
            std::cout << "[Chat] " << msg << std::endl;
        });
        
        pipe.start();
    }
    
    void sendMessage(const std::string& username, const std::string& message) {
        std::string formatted = 
            "{\"user\":\"" + username + "\"," +
            "\"message\":\"" + message + "\"}\n";
        pipe.send(formatted);
    }
    
    std::vector<std::string> getHistory() const {
        return chatHistory;
    }
};

Remote Control

class RemoteControl {
private:
    NetworkPipe commandPipe;
    
public:
    void startServer(int port) {
        commandPipe.setPort(port);
        
        commandPipe.onReceive([this](const std::string& cmd) {
            executeCommand(cmd);
        });
        
        commandPipe.start();
    }
    
    void executeCommand(const std::string& command) {
        if (command == "pause\n") {
            gamePaused = true;
        }
        else if (command == "resume\n") {
            gamePaused = false;
        }
        else if (command.find("spawn") != std::string::npos) {
            // Parse spawn parameters
            spawnEntity(command);
        }
        
        // Send acknowledgment
        commandPipe.send("OK\n");
    }
};

Diagnostics Dashboard

class DiagnosticsDashboard {
private:
    NetworkPipe telemetryPipe;
    unsigned int frameCount = 0;
    
public:
    void connect(int port) {
        telemetryPipe.setPort(port);
        telemetryPipe.start();
    }
    
    void sendFrameStats(float fps, int drawCalls, float frameTimeMs) {
        std::string stats = 
            "{\"frame\":" + std::to_string(frameCount) + "," +
            "\"fps\":" + std::to_string(fps) + "," +
            "\"draw_calls\":" + std::to_string(drawCalls) + "," +
            "\"frame_time\":" + std::to_string(frameTimeMs) + "}\n";
        
        telemetryPipe.send(stats);
        frameCount++;
    }
    
    void sendResourceStats(int textureCount, float memoryMB) {
        std::string stats = 
            "{\"type\":\"resources\"," +
            "\"textures\":" + std::to_string(textureCount) + "," +
            "\"memory_mb\":" + std::to_string(memoryMB) + "}\n";
        
        telemetryPipe.send(stats);
    }
};

Thread Safety

NetworkPipe is designed for concurrent access:
// Thread-safe message storage
std::vector<std::string> messages;
std::mutex messagesMutex;

// Atomic socket handle
std::atomic<int> clientSocket{-1};
Safe usage patterns:
// Safe to call from any thread
pipe.send("message");

// Returns a copy - thread safe
auto msgs = pipe.getMessages();

// Callback runs on receive thread - use appropriate synchronization
pipe.onReceive([](const std::string& msg) {
    std::lock_guard<std::mutex> lock(myMutex);
    processMessage(msg);
});

Message Protocol

NetworkPipe uses newline-delimited messages:
// Messages should end with \n
pipe.send("single line message\n");

pipe.send("{"
          "\"type\":\"event\","
          "\"data\":123"
          "}\n");

// Multiple lines in one send
pipe.send("line1\nline2\nline3\n");
Ensure all messages end with \n for proper message delimiting. The receive loop splits messages on newline boundaries.

Error Handling

The NetworkPipe handles errors gracefully:
// Connection failures trigger automatic retry
pipe.start(); // Returns immediately even if connection fails

// Check connection status via Tracer logs
if (TracerServices::getInstance().isOk()) {
    // Connection active
}

// Send fails silently if disconnected
pipe.send("message\n"); // Safe to call even when disconnected

Performance Considerations

Background Threads

NetworkPipe uses dedicated threads for I/O - minimal impact on main thread

Message Buffering

All received messages are buffered in memory until retrieved

Lock-Free Sending

Send operations use atomic socket handles for low-latency transmission

Automatic Retry

Connection loop retries at 1-second intervals without blocking

Best Practices

  1. Newline Termination: Always append \n to messages
  2. JSON Format: Use JSON for structured data exchange
  3. Callbacks: Keep onReceive callbacks fast - defer heavy processing
  4. Cleanup: Call stop() before application exit
  5. Error Handling: Don’t assume connection is always active
  6. Message Size: Keep messages reasonably sized for network efficiency
NetworkPipe is part of Atlas Engine’s alpha networking API. The interface may change in future versions. For production multiplayer games, consider using a more feature-complete networking library.
The default server address is 127.0.0.1 (localhost). For remote connections, you would need to modify the serverAddress member or extend the API to support custom addresses.

Build docs developers (and LLMs) love