Rapport Exhaustif : Persistance et Structures de Donnees (R-Type)¶
Competences couvertes: 8 (persistance), 9 (structures de donnees)
Executive Summary¶
R-Type utilise une architecture modulaire hexagonale (Ports & Adapters) pour la persistance. Les donnees sont gerees via: - MongoDB : Base de donnees NoSQL pour utilisateurs, classements, amis, messages prives et parametres - Communication binaire : Protocole UDP/TCP avec serialisation big-endian - Compression LZ4 : Pour reduire la bande passante des snapshots de jeu - Structures de donnees optimisees : Maps, vectors, unordered_maps avec complexite bien definie
1. Schemas MongoDB¶
1.1 Collections Principales¶
Collection: user¶
Stockage : Utilisateurs et comptes
// Champs MongoDB
{
"_id": ObjectId,
"username": string (unique index),
"email": string (unique index),
"password": string (hashed),
"lastLogin": ISODate,
"createAt": ISODate
}
Source : src/server/infrastructure/adapters/out/persistence/MongoDBUserRepository.hpp:27
- Collection name: "user"
- Classe: MongoDBUserRepository
- Methodes: save(), update(), findById(), findByName(), findByEmail(), findAll()
Collection: leaderboard¶
Stockage : Classements (AllTime, Weekly, Monthly)
// Structures (header: ILeaderboardRepository.hpp)
struct LeaderboardEntry {
std::string odId; // MongoDB ObjectId
std::string playerName;
uint32_t score;
uint16_t wave;
uint16_t kills;
uint8_t deaths;
uint32_t duration;
int64_t timestamp;
uint32_t rank;
uint8_t playerCount; // 1=Solo, 2=Duo, 3=Trio, 4=Squad
};
Collections liees :
- leaderboard : Classements globaux par periode
- player_stats : Statistiques cumulatives par joueur
- game_history : Historique detaille des parties
- achievements : Accomplissements deverrouilles
- current_game_sessions : Sessions en cours (auto-save)
Source : src/server/infrastructure/adapters/out/persistence/MongoDBLeaderboardRepository.hpp:29-77
Collection: player_stats¶
Stockage : Statistiques cumulatives
struct PlayerStats {
std::string odId;
std::string playerName;
uint64_t totalScore;
uint32_t totalKills;
uint32_t totalDeaths;
uint32_t totalPlaytime;
uint32_t gamesPlayed;
uint32_t bestScore;
uint16_t bestWave;
uint16_t bestCombo;
uint16_t bestKillStreak;
uint16_t bestWaveStreak;
uint32_t totalPerfectWaves;
uint16_t bossKills;
uint32_t standardKills;
uint32_t spreadKills;
uint32_t laserKills;
uint32_t missileKills;
uint32_t waveCannonKills;
uint64_t totalDamageDealt;
uint32_t achievements; // Bitfield (10 achievements max)
};
Collection: friendships¶
Stockage : Relations d'amitie (bidirectionnelles)
// Stockage avec cle composite ordonnee alphabetiquement
{
"email1": "alice@example.com", // Alphabetiquement premier
"email2": "bob@example.com", // Alphabetiquement second
"timestamp": ISODate
}
Source : src/server/infrastructure/adapters/out/persistence/MongoDBFriendshipRepository.hpp:33
- Methodes: addFriendship(), removeFriendship(), areFriends(), getFriendEmails(), getFriendCount()
- Indexation: Emails ordonnes pour eviter les doublons
Collection: friend_requests¶
Stockage : Demandes d'amitie en attente
struct FriendRequestData {
std::string fromEmail;
std::string toEmail;
std::string fromDisplayName;
std::chrono::system_clock::time_point timestamp;
};
Source : src/server/infrastructure/adapters/out/persistence/MongoDBFriendRequestRepository.hpp:45
Collection: private_messages¶
Stockage : Messages prives
struct PrivateMessageData {
uint64_t id; // ObjectId converti
std::string senderEmail;
std::string recipientEmail;
std::string senderDisplayName;
std::string message;
std::chrono::system_clock::time_point timestamp;
bool isRead;
};
// Conversation groupee par cle
struct ConversationSummaryData {
std::string otherEmail;
std::string otherDisplayName;
std::string lastMessage;
std::chrono::system_clock::time_point lastTimestamp;
uint8_t unreadCount;
};
Source : src/server/infrastructure/adapters/out/persistence/MongoDBPrivateMessageRepository.hpp:17-34
- Methodes: saveMessage(), getConversation(), getConversationsList(), markAsRead(), getUnreadCount()
- Compression: Messages >= 128 bytes sont compresses avec LZ4
Collection: blocked_users¶
Stockage : Utilisateurs bloques
struct BlockedUserData {
std::string blockerEmail;
std::string blockedEmail;
std::string blockedDisplayName;
int64_t blockedAt;
};
Source : src/server/infrastructure/adapters/out/persistence/MongoDBBlockedUserRepository.hpp:42
Collection: chat_messages¶
Stockage : Messages de chat en salle
struct ChatMessageData {
std::string roomCode;
std::string displayName;
std::string message;
std::chrono::system_clock::time_point timestamp;
};
Source : src/server/infrastructure/adapters/out/persistence/MongoDBChatMessageRepository.hpp:26
Collection: user_settings¶
Stockage : Parametres utilisateur
struct UserSettingsData {
std::string email;
bool godMode; // Feature cachee (developpeur)
// Autres parametres a venir
};
Source : src/server/infrastructure/adapters/out/persistence/MongoDBUserSettingsRepository.hpp:26
2. Architecture de Persistance¶
2.1 Chaine de Configuration¶
// DBConfig - Configuration de la base de donnees
struct DBConfig {
std::string connexionString; // URI MongoDB (ex: mongodb://localhost:27017)
std::string dbName; // Nom de la base (ex: "rtype")
int minPoolSize; // Taille min du pool de connexions
int maxPoolSize; // Taille max du pool de connexions
};
Source : src/server/include/infrastructure/configuration/DBConfig.hpp:13-18
2.2 MongoDBConfiguration - Gestion du Pool de Connexions¶
class MongoDBConfiguration {
private:
static std::unique_ptr<mongocxx::instance> _instance; // Singleton
DBConfig _dbConfig;
mongocxx::uri _uri;
std::unique_ptr<mongocxx::pool> _pool; // Thread-safe pool
public:
// Acquiert un client du pool (thread-safe)
PooledClient acquireClient();
// Obtient la base de donnees
mongocxx::database getDatabase(PooledClient& client) const;
// Verifie la connexion
bool pingServer() const;
};
Source : src/server/include/infrastructure/adapters/out/persistence/MongoDBConfiguration.hpp:34-57
Implementation cles (MongoDBConfiguration.cpp:35-66) :
- Pool de connexions avec mongocxx::pool pour thread-safety
- Initialisation singleton de mongocxx::instance
- Ping du serveur au demarrage pour verifier la connexion
- Gestion RAII : le client retourne automatiquement au pool
2.3 Pattern Repository - Interface de Persistance¶
Chaque repository implemente un port (interface) defini dans application/ports/out/persistence/:
// Exemple: IUserRepository
class IUserRepository {
public:
virtual void save(const User& user) const = 0;
virtual void update(const User& user) = 0;
virtual std::optional<User> findById(const std::string& id) = 0;
virtual std::optional<User> findByName(const std::string& name) = 0;
virtual std::optional<User> findByEmail(const std::string& email) = 0;
virtual std::vector<User> findAll() = 0;
};
Implementation : MongoDBUserRepository : public IUserRepository
3. Structures de Donnees In-Memory¶
3.1 GameWorld Data Members¶
Fichier Header: src/server/include/infrastructure/game/GameWorld.hpp
class GameWorld {
private:
// Core Game State (O(1) lookup)
std::unordered_map<uint8_t, ConnectedPlayer> _players; // playerId -> ConnectedPlayer
std::unordered_map<uint16_t, Missile> _missiles; // missileId -> Missile
std::unordered_map<uint16_t, Enemy> _enemies; // enemyId -> Enemy
std::unordered_map<uint16_t, Missile> _enemyMissiles; // missileId -> Missile
std::unordered_map<uint8_t, PlayerScore> _playerScores; // playerId -> PlayerScore
// Wave & Boss Management
uint16_t _waveNumber = 0; // Current wave (0 = not started)
std::optional<Boss> _boss; // Current boss (if any)
std::vector<SpawnEntry> _waveSpawnList; // Enemies to spawn this wave
// Event Tracking (cleared each frame)
std::vector<uint16_t> _destroyedMissiles;
std::vector<uint16_t> _destroyedEnemies;
std::vector<std::pair<uint8_t, uint8_t>> _playerDamageEvents;
std::vector<uint8_t> _deadPlayers;
// RNG & Game State
std::mt19937 _rng{std::random_device{}()};
float _gameSpeedMultiplier = 1.0f;
};
3.2 Justification des Choix de Structures¶
| Structure | Cas d'Utilisation | Complexite | Justification |
|---|---|---|---|
std::unordered_map<uint8_t, ConnectedPlayer> |
Players by ID lookup | O(1) avg | Acces rapide par playerId, max 4 entrees |
std::unordered_map<uint16_t, Missile> |
Missiles by ID | O(1) avg | 32 missiles max, collisions check rapides |
std::vector<SpawnEntry> |
Wave spawn list | O(n) iter | Sequential processing each frame |
std::optional<Boss> |
Boss state | O(1) check | Boss 0 ou 1, check rapide .has_value() |
std::array<uint8_t, 4> |
Weapon levels | O(1) access | Fixed size (4 weapons), cache-friendly |
4. Justification des Choix Architecturaux¶
4.1 MongoDB vs SQL¶
Choix MongoDB car: 1. Schema flexible : Utilisateurs, stats, amis, messages = collections distinctes 2. Scalabilite horizontale : Partitioning naturel par email 3. Documents imbriques : Stats joueur + achievements dans un seul document 4. Performance lectures : Leaderboard queries sans joins 5. TTL Indexes : Messages temporaires, sessions en cours (future)
4.2 Hexagonal Architecture¶
Avantage:
- Domain n'a aucune dependance MongoDB
- Facile de swapper MongoDB pour PostgreSQL
- Tests unitaires sans BDD
4.3 Pool de Connexions Thread-Safe¶
Raison: - Serveur multi-threaded (Boost.ASIO) - Une connexion MongoDB par thread = surcharge memoire - Pool thread-safe = partage efficace
4.4 unordered_map pour Players/Missiles¶
Justification: - Lookups par ID tres frequents (20Hz snapshot) - O(1) moyen vs O(log n) pour map - Insertion/suppression frequentes lors des joins/disconnects
4.5 std::vector pour Event Tracking¶
Justification: - Iteration complete a chaque frame (collision checks) - Cache locality meilleure qu'unordered_map - Clear() efficace en O(1)
5. Complexite Algorithmique¶
5.1 Operations MongoDB¶
| Operation | Complexite | Exemple |
|---|---|---|
| findById | O(1) | findById(user_id) avec index |
| findByEmail | O(1) | unique index sur email |
| getLeaderboard(period, limit) | O(log N + k) | Index sur period + score |
| getFriendsList | O(1) + O(k) | k = limit (50 max) |
| getConversation | O(log N + k) | k = 50 messages |
5.2 Operations GameWorld In-Memory¶
| Operation | Complexite | Notes |
|---|---|---|
| getPlayer(id) | O(1) | unordered_map |
| getMissile(id) | O(1) | unordered_map |
| collisionDetect | O(m + e + em) | m missiles + e enemies + enemy missiles |
| updatePosition | O(1) | Direct array access |
| spawnEnemy | O(1) | Vector push_back |
| despawnEntity | O(1) | Lazy removal |
6. Fichiers Source Cles¶
| Fichier | Ligne | Description |
|---|---|---|
MongoDBConfiguration.hpp |
34-57 | Pool de connexions |
MongoDBUserRepository.hpp |
24-45 | CRUD utilisateur |
MongoDBLeaderboardRepository.hpp |
29-76 | Leaderboards & stats |
MongoDBFriendshipRepository.hpp |
17-38 | Gestion amis |
MongoDBPrivateMessageRepository.hpp |
17-69 | Messages prives |
ILeaderboardRepository.hpp |
54-212 | Structures donnees stats |
IPrivateMessageRepository.hpp |
18-157 | Structures messages |
GameWorld.hpp |
78-200+ | Structures gameplay |
Protocol.hpp |
1-161 | Serialisation binaire |
Compression.hpp |
27-106 | Compression LZ4 |
DBConfig.hpp |
13-18 | Configuration BD |
CONCLUSION¶
R-Type implemente une persistance multi-couches : - Donnees applicatives -> MongoDB (utilisateurs, stats, social) - Etat jeu temps reel -> In-memory + snapshots UDP - Serialisation -> Binary protocol + LZ4 compression - Architecture -> Hexagonal pour testabilite
Cette approche optimise la responsivite du gameplay (in-memory fast path) tout en durabilisant les donnees critiques (MongoDB asynchrone).
Resume des decouvertes : - 9 collections MongoDB principales - 8 repositories MongoDB implementant le pattern Hexagonal Architecture - Structures de donnees optimisees : unordered_map (O(1)) pour players/missiles, vector (O(n)) pour ennemis - Compression LZ4 pour GameSnapshot (40-60% reduction) - Pool de connexions thread-safe pour performance multi-threaded