Server Architecture Overview
Executive Summaryโ
The R-Type server is a high-performance multiplayer game server built with modern C++ (C++17) that handles game logic, network synchronization, and player management for 1-4 concurrent players. The architecture leverages an Entity Component System (ECS) pattern combined with multithreading to achieve optimal performance and scalability.
Key Featuresโ
- ๐ฎ 60 FPS game simulation with deterministic logic
- ๐ UDP-based networking with reliable packet delivery
- ๐งต Multithreaded architecture (network thread + game thread)
- ๐ง Entity Component System (ECS) for flexible game logic
- ๐ Thread-safe communication via lock-free queues
- ๐ก๏ธ Graceful error handling and automatic client timeout
Architectural Layersโ
The server follows a layered architecture common in multiplayer game development:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Application Layer โ
โ (GameServer) โ
โ - Lobby management โ
โ - Player lifecycle โ
โ - Game state coordination โ
โโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโดโโโโโโโโโโโโ
โ โ
โโโโโโโโโผโโโโโโโโโโโ โโโโโโโโโโผโโโโโโโโโ
โ Network Layer โ โ Game Layer โ
โ (NetworkServer) โ โ (GameLoop) โ
โ โ โ โ
โ - UDP Socket โ โ - ECS Engine โ
โ - Client Mgmt โ โ - Systems โ
โ - Protocol โ โ - Entities โ
โ - Reliability โ โ - Components โ
โโโโโโโโโฌโโโโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโ
โ โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
โโโโโโโโโโโผโโโโโโโโโโ
โ Common Services โ
โ โ
โ - Threading โ
โ - Logging โ
โ - Protocol โ
โโโโโโโโโโโโโโโโโโโโโ
Core Componentsโ
1. GameServer (Orchestrator)โ
Location: server/GameServer.{hpp,cpp}
The GameServer class is the main entry point that orchestrates all server operations.
Key Responsibilities:
- Initialize and coordinate network and game threads
- Manage the player lobby (waiting for 1-4 players)
- Handle player connection lifecycle
- Bridge network events to game logic
Architecture Pattern: Facade Pattern - provides a unified interface to the complex subsystems.
class GameServer {
NetworkServer _networkServer; // Network thread
engine::GameLoop _gameLoop; // Game thread
std::atomic<bool> _gameStarted;
std::atomic<int> _playerCount;
std::unordered_map<uint32_t, bool> _playersReady;
};
2. NetworkServer (Network Layer)โ
Location: server/network/NetworkServer.{hpp,cpp}
Handles all network communication using Boost.Asio for asynchronous UDP I/O.
Key Responsibilities:
- Asynchronous packet reception and transmission
- Client session management and authentication
- Protocol encoding/decoding
- Reliable packet delivery (ACK/retry mechanism)
- Client timeout detection
Architecture Pattern: Reactor Pattern (via Boost.Asio) - handles I/O events asynchronously.
Thread Safety: All network I/O runs on a dedicated thread. Events are queued for the main thread.
class NetworkServer {
boost::asio::io_context _ioContext;
boost::asio::ip::udp::socket _socket;
std::thread _networkThread;
std::map<uint32_t, ClientSession> _clients;
ThreadSafeQueue<NetworkEvent> _eventQueue;
};
3. GameLoop (Game Logic Layer)โ
Location: server/engine/system/GameLoop.{hpp,cpp}
The heart of the game simulation, running at 60 FPS in a separate thread.
Key Responsibilities:
- Manage the Entity Component System (ECS)
- Execute game systems in priority order
- Process player input commands
- Generate entity state updates for network broadcast
- Handle entity lifecycle (spawn, update, destroy)
Architecture Pattern: Game Loop Pattern with ECS Architecture.
class GameLoop {
EntityManager _entityManager;
std::vector<std::unique_ptr<ISystem>> _systems;
std::thread _gameThread;
ThreadSafeQueue<NetworkInputCommand> _inputQueue;
ThreadSafeQueue<EntityStateUpdate> _outputQueue;
};
Data Flow Diagramโ
Understanding how data flows through the system is crucial:
Client Input โ NetworkServer โ Input Queue โ GameLoop โ ECS Systems
โฒ โ
โ โผ
โ Entity Updates
โ โ
โ โผ
โโโโโโโโโ Output Queue โโโโโโโโโโ State Sync
โ
โผ
Broadcast to Clients
Step-by-Step Flowโ
-
Input Reception
- Client sends input packet (UDP)
- NetworkServer receives and validates
- Input queued to thread-safe input queue
-
Game Simulation
- GameLoop dequeues inputs from queue
- Each system processes entities in order:
- MovementSystem: Updates positions
- CollisionSystem: Detects hits
- EnemySpawnerSystem: Spawns enemies
- LifetimeSystem: Removes expired entities
- State changes queued to output queue
-
Network Synchronization
- NetworkServer dequeues entity updates
- Broadcasts to all connected clients
- Reliable packets await ACK with retry logic
Threading Modelโ
The server uses two primary threads plus one I/O thread:
Thread Architectureโ
| Thread | Purpose | Frequency | Components |
|---|---|---|---|
| Main Thread | Server lifecycle, lobby management | Event-driven | GameServer |
| Network Thread | Asynchronous I/O, packet handling | Event-driven (Boost.Asio) | NetworkServer, io_context |
| Game Thread | Game simulation loop | 60 FPS (16.67ms) | GameLoop, ECS Systems |
Thread Communicationโ
Problem: Threads must share data safely without race conditions.
Solution: Thread-safe queues with mutex protection.
template <typename T>
class ThreadSafeQueue {
std::queue<T> _queue;
std::mutex _mutex;
std::condition_variable _condVar;
void push(T item);
std::optional<T> tryPop();
};
Benefits:
- โ No data races
- โ Lock-free reads (tryPop is non-blocking)
- โ Minimal contention
- โ Clear ownership semantics
Entity Component System (ECS)โ
The server uses a data-oriented ECS architecture for game logic.
ECS Principlesโ
- Entities: Just an ID (uint32_t)
- Components: Pure data structures (Position, Velocity, Health)
- Systems: Logic that operates on components
- Factory: Centralized entity creation (GameEntityFactory)
Why ECS?โ
| Benefit | Explanation |
|---|---|
| Performance | Cache-friendly, data-oriented design |
| Flexibility | Easy to add new entity types |
| Maintainability | Clear separation of data and logic |
| Scalability | Systems can be parallelized (future) |
GameEntityFactory & Event-Driven Spawningโ
Location: server/engine/entity/GameEntityFactory.{hpp,cpp}
The GameEntityFactory centralizes all entity creation logic. Systems do not create entities directly - instead, they emit spawn events that the GameLoop processes using the factory. This follows pure ECS architecture where systems only contain logic, not entity creation.
Key Responsibilities:
- Create all game entities (players, enemies, bullets)
- Manage entity IDs (network synchronization)
- Ensure consistent component configuration
- Provide a single source of truth for entity templates
Architecture Pattern: Factory Pattern + Event-Driven Architecture
class GameEntityFactory {
private:
EntityManager& _entityManager;
uint32_t _nextEnemyId;
uint32_t _nextBulletId;
public:
// Creates a player with all required components
Entity createPlayer(uint32_t clientId, uint32_t playerId, float x, float y);
// Creates an enemy with type-specific stats
Entity createEnemy(Enemy::Type type, float x, float y);
// Creates player/enemy bullets
Entity createPlayerBullet(EntityId ownerId, const Position& pos);
Entity createEnemyBullet(EntityId ownerId, const Position& pos);
};
Benefits:
- โ Single Responsibility: Entity creation isolated in factory
- โ Consistency: All entities created the same way
- โ Maintainability: Change entity structure in one place
- โ Testability: Easy to mock spawn events and factory
- โ Pure ECS: Systems only process logic, never create entities
- โ Minimal Coupling: Systems only know about spawn event queue
Event-Driven Spawning Flow:
// 1. Define spawn events (server/engine/events/SpawnEvents.hpp)
struct SpawnEnemyEvent { Enemy::Type type; float x, y; };
struct SpawnPlayerBulletEvent { EntityId ownerId; Position position; };
struct SpawnEnemyBulletEvent { EntityId ownerId; Position position; };
using SpawnEvent = std::variant<SpawnEnemyEvent,
SpawnPlayerBulletEvent,
SpawnEnemyBulletEvent>;
// 2. Systems receive spawn queue reference at construction
class EnemySpawnerSystem : public ISystem {
std::vector<SpawnEvent>& _spawnQueue;
public:
EnemySpawnerSystem(std::vector<SpawnEvent>& spawnQueue)
: _spawnQueue(spawnQueue) {}
void spawnEnemy() {
// Emit spawn event instead of creating entity directly
_spawnQueue.push_back(SpawnEnemyEvent{Enemy::Type::BASIC, x, y});
}
};
// 3. GameLoop processes events each frame
void GameLoop::processSpawnEvents() {
for (const auto& event : _spawnEvents) {
std::visit([this](const auto& e) {
processSpawnEvent(e); // Compile-time dispatch
}, event);
}
_spawnEvents.clear();
}
// Overloaded handlers for each event type
void processSpawnEvent(const SpawnEnemyEvent& e) {
_entityFactory.createEnemy(e.type, e.x, e.y);
}
Entity Creation Architectureโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GameLoop โ
โ - Owns GameEntityFactory โ
โ - Owns spawn event queue (vector<SpawnEvent>) โ
โ - Processes spawn events each frame โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโบ Creates systems with spawn queue ref
โ system = new EnemySpawnerSystem(_spawnEvents)
โ
โโโบ Systems emit spawn events
โ _spawnQueue.push_back(SpawnEnemyEvent{...})
โ
โโโบ GameLoop processes events via factory
processSpawnEvent() โ _entityFactory.createEnemy()
Why Event-Driven Spawning?
- ๐ฏ Decoupling: Systems don't need to know about GameLoop or Factory
- ๐งช Testability: Easy to mock a
vector<SpawnEvent>for testing - ๐ Extensibility: Add new entity types without changing systems
- ๐ง Maintainability: Single spawn processing location in GameLoop
- โก Performance: Batch entity creation, better cache locality
Systems (logic) automatically process entities with required components:
- MovementSystem: operates on Position + Velocity
- CollisionSystem: operates on Position + Hitbox + Health
Design Patterns Usedโ
1. Entity Component System (ECS)โ
- Where: Game logic layer
- Why: Performance, flexibility, maintainability
- Implementation: EntityManager + ComponentManager + Systems
2. Factory Pattern + Event-Driven Architectureโ
- Where: Entity creation (GameEntityFactory + SpawnEvents)
- Why: Centralize entity creation, decouple systems from factory, pure ECS
- Implementation: Systems emit spawn events, GameLoop processes via factory
- Benefits: Pure ECS, minimal coupling, testability, extensibility
3. Reactor Pattern (Asio)โ
- Where: Network layer
- Why: Asynchronous I/O without threads-per-connection
- Implementation: Boost.Asio io_context
4. Producer-Consumer Patternโ
- Where: Thread communication
- Why: Safe data exchange between threads
- Implementation: ThreadSafeQueue
5. Facade Patternโ
- Where: GameServer
- Why: Simplify complex subsystem interactions
- Implementation: GameServer provides simple start()/run()/stop()
5. Observer Pattern (Callbacks)โ
- Where: Network events
- Why: Decouple network layer from game logic
- Implementation: Function callbacks (std::function)
Performance Characteristicsโ
Target Metricsโ
- Game Loop: 60 FPS (16.67ms per frame)
- Network Tick Rate: 30 Hz (updates every 2 frames)
- Player Capacity: 1-4 concurrent players
- Network Protocol: UDP (low latency)
Optimization Strategiesโ
- ECS Architecture: Cache-friendly data layout
- Minimal Locking: Lock-free queues where possible
- Batch Processing: Systems process entities in batches
- Selective Sync: Only sync entities that changed (
needsSyncflag) - Smart Spawning: Enemy spawner throttled by timer
Scalability Considerationsโ
Current Designโ
The architecture is optimized for small multiplayer sessions (1-4 players):
| Aspect | Current Design | Scaling Strategy |
|---|---|---|
| Players per game | 1-4 | Room/lobby system for multiple games |
| Game thread | Single thread | Can partition by game instance |
| Network thread | Single thread | Can scale with io_context thread pool |
| Entity limit | ~1000s | ECS handles efficiently |
Future Scaling Pathsโ
- Multiple Game Instances: Run separate GameServer instances per game room
- Thread Pool for Systems: Parallelize independent systems
- Spatial Partitioning: Optimize collision detection for large entity counts
- Database Integration: Persist player data and statistics
Error Handling & Resilienceโ
Defensive Programmingโ
The server is designed to never crash from client actions:
try {
// Risky operation
processClientPacket(packet);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
// Server continues running
}
Graceful Degradationโ
- Client timeout: Automatic disconnection after 30s of inactivity
- Invalid packets: Logged and ignored, not crash
- Player disconnect: Game continues if others remain
- Signal handling: SIGINT/SIGTERM trigger graceful shutdown
System Requirementsโ
Build Dependenciesโ
- C++17 Compiler: GCC 7+, Clang 5+, MSVC 2017+
- CMake: 3.16+
- Boost: 1.70+ (Asio, System)
- vcpkg: For dependency management
Runtime Requirementsโ
- OS: Linux, Windows, macOS
- RAM: ~50 MB per game instance
- CPU: 1 core (2+ recommended)
- Network: UDP port 8080
File Structureโ
server/
โโโ main.cpp # Entry point
โโโ GameServer.{hpp,cpp} # Main orchestrator
โโโ GameEvents.hpp # Event documentation
โโโ network/
โ โโโ NetworkServer.{hpp,cpp} # Network layer
โโโ engine/
โ โโโ component/ # ECS components
โ โ โโโ ComponentManager.{hpp,cpp}
โ โ โโโ GameComponents.hpp
โ โโโ entity/ # Entity management
โ โ โโโ EntityManager.{hpp,cpp}
โ โ โโโ Entity.hpp
โ โโโ system/ # ECS systems
โ โ โโโ GameLoop.{hpp,cpp}
โ โ โโโ GameSystems.hpp
โ โ โโโ System.{hpp,tpp}
โ โโโ threading/ # Thread utilities
โ โโโ ThreadSafeQueue.hpp
โโโ tests/ # Unit tests
Next Stepsโ
- Systems & Components: Deep dive into ECS implementation
- Networking: Protocol and network architecture
- Technical Comparison: Technology choices and alternatives
- How-To Guides: Practical tutorials for development