Aller au contenu

Compétence 6 : Segmentation du Code

Segmenter chaque problème complexe en un ensemble de sous-problèmes afin d'obtenir des tâches atomiques dans un objectif de performance, d'adaptabilité et de maintenabilité en fonction des besoins du client.


Observable 6.1 : Organisation Rationnelle du Code

Structure Globale du Projet

Le projet R-Type est organisé selon une structure modulaire claire, séparant les responsabilités.

rtype/
├── src/
│   ├── server/                    # Application serveur
│   │   ├── include/               # Headers
│   │   │   ├── domain/            # Logique métier pure
│   │   │   ├── application/       # Cas d'usage et ports
│   │   │   └── infrastructure/    # Adapters et implémentations
│   │   └── src/                   # Implémentations
│   │
│   ├── client/                    # Application cliente
│   │   ├── include/               # Headers
│   │   │   ├── scenes/            # Scènes (Game, Login, Menu)
│   │   │   ├── ui/                # Composants UI
│   │   │   ├── network/           # Clients réseau
│   │   │   ├── graphics/          # Abstraction graphique
│   │   │   ├── audio/             # Voice chat
│   │   │   ├── events/            # Système d'événements
│   │   │   └── accessibility/     # Accessibilité
│   │   ├── src/                   # Implémentations
│   │   └── lib/                   # Backends graphiques
│   │       ├── sfml/              # Plugin SFML
│   │       └── sdl2/              # Plugin SDL2
│   │
│   └── common/                    # Code partagé
│       ├── protocol/              # Définitions protocole
│       ├── compression/           # LZ4 wrapper
│       └── collision/             # AABB
├── assets/                        # Ressources
│   ├── sprites/                   # Images
│   ├── fonts/                     # Polices
│   ├── audio/                     # Sons
│   └── shaders/                   # GLSL
├── tests/                         # Tests unitaires
├── docs/                          # Documentation
├── scripts/                       # Scripts build/deploy
└── discord-bot/                   # Bots Discord

Segmentation par Domaine Fonctionnel

graph TB
    subgraph "Domaines Fonctionnels"
        AUTH["Authentification<br/>(Login, Register, Sessions)"]
        GAME["Gameplay<br/>(GameWorld, Collisions, Score)"]
        SOCIAL["Social<br/>(Friends, Chat, Voice)"]
        ADMIN["Administration<br/>(AdminServer, Bots)"]
    end

    subgraph "Composants Transversaux"
        NET["Réseau<br/>(Protocol, Compression)"]
        PERSIST["Persistance<br/>(MongoDB Repositories)"]
        UI_SYS["Interface<br/>(Scenes, UI Components)"]
    end

    AUTH --> NET
    GAME --> NET
    SOCIAL --> NET
    AUTH --> PERSIST
    GAME --> PERSIST
    SOCIAL --> PERSIST
    UI_SYS --> AUTH
    UI_SYS --> GAME
    UI_SYS --> SOCIAL

Organisation des Namespaces

Les namespaces reflètent la structure des répertoires :

Namespace Contenu Fichiers
domain::entities Entités métier User.hpp, Player.hpp, Room.hpp
domain::value_objects Objets de valeur Health.hpp, Position.hpp
domain::services Services domaine GameRule.hpp
application::use_cases Cas d'usage Register.hpp, Login.hpp
application::ports::out Ports sortie ILogger.hpp, IUserRepository.hpp
infrastructure::adapters Adapters TCPAuthServer.hpp, MongoDBUserRepository.hpp
events Événements client Event.hpp, Signal.hpp
graphics Abstraction graphique IWindow.hpp
ui Composants UI Button.hpp, TextInput.hpp
protocol Protocole réseau Protocol.hpp
collision Détection collision AABB.hpp
compression Compression Compression.hpp

Exemple de Segmentation : Serveur

Problème complexe : Gérer un serveur de jeu multijoueur temps réel

Segmentation en sous-problèmes :

graph TD
    SERVEUR["Serveur Multijoueur"]

    SERVEUR --> AUTH_PROB["Authentification"]
    SERVEUR --> GAME_PROB["État de Jeu"]
    SERVEUR --> NET_PROB["Communication Réseau"]
    SERVEUR --> PERSIST_PROB["Persistance"]

    AUTH_PROB --> A1["Validation credentials"]
    AUTH_PROB --> A2["Gestion sessions"]
    AUTH_PROB --> A3["Génération tokens"]

    GAME_PROB --> G1["Collision detection"]
    GAME_PROB --> G2["Score & Combo"]
    GAME_PROB --> G3["Vagues ennemis"]
    GAME_PROB --> G4["Boss phases"]

    NET_PROB --> N1["TCP (Auth/Rooms)"]
    NET_PROB --> N2["UDP (Game)"]
    NET_PROB --> N3["Voice (Opus)"]
    NET_PROB --> N4["Compression LZ4"]

    PERSIST_PROB --> P1["Users"]
    PERSIST_PROB --> P2["Leaderboards"]
    PERSIST_PROB --> P3["Friends"]
    PERSIST_PROB --> P4["Messages"]

Mapping vers le code :

Sous-problème Fichier(s) Responsabilité
Validation credentials Password.cpp, Login.hpp Vérifier username/password
Gestion sessions SessionManager.hpp Créer/valider/expirer sessions
Génération tokens SessionManager.hpp CSPRNG via OpenSSL
Collision detection AABB.hpp, GameWorld.cpp Intersections AABB
Score & Combo GameWorld.cpp:1683-1772 Calculs points, multiplicateurs
TCP Auth TCPAuthServer.hpp TLS, login, rooms
UDP Game UDPServer.hpp Inputs, snapshots 20Hz
MongoDB Users MongoDBUserRepository.hpp CRUD utilisateurs

Observable 6.2 : Objectifs de Performance, Adaptabilité et Maintenabilité

Performance : Segmentation Optimisée

Collision Detection

Problème : Vérifier collisions entre N entités = O(N²) naïf

Segmentation appliquée :

graph LR
    subgraph "Phase 1: Missiles vs Ennemis"
        M["32 missiles max"]
        E["16 ennemis max"]
        M --> |"O(32×16)=512"| CHECK1
    end

    subgraph "Phase 2: Missiles Ennemis vs Joueurs"
        EM["32 missiles ennemis"]
        P["4 joueurs"]
        EM --> |"O(32×4)=128"| CHECK2
    end

    CHECK1["Total: 640 ops/frame"]
    CHECK2 --> CHECK1

Implémentation (GameWorld.cpp:1474-1632) :

void GameWorld::checkCollisions() {
    // Phase 1: Player missiles vs enemies (O(M×E))
    for (auto& [missileId, missile] : _missiles) {
        collision::AABB missileBox(missile.x, missile.y, Missile::WIDTH, Missile::HEIGHT);

        for (auto& [enemyId, enemy] : _enemies) {
            collision::AABB enemyBox(enemy.x, enemy.y, Enemy::WIDTH, Enemy::HEIGHT);

            if (missileBox.intersects(enemyBox)) {  // O(1)
                // Handle collision
                break;  // Missile détruit, sortir de la boucle interne
            }
        }
    }

    // Phase 2: Enemy missiles vs players (O(EM×P))
    for (auto& [missileId, missile] : _enemyMissiles) {
        // Similar logic
    }
}

Résultat : ~640 opérations/frame à 20 fps = 12 800 ops/sec (négligeable CPU)

Sérialisation Protocole

Segmentation : Chaque structure a son propre to_bytes()/from_bytes()

// Chaque structure est atomique
struct PlayerState {
    static constexpr size_t WIRE_SIZE = 23;
    void to_bytes(uint8_t* buf) const;
    static std::optional<PlayerState> from_bytes(const void* buf, size_t len);
};

struct MissileState {
    static constexpr size_t WIRE_SIZE = 8;
    // Same pattern
};

// Composition pour GameSnapshot
struct GameSnapshot {
    void to_bytes(uint8_t* buf) const {
        // Sérialise chaque sous-composant
        for (const auto& player : players) {
            player.to_bytes(ptr);
            ptr += PlayerState::WIRE_SIZE;
        }
    }
};

Adaptabilité : Points d'Extension

1. Ajout d'un Nouveau Backend Graphique

Segmentation : Interface IWindow découple le code métier du rendu

// Ajouter Raylib = 1 nouveau fichier
class RaylibWindow : public IWindow {
    void drawSprite(...) override { /* Raylib */ }
    void drawRect(...) override { /* Raylib */ }
};

Fichiers impactés : lib/raylib/ (nouveau) uniquement

2. Ajout d'un Nouveau Type d'Arme

Segmentation : Pattern Strategy avec enum

// Étape 1: Ajouter à l'enum
enum class WeaponType { Standard, Spread, Laser, Missile, WaveCannon, NewWeapon };

// Étape 2: Ajouter le case dans getDamage/getSpeed
static uint8_t getBaseDamage(WeaponType type) {
    switch (type) {
        case WeaponType::NewWeapon: return 30;  // Nouveau
        // ...
    }
}

Fichiers impactés : Protocol.hpp (enum), GameWorld.hpp (constantes)

3. Ajout d'un Nouveau Repository

Segmentation : Pattern Repository avec interface

// Étape 1: Créer l'interface (port)
class IAchievementRepository {
    virtual void save(const Achievement& a) = 0;
    virtual std::vector<Achievement> findByUserId(const std::string& id) = 0;
};

// Étape 2: Implémenter l'adapter
class MongoDBAchievementRepository : public IAchievementRepository {
    // Implementation
};

// Étape 3: Injecter dans les UseCases

Fichiers impactés : - application/ports/out/persistence/IAchievementRepository.hpp (nouveau) - infrastructure/adapters/out/persistence/MongoDBAchievementRepository.hpp (nouveau)

Maintenabilité : Isolation des Changements

Tableau d'Impact des Modifications

Type de Changement Fichiers Impactés Risque Régression
Changer cipher TLS TCPAuthServer.cpp Faible
Ajouter champ User User.hpp, MongoDBUserRepository.cpp Moyen
Modifier collision AABB.hpp, GameWorld.cpp Faible
Nouveau message protocole Protocol.hpp, handlers Moyen
Ajouter scène NewScene.hpp/cpp, SceneManager Faible
Changer DB (MongoDB→Postgres) infrastructure/adapters/out/ Faible (ports isolent)

Métriques de Couplage

graph LR
    subgraph "Faible Couplage (Bon)"
        A["Domain"] -.->|"0 dépendances"| B["Infrastructure"]
    end

    subgraph "Couplage Maîtrisé"
        C["UseCase"] -->|"via ports"| D["Repositories"]
    end

    subgraph "Couplage Fort (Évité)"
        E["Code métier"] -->|"INTERDIT"| F["MongoDB direct"]
    end

    style A fill:#90EE90
    style E fill:#FFB6C1

Tests Unitaires : Bénéfice de la Segmentation

La segmentation permet de tester chaque composant isolément :

// Test du Domain (aucune dépendance)
TEST(HealthTest, DamageReducesHealth) {
    Health h(100.0f);
    Health damaged = h.damage(30.0f);
    EXPECT_EQ(damaged.getValue(), 70.0f);
}

// Test du UseCase (mock des ports)
TEST(RegisterTest, SuccessfulRegistration) {
    auto mockRepo = std::make_shared<MockUserRepository>();
    auto mockIdGen = std::make_shared<MockIdGenerator>();
    auto mockLogger = std::make_shared<MockLogger>();

    Register useCase(mockRepo, mockIdGen, mockLogger);
    auto result = useCase.execute("user", "email@test.com", "password");

    EXPECT_TRUE(result.has_value());
}

// Test du Protocol (parsing)
TEST(ProtocolTest, PlayerStateSerialization) {
    PlayerState ps{.id = 1, .x = 100, .y = 200, .health = 75};

    uint8_t buffer[PlayerState::WIRE_SIZE];
    ps.to_bytes(buffer);

    auto parsed = PlayerState::from_bytes(buffer, sizeof(buffer));
    EXPECT_TRUE(parsed.has_value());
    EXPECT_EQ(parsed->id, 1);
    EXPECT_EQ(parsed->x, 100);
}

Conclusion

La segmentation du code R-Type répond aux trois objectifs :

Objectif Réalisation
Performance Collisions O(M×E), sérialisation O(n)
Adaptabilité Interfaces (IWindow, IRepository), enums extensibles
Maintenabilité Isolation domaine/infra, namespaces clairs, tests isolés

Cette organisation permet : - Développement parallèle : Équipes peuvent travailler sur serveur/client/common séparément - Évolution incrémentale : Ajouter features sans refactoring massif - Debugging ciblé : Problème localisé dans un namespace/fichier spécifique - Documentation automatique : Structure reflète l'architecture logique