Compétence 9 : Structures de Données¶
Sélectionner les structures de données répondant aux contraintes de l'application en tenant compte de leur complexité algorithmique et spatiale (tableaux, listes, sets, tables de hashage...) dans un objectif de performance, de maintenabilité et d'évolutivité de l'application.
Observable 9.1 : Structures de Données Implémentées¶
Vue d'Ensemble des Structures Utilisées¶
graph TB
subgraph "Structures à Accès O(1)"
UM["std::unordered_map<br/>Players, Missiles, Enemies"]
ARR["std::array<br/>Weapon levels, Fixed slots"]
OPT["std::optional<br/>Boss, Parse results"]
end
subgraph "Structures Séquentielles O(n)"
VEC["std::vector<br/>Events, Spawn lists"]
STK["std::stack<br/>Scene management"]
end
subgraph "Structures Ordonnées O(log n)"
MAP["std::map<br/>Sorted leaderboards"]
end
Structure 1 : std::unordered_map - État du Jeu¶
Usage dans GameWorld¶
Fichier : src/server/include/infrastructure/game/GameWorld.hpp
class GameWorld {
private:
// Joueurs indexés par ID (0-3)
std::unordered_map<uint8_t, ConnectedPlayer> _players;
// Missiles indexés par ID unique
std::unordered_map<uint16_t, Missile> _missiles;
// Ennemis indexés par ID unique
std::unordered_map<uint16_t, Enemy> _enemies;
// Missiles ennemis
std::unordered_map<uint16_t, Missile> _enemyMissiles;
// Scores par joueur
std::unordered_map<uint8_t, PlayerScore> _playerScores;
};
Complexité¶
| Opération | Complexité | Usage |
|---|---|---|
operator[] |
O(1) moyen | Accès par ID |
find() |
O(1) moyen | Vérification existence |
insert() |
O(1) moyen | Ajout entité |
erase() |
O(1) moyen | Suppression entité |
begin()/end() |
O(1) | Itération |
Exemple d'Utilisation¶
void GameWorld::updatePlayerPosition(uint8_t playerId, uint16_t x, uint16_t y) {
auto it = _players.find(playerId); // O(1)
if (it != _players.end()) {
it->second.x = x;
it->second.y = y;
}
}
Missile* GameWorld::getMissile(uint16_t missileId) {
auto it = _missiles.find(missileId); // O(1)
return (it != _missiles.end()) ? &it->second : nullptr;
}
Structure 2 : std::vector - Collections Dynamiques¶
Usage pour Événements¶
class GameWorld {
private:
// Événements par frame (vidés après broadcast)
std::vector<uint16_t> _destroyedMissiles;
std::vector<uint16_t> _destroyedEnemies;
std::vector<std::pair<uint8_t, uint8_t>> _playerDamageEvents;
std::vector<uint8_t> _deadPlayers;
// Liste de spawn pour la vague courante
std::vector<SpawnEntry> _waveSpawnList;
};
Complexité¶
| Opération | Complexité | Usage |
|---|---|---|
push_back() |
O(1) amorti | Ajout événement |
clear() |
O(n) | Reset par frame |
operator[] |
O(1) | Accès indexé |
begin()/end() |
O(1) | Itération |
size() |
O(1) | Comptage |
Avantage : Cache Locality¶
void GameWorld::broadcastDestroyedMissiles() {
// Itération séquentielle = excellent cache
for (uint16_t id : _destroyedMissiles) {
// Broadcast message
}
_destroyedMissiles.clear(); // O(1) car trivially destructible
}
Structure 3 : std::optional - Valeurs Nullables¶
Usage pour Boss et Parsing¶
// Boss optionnel (0 ou 1)
std::optional<Boss> _boss;
// Utilisation
if (_boss.has_value()) { // O(1)
updateBossPhase(*_boss);
}
// Parsing sécurisé
static std::optional<PlayerState> from_bytes(const void* buf, size_t len) {
if (len < WIRE_SIZE) return std::nullopt; // Erreur explicite
// ... parsing
return playerState;
}
Avantages vs Pointeurs¶
| Aspect | std::optional<T> |
T* |
|---|---|---|
| Sémantique | Valeur optionnelle claire | Ambigu (nullable? ownership?) |
| Sécurité | Accès vérifié via .value() |
Déréférencement null possible |
| Mémoire | Inline (pas d'allocation) | Peut pointer vers heap |
| Copie | Copie la valeur | Copie le pointeur |
Structure 4 : std::array - Tailles Fixes¶
Usage pour Armes et Slots¶
// Niveaux d'armes (4 types)
std::array<uint8_t, MAX_SELECTABLE_WEAPONS> _weaponLevels;
// Utilisation
uint8_t level = _weaponLevels[static_cast<size_t>(weaponType)]; // O(1)
Avantages vs Vector¶
| Aspect | std::array<T, N> |
std::vector<T> |
|---|---|---|
| Taille | Fixe (compile-time) | Dynamique |
| Mémoire | Stack (inline) | Heap |
| Bounds check | at() optionnel |
at() optionnel |
| Cache | Excellent | Bon |
Structure 5 : std::stack - Navigation Scènes¶
Usage dans SceneManager¶
Fichier : src/client/include/scenes/SceneManager.hpp
class SceneManager {
private:
std::stack<std::unique_ptr<IScene>> _sceneStack;
public:
void pushScene(std::unique_ptr<IScene> scene) {
_sceneStack.push(std::move(scene)); // O(1)
}
void popScene() {
if (!_sceneStack.empty()) {
_sceneStack.pop(); // O(1)
}
}
IScene* currentScene() {
return _sceneStack.empty() ? nullptr : _sceneStack.top().get(); // O(1)
}
};
Sémantique LIFO¶
pushScene(GameScene) -> [GameScene]
pushScene(PauseOverlay) -> [GameScene, PauseOverlay]
popScene() -> [GameScene]
changeScene(MenuScene) -> [MenuScene]
Structure 6 : std::variant - Événements Typés¶
Usage pour Système d'Événements¶
Fichier : src/client/include/events/Event.hpp
namespace events {
struct KeyPressed { Key key; };
struct KeyReleased { Key key; };
struct MouseMoved { int x, y; };
struct MouseButtonPressed { MouseButton button; int x, y; };
struct WindowClosed {};
struct None {};
using Event = std::variant<None, KeyPressed, KeyReleased, MouseMoved,
MouseButtonPressed, MouseButtonReleased,
TextEntered, WindowClosed>;
}
Utilisation avec std::get_if¶
void Button::handleEvent(const events::Event& event) {
if (auto* pressed = std::get_if<events::MouseButtonPressed>(&event)) {
if (contains(pressed->x, pressed->y)) {
_onClick();
}
}
}
Observable 9.2 : Justification des Choix¶
Tableau Récapitulatif des Structures¶
| Structure | Usage | Complexité Accès | Complexité Insertion | Mémoire | Justification |
|---|---|---|---|---|---|
unordered_map |
Players, Missiles | O(1) | O(1) | Overhead hash | Lookups fréquents par ID |
vector |
Events, Spawns | O(1) | O(1) amorti | Contiguë | Itération séquentielle |
optional |
Boss, Parse | O(1) | N/A | Inline | Valeur nullable explicite |
array |
Weapon levels | O(1) | N/A | Inline | Taille fixe connue |
stack |
Scenes | O(1) | O(1) | Adapter | Sémantique LIFO |
variant |
Events | O(1) | N/A | Inline | Type-safe union |
Critère 1 : Performance¶
Collisions - O(M × E) avec O(1) par lookup¶
void GameWorld::checkCollisions() {
for (auto& [missileId, missile] : _missiles) { // O(M)
for (auto& [enemyId, enemy] : _enemies) { // O(E)
// AABB intersection O(1)
}
}
}
// Total: O(32 × 16) = O(512) par frame
Justification : unordered_map permet des suppressions O(1) lors des collisions.
Snapshot Generation - O(P + M + E)¶
GameSnapshot GameWorld::getSnapshot() const {
GameSnapshot snap;
for (const auto& [id, player] : _players) { // O(4)
snap.players.push_back(player.toState());
}
for (const auto& [id, missile] : _missiles) { // O(32)
snap.missiles.push_back(missile.toState());
}
// ...
return snap;
}
Critère 2 : Maintenabilité¶
std::optional vs Raw Pointers¶
// AVANT (pointer)
Boss* _boss = nullptr;
if (_boss != nullptr) { /* ... */ } // Facile à oublier
// APRÈS (optional)
std::optional<Boss> _boss;
if (_boss.has_value()) { /* ... */ } // Intention claire
std::variant vs enum + union¶
// AVANT (C-style)
struct Event {
enum Type { KEY_PRESSED, MOUSE_MOVED, ... } type;
union {
struct { int key; } keyPressed;
struct { int x, y; } mouseMoved;
} data;
};
// APRÈS (type-safe)
using Event = std::variant<KeyPressed, MouseMoved, ...>;
// Impossible d'accéder au mauvais membre
Critère 3 : Évolutivité¶
Ajout d'un Nouveau Type d'Ennemi¶
// 1. Ajouter à l'enum (Protocol.hpp)
enum class EnemyType : uint8_t {
Basic, Tracker, Zigzag, Fast, Bomber, POWArmor,
NewEnemy // Ajout
};
// 2. Ajouter comportement (GameWorld.cpp)
case EnemyType::NewEnemy:
// Nouveau pattern de mouvement
break;
// Structures existantes inchangées (unordered_map<uint16_t, Enemy>)
Ajout d'un Nouvel Événement¶
// 1. Définir la structure
struct ControllerConnected { int controllerId; };
// 2. Ajouter au variant
using Event = std::variant<..., ControllerConnected>;
// 3. Handler (pattern matching)
if (auto* controller = std::get_if<ControllerConnected>(&event)) {
// ...
}
Métriques Mémoire¶
| Structure | Taille Instance | Instances Max | Total Max |
|---|---|---|---|
ConnectedPlayer |
~100 B | 4 | 400 B |
Missile |
~32 B | 32 | 1 KB |
Enemy |
~40 B | 16 | 640 B |
PlayerScore |
~50 B | 4 | 200 B |
Boss |
~120 B | 1 | 120 B |
| GameWorld total | ~3 KB |
Conclusion¶
Les structures de données de R-Type sont sélectionnées selon :
- Performance :
unordered_mappour O(1) lookups,vectorpour cache locality - Maintenabilité :
optionalpour nullabilité explicite,variantpour type-safety - Évolutivité : Structures génériques (
unordered_map<ID, T>) adaptables
Cette sélection garantit des performances optimales (20 Hz gameplay) tout en maintenant un code clair et extensible.