• Aucun résultat trouvé

Choix de conception des Systèmes, Composants et périphériques

gentleman gentlemwe

Chapitre 3. La boîte à outils Polyphony

3.3 Architecture de Polyphony

3.3.3 Choix de conception des Systèmes, Composants et périphériques

Cette partie illustre le deuxième niveau de Polyphony (représenté en figure  32), construit par dessus l'implémentation d'ECS présentée dans la section précédente. Ce niveau est constitué des éléments réutilisables fournis par Polyphony, pour aider les programmeurs à construire rapidement des interfaces complètes. Le choix des Systèmes proposés, des Composants disponibles ainsi que l'usage des Sélections, résultent de compromis qu'il est important d'expliquer afin de soutenir les futures itérations de boîtes à outils d'IHM basées sur ECS.

3.3.3.1 Choix des Systèmes

La conception des Systèmes dans Polyphony résulte d'un compromis entre comportements simples et comportements complexes. Prenons par exemple le dessin des bordures de widgets (rectangulaires ou polygonales) dans l'application. Nous avons quatre alternatives, classées de la plus simple à la plus complexe :

Créer un Système dessinant des segments, avec lequel une bordure est formée de plusieurs Entités positionnées en polygone.

Créer un Système dessinant des rectangles, qui conviendra pour la majorité des besoins.

Créer un Système dessinant des formes géométriques arbitraires, pour lequel le dessin d'une bordure revient à fournir un tableau de points.

Créer un Système dessinant des widgets de base (fond, bordure, image, et texte), pour lequel le dessin d'une bordure sera un sous-ensemble de ses capacités.

Avec des types de comportements simples, il est facile de les assembler de façon créative. Avec un Système dessinant des segments, on pourrait ainsi changer la couleur d'un des bords, indépendamment des autres, ce qui n'est pas réalisable avec les Systèmes alternatifs plus complexes (s'ils se basent sur une seule couleur). À l'opposé avec des types de comportements complexes, on définit globalement moins de Systèmes, donc la chaîne d'exécution des Systèmes est plus courte, et il est plus facile d'en avoir une vue d'ensemble. De façon générale, nous voulons faciliter la programmation d'applications complexes, et rendre la structure des Systèmes la plus simple possible, donc nous favorisons les Systèmes complexes et en faible nombre.

Dans l'exemple ci-dessus, le premier Système dessinant un segment par Entité est trop simple. Pour dessiner une bordure rectangulaire complète (sans “trous”), il nécessite de créer quatre Entités, et de les positionner relativement à l'Entité du widget qu'on souhaite dessiner. De plus, ce Système n'apporte pas un comportement de “grande valeur”, car il peut être recréé rapidement si un développeur avait besoin de dessiner des lignes.

Le second Système est plus avancé, mais stéréotypé. En effet, chaque widget qui nécessite une bordure n'aura qu'à acquérir quelques Composants (sans nécessiter de nouvelles Entités), donc il sera simple et rapide d'ajouter des bordures rectangulaires aux widgets. Cependant, des chercheurs expérimentant la conception de contrôles non-rectangulaires ne pourront pas utiliser ce Système, et devront en créer un autre.

Le troisième Système est plus avancé et moins stéréotypé, mais complexe pour des usages simples. Des bordures pour contrôles non-rectangulaires seraient simples à créer, et leurs coordonnées seraient incluses dans un Composant, donc ne nécessiteraient pas de multiplier les Entités. Pourtant, elles rendent plus complexe le dessin de bordures pour les contrôles rectangulaires, qui devraient alors spécifier quatre points.

Enfin, le quatrième Système est trop complexe. En effet, bien qu'il soit particulièrement adapté pour dessiner des contrôles standards, ce Système est “monolithique” et ne fait aucun usage des capacités de composition d'ECS. La création de nouveaux types de contrôles se ferait en multipliant les Entités, chacune utilisant un sous-ensemble des capacités du Système. De plus ce type de Système est le plus difficile à remplacer, donc contribue à la cristallisation des types de contrôles disponibles, et plus généralement de l'expérience utilisateur.

L'exemple que nous avons choisi est volontairement simple, de manière à bien illustrer les choix qui s'offrent aux programmeurs pour concevoir des Systèmes selon leurs besoins. En pratique il est possible de faire un Système qui couvre plusieurs usages simultanément (ex. qui permette de spécifier une bordure complète, ou chaque côté séparément). Lors de l'élaboration d'un Système, nous pourrions être tentés de multiplier ces options, et d'inclure des besoins hypothétiques futurs, pour faciliter le prototypage de nouvelles interactions. Or l'extrapolation des besoins crée des Systèmes plus complexes, car il existe de nombreuses pistes de développements, dont certaines ne se développeront jamais. Dans l'exemple des bordures ci-dessus, de telles pistes seraient : des bordures polygonales, des bordures en courbes paramétriques, des bordures d'épaisseurs/couleurs non-uniformes, ou encore des bordures animées dans le temps. À partir de cette remarque, notre première recommandation est de supporter uniquement les usages contemporains. Le modèle ECS est conçu pour faciliter la création de nouveaux Composants et Systèmes, ce qui est préférable à des Systèmes complexes supportant des besoins peu communs. Enfin, de la même manière qu'on quantifie le nombre d'éléments que peut retenir la mémoire de travail (empan mnésique) autour de 7 [Mil56], nous préférons réduire le nombre de Systèmes pour permettre aux développeurs d'en avoir une “vue d'ensemble” (voire de les représenter graphiquement). Il faut alors identifier des comportements indépendants (ex. le dessin de bordures, d'arrière-plans, de texte, ou d'images), et les répartir en un nombre raisonnable. Le choix final est un compromis qui dépend du contexte, et n'a pas une unique solution. Nous recommandons donc de diviser les Systèmes en un faible nombre de comportements indépendants.

3.3.3.2 Choix des Composants

La fonction première des Composants est d'aggréger des données. Ces données peuvent toujours être divisées en des valeurs atomiques — par exemple, une coordonnée x, une coordonnée y, une largeur width, une hauteur height. D'un autre côté, ces données peuvent aussi être regroupées dans des ensembles partageant un sens commun — par exemple, x, y, width et height forment un rectangle, ou des bornes. À l'extrême, les données peuvent être regroupées dans de grands ensembles avec un sens large — comme les classes de widgets de base définies dans la plupart des frameworks. Il existe donc un équilibre à trouver entre Composants légers (voire atomiques), et Composants lourds.

Les programmeurs interagissent directement avec les Composants, pour gérer les comportements ajoutés aux diverses Entités. L'enjeu de ce choix de conception est donc la facilité qu'auront les programmeurs à s'abstraire du bas-niveau, et à manipuler peu de Composants à la fois. Si les Composants sont trop lourds (comme des widgets complets), alors le mécanisme de composition d'ECS ne peut pas être exploité à son maximum, puisqu'on ajouterait des comportements en activant des options dans un Composant, plutôt qu'en acquérant de nouveaux Composants. Si les Composants sont trop légers, alors les différents comportements nécessitent beaucoup de Composants, et les programmeurs doivent écrire plus de code pour les ajouter aux Entités.

La performance de l'application influence aussi ce choix de conception. En faveur des Composants lourds, l'accès à une donnée d'un Composant (en lecture et écriture) est plus rapide que l'accès à un Composant d'une Entité, à cause de la flexibilité du stockage des Composants d'une Entité. En faveur des Composants légers, le regroupement en mémoire de toutes les données utilisées par un même Système favorise l'utilisation des caches du processeur, donc les données inutilisées d'un Composant diminuent globalement la performance de l'application.

Pour le choix des Composants de Polyphony, nous avons donc appliqué ces recommandations : les données requises par un Système devraient appartenir à un seul Composant

les données partagées par plusieurs Systèmes devraient être réparties pour que chaque Système dépendant d'un Composant en utilise toutes les variables

En exemple d'application de ces recommandations, nous avons observé que tous les éléments affichables de l'interface possédaient nécessairement les variables x, y, width et height — y compris ceux non-rectangulaires qui possèdent alors un rectangle englobant. Seul le pointeur système possède uniquement des coordonnées x et y, cependant ses comportements ne sont pas destinés à être composés avec ceux des widgets de l'interface. En conséquence, Polyphony propose un Composant Bounds incluant les quatre variables.

3.3.3.3 Choix liés aux Sélections

En programmation par objets (et Entités), les objets se “connaissent” les uns les autres en stockant des références (généralement des adresses en mémoire) :

un objet en “connaît” un autre en stockant une référence vers celui-ci

Avec l'utilisation des Sélections, de nouvelles manières de référer aux objets apparaissent :

on peut référer à une Entité qu'on sait être le premier élément d'une Sélection (ex  : le premier pointeur, Pointers[0])

on peut référer à plusieurs Entités en leur attribuant un Composant les différenciant des autres Entités, et en les récupérant par une Sélection

Dans Polyphony, toutes ces manières de référer aux Entités coexistent, et offrent souvent plusieurs manières de faire. Dans l'exemple de notre application de dessin, pour référer aux boutons activables mutuellement exclusifs (les outils de déplacement, rectangles, et ellipses), faut-il conserver un tableau de références, ou attribuer aux boutons un même Composant toggleGroup ? De même, pour définir les relations de parenté dans un arbre de scène, chaque noeud stocke-t-il un tableau d'enfants, ou les enfants portent-ils un Composant parent ?

Pour répondre à ces questions, il faut rappeler que les Sélections sont des structures coûteuses en performance, car Polyphony les maintient à jour en leur présentant chaque Entité qui est modifiée. Il faut donc éviter de les utiliser abusivement. Dans le premier cas, on utilisera une sélection ToggleGroups qui réfère à toutes les Entités possédant un Composant toggleGroup, et on filtrera lors de leur énumération les Entités avec un même toggleGroup (car on sait qu'elles sont peu nombreuses). Ce choix permet en outre d'ajouter facilement un nouveau bouton à un groupe, en lui attribuant le même Composant toggleGroup. Dans le second cas, on suppose que de grands arbres de scène risquent d'être utilisés. Avec une Sélection de toutes les Entités ayant un Composant parent, on aurait beaucoup d'Entités à ignorer pour énumérer celles avec un parent donné. De plus, l'ordre des enfants d'un noeud est important (qui ne serait pas garanti dans une Sélection sans critère de tri). On utilisera donc plutôt un tableau d'enfants sur chaque noeud parent de l'arbre. Ainsi, pour différencier les deux cas, nous recommandons d'utiliser une Sélection lorsque le nombre d'Entités à écarter est faible, car chaque Entité ignorée dans une Sélection est beaucoup plus coûteuse qu'une Entité ignorée dans un tableau.

Enfin, lors de la détection de techniques d'interaction sur les périphériques, il se pose la question des Entités auxquelles attribuer les Composants générés. Prenons par exemple la technique du glisser- déposer : lors du dépôt d'un objet sur un autre, un évènement de dépôt temporaire est généré, qui doit être communiqué aux Systèmes en aval dans la chaîne d'exécution. Les Composants à créer peuvent être stockés :

sur l'objet receveur, qui indique quel objet il a reçu (tmpDropOf), et éventuellement quel pointeur l'a déposé (tmpDropBy)

sur l'objet déposé, qui indique sur quel objet il est relâché (tmpDropOn), et éventuellement quel pointeur l'a déposé (tmpDropBy)

sur le pointeur à l'origine du dépôt, qui indique quel objet a été déplacé (tmpDropOf), et sur quel objet il l'a déposé (tmpDropOn)

sur une combinaison des trois

L'interaction entre les périphériques et les Entités induit la possibilité de stocker l'information d'un côté ou de l'autre (ou des deux côtés). Dans le cas du glisser-déposer, on suppose que beaucoup d'Entités seront déplaçables, beaucoup pourront recevoir des dépôts, mais qu'il existera peu de

les Systèmes en aval n'auront qu'à énumérer la Sélection Pointers pour détecter l'action de dépôt (et écarteront peu d'Entités). Ainsi, nous recommandons de stocker les Composants temporaires sur les Entités les moins nombreuses (c'est-à-dire accessibles à partir des Sélections les plus réduites).