Modifier un NSTimer....

ceburoceburo Membre
07:54 modifié dans API UIKit #1
Salut tout le monde,

le petit problème du jour :
- j'ai une animation d'éléments CoreGraphics (méthode drawRect) en boucle,
- je voudrais la faire accélérer toutes les x secondes,
- je suis parti sur un NSTimer,
- il ne faut pas que l'animation soit bloquée pendant les interactions utilisateurs.

Problème, une fois créé, on ne peut pas modifier l'interval d'un timer.
Donc, comment palier ce problème ?

Merci.

Réponses

  • apocaalypsoapocaalypso Membre
    07:54 modifié #2
    Tu l'arrête en faisant [monTimer invalidate]; puis tu le recréé avec la nouvelle durée.
  • NseaProtectorNseaProtector Membre
    07:54 modifié #3
    Ou tu créer un ratio qui te permet d'accélérer ton déplacement.
    Genre:
    seconde = seconde +1;
    Distance = ratio * seconde;

    Et remettre a 0 seconde selon 1 ou plusieurs critères ...
  • Philippe49Philippe49 Membre
    juin 2009 modifié #4
    On peut jouer sur le fireDate du timer . Dans l'exemple qui suit, je change la couleur utilisée dans une custom view, en faisant ce changement en accéléré.

    <br />-(void) firingAction:(NSTimer *) aTimer {<br />	// Changement de la couleur de la vue<br />	NSArray * colors=[NSArray arrayWithObjects:[NSColor yellowColor],[NSColor redColor],[NSColor greenColor],[NSColor orangeColor],nil];<br />	theView.color=[colors objectAtIndex:index];	<br />	[theView setNeedsDisplay:YES];<br /><br />	// Mises à  jour pour la prochaine exécution<br />	index=(index+1)%[colors count];<br />	timeInterval=(timeInterval&lt;0.01)? 1.:timeInterval*0.9;<br />	[timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]];<br />}<br /><br />-(IBAction) toggleTimer:(id)sender {<br />	if(timer!=nil){<br />		[timer invalidate];<br />		timer=nil;<br />	} else {<br />		timeInterval=1.0;<br />		index=0;<br />		timer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(firingAction:) userInfo:nil repeats:YES];<br />	}<br />}	<br />
    


  • ceburoceburo Membre
    07:54 modifié #5
    Merci les gars,

    J'ai utilisé la méthode de NseaProtector, ça me va pour le moment, on verra pour la suite.
  • Philippe49Philippe49 Membre
    07:54 modifié #6
    dans 1244366491:

    J'ai utilisé la méthode de NseaProtector, ça me va pour le moment, on verra pour la suite.

    Ok, cela redéfinit autrement le problème. Cependant :

    dans 1244330890:

    Problème, une fois créé, on ne peut pas modifier l'interval d'un timer.

    C'est faux, voir deux posts au-dessus.
  • DrakenDraken Membre
    07:54 modifié #7
    dans 1244356680:

    Ou tu créer un ratio qui te permet d'accélérer ton déplacement.
    Genre:
    seconde = seconde +1;
    Distance = ratio * seconde;

    Et remettre a 0 seconde selon 1 ou plusieurs critères ...


    C'est la bonne technique à  adopter, surtout si tu as plusieurs animations a gérer sur l'écran. Tu ne vas pas créer un timer spécifique pour chaque animation. Imagine un jeu vidéo où chaque ennemi posséderais son propre timer !

    Il est préférable d'avoir un seul timer donnant une "ligne temporelle unique", et de l'utiliser pour calculer les déplacements et les vitesses/accélérations de chaque animation.

    Par exemple tu prend un timer calibré à  60 fois par seconde, pour coà¯ncider avec la fréquence de rafraà®chissement de l'écran.

    Quoi que. Une question pour les connaisseurs, l'écran de l'iPhone est-il rafraà®chis à  60 Hz comme un LCD classique, ou est-ce différent ?


  • Philippe49Philippe49 Membre
    07:54 modifié #8
    dans 1244368399:

    Par exemple tu prend un timer calibré à  60 fois par seconde, pour coà¯ncider avec la fréquence de rafraà®chissement de l'écran.

    C'est une utopie, cela ne sera pas respecté au run-time.


    Exemple :
    <br />-(void) firingAction:(NSTimer *) aTimer {<br />	NSArray * colors=[NSArray arrayWithObjects:[NSColor yellowColor],[NSColor redColor],[NSColor greenColor],[NSColor orangeColor],nil];<br />	theView.color=[colors objectAtIndex:index%[colors count]];	<br />	[theView setNeedsDisplay:YES];<br /><br />	index++;<br />	if(index%60==0) {<br />		NSLog(@&quot; +1 seconde&quot;);<br />	}<br />}<br />
    


    2009-06-07 13:10:57.804 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:10:58.804 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:10:59.804 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:00.804 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:01.804 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:02.805 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:03.808 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:04.809 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:05.811 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:06.814 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:07.815 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:08.818 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:09.820 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:10.821 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:11.824 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:12.825 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:13.828 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:14.829 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:15.831 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:16.849 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:17.852 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:18.854 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:19.855 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:20.858 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:21.859 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:22.860 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:23.863 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:24.865 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:25.866 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:26.869 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:27.871 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:28.873 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:29.874 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />2009-06-07 13:11:30.877 AcceleratedTimer[1735:10b]&nbsp; +1 seconde<br />
    

  • DrakenDraken Membre
    07:54 modifié #9
    Snif, c'est dur de passer de la programmation Xbox 360 à  l'iPhone.. Tellement de nouvelles contraintes techniques ! Bon d'accord, les plates-formes ne sont pas exactement les mêmes..

    On peut raisonnablement espérer quel framerate avec une application Core Graphics pas trop gourmande, sur iPhone ?


  • Philippe49Philippe49 Membre
    juin 2009 modifié #10
    dans 1244376269:

    On peut raisonnablement espérer quel framerate avec une application Core Graphics pas trop gourmande, sur iPhone ?

    On ne raisonne pas comme cela. On place l'appli en tant que client, en demandant une exécution dans un certain "délai", et le scheduler fait le reste. En tant que programmeur, on s'adapte à  chaque étape. Essaie une simple application présentant une horloge, tu verras que c'est tout simple à  condition de se dire : "bon quand j'arrives là , j'interroge le système pour savoir où il en est" plutôt que d'essayer de se dire "bon à  cette étape le système en sera exactement là ". Bref, c'est pas toi le chef en ce qui concerne la gestion de l'iPhone.

    De plus n'oublions le phénomène de persistence rétinienne, dans le domaine visuel, 60 images par seconde cela ne sert à  rien qu'à  faire des calculs intermédiaires inutiles.
  • DrakenDraken Membre
    07:54 modifié #11
    25 à  30 images/s conviennent parfaitement pour la plupart des applications graphiques. Sauf pour les scrollings, souvent plus exigeant. Mais une fois de plus la qualité du rendu dépend de la plate-forme. Donc go => tests.

  • DrakenDraken Membre
    07:54 modifié #12
    Pour en revenir à  cette histoire de Timer cadencé à  60 Hz, je viens de m'apercevoir que c'est utilisé dans le template "OpenGL ES Application". Ainsi que dans l'exemple "Sprite" disponible sur le site d'Apple.

    <br />- (void)applicationDidFinishLaunching:(UIApplication *)application {<br />&nbsp; &nbsp; <br />	glView.animationInterval = 1.0 / 60.0;<br />	[glView startAnimation];<br />}<br />
    


  • NseaProtectorNseaProtector Membre
    07:54 modifié #13
    J'ai bien noté que l'on parlait dev. sur iPhone.
    Sur mac, pour avoir un timer précis (séquenceur) j'utilise un thread comme ceci. (Je dis cela parce que les timers ne sont pas forcement ce qu'il y'a de mieux comme référence temps.)

    -(void)tempoClock:(NSString *)directoryName<br />{<br />	NSAutoreleasePool	*pool = [[NSAutoreleasePool alloc] init];<br />	// boucle while<br />	// tant que la condition n&#39;est pas remplie, on incrémente l&#39;horloge<br />	while (isRunning == TRUE)<br />	{<br />		clockTempo++;<br />		usleep(intervalTempo);<br />	}<br />	[pool release];<br />	<br />}
    
  • AliGatorAliGator Membre, Modérateur
    07:54 modifié #14
    C'est pas plus précis avec ta méthode. La méthode usleep permet de mettre en attente un thread pendant une durée minimum. Mais justement puisqu'il y a le séquenceur derrière, de toute façon tu n'as aucune garantie de tomber pile sur le temps indiqué, ça fait partie des subtilités qu'il faut prendre en compte quand on développe une appli multithread.

    Les timers sont installés sur la runloop de ton thread (par exemple la runloop de ton thread principal, si tu ne fais pas une appli multihread, runloop qui est créée et lancée automatiquement au lancement de ton appli ça fait partie des choses que fait NSApplicationMain). Quand tu crées un timer, tu le schedule sur la RunLoop, il va donc s'installer tout seul sur ta runloop en tant que RunLoopSource, et la prochaine fois que la RunLoop détecte qu'un des timers qu'elle a comme source doit être déclenché, elle le déclenche.

    Faire un thread séparé avec une boucle while qui fait des usleep revient exactement à  reproduire le schéma de la runloop qui vérifie ses inputsources et déclenche les timers quand il y a besoin.

    Dans tous les cas, lorsque l'on a besoin de faire une horloge, quelle que soit la méthode choisie, de par le fait de gérer les problématiques temps réel, il faut appliquer les règles qui s'imposent. En particulier il faut calculer le temps par rapport à  un temps de référence et non supposer qu'il s'est passé N secondes (ou millisecondes) depuis le dernier appel, car ça ce n'est jamais vérifiable. Puisque dans les applis temps réel que ce soit une runloop et un timer ou un thread et une boucle while avec usleep ou des interruptions par alarm() ou autre, dans tous les cas le temps écoulé en général est relativement proche de celui demandé, mais jamais pile poil exact, et donc il ne faut pas s'y fier au risque de créer une dérive.
  • ceburoceburo Membre
    07:54 modifié #15
    Merci les gars pour votre aide.

    J'ai réussi à  faire ce que je voulais en "détruisant" un timer puis en en créant un autre avce le nouvel Interval.
    Ma deuxième application avance bien grâce à  vous.

    Merci.
  • NseaProtectorNseaProtector Membre
    07:54 modifié #16
    dans 1245068926:

    C'est pas plus précis avec ta méthode. La méthode usleep permet de mettre en attente un thread pendant une durée minimum. Mais justement puisqu'il y a le séquenceur derrière, de toute façon tu n'as aucune garantie de tomber pile sur le temps indiqué, ça fait partie des subtilités qu'il faut prendre en compte quand on développe une appli multithread.

    Les timers sont installés sur la runloop de ton thread (par exemple la runloop de ton thread principal, si tu ne fais pas une appli multihread, runloop qui est créée et lancée automatiquement au lancement de ton appli ça fait partie des choses que fait NSApplicationMain). Quand tu crées un timer, tu le schedule sur la RunLoop, il va donc s'installer tout seul sur ta runloop en tant que RunLoopSource, et la prochaine fois que la RunLoop détecte qu'un des timers qu'elle a comme source doit être déclenché, elle le déclenche.

    Faire un thread séparé avec une boucle while qui fait des usleep revient exactement à  reproduire le schéma de la runloop qui vérifie ses inputsources et déclenche les timers quand il y a besoin.

    Dans tous les cas, lorsque l'on a besoin de faire une horloge, quelle que soit la méthode choisie, de par le fait de gérer les problématiques temps réel, il faut appliquer les règles qui s'imposent. En particulier il faut calculer le temps par rapport à  un temps de référence et non supposer qu'il s'est passé N secondes (ou millisecondes) depuis le dernier appel, car ça ce n'est jamais vérifiable. Puisque dans les applis temps réel que ce soit une runloop et un timer ou un thread et une boucle while avec usleep ou des interruptions par alarm() ou autre, dans tous les cas le temps écoulé en général est relativement proche de celui demandé, mais jamais pile poil exact, et donc il ne faut pas s'y fier au risque de créer une dérive.

    J'aime bien ce que tu dis, comme d'habitude c'est bien étayé... Sauf que, le timer dans la runloop peut-être gelé par un event ou une méthode gourmand et donc être suffisamment imprécis pour que cela se remarque. Pour avoir essayé, mon thread est très au dessus d'un timer en terme de régularité. Maintenant, je reconnais que se baser sur une interruption machine ou un compteur "machine" se serait surrement mieux, mais je ne sais pas sur quoi me baser ? Je crois qu'il existe un temps écoulé depuis l'allumage de la machine ? Est-ce différent sur mac et sur iPhone ?
  • DrakenDraken Membre
    07:54 modifié #17
    La question est abordée dans ce tuto :

    http://iphonedevelopment.blogspot.com/2009/05/opengl-es-from-ground-up-part-6_25.html

    Voici une séquence de code prise dans la routine de dessin d'une application temps réel, piloté par un timer à  60 hz. Elle calcule le temps écoulé depuis la dernière séquence d'affichage.

    <br />&nbsp; &nbsp; static NSTimeInterval lastDrawTime;<br />&nbsp; &nbsp; if (lastDrawTime)<br />&nbsp; &nbsp; {<br />&nbsp; &nbsp; &nbsp; &nbsp; NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;<br />&nbsp; &nbsp; &nbsp; &nbsp; rot+=&nbsp; 60 * timeSinceLastDraw;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br />&nbsp; &nbsp; }<br />&nbsp; &nbsp; lastDrawTime = [NSDate timeIntervalSinceReferenceDate];<br />
    


  • Philippe49Philippe49 Membre
    juin 2009 modifié #18
    Il faut initialiser la variable static pour que le test fonctionne
      static NSTimeInterval lastDrawTime=0;

    NSTimeInterval est un double. A moins que gcc fasse une initialisation automatique des variables statiques ?
  • DrakenDraken Membre
    07:54 modifié #19
    Oui, tu as raison. Le code parait un peu louche. Etrange de faire un test sur une variable non initialisée. Je comptais tester ça ce WE, je verrais bien. * croise les doigts *




  • Philippe49Philippe49 Membre
    07:54 modifié #20
    Autant pour moi, après quelques recherches,

    "Unless they have an explicit initializer, all objects with static duration are given implicit initializers"the effect is as if the constant 0 had been assigned to their components. This is in fact widely used"it is an assumption made by most C programs that external objects and internal static objects start with the value zero."

    Il n'est donc pas nécessaire d'initialiser une variable static.
Connectez-vous ou Inscrivez-vous pour répondre.