Table of Contents
Commit 2010-12-20
Visu rupture (3/3)
- La visu de la rupture est terminée. Le maillage peut donc évoluer au cours du calcul et même totalement disparaitre (et réapparaitre quand on remonte dans le temps en rechargeant un fac).
- Tous les filtres fonctionnent correctement sur maillage variable (contours, feature edges, points de gauss, champs discontinus par éléments, extrusion, etc.)
- Pour que ca soit propre, j'ai supprimé tous les memory leaks VTK de la visu. Il est donc maintenant utile que tout le monde utilise une version de VTK où
VTK_DEBUG_LEAKS
est actif (Luc va fournir une nouvelle version de ses libdeluques). Ceci permettra de repérer plus facilement les futurs leaks (si vous ne savez pas ce qu'est un leak, il est toujours temps d'apprendre ici). - L'
ObjectBrowser
a été nettoyé et quelques bugs ont été corrigés. En particulier, son destructeur est maintenant appelé et la fenêtre se détruit correctement malgré le fait que les objets affichés n'existent plus (ça parait bête mais ça ne l'est pas du tout!). La fermeture de Metafor est en conséquence beaucoup plus rapide qu'avant. - La communication entre le thread graphique et le thread de calcul concernant la rupture d'éléments s'effectue maintenant avec le nouveau système d'events (voir plus bas). Ceci permet de mettre à jour la visu d'un objet (un maillage qui perd ou gagne des mailles, dans le cas qui nous occupe) de manière automatique et uniquement lorsque c'est réellement nécessaire. Autrement dit, si on ne fait pas de rupture, le maillage ne se remet plus à jour à tous les pas de temps. On a donc le comportement classique tout aussi rapide (ou tout aussi lent selon le point de vue).
Interface rupture
L'interface “rupture” de Pierre-Paul a été nettoyée parce qu'elle n'était pas claire. Avant, le fait qu'un élément soit cassé était décrit par un booléen nommé activity
qui n'a rien à voir avec la classe Activable
de Metafor. Pour manipuler cette variable à partir d'un critère de rupture par exemple, il y avait les traditionnels setActivity()
et getActivity()
mais aussi setActive()
et setInActive()
qui géraient la transition d'un état à l'autre. Pour corser le tout, malgré le fait que les Elements ne soient pas Activable
, il était possible de les désactiver (au sens Activable
du terme) par un appel d'une interface vers l'autre, camouflé dans Interaction
.
Outre le problème de confusion de nom, le plus gros problème, selon moi, est qu'il était possible de casser un élément par setActivity(false)
sans passer par la routine setInActive()
, et donc sans gérer la transition d'état!! On était donc pas loin d'un état “ça marche, j'y touche plus”.
Donc, j'ai renommé tout d'abord activity
en enabled
pour éviter la confusion de nom avec Avtivable
. Les fonctions setActivity()
et getActivity()
deviennent naturellement setEnabled()
etgetEnabled()
. J'ai ensuite fusionné le contenu de setActive()
et setInActive()
dans ces mêmes fonctions pour éviter les problèmes de transition d'état. En rendant setEnabled()
virtuelle, on peut être certain que la transition d'un état à l'autre (cassé ⇔ pas cassé) se fait à un endroit unique! (c'est évidemment à cet endroit que je remets à jour la visu)
J'ai eu de gros problèmes avec cette modif parce que la confusion des deux systèmes d'activation (Activable et rupture) était un moyen de faire fonctionner beaucoup de tests, notamment les tests de soudure (welding
). Pour arriver à faire tout fonctionner avec l'interface nettoyée, j'ai du faire des hypothèses au niveau de l'activation/désactivation des interactions. la logique est la suivante:
Lors d'une transition de stage:
- Si une Interaction n'est pas active, tous les éléments sont désactivés, quel que soit son état précédent (ca permet d'initialiser correctement les interactions non actives dès le début)
- Si elle est active:
- Si elle était active auparavant, on ne fait rien (ça évite de réactiver des éléments rompus)
- Sinon, on active tous les éléments.
Nouveau système d'Events
Le problème est le suivant:
- Il faut mettre à jour la visu quand les éléments se rompent. Cela veut dire, en pratique, reconstruire complètement l'objet
DataSet
(unvtkUnstructuredGrid
de VTK pour simplifier) puisqu'il n'est pas possible de supprimer directement une maille d'un maillage dans VTK. - Idéalement, il faut le faire uniquement quand ils se rompent (histoire de ne pas manger inutilement du CPU).
- Idéalement, il faut aussi que seul les DataSets concernés se reconstruisent (pas le reste, pour la même raison que précédemment).
- On ne connait pas le nombre de fenêtres ouvertes (généralement une seule, mais c'est pas sûr, surtout quand Luc teste un nouveau truc).
- On ne veut pas appeler explicitement l'interface graphique à partir du code “calcul” (pour éviter de perturber ceux qui ont déjà du mal avec du C++ basique).
Pour résoudre ce genre de problème, il existe un “Design Pattern” nommé Subject/Observer. Je l'ai déjà utilisé plusieurs fois dans le code (dans Gen4 et pour les DataCurve
, c'est-à-dire la visu des courbes).
Le “sujet” (subject), ici, est l'élément fini qui peut se rompre. Il doit informer la visu d'une manière ou d'une autre qu'il est cassé. Il contient une liste d'observateurs (observers) qui peuvent s'enregistrer auprès de lui (ici, ce sont les objets “drawables” associés de la visu). Quand l'élément se casse, il prévient les observateurs en appellant explicitement une fonction “notify()” qui transmet l'info aux observateurs. Eux font ce qu'ils veulent à ce moment là (ici, ils se déclarent “invalides” pour le remettre à jour lors de la prochaine mise à jour de la visu.
Si on veut un peu complexifier le système, comme par exemple ajouter des arguments aux appels “notify()”, on ajoute le concept d' “événement” (Event
). La fonction notify()
envoie alors aux observateurs un objet de type Event
qui peut contenir autant d'infos qu'on veut. Le sujet peut être vu ainsi comme un générateur d'événements. Il dit “je suis cassé”, “je suis sur le pont de me détruire”, … et n'importe quel objet peut l' “écouter” et réagir à ces événements.
Pour que tout ceci soit codé de manière générique tout en restant simple, j'ai fusionné les interfaces “subject” et “observer” traditionnelles en une seule classe que j'ai nommée EventAware
(en hommage à JCVD, mon acteur préféré).
En pratique, Element
, ElementSet
, Interaction
, InteractionSet
sont awares (EventAware
) et utilisent uiquement l'interface “sujet” de la classe. ElementDrawable
, ElementSetDrawable
, InteractionDrawable
, InteractionSetDrawable
sont aussi awares mais ils utilisent l'interface “observateur”.
Dans d'autres libs, ce type de pattern est poussé encore plus loin. Par exemple, dans VTK, il est possible de mettre des priorités d'appels parmi les observateurs ou même enregistrer un observateur pour écouter un type d'événement particulier (et pas les autres). Dans Qt, ce pattern se limite à tout ce qui n'est pas gérable avec le mécanisme signaux/slots (c'est-à-dire très peu de choses).
Si je me suis cassé la tête à écrire du code générique, c'est évidemment pour pouvoir le réutiliser. Il serait possible de remplacer le vieux code de DataCurve
par ce système. On pourrait aussi imaginer d'utiliser ce code pour mettre à jour certains objets de calcul sans lier explicitement deux objets parfois très éloignés. Par exemple, certains appels à Metafor au milieu du code de calcul pourrait être remplacé par un évémenement que Metafor viendrait écouter si nécessaire.
Ce travail est également un très grand pas vers la visu de maillages dynamique (cas du remaillage par exemple, si j'ai un jour le temps de faire la thèse d'Antoine).
Nettoyage divers
- Nettoyage des courbes (
DataCurveBase
et dérivées) en vue d'utiliser le nouveau système d'events au lieu du système un peu trop particularisé aux courbes. - Possibilité d'effectuer une capture d'écran en DEBUG (ça plantait!).
Debug leaks
- La classe
MemManager
a été supprimée. Elle permettait de suivre les allocations de mémoire du code si on le compilait avecDEBUGMEM
. Le système était vieux et n'était plus vraiment utilisable sans bidouiller le source (tous les includes Qt plantent, par exemple). - J'ai essayé de faire un truc similaire à
VTK_DEBUG_LEAKS
que j'ai appeléMETAFOR_DEBUG_LEAKS
. L'idée est simple: chaque fois qu'un objet est alloué, on met à jour une table globale qui compte le nombre d'objets de chaque type. Quand l'objet est détruit, on décrémente la table. Au final, toutes les valeurs doivent valoir 0 si on n'a pas fait l'andouille. Pour des raisons C++, il n'est possible d'effectué ces comptes que pour les objetsRefCounted
lorsqu'il sont utilisés +/- correctement (c'est-à-dire qu'on ne les a pas alloué sur la pile et qu'on a pensé à incrémenter au moins 1x leur compteur de référence par unincRef
). - La nouvelle classe qui gère tout ca est
DebugLeaks
dansmtGlobal
. Il suffit de compiler le code avecMETAFOR_DEBUG_LEAKS
pour avoir un petit tableau récapitulatif des leaks en sortie de Metafor:
Voilà ce que ça donne sur le test apps.ale.testConv2D
par exemple. Le texte suivant est affiché à la fin du test.
==================================== SUMMARY OF POSSIBLE LEAKS ==================================== NAME REMAINING REFS BcInteraction 4 GPointFieldApplicator 2 Geometry 2 Group 4 MaterialSet 1 Mesh 4 Node 120 Point 562 Properties 3 Side 120 Wire 120 non-VirtualObject 3
Dans ce cas-ci, j'ai l'impression que les maillages ALE ne sont pas (plus) détruits. Il reste donc du boulot…
On pourrait imaginer de sommer ces références non détruites et les mettre dans un TSC pour la batterie. C'est ce que je ferai prochainement.
— Romain BOMAN 2010/12/20 09:47