[Tutoriel] Maitrisez le temps en C/C++ !
Posté le 27/02/2014 21:59
Un des meilleurs atouts que possède la programmation d'addins en C/C++, c'est de bénéficier du système Real Time Clock (RTC), et surtout du syscall RTC_getTicks(). Dans ce tutoriel, nous verrons en détail les différentes façon d'utiliser RTC_getTicks(), comme régulateur de FPS, compte à rebours, etc.
Sommaire
1. Rappels sur les sycalls
2. Appeler RTC_getTicks()
3. Réguler ses FPS
4. Afficher les FPS
5. Chronométrer du temps
6. Régulariser des opérations avec les timers (n'utilise pas RTC_getTicks)
7. Générer des nombres aléatoires
Conclusion
1. Rappels sur les syscalls
RTC_getTicks() est un syscall (
"System Call" ou
"Appel système"), c'est à dire une fonction déjà présente dans l'OS de la calculatrice. Il suffit donc de sauter à l'endroit où elle est stockée pour l'exécuter.
Pour cela, la meilleure méthode est de créer un fichier contenant les instructions en assembleur (ASM), puis de compiler le tout. Toutefois, cette méthode est lourde, et pas forcément très compréhensible par un programmeur lambda, d'autant plus que l'assembleur est un langage de bas niveau, c'est à dire très abstrait. Nous utiliserons donc une astuce plus ou moins correcte et valide, mais qui reste intégralement fonctionnelle.
Voici le code commenté pour appeler un syscall (ici
RTC_getTicks()) :
// Code d'une fonction *globale* pour appeler un syscall
static int SysCallCode[] = { 0xD201422B, 0x60F20000, 0x80010070 };
// Nom et paramètres de la fonction
static int (*SysCall)(int R4, int R5, int R6, int R7, int FNo ) = (void *)&SysCallCode;
// (Ces deux lignes ne sont à placer dans votre code qu'une seule fois)
int RTC_getTicks(void)
{
// On donne le numéro du syscall (0x3b) et on transmet les arguments
// (ici il n'y en a pas donc on met des 0, ça n'a aucune importance)
return (*SysCall)(0, 0, 0, 0, 0x3b);
}
// On peut répéter l'opération avec d'autres syscalls
// (ceci est un exemple sûrement non fonctionnel) :
int SYS_call(int x, int y)
{
return (*SysCall)(x, y, 0, 0, 0x42);
}
Vous pouvez placer ces fonctions n'importe où tant que vous avez mis un prototype avant le
main().
2. Appeler RTC_getTicks()
Maintenant que nous avons défini
RTC_getTicks(), nous allons l'utiliser. La fonction retourne le nombre de
"ticks", des périodes durant
1/128ème de seconde, mesurées par l'horloge interne de la calculatrice depuis sa mise en service (ou une autre date, Casio n'ayant jamais fourni de renseignements sur ce point).
Pour cela, prévoyez un
int pour récupérer la valeur :
int ticks = 0;
ticks = RTC_getTicks();
3. Réguler ses FPS
Les
Frames Per Second (FPS), en français "images par seconde", sont un élément important d'un jeu. En effet, si ceux-ci ne sont pas régulés, il peut et y aura des différences d'exécution entre différents modèles, particulièrement entre les SH3 et les SH4. De plus, certaines fonctions comme
IsKeyDown() provoquent des ralentissements lorsque des touches sont enfoncées, et peuvent fortement modifier la vitesse d'exécution du jeu, le rendant moins agréable à utiliser.
Heureusement, nous avons à notre disposition en C/C++ la fonction
RTC_getTicks(). Grace à celle-ci, nous allons réguler de manière dynamique le nombre de FPS de notre jeu.
Nous allons donc créer une fonction qui retiendra le nombre de
ticks écoulés depuis le dernier appel de la fonction. Du coup, si le programme est en avance, nous le mettons en pause jusqu'à ce qu'il soit à l'heure. Nous définissons donc un nombre précis de
ticks qu'il faudra laisser écouler par frame.
Sachant que 1
tick = 1/128 seconde, nous pouvons en déduire que laisser 1
tick s'écouler équivaut à 128 / 1 = 128 FPS, 2
ticks = 128 / 2 = 64 FPS, 3
ticks = 128 / 3 = 42 FPS, etc. Nous donnerons donc en argument à la fonction le nombre de
ticks à laisser tourner. Voici un récapitulatif des valeurs les plus utilisées :
Cliquez pour découvrir
Cliquez pour recouvrir
1 = 128 FPS
2 = 64 FPS
3 = 42 FPS
4 = 32 FPS
5 = 25 FPS
6 = 21 FPS
7 = 18 FPS
...
128 / x = y FPS
Voici la fonction complète :
void setFps(int fpsWish)
{
// "static" permet de garder les valeurs en mémoire entre les différents appels
static unsigned int fps = 0, fps_count = 0;
do
{
fps = RTC_getTicks(); // on enregistre les "ticks"
Sleep(1); // permet d'économiser de la batterie
}
while(fps < fps_count+fpsWish); // tant que ceux-ci ne se sont pas suffisamment écoulés
fps_count = RTC_getTicks(); // on met à jour les dernières valeurs
}
Pour l'utiliser, rien de plus simple :
while(1) // boucle principale
{
... // instructions
setFps(5); // pour réguler à 25 FPS
}
4. Afficher les FPS
Réguler les FPS, c'est cool, mais dans le cas d'un jeu qui demande beaucoup de ressources, il est intéressant de connaitre le nombre maximal de FPS que l'on peut avoir. Pour cela, nous allons une fois de plus utiliser
RTC_getTicks(). La fonction que nous allons créer retournera un
int, le nombre de FPS de la dernière seconde écoulée.
int getFps()
{
// variables utilisées (en static, pour pouvoir garder en mémoire les valeurs)
static int disp_fps=0, fps=1, time=0;
if(RTC_getTicks() - time > 128) // si il s'est écoulé une seconde complète
{
disp_fps = fps; // alors on récupère le nombre de FPS
fps = 0; // on remet à 0 le compteur
time = RTC_getTicks(); // et on se rappelle du nombre de ticks de la dernière seconde écoulée
}
fps++; // on monte la valeur des FPS
return disp_fps;
}
Vous n'avez plus qu'à appeler la fonction dans votre boucle principale pour récupérer le nombre de FPS :
int fps = 0;
while(1)// boucle principale
{
... // instructions
fps = getFps();
PrintVariable(fps);
}
NB 1 : La fonction
PrintVariable(int variable) est supposée afficher une variable à l'écran. Vous devez adapter en fonction de vos besoins.
NB 2 : Vous trouverez dans les commentaires de ce tutoriel une autre méthode, tout aussi valable. A vous de choisir
5. Chronométrer du temps
Dans le cas d'un jeu qui a besoin de savoir combien de temps s'est écoulé depuis une action, vous pouvez enregistrer un temps de référence, puis calculer le temps écoulé en
ticks en faisant une simple soustraction, puis convertir le résultat en secondes, minutes et autres unités de temps.
Par exemple, pour indiquer le temps écoulé depuis le lancement de la boucle :
... // instructions
temps_reference = RTC_getTicks(); // on enregistre le temps de référence
while(1)
{
... // instructions
temps_actuel = RTC_getTicks(); // on récupère le temps actuel
temps_ecoule = (int)((temps_actuel - temps_reference) / 128); // le cast (int) permet d'avoir un code plus propre et d'éviter des (W) lors de la compilation
PrintVariable(temps_ecoule); // on affiche le temps en secondes
... // instructions
}
6. Régulariser des opérations avec les timers (sans RTC_getTicks())
En construction...
Vous pouvez déjà aller voir la documentation française de fxlib.
Je pense créer un tutoriel entier sur les timers, puisque nombreuses sont les astuces et utilisations que l'on peut en faire.
7. Générer des nombres aléatoires
Un tutoriel à déjà été écrit sur ce sujet, vous trouverez
ici des explicitons sur l'utilisation de
RTC_getTicks() dans la création de nombres aléatoires.
Conclusion
Avec ces quelques explications, vous devriez pouvoir maîtriser le temps sur votre calculatrice, même si, bien entendu, ces quelques exemples ne sont pas représentatifs de toutes les possibilités offertes par le système RTC. Vous pouvez par exemple créer un jeu qui lance des quêtes qu'à des horaires réguliers, des succès à débloquer si on a joué plus de 1 heure, etc.
Si toutefois vous avez encore des questions, n'hésitez pas à les poser-ci dessous, dans ce topic dédié.
A bientôt sur Planète Casio !
Citer : Posté le 02/06/2014 19:32 | #
J'ai un petit pb, après avoir ajouté le premier code avec les syscalls, j'ai cette erreur :
(E) A value of type "void *" cannot be used to initialize an entity of type "int (*)(int, int, int, int, int)"
Peut être que c'est parce que je suis en c++ ?
Citer : Posté le 03/07/2014 14:21 | #
Pour un jeu de type action, il faut combien de FPS au minimum ?
Escape prison
Bloxorz
Free wheel
QR code
Nombre en or
RayCasting Engine
Mario Party
Zelda
et Planète Casio
Citer : Posté le 03/07/2014 14:22 | #
Il faut au moins strictement 20 FPS -- fréquence d'image de l'oeil. De manière générale, je dirais au moins 24. Pour être sûr dans les tours où tu fais plus de calculs, je te conseille 30 FPS.
Citer : Posté le 03/07/2014 14:26 | #
Parce j'ai un gros moteur de jeu et pour l'instant je suis à 10 FPS en moyenne.
Donc je pense que je vais overclocker
Escape prison
Bloxorz
Free wheel
QR code
Nombre en or
RayCasting Engine
Mario Party
Zelda
et Planète Casio
Citer : Posté le 03/07/2014 14:29 | #
Je ne pense pas qu'overclocker te permette de multiplier ta fréquence par 3
Ou alors, ce serait vraiment fantastique de pouvoir démultiplier la puissance des programmes de cette façon.
Citer : Posté le 03/07/2014 14:29 | #
Overclocker devrait être le dernier recours à mes yeux, essaye d'abord de bien tout optimiser, bien penser tes boucles, ton dessin à l'écran....
Citer : Posté le 03/07/2014 14:47 | #
Je ne pense pas qu'overclocker te permette de multiplier ta fréquence par 3
On peut la multiplier par 4 8)
Mais je pense pas le faire car je perds alors la compatibilité SH4.
Concernant la compatibilité SH4, est-ce qu'il existe une fonction "FastKey" compatible ?
Escape prison
Bloxorz
Free wheel
QR code
Nombre en or
RayCasting Engine
Mario Party
Zelda
et Planète Casio
Citer : Posté le 03/07/2014 15:09 | #
Ca ne multiplie pas par 4 les performances il me semble.
Citer : Posté le 03/07/2014 15:11 | #
Purobaz, tu sembles oublier que même si la fréquence proco est multipliée par 4, celle des accès mémoires ne l'est pas.
D'après ce que je me souviens avoir lu, on atteint difficilement le double.
Autrement dit, tu vas devoir passer par la case optimisation.
Citer : Posté le 03/07/2014 15:26 | #
Je sors du HS pour dire un truc cool
Ce tutoriel est compatible SH4
Pourras-tu survivre plus de 20 secondes dans ce fameux tunnel appelé Graviton
Rebondis entre les murs en évitant les piques dans SpikeBird
Pourras-tu éviter de te faire écraser dans FallBlocs (élu Jeu Du Mois)
La version 2048 tactile amélioré au plus haut point : 2048 Delux !
Pars à la recherche des morceaux d'étoile dans Lumyce (élu Jeu Du Mois)
Citer : Posté le 20/08/2014 19:30 | #
La fonction setFps demande en paramètre le nombre de ticks censé s'écouler en chaque frame. Sauf que lorsqu'on souhaite paramétrer un nombre de F.P.S., il faut passer par un calcul. Pourquoi ne pas demander directement en paramètre le nombre de F.P.S. ?
{
static unsigned char fps=0,fps_count=0;
short nombre_ticks=128/fpsWish;
do
{
fps=RTC_getTicks();
}
while(fps<fps_count+nombre_ticks);
fps_count=RTC_getTicks();
}
Citer : Posté le 20/08/2014 20:51 | #
Dans ce cas passe la division en static : faire une division à fois, ça ralenti (un peu) le programme
Citer : Posté le 25/09/2014 09:49 | #
J'ai un petit pb, après avoir ajouté le premier code avec les syscalls, j'ai cette erreur :
(E) A value of type "void *" cannot be used to initialize an entity of type "int (*)(int, int, int, int, int)"
Peut être que c'est parce que je suis en c++ ?
J'ai exactement le même problème, même en mettant le code dans un extern "C" ça ne marche pas, une idée?
Citer : Posté le 25/09/2014 18:17 | #
Vous devriez utiliser un fichier Assembleur pour mettre vos syscalls, là c'est vraiment trop le bordel... je veux dire, coller du code compilé pour l'exécuter en sautant dans le stack...
Citer : Posté le 25/09/2014 19:26 | #
Méthode? Je ne l'ai jamais fait.
Citer : Posté le 25/09/2014 19:30 | #
/*Syscall based :
GetTicks : 0x03B*/
.global _GetTicks ;
.type _GetTicks, @function ;
.align 2 ;
.align 2 ;
_GetTicks:
mov.l syscallAddress, r1 ;
mov.l _GetTicks_id, r0 ;
jmp @r1 ;
nop ;
.align 4 ;
_GetTicks_id:
.long 0x03B ;
.section ".text"
.align 4
syscallAddress:
.long 0x80010070
Il y a 6 occurrences du nom de la fonction (mettez celui que vous voulez) et 2 de l'id du syscall à remplacer.
Citer : Posté le 30/09/2014 12:33 | #
J'ai mis ce code dans un .src (j'utilise le SDK de Casio) et j'ai de belles erreurs (une à chaque ligne presque)... Je n'ai jamais manipulé l'assembleur donc je ne sais pas trop quoi faire
Citer : Posté le 30/09/2014 21:06 | #
; GetTicks : 0x03B
.global _GetTicks
_GetTicks:
mov.l syscallAddress, r1
mov.l _GetTicks_id, r0
jmp @r1
nop
.align 4
_GetTicks_id:
.data h'03B
.align 4
syscallAddress:
.data h'80010070
.end
Citer : Posté le 12/10/2014 19:34 | #
Non bah finalement j'ai mis le code du tuto dans un .c et ça marche très bien, ton code n'est pas bon Lephenixnoir, je n'ai pas réussit avec.
Citer : Posté le 13/10/2014 06:41 | #
Oui, désolé j'ai oublié plusieurs choses.
J'ai modifié le post au-dessus, le nouveau code fonctionne.
Citer : Posté le 13/10/2014 06:42 | #
Ok, de toute façon c'est réglé