Migration d’agents

La migration d’agents permet de déplacer un agent en cours d’exécution d’une machine vers une autre. L’agent transfère ses attributs (son état observable) et se recrée sur la machine cible, grâce à une factory enregistrée côté destination.

Note

En C++, les objets ne sont pas sérialisables automatiquement comme en Java. La migration gAgent transfère les attributs de l’agent (addAttribut / setAttribut), mais pas l’état interne des behaviours. L’agent repart de setup() avec ses attributs restaurés.


Principe de fonctionnement

Machine A (source)                  Machine B (destination)
──────────────────                  ───────────────────────
Agent X actif                       AgentFactory::startMigrationServer()
     │                                    │
     │  doMove({"192.168.1.20"})           │
     │──────── ARRIVE MonAgent X ─────────>│
     │         id:x;pos_x:10;pos_y:5;      │
     │                                     │  factory.create("MonAgent")
     │                               <─── OK │  agent->setAgentName("X")
     │                                     │  restoreAttributs(...)
     │  deregister AMS/DF                  │  agent->init()
     │  _exit(0)                           │

Le port de migration est control_port + 1 (défaut : 40016).


Étape 1 — Déclarer le type d’agent

Chaque agent migrable doit surcharger agentTypeName() pour retourner un identifiant de type unique :

#include <gagent/core/Agent.hpp>
#include <gagent/core/AgentFactory.hpp>

class AgentMobile : public Agent {
    int missions_faites_ = 0;

public:
    // ← Obligatoire pour la migration
    std::string agentTypeName() const override { return "AgentMobile"; }

    void setup() override {
        // Les attributs sont restaurés AVANT setup()
        std::string pos = getAttribut("position");
        std::cout << "Démarrage à la position : " << pos << "\n";

        addBehaviour(new MissionBehaviour(this));
    }
};

Étape 2 — Enregistrer le type et démarrer le serveur de migration

Dans le main() de chaque machine participante :

#include <gagent/core/AgentFactory.hpp>
#include <thread>

int main() {
    // Enregistrer le type
    AgentFactory::instance().registerType(
        "AgentMobile",
        []() -> Agent* { return new AgentMobile(); }
    );

    // Démarrer le serveur de migration en arrière-plan (port 40016 par défaut)
    std::thread([] {
        AgentFactory::instance().startMigrationServer();
    }).detach();

    // Créer et lancer l'agent normalement
    AgentMobile ag;
    ag.init();

    AgentCore::syncAgentSystem();
    return 0;
}

Étape 3 — Déclencher la migration depuis un behaviour

Depuis n’importe quel behaviour, appelez doMove(target) :

#include <gagent/core/Agent.hpp>

class MissionBehaviour : public Behaviour {
    int etapes_ = 0;
public:
    MissionBehaviour(Agent* ag) : Behaviour(ag) {}

    void action() override {
        etapes_++;
        this_agent->setAttribut("etapes", std::to_string(etapes_));
        std::cout << "Étape " << etapes_ << " terminée\n";

        if (etapes_ >= 3) {
            std::cout << "Migration vers 192.168.1.20...\n";
            // doMove suspend les behaviours, sérialise les attributs,
            // contacte la destination et se supprime si succès.
            this_agent->doMove(Agent::MigrationTarget{"192.168.1.20"});
        }
    }

    bool done() override { return false; }  // la migration fait _exit(0)
};

Exemple complet — agent nomade ping/pong

Deux machines : 192.168.1.10 (A) et 192.168.1.20 (B). L’agent démarre sur A, migre vers B après 3 itérations, puis termine.

Code commun (même binaire déployé sur A et B) :

#include <gagent/core/Agent.hpp>
#include <gagent/core/AgentCore.hpp>
#include <gagent/core/AgentFactory.hpp>
#include <iostream>
#include <thread>

class BehaviourNomade : public Behaviour {
    int tours_ = 0;
public:
    BehaviourNomade(Agent* ag) : Behaviour(ag) {}

    void onStart() override {
        // Récupérer le compteur depuis les attributs migrés
        std::string s = this_agent->getAttribut("tours");
        if (!s.empty()) tours_ = std::stoi(s);
        std::cout << "[" << this_agent->getAgentId().getAgentName()
                  << "] Démarré, tours = " << tours_ << "\n";
    }

    void action() override {
        tours_++;
        this_agent->setAttribut("tours", std::to_string(tours_));
        std::cout << "[agent] Tour " << tours_ << "\n";
        sleep(1);

        if (tours_ == 3) {
            std::cout << "[agent] Migration vers B...\n";
            this_agent->doMove(Agent::MigrationTarget{"192.168.1.20"});
        }
    }

    bool done() override { return tours_ >= 6; }
    void onEnd() override { this_agent->doDelete(); }
};

class AgentNomade : public Agent {
public:
    std::string agentTypeName() const override { return "AgentNomade"; }

    void setup() override {
        addAttribut("tours");
        addBehaviour(new BehaviourNomade(this));
    }
};

int main() {
    AgentFactory::instance().registerType(
        "AgentNomade", []() -> Agent* { return new AgentNomade(); });

    std::thread([] {
        AgentFactory::instance().startMigrationServer();
    }).detach();

    // Sur machine A uniquement : créer l'agent initial
    AgentNomade ag;
    ag.setAgentName("nomade");
    ag.init();

    AgentCore::syncAgentSystem();
    return 0;
}

Lancement :

# Sur B (démarrer d'abord le serveur de réception) :
./nomade_agent   # le serveur de migration écoute sur :40016

# Sur A (créer l'agent) :
./nomade_agent   # l'agent démarre, migre vers B après 3 tours

Attributs et migration

Seuls les attributs déclarés avec addAttribut() sont transférés. Déclarez-les dans setup() avant d’utiliser setAttribut() :

void setup() override {
    addAttribut("position");   // déclarer
    addAttribut("compteur");

    // Lire la valeur migrée (vide si premier démarrage)
    std::string pos = getAttribut("position");
    if (pos.empty()) pos = "depart";

    setAttribut("position", pos);
    // ...
}

Limitations

  • L’état interne des behaviours (variables locales, compteurs) n’est pas migré automatiquement — passez-le via les attributs comme dans l’exemple.

  • La classe de l’agent doit être compilée et enregistrée sur la machine cible.

  • En cas d’échec de connexion vers la destination, la migration est annulée et l’agent reprend son exécution normalement.

  • Port 40016 (control_port + 1) doit être ouvert entre les machines.