Architecture par composant - Tetris générique (2)

Nous avons expliqué dans notre premier article le but de notre travail et l’architecture générique par composants que nous allons mettre en place en mettant en œuvre le jeu Tetris sur plusieurs plateformes. Continuons l’aventure avec une première implémentation simple.

Définitions : composant, entité, module, système ?

Aïe, on attaque quelque chose de sacré, selon moi chacun peut définir les concours de tel ou tel mot en fonction de sa vision et son besoin. Il y a cependant des modes ou des nouvelles techniques liées à l’évolution du matériel.

Quelques nouvelles méthodes de design ont émergées ces dernières années : ECS (Entity Component System), DOD (Data oriented Design). A priori, elles sont apparues dans le domaine du jeu vidéo pour résoudre des problèmes de performances liées à la gestion mémoire de milliers d’objet de façon efficace (sur les consoles limitées et éviter des ennuis de mémoire cache). Ces deux méthodes sont un peu liées, voire complémentaires. Dans tous les cas, essayez d’oublier le sacro-saint héritage et design objet appris lors de vos études. Il est utile, mais pas toujours. L’architecture logicielle répond surtout à des contraintes, il faut savoir y répondre en utilisant la méthode qui convient le mieux sans être dogmatique. Si on fait le parallèle avec les design patterns du C++, on peut dire que la mode est à la composition plutôt qu’à l’héritage, l’idée générale est de regrouper les données au même endroit en mémoire.

Voici ce que l’on peut dire sur ces nommages :

  • Un composant peut être vu comme un composant électronique : il a des broches définissant son interface ; c’est avec ces broches que nous allons communiquer avec lui (des fonctions). Il est multipliable à volonté et sera utilisé par des entités concrètes
  • Une entité représente quelque chose de réel dans le monde, elle sera composée de composants (la boucle est bouclée)
  • Un module = un composant
  • Un système regroupera tout cela et se chargera de la partie dynamique

Notre Tetris sera porté sur plusieurs plateformes ; dès lors, nous aurons à créer un système différent à chaque fois, avec ses propres contraintes, ses spécificités. Ce système créera des entités en ré-utilisant nos modules (composants) logiciels génériques.

Bonnes pratiques en C

Il existe quelques règles simples pour avoir un code le plus modulaire possible en langage C. On peut par exemple copier un peu le modèle objet : un design pattern que j’utilise fréquemment est de développer un module logiciel de la façon suivante :

  • Un module possède plusieurs variables, publiques (peut être utilisées par d’autres modules) ou privées (variables utilisées uniquement pour le bon fonctionnement du module en lui-même)
  • On regroupe ces variables dans une structure de contexte. Cette structure sera passée par référence (pointeur) à toutes les fonctions du module qui en auront besoin
  • C’est le code appelant qui aura la charge d’instancier cette structure de contexte et de la passer aux fonctions du module pour traitement

Contours d’un composant

Autant la définition d’un composant, au sens UML, est claire, autant sa mise en œuvre est plus compliquée. Déjà, première question : quel est le contour du composant ? Quelles sont ses limites ? Quelles fonctions mettre dedans ?

C’est là le rôle d’un architecte logiciel. Il aura la tâche de découper en composants son application. Il n’y a rien de fondamentalement faux, tout du moins au début, c’est vraiment une histoire de vision personnelle et de stratégie à moyen terme (évolutions que l’on désire apporter dans un avenir proche). En effet, un composant peu être soit “gros”, touffu, comportant beaucoup de fonctions ou soit léger et très limité.

Pour ma part, j’opte pour le second choix : je vois un composant applicatif comme quelque chose de petit, un logiciel en C pur, c’est-à-dire qu’il peut potentiellement être compilé par n’importe quel compilateur. On va donc éviter d’embarquer du code d’appel système, d’accès au réseau ou fichier ou d’autres composants.

Dès lors, voici les éléments clés de notre architecture par composant :

  • Des composants léger, sans dépendances, purement logiciels (C pur)
  • Une librairie externe est un composant
  • Des composants systèmes, propres à une plateforme
  • Des entités dotées d’une interface commune de gestion dynamique (init, start, stop…)
  • Un système principal, le point d’entrée, de plus haut niveau se chargeant de piloter l’ensemble

Reprenons le diagramme d’architecture du code d’origine. Nous avons, dans un seul fichier, le code de la logique du Tetris (déplacement des pièces dans une matrice) et le code du “rendu”. Celui-ci est très fortement lié à l’écran monochrome de la centrale de mesure et de la librairie de primitives utilisée dans les écrans de l’appareil.

Il faut casser cela. Pour deux raisons :

  1. Nous avons dit que nous voulions des composants “légers”, purement logiciels, réalisant une fonction logique. On voit bien ici que l’on mélange deux notions
  2. Mettre le code de rendu dans le code de la logique va nous bloquer demain : changement de résolution, ajout de couleurs … il faudra tout changer dans ce composant. Ce qui est dommage, car la logique elle ne changera pas.

Dès lors, on peut déjà créer deux composants : le composant A se chargera de la logique de Tetris, le composant B un rendu. Nous allons d’ailleurs nommer ce composant B explicitement : il s’agira d’un rendu “faible résolution et monochrome”. Selon la cible choisie, on choisira tel ou tel composant de rendu.

image

Premier composant

Après cette introduction un peu théorique, continuons notre travail sur le Tetris. Nous allons créer notre premier composant : le moteur de jeu. On l’extrait du fichier, en renommant au passage les variables et les fonctions (nommage du style XXX_MaFonction(), XXX étant le nom du module). On n’oublie pas ses fondamentaux en C : les fonctions privée préfixée par “static”. On en profite pour renommer quelques variables et fonction en français.

La gestion des événements du clavier était avant réalisée par un appel à une fonction globale : on oublie cette mauvaise pratique et on les passe plutôt par arguments.

Enfin, on supprime toutes les variables globales et on les positionne dans une structure de contexte. Notre code pourra donc opérer sur un nombre indéfini de contextes … pensez au futur ! (multijoueurs par exemple). Au final, notre module ne comprend que des fonctions et quelques constantes servant à décrire les niveaux de jeu. On pourra imaginer plus tard un composant dédié à cet effet pour rendre cette partie plus modulaire !

Première compilation

Le moteur de construction que nous allons utiliser est lui aussi modulaire. Il est à base de makefile, car disponible partout, et se veut simple et portable. Il a été conçu avec les objectifs suivants en tête :

  • Orienté composant : un simple fichier à ajouter listant les fichiers à construire suffit
  • Multi-plateforme : les compilateurs sont fournis sous forme de plug-in à part
  • Unique fichier Makefile racine listant toutes les cibles
  • Ré-utilisation de composants au sein du Makefile

Un article dédié a été rédigé sur ce blog, je vous renvoie à cette lecture ainsi qu’au dépôt Git correspondant.

Pour construire notre cible, on ajoute les différents composants dont nous avons besoin :

ifeq ($(MAKECMDGOALS), unitris_cli)

APP_MODULES 	:= projects/unitris_cli $(LIB_UNITRIS) $(LIB_SYS_PRINTF) components/debug
APP_LIBPATH 	:= 
APP_LIBS 	:= 

endif

unitris_cli: $(OBJECTS)
	$(call linker, $(OBJECTS), $(APP_LIBS), unitris_cli)

Composant de débogage

Pour tester ce premier composant, nous allons afficher la grille du moteur de jeu dans la console (avec des 1 et des 0). Le code correspondant ne sera pas placé dans le composant du moteur de jeu : cela ajoute une dépendance forte (stdio et printf), il se peut tout à fait qu’une des cibles future ne dispose pas de printf (dans le cas de l’embarqué) et que l’on souhaite déboguer vers une sortie spécifique (UART, SSH, fichier …).

Voici donc notre code qui va “dumper” la grille de Tetris dans la console :

void DBG_DumpGrid(const unitris_t *ctx)
{
    // init variables
    for (int i = 0; i < 21; i++)
    {
        for (int j = 0; j < 12; j++)
        {
            printf("%d ", ctx->grid[i][j]);
        }
        printf("\n");
    }
}

Fonctionnement du moteur de jeu Tetris

Le jeu de Tetris utilise des formes appelés tétrominos, c’est à dire de figures fabriquées à partir de quatre carrés. Ils tombent sur une grille et il va falloir les imbriquer judicieusement pour faire disparaître des lignes complètes. Le contexte (structure C) contient une grille de 20 lignes sur 10 colonnes. Nous ajoutons une ligne fantôme (en bas) et deux colonnes sur les côtés afin de simuler les bords. Cette astuce va nous permettre de limiter le déplacement du tétromino, un “mur” est vu comme une autre pièce. Au démarrage, voici la grille vide, un zéro est un carré vide, un 1 est une pièce ou un mur.

1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 1 1 1 1 1 1 1 1 1 1 1 

Chaque pièce est dessinée de la même façon, en dur dans le code, avec toutes ses rotations possibles. Voici par exemple le L inversé :

  // le L inversé
  0,0,0,0,
  1,1,1,0,
  0,0,1,0,
  0,0,0,0,

 // le L inversé avec une rotation
  0,1,1,0,
  0,1,0,0,
  0,1,0,0,
  0,0,0,0,

La logique du code s’occupera donc :

  1. à chaque “tick”, de faire tomber la pièce en cours
  2. De tester les cas de blocage
  3. De gérer les niveaux et les scores

Exécution du moteur

Notre programme principal va prendre ces deux composants, unitris et debug, et va procéder à 50 appels au moteur de Tetris. Après chaque appel, on affiche le contenu de la grille.

#include <stdio.h>      /* printf, NULL */
#include <stdlib.h>     /* srand, rand */
#include <time.h>       /* time */

#include "unitris.h"
#include "debug.h"
 
int main(int argc, char **argv)
{
    unitris_t ctx;
    
    srand(time(NULL)); // initialize seed
    UNI_Initialize(&ctx);
    
    for (int i = 0; i < 50; i++)
    {
        DBG_DumpGrid(&ctx);
        UNI_Tick(&ctx, 0, 0, 0);
        printf("\n");
    }
    
    return 0;
}

Miracle ! On voit notre pièce qui descend progressivement :

1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 1 1 1 0 0 0 0 1 
1 0 0 0 0 0 1 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 0 0 0 0 1 
1 1 1 1 1 1 1 1 1 1 1 1 

Comment déboguer ?

Le débogage va dépendre de la cible. Notre système de build ne fait que générer un exécutable, il va falloir trouver le bon programme. Sur PC, j’utilise Qt Creator, il offre une interface pratique et est disponible sur tous les systèmes. Nous verrons comment faire au cas par cas, surtout pour les cibles embarquées.

Conclusion

Nous voici donc doté d’un moteur de Tetris simple. Le code en lui même fait 200 lignes et quelques fonctions. Un peu de code statique pour définir différents éléments du jeu et c’est tout !

Architecture par composant - Tetris générique (1)

Le code source embarqué (ou non) d’un logiciel termine toujours par un tas de code difficilement maintenable. Il existe néanmoins quelques solutions simples pour éviter le pire et permettre d’avoir, dès le début d’un projet, une base de code évolutive et flexible. Nous verrons cela dans une série d’article en mettant en œuvre le jeu de Tetris dans une architecture par composant.

But de notre travail

Le but sera de porter le jeu Tetris sur plusieurs plateformes. Votre projet ne vise qu’une seule plateforme ? Insérez une deuxième cible dès le début du projet, ceci pour plusieurs raisons :

  • Cela vous forcera à créer des couches d’abstractions
  • Cela vous forcera à avoir un système de construction générique
  • Utiliser un système différent, et si possible un compilateur différent, permet de tester un peu le code à moindre coût

Dans le cas d’une cible principale embarquée, il est par exemple intéressant d’avoir comme seconde cible l’ordinateur hôte. Celui-ci est plus rapide et permet par exemple d’effectuer des simulations et du test automatisé rapide du code.

image

Technologies utilisées (et limitations imposées)

Le code principal sera en langage C. Déjà parce que le code source d’origine est en C et qu’il est facile de porter du code C à peu près partout. Ce langage est également facilement intégrable à des langages de plus hauts niveaux (langages de scripts) via des appels natifs ou des librairies pré-compilées.

Ce ne sera pas notre unique langage : selon les plateformes cibles, notre code de base sera enrobé de différentes couches en C++ ou en Javascript… nous verrons cela par la suite.

En ce qui concerne le langage C proprement dit, nous allons le limiter au maximum, toujours dans l’optique de la portabilité :

  • Faible dépendance sur la librairie standard C : on limitera les en-têtes au script minimum
  • PAS d’allocation dynamique : et oui, on garde à l’esprit les cibles embarquées
  • PAS de C99, on va essayer, c’est dommage mais par exemple à l’heure d’écriture de cet article Visual C++ ne supporte toujours pas cette norme. La honte. (alors que la plupart des compilateurs embarqués sont ok là dessus comme IAR ou Keil).

Trois notions importantes et complémentaires

De part mon expérience, on peut classer dans trois catégories les éléments importants qui vont nous permettre d’avoir une architecture correcte.

  • D’abord, il nous faut une organisation modulaire des répertoires et du système de construction
  • Puis, il faut s’occuper de l’architecture statique, on visera ici à limiter les dépendances entre les composants
  • Enfin, il faudra travailler sur la partie dynamique (dialogue entre composants et contextes d’exécutions)

Le fil rouge sera de toujours limiter au maximum les interactions dépendantes qui font qu’un composant A nécessite un composant B. Pour cela, on utilisera des techniques de messagerie, d’événements et de liaisons dynamiques (listeners). En prenant en compte tout ceci, la base de notre code sera saine et portable à souhait. Bien entendu, rien n’est parfait, et il y a toujours une tonne de particularités liée à une plateforme.

Dans tous les cas, pensez générique : en général, essayez d’utiliser les particularités d’une spécification tordue (issue d’un service marketing ou d’un client) pour rendre cette fonction générique et commune au reste de votre code.

Au niveau du code proprement dit, nous éviterons les #ifdef le plus possible, cela ne rend pas le code facile à porter.

Architecture par composants

Une architecture par composant va nous permettre de bien organiser le code source au niveau répertoire et au niveau des API : un composant fournit une interface (fonctions), son implémentation est cachée. Demain, vous pouvez tout à fait ré-écrire l’implémentation sans bouger une ligne de code dans le reste du projet.

Dans les faits, cette architecture génère deux gros problèmes :

  • La dépendances entre les composants, nous verrons comment s’en sortir avec quelques principes logiciels
  • L’architecture dynamique difficile à contrôler (phases de démarrage, d’arrêts, synchronisation inter-composants et événements temps réels)

Présentation du code Tetris d’origine

Le code de Tetris d’origine que nous utiliserons pour illustrer notre architecture provient d’un easter egg caché au sein d’un produit industriel doté d’un écran graphique monochrome. Il s’agit d’une centrale de mesure disposant de deux boutons, le bouton de gauche permettant de valider et le bouton de droite disposant des quatre directions de navigation. Pour lancer le jeu, aller dans l’écran de la date-heure, appuyez trois fois sur “OK” puis “OK + gauche”.

image

Nous voyons déjà ce que nous aurons à abstraire : l’affichage et les contrôles. Un équipement électronique embarqué sara totalement différent par rapport à un portage Web, PC ou mobile.

Notre but sera d’abstraire au maximum pour isoler le moteur Tetris proprement dit et ainsi permettre de réutiliser le code au maximum.

Vers quelles plateformes cibles ?

Alors nous allons nous amuser un peu en variant les contraintes, et dans le désordre :

  • Un portage natif sur PC, avec mettons un affichage graphique en utilisant la librairie Qt
  • Un portage en mode console
  • Un portage vers le Web, eh oui, en utilisant le WebAssembly
  • Un portage embarqué sur plateforme RISC-V et un écran LCD

Travail préparatoire

Ok, attaquons la chose. Dans un premier temps, nous allons nous extraire le code source du programme d’origine. Pas de problème légal, c’est moi qui l’ai programmé et c’est une fonction cachée, donc aucun soucis là dessus (en espérant que mes anciens collègues aient retiré le code en production ;D). Le code a été écrit en langage C sur cible DSP (un Texas TMS320C5502). Heureusement, le code est assez indépendant du reste de l’application, j’avais bien fait les choses à l’époque. Néanmoins, j’ai dû réaliser les tâches de nettoyage suivantes :

  • Passer tous les types propriétaires en types standards (uint32_t, uint8_t, bool) à l’aide des en-têtes stdint.h et stdbool.h
  • Retirer les inclusions d’en-têtes spécifiques à la plateforme (C5502.h)
  • Déménager quelques définitions externes et une variable globale : la mémoire graphique virtuelle

Ce dernier point nécessite une petite explication et un schéma présentant l’architecture. Afin de maximiser le code en commun sur toutes les plateformes et minimiser les couches d’abstractions, le choix avait été fait à l’époque (et quel bon choix !) de dessiner ce que l’on voit à l’écran dans un buffer virtuel.

Ce buffer est ensuite envoyé vers le contrôleur graphique. Il s’agit, en gros, de dessiner sur un fichier, cela génère une image que l’on peut envoyer n’importe où. Le code de notre Tetris repose donc sur une petite librairie graphique permettant de dessiner des primitives : cercle, rectangle, segments, caractères et quasiment le plus important : afficher une image.

image

A noter que le fait de travailler dans un buffer permet de réaliser des fonctions bien pratiques :

  • Nous avions développé à l’époque un double buffer (image N et N-1) permettant de détecter les lignes modifiées entre deux appels : ainsi, nous soulagions le rafraîchissement du LCD afin de limiter le clignotement qui était assez visible par l’utilisateur.
  • Une capture d’écran : sur une requête, on copie le buffer en cours dans un autre coin en RAM, puis on télécharge cette image et on génère un fichier Bitmap.

Génération d’images

Nous avons à disposition un écran graphique de résolution 160x128 pixels monochromes. Il est intéressant de pouvoir afficher des images tirées d’un fichier image généré avec un logiciel de dessin type Gimp (ou piqué sur Internet). Or, le format d’une image est un peu trop lourd à décoder par un processeur embarqué et on ne dispose pas, généralement, d’un système de fichier assez gros pour stocker les images brutes. Nous allons donc générer, en dur dans le code, un extrait de ces images, un tableau d’octets brut de ce dessin. Ainsi, c’est de la Flash code qui sera utilisée (et non de la data).

Le format source choisi est le BMP car il permet d’encoder l’image avec un bit par pixel, c’est à dire en monochrome, parfaitement optimisé pour nous. Notez que tous les logiciels ne permettent pas cet encodage sauf … Paint, de Microsoft, qui le gère très bien depuis le début, ce qui est assez rigolo pour le noter.

Pour cela, j’avais développé à l’époque un utilitaire pour générer un tableau d’octets en C à partir d’une image monochrome BMP. Vous trouverez cet utilitaire dans le répertoire ‘tools’ du dépôt du projet.

Dans un premier temps on compile l’utilitaire :

gcc bmp2c.c -o bmp2c

Une petite aide en ligne permet de voir le format d’appel et les options :

C:\git\unitris\tools>bmp2c.exe
Usage: bmp2c.exe input.bmp output.c [-six_pixels] [-16bits_array] [-32bits_array]

On lance la conversion, aucune erreur, tout s’est bien passé !

C:\git\unitris\tools>bmp2c.exe ..\assets\tetris.bmp output.c  -16bits_array

Option enabled : 16bits array.

width : 96 height : 80
bits utilises : 96 padding : 0 octets ajoutes.
Taille du tableau : 960

L’opération a donc transformé une image vers un tableau d’octets.

image

Conclusion et suite

Nous venons d’expliquer un peu notre projet, notre but et surtout notre vision concernant l’architecture. Nous avons également fait un petit tour d’horizon sur l’architecture dynamique du code d’origine.

Dans le prochain article, nous porterons le code en console, dans une première version permettant de générer une image Bitmap puis avec Qt !

Vous trouverez le dépôt de ce projet sur github https://github.com/arabine/unitris ainsi que le code source en cours d’écriture.

Systemd est magique

J’adore systemd, la transversale boîte à outil système intégrée à de nombreuses distributions de Linux. Les commandes simples et puissantes me rendent la vie facile lors de mes prestations ! Voici quelques fonctions que j’utilise

Démarrer un processus automatiquement

Pratique pour lancer un serveur Web par exemple, en tâche de fond. S’il plante, systemd va le redémarrer. Des logs sont enregistrés pour permettre une analyse à postériori. N’oubliez pas d’indiquer le ‘WorkingDirectory’ car souvent les programmes utilisent des chemins relatifs à leur exécutable pour charger des ressources (images, logs, config, etc…).

Dans l’exemple suivant, il s’agit de démarrer un serveur Web en Nodejs. J’ai ajouté des options pour injecter une variable d’environnement et j’utilise un utilisateur spécifique pour ce service.

[Unit]
Description=tarotclubweb
After=network.target

[Service]
WorkingDirectory=/opt/www/tarotclub/tarotclub/www
ExecStart=/usr/bin/node /opt/www/tarotclub/tarotclub/www/app.js
Restart=alway
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=tarotclubweb
Environment='NODE_ENV=production'
User=d8s

[Install]
WantedBy=multi-user.target

Sauvegardez ce script dans un fichier situé dans : /etc/systemd/system/.service

Les commandes principales sont :

systemctl enable tarotclubweb.service
systemctl start tarotclubweb.service
systemctl stop tarotclubweb.service
systemctl status tarotclubweb.service

La première est à exécutée une seule fois. Les autres sont explicites ! Il existe PLEIN d’options, le tout est je trouve très simple d’écriture. Enfin un truc non cryptique sous Linux.

Montage des disques automatique

Il m’arrive souvent de devoir monter des disques réseaux, soit chez moi pour des sauvegardes ou syncrhonisation, soit chez des clients pour les mêmes raisons. Facile, on utilise une entrée dans le fichier fstab pour que ce disque soit monté automatiquement au démarrage de la machine, par exemple :

//10.0.0.1/nas /media/nas cifs vers=3.0,user,guest,sec=ntlmssp,auto,rw,uid=pi,gid=pi,iocharset=utf8  0  0

Or, il arrive que ces disques soient réguièrement déconnectés (extinction d’oridinateur, disque dur USB débranché, réseau ou Internet qui va et vient …) ou si l’ordre de démarrage n’est pas respecté (Linux démarre avant le branchement du disque) . Si un script de sauvegarde doit s’exécuter, il va planter. Ajoutez cette option à votre ligne fstab (dans la liste des options séparées par des virgules) :

x-systemd.automount

Magique ! Un simple ‘ls’ ou tout autre accès sur le répertoire de destination va lancer son montage automatique.