3 — Les behaviours
Un behaviour est une tâche que vous confiez à un agent. L’agent peut en avoir plusieurs, qui s’exécutent toutes en même temps, de façon indépendante.
Ce tutoriel présente les différents types de behaviours disponibles et quand les utiliser.
Qu’est-ce qu’un behaviour ?
Imaginez un agent comme un employé. Cet employé peut avoir plusieurs responsabilités en parallèle : répondre aux mails, surveiller un tableau de bord, envoyer des rapports toutes les heures. Chacune de ces responsabilités est un behaviour.
Un behaviour répond à deux questions :
Que faire ? → défini dans
action()Est-ce terminé ? → défini dans
done()
Le moteur de gAgent appelle action() encore et encore, jusqu’à ce
que done() réponde « oui ».
Vue d’ensemble des types
Type |
Quand l’utiliser |
|---|---|
|
Quand vous voulez contrôler vous-même la condition d’arrêt |
|
Une tâche qui s’exécute une seule fois puis s’arrête |
|
Une tâche qui tourne indéfiniment |
|
Attendre un certain délai, puis faire quelque chose une fois |
|
Répéter quelque chose à intervalle régulier |
|
Enchaîner plusieurs sous-behaviours dans l’ordre |
|
Exécuter plusieurs sous-behaviours en même temps |
|
Machine à états finis — un état = un behaviour |
Behaviour — le behaviour de base
C’est le type le plus flexible. Vous implémentez action() pour décrire
ce que fait l’agent, et done() pour dire quand il doit s’arrêter.
Exemple — un agent qui compte jusqu’à 10 :
class CompteurBehaviour : public Behaviour {
int count_ = 0;
public:
CompteurBehaviour(Agent* ag) : Behaviour(ag) {}
void action() override {
std::cout << "Compteur : " << ++count_ << std::endl;
sleep(1);
}
bool done() override {
return count_ >= 10;
}
};
Vous pouvez aussi utiliser onStart() et onEnd() pour exécuter du
code une seule fois au début et à la fin :
class AvecInitBehaviour : public Behaviour {
int count_ = 0;
public:
AvecInitBehaviour(Agent* ag) : Behaviour(ag) {}
void onStart() override {
std::cout << "Démarrage de la tâche." << std::endl;
}
void action() override {
std::cout << "Travail en cours... (" << ++count_ << ")" << std::endl;
sleep(1);
}
bool done() override {
return count_ >= 5;
}
void onEnd() override {
std::cout << "Tâche terminée." << std::endl;
}
};
OneShotBehaviour — faire quelque chose une seule fois
Le behaviour s’exécute exactement une fois, puis s’arrête
automatiquement. Vous n’avez pas à implémenter done().
Utile pour : une initialisation, l’envoi d’un message de démarrage, l’écriture d’un fichier de log, une action déclenchée par un événement.
Exemple — envoyer une notification au démarrage :
class NotificationDemarrage : public OneShotBehaviour {
public:
NotificationDemarrage(Agent* ag) : OneShotBehaviour(ag) {}
void action() override {
std::cout << "Agent démarré, notification envoyée." << std::endl;
// envoyer un message, écrire dans un fichier, etc.
}
};
CyclicBehaviour — tourner indéfiniment
Le behaviour ne s’arrête jamais de lui-même. Vous n’avez pas à implémenter
done(). L’agent continue jusqu’à ce qu’il reçoive l’ordre de
s’arrêter (Ctrl+C ou doDelete()).
Utile pour : surveiller une source de données en continu, écouter des messages entrants, maintenir un état actif en permanence.
Exemple — surveiller une valeur en continu :
class SurveillanceBehaviour : public CyclicBehaviour {
public:
SurveillanceBehaviour(Agent* ag) : CyclicBehaviour(ag) {}
void action() override {
float temperature = lireTemperature(); // ta fonction
if (temperature > 80.0f) {
std::cout << "ALERTE : température critique !" << std::endl;
}
sleep(5); // vérifie toutes les 5 secondes
}
};
WakerBehaviour — attendre puis agir
Le behaviour attend un certain délai (en millisecondes), puis exécute
onWake() une seule fois et s’arrête. C’est l’équivalent d’un
minuteur.
Utile pour : déclencher une action après un délai, simuler un temps de traitement, planifier une tâche différée.
Exemple — envoyer un rapport 30 secondes après le démarrage :
class RapportDiffere : public WakerBehaviour {
public:
RapportDiffere(Agent* ag) : WakerBehaviour(ag, 30000) {}
// ↑
// 30 000 ms = 30 s
void onWake() override {
std::cout << "30 secondes écoulées — envoi du rapport." << std::endl;
// générer et envoyer le rapport
}
};
TickerBehaviour — répéter à intervalle régulier
Le behaviour appelle onTick() toutes les N millisecondes, indéfiniment.
C’est l’équivalent d’une horloge ou d’un timer périodique.
Utile pour : envoyer des mises à jour régulières, collecter des métriques, publier un état toutes les X secondes.
Exemple — publier l’état de l’agent toutes les 10 secondes :
class PublicationEtat : public TickerBehaviour {
public:
PublicationEtat(Agent* ag) : TickerBehaviour(ag, 10000) {}
// ↑
// 10 000 ms = 10 s
void onTick() override {
std::cout << "État : en fonctionnement." << std::endl;
// publier l'état, mettre à jour une base de données, etc.
}
};
Combiner plusieurs behaviours
Un agent peut avoir autant de behaviours que nécessaire. Ils s’exécutent tous en même temps, chacun de façon indépendante.
Exemple — un agent de supervision avec trois tâches simultanées :
class AgentSupervision : public Agent {
public:
void setup() override {
// Envoie une notification de démarrage (une seule fois)
addBehaviour(new NotificationDemarrage(this));
// Surveille la température en continu
addBehaviour(new SurveillanceBehaviour(this));
// Publie un rapport toutes les 10 secondes
addBehaviour(new PublicationEtat(this));
}
};
Les trois tâches tournent en parallèle dès que l’agent démarre.
L’agent s’arrête lorsque tous ses behaviours sont terminés — ici,
SurveillanceBehaviour et PublicationEtat étant cycliques,
l’agent tourne jusqu’à Ctrl+C.
Behaviours composites
Les behaviours composites permettent d”orchestrer plusieurs sous-tâches à l’intérieur d’un seul behaviour. Ils s’ajoutent à l’agent comme n’importe quel autre behaviour, mais délèguent leur exécution à des enfants.
Note
Les sous-behaviours d’un composite s’exécutent dans le même thread
que le composite lui-même. Pour une vraie exécution en parallèle sur des
threads séparés, utilisez addBehaviour() directement dans setup().
SequentialBehaviour — enchaîner des étapes dans l’ordre
Exécute les sous-behaviours un par un dans l’ordre où ils ont été
ajoutés. Dès qu’un enfant est terminé (done() retourne true),
le suivant démarre.
Exemple — processus en trois étapes :
class EtapeInit : public OneShotBehaviour {
public:
EtapeInit(Agent* ag) : OneShotBehaviour(ag) {}
void action() override {
std::cout << "Étape 1 : initialisation" << std::endl;
}
};
class EtapeTraitement : public Behaviour {
int steps_ = 0;
public:
EtapeTraitement(Agent* ag) : Behaviour(ag) {}
void action() override {
std::cout << "Étape 2 : traitement " << ++steps_ << "/3" << std::endl;
sleep(1);
}
bool done() override { return steps_ >= 3; }
};
class EtapeConclusion : public OneShotBehaviour {
public:
EtapeConclusion(Agent* ag) : OneShotBehaviour(ag) {}
void action() override {
std::cout << "Étape 3 : rapport final" << std::endl;
}
};
class MonAgent : public Agent {
public:
void setup() override {
auto* seq = new SequentialBehaviour(this);
seq->addSubBehaviour(new EtapeInit(this));
seq->addSubBehaviour(new EtapeTraitement(this));
seq->addSubBehaviour(new EtapeConclusion(this));
addBehaviour(seq);
}
};
ParallelBehaviour — plusieurs tâches en un seul behaviour
Appelle action() sur tous les enfants à chaque tick. La condition
de fin est configurable :
WhenDone::ALL(défaut) — attend que tous les enfants aient finiWhenDone::ANY— termine dès qu”un enfant est terminé
Exemple — attendre que deux capteurs soient prêts :
class LectureCapteur : public Behaviour {
std::string nom_;
int lectures_ = 0;
public:
LectureCapteur(Agent* ag, std::string nom)
: Behaviour(ag), nom_(std::move(nom)) {}
void action() override {
std::cout << nom_ << " : lecture " << ++lectures_ << std::endl;
sleep(1);
}
bool done() override { return lectures_ >= 5; }
};
class MonAgent : public Agent {
public:
void setup() override {
// Termine quand les DEUX capteurs ont fait 5 lectures
auto* par = new ParallelBehaviour(this, ParallelBehaviour::WhenDone::ALL);
par->addSubBehaviour(new LectureCapteur(this, "Capteur-A"));
par->addSubBehaviour(new LectureCapteur(this, "Capteur-B"));
addBehaviour(par);
}
};
FSMBehaviour — machine à états finis
Chaque état est un behaviour. Quand un état est terminé, sa méthode
exitValue() détermine la prochaine transition.
Exemple simple — feu de signalisation (Rouge → Vert → Orange) :
class EtatFeu : public OneShotBehaviour {
std::string couleur_;
int exit_val_;
public:
EtatFeu(Agent* ag, std::string couleur, int exit_val)
: OneShotBehaviour(ag), couleur_(std::move(couleur))
, exit_val_(exit_val) {}
void action() override {
std::cout << "Feu : " << couleur_ << std::endl;
sleep(2);
}
int exitValue() override { return exit_val_; }
};
class AgentFeu : public Agent {
public:
void setup() override {
auto* fsm = new FSMBehaviour(this);
fsm->registerFirstState(new EtatFeu(this, "ROUGE", 0), "rouge");
fsm->registerState (new EtatFeu(this, "VERT", 0), "vert");
fsm->registerLastState (new EtatFeu(this, "ORANGE", 0), "orange");
fsm->registerDefaultTransition("rouge", "vert");
fsm->registerDefaultTransition("vert", "orange");
// orange est un état "last" : plus de transition nécessaire
addBehaviour(fsm);
}
};
Pour une FSM qui boucle indéfiniment, utilisez des états normaux (non
last) avec une transition qui revient au départ :
fsm->registerState(new EtatFeu(this, "ROUGE", 0), "rouge");
fsm->registerState(new EtatFeu(this, "VERT", 0), "vert");
fsm->registerState(new EtatFeu(this, "ORANGE", 0), "orange");
fsm->registerDefaultTransition("rouge", "vert");
fsm->registerDefaultTransition("vert", "orange");
fsm->registerDefaultTransition("orange", "rouge"); // ← boucle
fsm->registerFirstState(/* même état */ ..., "rouge");
Exemple avec graphe — distributeur automatique
Le graphe ci-dessous montre les états et les transitions conditionnelles.
Les labels sur les flèches correspondent aux valeurs retournées par
exitValue().
![digraph distributeur {
rankdir=LR;
node [shape=rectangle, style=rounded, fontname="sans-serif"];
edge [fontname="sans-serif", fontsize=10];
start [shape=point, width=0.2];
DIST [label="DISTRIBUTION\n(last)", shape=doublecircle];
ANNUL [label="ANNULATION\n(last)", shape=doublecircle];
start -> ATTENTE;
ATTENTE -> SELECTION [label=" 0 : monnaie insérée"];
SELECTION -> VERIFICATION [label=" 0 : produit choisi"];
SELECTION -> ANNULATION [label=" 1 : annulé"];
VERIFICATION -> DISTRIBUTION [label=" 0 : en stock"];
VERIFICATION -> ANNULATION [label=" 1 : rupture"];
ANNULATION -> ATTENTE [label=" 0 (défaut)"];
}](../_images/graphviz-bdff708d4c26a898f064882a837040f8e28ba4ab.png)
Correspondance graphe → code :
// ── États ────────────────────────────────────────────────────────────────
// Attend qu'une pièce soit insérée (simulé par un délai).
// exitValue 0 = pièce détectée
class EtatAttente : public Behaviour {
int ticks_ = 0;
public:
EtatAttente(Agent* ag) : Behaviour(ag) {}
void onStart() override {
std::cout << "[distributeur] En attente d'une pièce...\n";
ticks_ = 0;
}
void action() override { sleep(1); ticks_++; }
bool done() override { return ticks_ >= 2; } // 2s = pièce insérée
int exitValue() override { return 0; }
};
// Simule le choix d'un produit.
// exitValue 0 = produit sélectionné, 1 = annulé
class EtatSelection : public Behaviour {
int choix_ = 0;
public:
EtatSelection(Agent* ag) : Behaviour(ag) {}
void onStart() override {
std::cout << "[distributeur] Sélection du produit...\n";
choix_ = 0;
}
void action() override { sleep(1); choix_++; }
bool done() override { return choix_ >= 1; }
// 0 = produit choisi, 1 = annulé (simulé selon la parité)
int exitValue() override { return (choix_ % 2 == 0) ? 1 : 0; }
};
// Vérifie le stock.
// exitValue 0 = en stock, 1 = rupture
class EtatVerification : public OneShotBehaviour {
bool en_stock_;
public:
EtatVerification(Agent* ag, bool en_stock)
: OneShotBehaviour(ag), en_stock_(en_stock) {}
void action() override {
std::cout << "[distributeur] Vérification stock : "
<< (en_stock_ ? "OK" : "rupture") << "\n";
}
int exitValue() override { return en_stock_ ? 0 : 1; }
};
class EtatDistribution : public OneShotBehaviour {
public:
EtatDistribution(Agent* ag) : OneShotBehaviour(ag) {}
void action() override {
std::cout << "[distributeur] Produit distribué !\n";
}
};
class EtatAnnulation : public OneShotBehaviour {
public:
EtatAnnulation(Agent* ag) : OneShotBehaviour(ag) {}
void action() override {
std::cout << "[distributeur] Monnaie rendue, opération annulée.\n";
}
int exitValue() override { return 0; } // → retour ATTENTE
};
// ── Agent ─────────────────────────────────────────────────────────────────
class AgentDistributeur : public Agent {
public:
void setup() override {
auto* fsm = new FSMBehaviour(this);
// États
fsm->registerFirstState(new EtatAttente(this), "attente");
fsm->registerState (new EtatSelection(this), "selection");
fsm->registerState (new EtatVerification(this, true), "verification");
fsm->registerLastState (new EtatDistribution(this), "distribution");
fsm->registerState (new EtatAnnulation(this), "annulation");
// Transitions depuis ATTENTE
fsm->registerTransition("attente", "selection", 0); // pièce OK
// Transitions depuis SELECTION
fsm->registerTransition("selection", "verification", 0); // produit choisi
fsm->registerTransition("selection", "annulation", 1); // annulé
// Transitions depuis VERIFICATION
fsm->registerTransition("verification", "distribution", 0); // en stock
fsm->registerTransition("verification", "annulation", 1); // rupture
// ANNULATION retourne à ATTENTE
fsm->registerDefaultTransition("annulation", "attente");
addBehaviour(fsm);
}
};
Note
registerLastState marque distribution comme état terminal :
la FSM s’arrête dès que cet état est terminé.
annulation est un état ordinaire avec une transition de retour
vers attente — la machine continue à servir.
Résumé
Type |
À implémenter |
Comportement |
|---|---|---|
|
|
S’arrête quand |
|
|
S’exécute une seule fois puis s’arrête |
|
|
Tourne indéfiniment |
|
|
Attend N millisecondes, exécute |
|
|
Exécute |
|
|
Enchaîne les enfants dans l’ordre |
|
|
Tick tous les enfants à chaque tour (ALL ou ANY) |
|
|
Machine à états, transitions par |