NSThread et mémoire

03:48 modifié dans API AppKit #1
Bonjour !

J'ai le schéma simplifié suivant :
- (IBAction)rotationContinue:(id)sender<br />{<br />[NSThread detachNewThreadSelector:@selector(rotationContinueEffectiveThread) toTarget:self withObject:self];	<br />}<br /><br />-(void)rotationContinueEffectiveThread<br />{<br />	NSAutoreleasePool *pool=[[NSAutoreleasePool alloc]init];<br />	timer=[NSTimer scheduledTimerWithTimeInterval:csteRotation target:self selector:@selector(rotationContinueEffective) userInfo:self repeats:YES];<br />	[[NSRunLoop currentRunLoop]run];<br />	[pool release];<br />}<br /><br />-(void)rotationContinueEffective<br />{<br />&nbsp; &nbsp; &nbsp; &nbsp; int index<br />&nbsp; &nbsp; &nbsp; &nbsp; int borneSup=[arrayOfBezier count];<br />	for(index=0;index&lt;borneSup;index++)<br />	{<br />		NSBezierPath *thePath1=[[NSBezierPath alloc]init];<br />		[......................]<br /><br />		[[arrayOfBezier objectAtIndex:index]replaceObjectAtIndex:0 withObject:thePath1];<br />		[thePath1 release];<br />	}<br />}<br />


Bien sûr sans la dernière ligne ( [thePath1 release]; ) mon application fuit en mémoire... (assez méchamment d'ailleurs ! )

Mais avec cette dernière ligne, cela ne fuit plus et tout se passe bien sauf qu'au bout de quelques secondes (quelques dizaines d'appels de rotationContinueEffective donc) l'application plante...

Visiblement je gère mal l'emplacement mémoire de theThread1... Mais alors quelle est la bonne méthode ?

Merci par avance....
«1

Réponses

  • schlumschlum Membre
    octobre 2007 modifié #2
    Déjà , il manque le [NSThread exit];

    [Edit] Ensuite, à  mon avis il faut que tu lances ta runloop de cette manière :
    while(YES) {<br />    NSAutoReleasePool *datePool;<br />    [NSRunLoop runUntilDate:[NSDate distantFuture]];<br />    [datePool release];<br />}<br /><br />
    


    À moins que le NSTimer soit inclus dans les "input sources", dans ce cas, le problème est ailleurs.

    [Edit 2]

    Ah, il y a une méthode :
    - (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
    


    Donc essaie d'ajouter :
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]
    

    Avant le "run"
  • psychoh13psychoh13 Mothership Developer Membre
    03:48 modifié #3
    Déjà , les méthodes pour les threads et pour les timers prennent un argument chacun :
    - (void)methodePourNSThread:(id)anObject;<br />- (void)methodePourNSTimer:(NSTimer *)timer;
    


    Dans le cas des NSThread, la méthode passe en argument l'objet anArgument dans la méthode :
    + (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
    


    Dans le cas des NSTimer, l'argument passé c'est le timer en question qui permet de récupérer les userInfo de la méthode :
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
    
  • 03:48 modifié #4
    dans 1192776065:

    Déjà , il manque le [NSThread exit];


    Ah... Je croyais que le thread était fermé quand il arrivait en fin de fonction ?


    [Edit] Ensuite, à  mon avis il faut que tu lances ta runloop de cette manière :
    while(YES) {<br />&nbsp; &nbsp; NSAutoReleasePool *datePool;<br />&nbsp; &nbsp; [NSRunLoop runUntilDate:[NSDate distantFuture]];<br />&nbsp; &nbsp; [datePool release];<br />}<br /><br />
    


    À moins que le NSTimer soit inclus dans les "input sources", dans ce cas, le problème est ailleurs.

    [Edit 2]

    Ah, il y a une méthode :
    - (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
    


    Donc essaie d'ajouter :
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]
    

    Avant le "run"


    Euh ok je vais essayer ça mais je comprends pas trop le rapport avec mon problème... Ma runloop est visiblement bien lancée puisque le timer est appelé et tout... Je rappelle que si j'enlève le "[thePath1 release]" tout à  la fin tout marche bien sauf que ça fuit en mémoire...

    Enfin j'essaie ce que tu m'as dit néanmoins...
  • 03:48 modifié #5
    Non désolé, ce que tu me proposes ne change rien  :'(

    Merci de te pencher sur mon problème en tout cas, mais je pense qu'il faut chercher plus basique, j'ai jamais réellement bien saisi comment fonctionne l'allocation mémoire, et visiblement je dois mal gérer l'objet thePath1.... Et ça doit être lié au fait que c'est dans un nouveau NSThread parce qu'il n'y a pas de problème dans le cas contraire...
  • schlumschlum Membre
    03:48 modifié #6
    Un release en trop sur l'objet remplacé ?
    Sinon, je ne vois pas, il faudrait plus de code.
  • psychoh13psychoh13 Mothership Developer Membre
    03:48 modifié #7
    Euh... J'ai quelques questions :
    • arrayOfBezier est un NSArray contenant des NSArray ?
    • Est-ce que tu utilises Xcode 2.x ? Si oui, lorsque ton programme plante, est-ce que Xcode ouvre GDB ? Si oui, retourne sur la page "Build" et regarde quel est le message d'erreur affiché, s'il y en a un, quel est-il ?


    Pour finir, étant donné qu'un NSArray retient tous les objets qu'il contient, je te conseille d'utiliser la méthode +[NSBezierPath bezierPath] pour créer tes objets, en effet, tu les relâches à  la fin de ta boucle donc ils n'ont aucune chance d'être supprimé avant que tu aies terminé le traitement dessus.

    Ensuite, je te conseille, en tout cas pour la première boucle d'utiliser un énumérateur plutôt que d'accéder aux membres de ton NSArray via la méthode -[NSArray objectAtIndex:] c'est beaucoup plus sécurisé. C'est très simple :

    -(void)rotationContinueEffective<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;// Ton énumérateur<br />&nbsp;&nbsp;&nbsp;&nbsp;NSEnumerator *en = [arrayOfBezier objectEnumerator];<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;// L&#39;objet temporaire auquel tu accèdes dans ton NSArray<br />&nbsp;&nbsp;&nbsp;&nbsp;// Tu peux le typer un peu mieux si besoin est<br />&nbsp;&nbsp;&nbsp;&nbsp;id myObject;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;// Boucle d&#39;énumération : -[NSEnumerator nextObject] retourne nil<br />&nbsp;&nbsp;&nbsp;&nbsp;// s&#39;il n&#39;y a plus d&#39;objet à  énumérer ce qui fera que la condition sera aussi nil<br />&nbsp;&nbsp;&nbsp;&nbsp;// la boucle s&#39;arrêtera donc à  la fin de l&#39;énumération<br />&nbsp;&nbsp;&nbsp;&nbsp;while(myObject = [en nextObject])<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NSBezierPath *thePath1 = [NSBezierPath bezierPath];<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// ton code<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Tu peux dans ta boucle manipuler l&#39;objet énuméré<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[myObject replaceObjectAtIndex:0 withObject:thePath1];<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}
    


    Ce genre de fonctionnement est déjà  beaucoup plus propre.
  • schlumschlum Membre
    03:48 modifié #8
    dans 1192818436:

    Pour finir, étant donné qu'un NSArray retient tous les objets qu'il contient, je te conseille d'utiliser la méthode +[NSBezierPath bezierPath] pour créer tes objets, en effet, tu les relâches à  la fin de ta boucle donc ils n'ont aucune chance d'être supprimé avant que tu aies terminé le traitement dessus.


    Mauvaise idée, vu que son thread est fait pour durer et exécuter ce truc à  l'infini, la mémoire ne serait jamais libérée. Ou alors il faut un NSAutoRelease local.

    Quand on peut, il vaut mieux utiliser l'allocation de mémoire classique, c'est ça qui est beaucoup plus propre !
  • psychoh13psychoh13 Mothership Developer Membre
    03:48 modifié #9
    Ce que j'appelais propre en l'occurrence c'était l'énumération :D
  • schlumschlum Membre
    03:48 modifié #10
    Oui, c'est plus propre (et encore... C'est sujet à  polémique !), mais ça crée également un gros objet dans l'autoReleasePool  ;)
  • psychoh13psychoh13 Mothership Developer Membre
    03:48 modifié #11
    Gros ? Pas vraiment !
    D'ailleurs, je viens de me rappeler de quelque chose. La méthode qui contient cette énumération et qui modifie des NSBezierPath... Elle est appelée par un NSTimer, il s'agit d'un événement, or Cocoa crée une NSAutoreleasePool pour encapsuler chaque événement. Donc à  la fin de l'énumération et donc à  la fin de la méthode tous les objets qui y ont reçus un message -[NSObject autorelease] sont supprimés.
    Il n'y a donc pas de surcharge de mémoire à  cause des autoreleases et des énumérations.

    Sinon, si Cocoa demande à  ce que dans la méthode appelée par le nouveau NSThread on ajoute une NSAutoreleasePool c'est simplement parce que Cocoa en a besoin pour la base, mais pour tous les événements "normaux" (clics, timers, etc.) il en génère au moins une.
    à‰crire soit-même une autorelease pool n'est utile que dans le cas d'un nouveau thread ou dans le cas d'une boucle très longue, donc à  moins qu'il n'ait mis un bon millier d'éléments dans son NSArray, l'autorelease pool locale n'est pas justifiée dans la boucle et on peut donc laisser à  celle qui est créée par et pour le timer le loisir de nettoyer les objets après la sortie de la méthode.
  • schlumschlum Membre
    03:48 modifié #12
    Exact 
    Ca n'empêche qu'il faut éviter de créer des objets auto-releasés quand on peut... Ceci-dit, c'est pas ça qui va résoudre son problème, là  on joue avec les diverses manières de gérer la mémoire pour un même résultat final  ???
  • psychoh13psychoh13 Mothership Developer Membre
    03:48 modifié #13
    Déjà , j'aimerais qu'il réponde aux questions que j'ai posé plus haut, ça m'aiderait beaucoup. Le problème étant qu'il nous demande de corriger un code sans même nous dire ce qu'il veut vraiment faire avec, car peut-être qu'on peut lui indiquer une autre direction pour son code.

    Sinon, je ne vois pas en quoi les autoreleases sont à  éviter B) c'est un mécanisme utilisé partout dans Cocoa, il ne dispense pas d'être vigilant mais peut aider à  éviter des manipulations hasardeuses.
  • schlumschlum Membre
    03:48 modifié #14
    dans 1192827351:
    Sinon, je ne vois pas en quoi les autoreleases sont à  éviter B) c'est un mécanisme utilisé partout dans Cocoa, il ne dispense pas d'être vigilant mais peut aider à  éviter des manipulations hasardeuses.


    C'est le débat du tout auto-release / Garbage Collector / Gestion manuelle de la mémoire...
    Les mécanismes d'auto-release sont bien pratiques pour éviter les callback, mais c'est quand même un mécanisme assez lourd qui, si on ne fait pas attention, peut soit remplir très vite une grosse partie de la mémoire au point de devoir déborder sur la swap et d'avoir des ralentissement, soit donner des ralentissements au moment où de nombreux objets sont relâchés.
  • schlumschlum Membre
    03:48 modifié #15
  • 03:48 modifié #16
    arrayOfBezier est un NSArray contenant des NSArray ?


    Affirmatif ;-)


    Est-ce que tu utilises Xcode 2.x ? Si oui, lorsque ton programme plante, est-ce que Xcode ouvre GDB ? Si oui, retourne sur la page "Build" et regarde quel est le message d'erreur affiché, s'il y en a un, quel est-il ?


    Pas trop compris... Il ouvre bien GDB.... Mais pas de message d'erreur !


    Ensuite, je te conseille, en tout cas pour la première boucle d'utiliser un énumérateur plutôt que d'accéder aux membres de ton NSArray via la méthode -[NSArray objectAtIndex:] c'est beaucoup plus sécurisé. C'est très simple :


    Merci beaucoup, je ne connaissais pas cette méthode :-)

    Mauvaise idée, vu que son thread est fait pour durer et exécuter ce truc à  l'infini, la mémoire ne serait jamais libérée.

    Oui je confirme, j'ai besoin du alloc...




    Donc pour reprendre... En fait j'ai un tableau arrayOfBezier dans lequel chaque ligne est constituée d'un bezierPath et d'un tableau de coordonnées de points...
    La fonction rotationContinue est chargée d'appliquer une rotation sur les tableaux des coordonnées de points, et d'en faire des bezierPath qu'elle remet dans arrayOfBezier.

    Or si je ne release pas thePath1 à  la fin de ma fonction de rotation, ça fuit.. Jusque là  c'est logique..
    Donc la logique est de releaser thePath1 une fois qu'il a été mis dans le tableau. Ce que je fais. Du coup ça ne fuit plus. Si ma fonction rotationContinue est appelée dans le thread principal, tout roule.
    Néanmoins, si ma fonction rotation est appelée par rotationContinueEffectiveThread, qui crée un nouveau thread juste pour lui, tout se passe bien pendant un certain temps (en fait une bonne centaine d'appels peut-être) et puis au bout d'un moment boum, ça plante...

    Apparemment quand il plante, c'est quand il arrive dans le drawRect :

    <br />- (void)drawRect:(NSRect)rect<br />{<br />	int i;<br /> &nbsp; &nbsp; &nbsp; &nbsp;for (i=0;i&lt;[arrayOfBezier count];i++)<br />		{<br />			[[[arrayOfBezier objectAtIndex:i]objectAtIndex:0] stroke];<br />		}<br />	[super drawRect:[self frame]];<br />}<br />
    



    Au bout d'un moment il ne trouve plus l'élément [[arrayOfBezier objectAtIndex:i]objectAtIndex:0].... Enfin voilà  j'espère que c'est à  peu près clair...

    Le truc vraiment bizarre c'est que le problème n'apparaisse que quand la fonction est appelée par un thread autre que le thread principal...

    Merci de m'aider dans tous les cas !
  • schlumschlum Membre
    03:48 modifié #17
    Essaie de mettre un breakpoint sur -[NSException raise] pour voir si une exception est levée...
  • psychoh13psychoh13 Mothership Developer Membre
    03:48 modifié #18
    dans 1192829002:
    Donc pour reprendre... En fait j'ai un tableau arrayOfBezier dans lequel chaque ligne est constituée d'un bezierPath et d'un tableau de coordonnées de points...
    La fonction rotationContinue est chargée d'appliquer une rotation sur les tableaux des coordonnées de points, et d'en faire des bezierPath qu'elle remet dans arrayOfBezier.


    Je ne comprends pas pourquoi tu as besoin de stocker un NSBezierPath suivit d'une série de point dans ton tableau. à‰tant donné qu'un NSBezierPath est déjà  un tableau de points, de plus tu peux y appliquer une NSAffineTransform qui permet la rotation de l'objet. Apparemment tu reconstruits ton bezierPath à  chaque fois, étant donné les outils fournis dans cette classe, ce que tu fais est totalement anti-programmation objet...

    dans 1192829002:
    Pas trop compris... Il ouvre bien GDB.... Mais pas de message d'erreur !


    Bon, je t'explique ce qu'il se passe chez moi.
    Pour commencer, dans mes préférences XCode (pour pouvoir modifier l'option il faut qu'aucun projet ne soit ouvert, de plus cette option n'existe que pour la version de Tiger), j'ai mis dans la partie "General", le "layout" à  "all-in-one" ce qui fait que sur une seule fenêtre j'ai tout ce qui concerne mon projet, à  savoir :
    • La page "Project" où je peux voir mes sources, mes groupes, toutes les ressources et où je peux coder.
    • La page "Build", là  où, lors d'une exécution normale, j'ai ma console Xcode et aussi un éditeur
    • La page "Debug" qui est ouverte si mon application a planté.


    Lorsque mon application plante donc, le projet se place sur la page "Debug" directement, ce qui me permet de voir la trace d'exécution les données qu'il y avait dans la mémoire au moment du plantage. Mais, en forçant l'exécution du programme dans GDB jusqu'au plantage définitif, lorsque je regarde dans "Build", souvent je vois des messages du genre "uncaught exception :..." ou d'autres types d'erreurs.
    Si tu es sous XCode 2.x (sous Tiger donc) et que t'es en mode "All-in-one", est-ce que des messages s'affichent dans la console de la page "Build" ?
  • 03:48 modifié #19
    Nan mais c'est plus compliqué que ça.... Bon enfin bref ça arrange pas trop mon problème mais c'est vrai que je sais pas expliquer correctement... Bon je vais essayer de comprendre par moi-même :p...
    Merci quand même en tout cas :D
  • schlumschlum Membre
    03:48 modifié #20
    Sinon, balance tes sources, ou un projet minimal sur lequel le problème arrive  :)
  • BruBru Membre
    03:48 modifié #21
    dans 1192880769:

    Nan mais c'est plus compliqué que ça.... Bon enfin bref ça arrange pas trop mon problème mais c'est vrai que je sais pas expliquer correctement... Bon je vais essayer de comprendre par moi-même :p...
    Merci quand même en tout cas :D


    Avec le peu de code que tu nous as fourni, j'ai refait un petit projet test... qui fonctionne bien sûr chez moi !
    Comme tu ne nous donnes pas le crash log, c'est très difficile de t'aider plus.

    Mais, garde à  l'esprit que les classes NSMutable... ne sont pas thread safe, donc c'est avec beaucoup de précaution que tu dois les modifier dans ton thread, puis de les lire (dans drawRect:) à  partir du main thread sous peine de ... crash.

    .
  • 03:48 modifié #22
    Oui oui j'ai conscience de ne pas fournir assez d'éléments... Là  malheureusement j'ai pas trop le temps mais je vais essayer de faire un projet minimal qui ait le même problème... Sinon ce que tu me dis sur les NSMutable est intéressant, j'avais perdu de vue cette histoire de thread safe !


    Merci beaucoup à  tous en tout cas !
  • psychoh13psychoh13 Mothership Developer Membre
    03:48 modifié #23
    Excuse-moi, mais je voudrais seulement savoir quel est l'intérêt de garder une série de points dans le même NSArray que la NSBezierPath qu'il contient... Est-ce que tu as lu ce que j'ai dit à  ce propos? :D
  • 03:48 modifié #24
    Oui j'ai lu et j'y ai déjà  répondu... Je n'ai jamais dit que les points du tableau coà¯ncidaient avec le bezier... Quand à  la NSAffineTransform oui je connais, je les utilise d'ailleurs autre part dans mon code, mais ici le mouvement n'est pas une simple rotation...
    Merci quand même ;-)

    J'essaie de faire un projet minimal qui pose souci, mais je pense que le problème vient des NSMutable comme me le soulignait Bru... je vais donc tâcher de voir comment y remédier !
  • 03:48 modifié #25
    Bon voilà  j'ai créé rapidement un petit truc qui illustre le problème...

    Encore une fois le code fourni ici est quelque chose de minimal qui ne correspond en fait à  rien dans mon code, donc inutile de me dire qu'il vaut mieux utiliser les NSTransform etc, je connais...

    La question est donc juste : pourquoi plante-t-il ?
    Sachant que la réponse est a priori triviale pour quelqu'un qui a bien saisi ce qui se passe en mémoire : ce qui n'est visiblement pas du tout mon cas !


    Voilà  donc merci à  ceux qui voudront se pencher sur mon problème, sûrement ridicule  ;)






    [Fichier joint supprimé par l'administrateur]
  • schlumschlum Membre
    octobre 2007 modifié #26
    Pas un problème de mémoire, mais un problème de thread-safety...
    À la fois tu essaies de dessiner un NSBezierPath (dans "drawRect"), et tu le modifies (dans "rotationContinueEffective")

    J'ai entouré le contenu de ces deux fonctions (de la classe "MyView") par :

        @synchronized(self) {<br />       // ...<br />    }
    


    Et ça m'a l'air stable depuis tout à  l'heure (bien que ça m'utilise 143% de CPU et que ça fait souffler mes ventilos !).

    [Edit] PS : "@synchronized(arrayOfBezier)"; devrait suffire...
  • 03:48 modifié #27
    hihi je connais pas du tout ça !!

    Je sais pas comment ça s'utilise... Pour l'instant chez moi ça plante toujours avec ça... Vais me documenter

    Merci beaucoup en tout cas ! :-) :-)

  • psychoh13psychoh13 Mothership Developer Membre
    03:48 modifié #29
    Bon, je regardais de loin et en passant vite ton code.
    Mes premières impressions sont que ton code est illisible... Pour l'amour de Dieu ! Aère ton code ! C'est extrêmement important, même si c'est pas le code final il faut absolument l'aérer parce que c'est carrément impossible à  lire.

    Ensuite, tu te fais chier à  créer des NSString pour contenir des variables floats...  ???
    Pourquoi tu n'utilises pas la classe NSNumber ?
    Elle est faite pour contenir des variables numériques (entier, flottant, bool, double, ce que tu veux !) pour pouvoir mettre ces objets dans une collection !

    Essaye ensuite de répartir un peu ton code, fais des variables temporaires qui vont contenir une valeur que tu vas utiliser plusieurs fois.

    Pourquoi n'utilises-tu pas les outils que te fournit Cocoa ? Par exemple, la classe NSValue possède des constructeurs pour des objets NSSize, NSPoint et NSRect...

    Dans ta méthode -drawRect:, il est totalement inutile (voire même dangereux) d'appeler la méthode de la super classe, pour plusieurs raisons. Déjà  ce n'est pas [self frame] qu'il aurait fallu passé mais "rect", et ensuite parce que drawRect: n'est pas définie dans la super-classe, NSView est une classe abstraite et -drawRect: une méthode abstraite qui ne fait donc rien au départ.

    Je te conseille aussi, au lieu d'utiliser un timer, d'utiliser dans ton thread séparé, la méthode : +[NSThread sleepUntilDate:] en utilisant une boucle infinie et une NSDate, de cette manière :

    -(void)rotationContinueEffectiveThread:(id)anArgument<br />{<br />	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];<br />	while(YES)<br />	{<br />		[self rotationContinueEffective:nil];<br />		[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]];<br />	}<br />	<br />	[pool release];<br />}
    


    Et pas besoin de runLoop. De plus inutile de mettre des userInfo dans un timer si tu ne les utilises pas; Il faut aussi savoir que l'argument d'une méthode appelée par un timer est un NSTimer, et c'est même le NSTimer qui a fait l'appel.

    Je pense aussi que tu peux remplacer tes NSArray par des NSDictionary, en utilisant des constantes en guises de clés.
    Par exemple :

    NSString *MyBezierPathKey = @&quot;MyBezierPathKey&quot;;<br />NSString *MyStrokeColorKey = @&quot;MyStrokeColorKey&quot;;<br />NSString *MyOriginPointKey = @&quot;MyOriginPointKey&quot;;
    


    Je pense que ça évitera beaucoup d'erreurs...

    Je vais continuer à  fouiller ton code.
  • 03:48 modifié #30
    voui voui mais je sais j'ai dit que c'était fait à  l'arrache.... le seul but c'était de savoir pourquoi ça plantait, et ça Schlum y a déjà  répondu... ce truc est fait n'importe comment c'était juste le plantage qui comptait


  • psychoh13psychoh13 Mothership Developer Membre
    03:48 modifié #31
    Bah justement c'est ça que je te reproche B) La programmation ça se fait pas à  l'arrache ! Quand on programme on réfléchit, même et surtout si c'est pour montrer qu'on a tord.

    Essaye de mieux organiser ton code, et surtout simplifie-le, là  en l'occurrence il est extrêmement tordu et doit être super lourd à  l'exécution...
Connectez-vous ou Inscrivez-vous pour répondre.