Forum Programmation.autre Architecture pour faire dialoguer une interface graphique et un moteur de jeu. Via XMPP ?

Posté par  (site web personnel, Mastodon) . Licence CC By‑SA.
Étiquettes :
1
21
nov.
2025

Hello,

J’ai envie de modifier mon jeu muttum pour le rendre multiplateforme (pour l’instant il ne vise que la plateforme GNOME).

Quand j’ai commencé à coder ce jeu, j’avais déjà pensé à ça et j’avais séparé la logique « métier » (enfin, le moteur de jeu) dans une bibliothèque indépendante (libmuttum).

Malgré cette séparation, l’interface graphique est très liée au moteur de jeu.

Je m’en suis rendu compte quand j’ai voulu commencer à porter le jeu pour Lomiri qui utilise Qt 5.15 (je dois partager mon code Rust avec du C++ via cxx-qt).

Actuellement, pour lier l’interface au moteur de jeu, je crée une instance de mon moteur au démarrage de l’application GNOME et je l’attache comme propriété (pour le garder en vie tant que l’application est ouverte).

Ensuite, la fenêtre principale (la seule fille de l’application) interagit avec le moteur à travers la propriété de l’application.

Concrètement ça donne un code dans ce genre quand la fenêtre veut ajouter une lettre dans le plateau de jeu :

mod imp {
    #[derive(Debug, Default)]
    pub struct MuttumApplication {
        pub engine: Rc<RefCell<Engine>>,
    }
}

impl MuttumWindow {
    fn engine(&self) -> Option<Rc<RefCell<Engine>>> {
        if let Some(app) = self.application() {
            let engine = app
                .downcast::<crate::application::MuttumApplication>()
                .unwrap()
                .engine();
            return Some(engine);
        }

        None
    }

    fn on_key_released(
        &self,
        _controller: &EventControllerKey,
        key_val: Key,
        _key_code: u32,
        state: ModifierType,
    ) {
        let engine = self.engine().unwrap();
        let mut engine = engine.borrow_mut();

      if let Some(letter) = key_val.to_unicode() {
          engine.add_letter(&String::from(letter)).unwrap();
      };
    }
}

Pour mieux séparer l’interface graphique et le moteur de jeu, je souhaite mettre le moteur de jeu dans un thread séparé et utiliser des messages pour que l’interface graphique puisse agir sur le moteur.

L’avantage de cette idée est que je n’aurai plus de risque de bloquer l’interface graphique quand mon moteur utilisera trop de CPU (c’est déjà le cas maintenant quand le moteur génère le dictionnaire de mots jouables depuis la liste de mots existants).

Est-ce que pour une tâche intensive en CPU, un thread séparé suffirait ou il faut créer un process complet en parallèle  ?

Je ne suis pas sûr, parce que la documentation de spawn_blocking dit clairement que c’est à utiliser pour des tâches intensives en I/O et pas en CPU. En soit, ça ne changera pas beaucoup la méthode à laquelle j’ai pensé pour faire communiquer l’interface graphique et le moteur de jeu.

Pour l’implémentation du transfert de message entre l’interface graphique et le moteur, ce que j’ai fais dans un premier essais pour GNOME :

  1. au démarrage l’application crée 2 channel de communications: 1 pour communiquer dans le sens Interface → Moteur de jeu et 1 second pour le sens inverse.
  2. ensuite, elle démarre le moteur de jeu via une nouvelle interface Runner du moteur de jeu dans un thread séparé (via méthode spawn_blocking de gio de la glib). Ce runner écoute les messages de l’interface sur le premier canal et répond sur le second
  3. l’application va rendre disponible globalement un « Client » pour envoyer des requêtes au moteur1
  4. l’application va générer des signaux pour chaque réponse reçue
  5. Chaque élément graphique qui envoie un message via le Client va générer sa requête avec un UUID. Ensuite il se connecte aux signaux de l’application et réagit si la réponse contient le UUID qu’il a crée juste avant

Ma réflexion maintenant est que, si je me souviens bien, ce genre de communication est typique de ce que propose XMPP avec les stanzas de type IQ (Information Query).

Si j’implémente la communication entre les 2 threads via des messages IQ, est-ce que vous pensez que ça serait trop complexe  ?

Pour l’instant, je ne ferais pas du XML, mais je ferais en sorte que mes objets Rust soient compatibles avec IQ et qu’ils pourront être sérializés/désérializés en XML.

L’idée, est que mon interface graphique pourrait être vu comme un client XMPP et le moteur de jeu pourrait être un composant XMPP.

Je me dis que si j’arrive bien à faire cette communication avec des stanzas IQ, je pourrais à l’avenir même proposer aux joueurs de jouer ensemble à distance :
j’installerai mon moteur de jeu comme un composant à côté de mon serveur XMPP et je fais en sorte que chaque joueur utilise un compte XMPP anonyme automatiquement généré par les différentes applications (GNOME, Lomiri, HTML…).

Cette dernière étape sera clairement plus difficile, car les applications devront devenir de réels clients XMPP (gestion de la connexion, des Streams et du XML), mais ça me plaît de me dire que ça sera possible d’essayer.

Je me suis déjà amusé à construire du code il y a quelques années qui connectait une application à un serveur XMPP en utilisant WebSocket, je sais que c’est quand même faisable  :)

Comme je n’ai pas trop l’habitude de faire des interfaces séparées en local (sans passer par le protocol HTTP), je serai vraiment intéresser par vos retours d’expérience sur la communication inter-thread / inter-processus et sur la manière de bien gérer ce genre d’architecture.


  1. je vais pouvoir aussi implémenter le client directement dans ma bibliothèque libmuttum. Dans ce cas, j’utiliserais plutôt std ::thread à la place de la glib. Et l’application se liera au client, comme je fais actuellement avec le moteur de jeu : elle l’instancie et le garde en propriété globale. 

  • # Je ne sais pas quel type de messages tu passes .....

    Posté par  . Évalué à 2 (+0/-0).

    … mais pour un jeu, mettre du XMPP ça pourrait être lourd (je pense notamment aux étapes de serialisation/deserialisation XML).

    Il ne faut pas oublier que XML, à la base, c'est un format commun pour que des applications qui tournent sur des systèmes avec format incompatibles puissent lire/écrire des docments - ou communiquer ensemble. LA tu es dans la communication inter-thread/inter-processus sur la même machine à priori, donc j'ai l'impression qu'un bon vieux format binaire que les threads et les processus pourront lire (via la même lib) fera l'affaire. Au pire voir s'il n'existe pas un bus local style dbus mais en plus léger qui pourrait faire l'affaire. A moins qu'a terme tu prévois de faire tourner tes processus sur des machines différentes (mais completement différentes en terme d'archi) et dans ce cas XML pourrait être utile..

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.