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:
Connection loop thread (pipe.h:68)
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 \n line2 \n line3 \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
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
Newline Termination : Always append \n to messages
JSON Format : Use JSON for structured data exchange
Callbacks : Keep onReceive callbacks fast - defer heavy processing
Cleanup : Call stop() before application exit
Error Handling : Don’t assume connection is always active
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.