Modifiez la classe
Entity pour qu'elle soit liée par composition à 0..1 composants avec la nouvelle interface.
Nous choisissons une composition à 0..1 composants pour permettre à une entité d'avoir (ou pas) un
Animator.
Sans
Animator l’entité se contentera d’avoir une animation de base qui consiste à intégrer la position de l’entité même pendant
dt.
En Rust, une composition à 0..1 s’obtient en utilisant un
Option<T>.
En particulier, dans notre cas, le type de la donnée membre sera :
Option<Box<dyn Animator>>>.
Appelez cette donnée
animator :
animator : Option<Box<dyn Animator>>;
Adaptez le constructeur pour initialiser correctement la nouvelle donnée membre de la classe
Entity.
Adaptez la fonction
animate() : elle doit toujours imposer
integrate() mais doit aussi invoquer la fonction
animate() de son
Animator si elle en possède un.
Remarquez que maintenant cette fonction doit recevoir aussi la taille de la fenêtre d’appartenance pour la passer ensuite à la fonction
animate() de son
Animator.
Dans l’état actuel, le code présente des erreurs… en effet, le constructeur de la classe
Entity a changé et la façon d’instancier des objets de ce type dans la fonction
create_window() du fichier
lib.rs n’est plus correcte.
Il faudra, notamment, passer au constructeur un pointer intelligent
Box pointant sur un objet dont le type implémente le trait
Animator.
Pour cela, avant toute instanciation d’une entité, déclarez un
Box<dyn Animator> pour qu’il pointe sur un objet de type
Gravity (qui, pour le moment, est le seul type concret implémentant le trait
Animator).
Passez ce pointeur au constructeur du type
Entity.
Attention ! Rappelez-vous que l’
Animator est optionnel !
Même si vous avez suivi les instructions à la lettre et implémenté tout le code demandé, vous tomberez quand même sur une erreur.
Vous verrez que le compilateur vous informe que, dans la fonction
animate() d’
Entity, vous essayez d’accéder à la même donnée (l’objet courant :
self) à la fois de façon partagée et mutable.
Pour comprendre le problème, considérez le dessin suivant, où la boîte à gauche représente un objet de type
Entity avec ses données membres et à gauche le prototype de la fonction
animate() de l’
Animator.
Lorsque la fonction
animate() de l’
Animator est invoquée par une entité, la référence
self pointera en seule lecture sur l’animator courant appartenant à l’entité et le paramètre
entity référencera en modification l’entité même.
Or, à travers la référence
entity nous pourrions accéder à l’animator qui est déjà consulté à travers la référence
self.
Cette situation, en Rust, est interdite.
Nous ne pouvons pas écrire un code qui permet plusieurs accès simultanés sur une donnée si un de ces accès est mutable.
En d’autres mots : on ne peut pas accéder à quelque chose qui est en cours de modification.
La cohérence des données ne serait pas garantie.
D’autres langages moins stricts que Rust peuvent permettre une telle situation, mais au même temps, contrairement à Rust, ils ne garantissent pas l’intégrité des données.
Pour résoudre ce problème, la solution consiste à déplacer temporairement l’animator dans une variable locale à l’aide de la fonction
take() fournie par le type
Option<T>.
L’animator sera alors extrait de l’objet de type
Entity et l’invocation de la fonction
animate() ne causera plus aucun conflit.
Évidemment, une fois l’invocation de la fonction
animate() terminée, il faudra rendre l’animator à l’entité, toujours en se rappelant qu’il s’agit d’une donnée optionnelle.
Si votre code est correct, le lancement de l’application montrera une fenêtre graphique contenant 8 cercles et 8 rectangles tous soumis à l’apesanteur.
Ces objets graphiques restent toujours visibles et réagissent au clic.