Les membres ayant 30 points peuvent parler sur les canaux annonces, projets et hs du chat.
La shoutbox n'est pas chargée par défaut pour des raisons de performances. Cliquez pour charger.

Forum Casio - Vos tutoriels et astuces


Index du Forum » Vos tutoriels et astuces » [Tutoriel] La gestion du clavier en C
Lephenixnoir En ligne Administrateur Points: 24232 Défis: 170 Message

[Tutoriel] La gestion du clavier en C

Posté le 17/02/2016 14:11

Pour des questions de pratique ou d'habitude, beaucoup d'entre nous utilisent des fonctions comme IsKeyDown() pour gérer leurs entrées claviers. C'est mal, et voici pourquoi.

Le problème

Le principe d'un programme disposant d'une interface graphique est simple : il travaille quand l'utilisateur lui donne du boulot et le reste du temps il attend patiemment de recevoir des ordres (travailleur acharné, je vous dis), et se met donc en pause pour un temps indéterminé. En clair, il dort.

Seulement voilà, les bons jeux en temps réel donnent au programme du travail à faire régulièrement (comme faire bouger les adversaires sur une map ou faire avancer des animations) et cet ordre ne se donne pas au clavier.

Et là, l'erreur de logique apparaît : au lieu de réveiller le programme régulièrement pour lui rappeler qu'il a du travail, on va plutôt l'empêcher de dormir. « De toute façon il fonctionne tout aussi bien donc autant le maltraiter, ça ne fait de mal à personne », me direz-vous.

Sauf que ça fait du mal à la machine, qui d'une part n'apprécie pas de ne jamais pouvoir se reposer (effet similaire à de l'overlock), ce qui use les piles, et la rend peu réceptive aux signaux qu'on lui envoie puisqu'elle est occupée à faire autre chose.

Alors n'en déplaise aux concepteurs de bibliothèques (je pense à input mais cela n'a rien contre Ninestars), les méthodes de gestion du clavier qui ne mettent pas le programme en pause (puisque finalement le clavier c'est l'interface de l'utilisateur pour donner des ordres) ne sont pas propres.

« On a setFps() » me répondrez-vous, mais setFps() utilise Sleep() (de manière un peu barbare d'ailleurs) et Sleep() est une fonction qui ne prend même pas la peine d'endormir le processeur (désassemblez le syscall 0x420, vous verrez, y'en a pour 20 lignes d'assembleur et c'est une horreur), donc c'est du pareil au même.

La solution

Assez parlé, la méthode correcte est donc, si l'on reprend notre analogie, de laisser dormir notre processeur, en lui programmant tout de même un réveil pour qu'il n'oublie pas de faire son boulot. Ça tombe bien, ce réveil s'appelle un timer et c'est probablement la fonctionnalité la moins utilisée de fxlib.

#include "fxlib.h"
#include "timer.h"

void SetTimer(int timer_id, int elapse, void (*callback)(void));
void KillTimer(int timer_id);

Ces fonctions sont très simples d'utilisation. Le timer_id s'écrit ID_USER_TIMERX, avec X variant de 1 à 5. Si vous avez la flemme vous pouvez aussi écrire de 1 à 5, ça fonctionne aussi.

La durée s'exprime en millisecondes mais le résultat est arrondi à un multiple de 25 ms (probablement tronqué en fait). Vous parlez donc en millisecondes mais vous indiquez un multiple de 25.

Si vous n'êtes pas familier avec la notation du troisième paramètre, il s'agit d'un pointeur sur une fonction de la forme « void fonction(void) ». En gros c'est la fonction quoi.

Une fois SetTimer() appelée, le timer se lance et chaque fois que la durée indiquée s'écoule, la fonction que vous avez passée en troisième argument est appelée (et ce quoi que le programme soit en train de faire à ce moment-là). Notez que le timer se recharge automatiquement.

Lorsque vous voulez arrêter le timer, vous faites appel à KillTimer(). N'oubliez pas d'arrêter le timer quand vous quittez votre fonction, vous n'avez définitivement pas envie que vos ennemis continuent à bouger sur l'écran alors que vous êtes revenu au menu principal.

Maintenant que vous avez de quoi réveiller votre processeur, vous allez pouvoir lui octroyer du temps de sommeil avec la fameuse fonction GetKey().

int GetKey(unsigned int *key);

GetKey() endort le processeur jusqu'à ce qu'une pression sur une touche le réveille, excepté qu'aujourd'hui vous avez aussi prévu un timer pour le réveiller. L'argument est l'adresse d'une variable dans laquelle le code de la touche sera stocké en suivant la norme de l'en-tête keybios.h, et la valeur de retour n'intéresse jamais personne. Votre processeur retrouve enfin le sommeil, et le sourire.

Un exemple

Voici un exemple simple permettant d'afficher des animations à deux frames d'une seconde. C'est volontairement exhaustif. Il n'y a pas particulièrement de commentaires à faire, je pense que c'est assez simple. Si vous avez une question, laissez un commentaire.

#include "fxlib.h"
#include "timer.h"

static void draw(void);
static void callback(void);
static int animation_state = 0;

int main(void)
{
    unsigned int key;

    // On dessine une premiere fois le contenu de l'ecran au frame 0
    animation_state = 0;
    draw();

    // On met en place le timer pour une seconde de delai
    SetTimer(ID_USER_TIMER1, 1000, callback);

    while(1)
    {
        // On laisse dormir le processeur \o/
        GetKey(&key);
        if(key == KEY_CTRL_EXIT) break;
    }

    // On arrete le timer avant de quitter la fonction
    KillTimer(ID_USER_TIMER1);
}

static void draw(void)
{
    Bdisp_AllClr_VRAM();

    if(animation_state == 0)
    {
        // Dessiner au frame 0
    }
    else
    {
        // Dessiner au frame 1
    }

    Bdisp_PutDisp_DD();
}

static void callback(void)
{
    // On passe de 0 a 1, ou de 1 a 0
    animation_state = !animation_state;
    // Et bien sur on redessine !
    draw();
}


Le problème du retour au menu

Vous êtes sans doute déjà fou de joie de ces nouvelles connaissances et vous vous apprêtez probablement à les mettre en œuvre avec un enthousiasme renouvelé, et vous vous rendez soudain compte que GetKey() possède quelques fonctionnalités de plus que ses cousines de la famille de IsKeyDown() :

- La capacité de revenir au menu quand on appuie sur MENU
- La modification du contraste avec SHIFT et REPLAY

Le premier vous intéresse peut-être, mais il y a un souci : en effet, lorsque vous revenez au menu, les timers ne sont pas arrêtés. Donc une seconde après votre retour au menu, votre fonction callback() est appelée de nouveau et redessine la map de votre jeu alors que l'utilisateur se trouve bien dans le menu principal de la calculatrice.

Pour cela, il existe une solution (un workaround, en fait) qui consiste à passer par un peu d'assembleur (c'est du syscall en fait, mais les syscalls C sont très peu pratiques à utiliser) mais qui nécessite de vous convertir aux matrix codes.

Les matrix codes, c'est une autre façon de noter les codes des touches. C'est plus intuitif que la liste classique de KEY_CTRL_EXE, KEY_CTRL_EXIT et tout le reste. Observez le tableau suivant :

F1      F2      F3      F4      F5      F6              09
SHIFT   OPTN    VARS    MENU    ←       ↑               08
ALPHA   ^2      ^       EXIT    ↓       →               07
XTT     log     ln      sin     cos     tan             06
ab/c    F↔D     (       )       ,       →               05
7       8       9       DEL                             04
4       5       6       x       div                     03
1       2       3       +       -                       02
0       .       EXP     (-)     EXE                     01
                                                AC      00
06      05      04      03      02      01      00

Vous voulez le code de la touche MENU ? Aucun problème, repérez la colonne... 3, et la ligne... 8. Vous ajoutez 1 (oui, désolé), ça fait 4/9, et vous avez tout de suite le code : 0x0409. EXE est en position 2/1, son code sera donc 0x0302. ALPHA donnera 0x0708. Facile, non ? Libre à vous de refaire des macros ensuite, par exemple :

#define KEY_UP          0x0209
#define KEY_RIGHT       0x0208
#define KEY_DOWN        0x0308
#define KEY_LEFT        0x0309
#define KEY_SHIFT       0x0709
#define KEY_ALPHA       0x0708
#define KEY_EXE         0x0302
#define KEY_EXIT        0x0408
#define KEY_MENU        0x0409

Pour régler le problème du menu, il vous suffit (si vous travaillez avec le fx-9860G SDK, les linuxiens trouveront tous seuls) d'inclure le fichier getkey.src, en fichier joint au tutoriel, à votre projet, et d'utiliser la fonction getkey() qui s'y trouve au lieu de GetKey(). N'oubliez pas de modifier l'extension pour un .src, le site n'accepte pas ce format.

unsigned int key = getkey();

Et là, miracle, la fonction ne vous renvoie plus au menu lorsque vous appuyez sur la touche mais vous renvoie le code de KEY_MENU défini plus haut (0x0409). Notez que cette fonction renvoie systématiquement les matrix codes, donc la touche EXE ne donne plus KEY_CTRL_EXE (30001) mais KEY_EXE (0x0302).

Et alors vous pouvez stopper vous-mêmes vos timers, lancer le menu principal en utilisant la deuxième fonction fournie dans le fichier, et redémarrer vos timers dès que l'utilisateur retourne dans votre application.

key = getkey();
if(key == KEY_MENU)
{
    KillTimer(ID_USER_TIMER1);
    system_menu();
    draw();
    SetTimer(ID_USER_TIMER1, 1000, callback);
}

Notez qu'il est intéressant de redessiner le contenu de votre écran pour éviter à l'utilisateur de contempler le menu principal encore une seconde avant que votre timer n'arrive à expiration et ne le fasse lui-même.

Je n'ai pas encore de solution viable pour le problème du contraste (SHIFT + REPLAY), n'oubliez pas lorsque vous testez sur émulateur qu'après SHIFT les directions gauche et droite ne répondent plus, ce n'est pas un bug de votre programme.

En conclusion

Si vous avez lu jusqu'ici, vous n'avez plus le droit d'utiliser IsKeyDown(). Rappelez-vous, il n'y a quasiment aucune circonstance atténuante qui justifie que vous maltraitiez votre processeur.

Je terminerai sur une petite citation de Kirafi :
Kirafi a écrit :
Bon c'est bien beau tout ça , mais maintenant faut balancer le tuto sur le Combo Getkey/Timer pour que Phénix arrête de vomir à chaque fois que quelqu'un sort un Add-In ...

Je compte sur vous...

Fichier joint


Lephenixnoir En ligne Administrateur Points: 24232 Défis: 170 Message

Citer : Posté le 26/11/2017 06:15 | #


As-tu bien vérifié que tu as utilisé un x multiple de 25 ? Le timer du système tourne à une vitesse de 40 Hz et n'est donc pas capable d'une précision plus élevée ; le délai passé à SetTimer() est automatiquement arrondi au plus proche multiple de 25 (pour une certaine notion de "plus proche" que je n'ai plus en tête).

Du reste, développer un timer qui attend la fin du callback n'est pas très intéressant et en fait plus compliqué que la façon naïve ; ça me surprendrait que ça se passe comme ça (en tous cas gint ne fonctionne pas comme ça).

Concernant ton hypothèse, le callback ne peut pas être interrompu par un autre callback dans un modèle simple de gestion des interruptions. Si le callback est plus lent que le timer, chaque appel sera exécuté juste après que le précédent aura terminé de travailler, donc le callback sera exéxuté en continu. Ce serait une manière de décider quel modèle est en jeu (car dans le tien, le programme principal ne s'arrête jamais entièrement, il dispose toujours exactement du délai entre deux exécutions du callback).

Du coup, que ça tourne en continu n'est pas contradictoire. Je peux développer plus si tu veux.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Ninestars Hors ligne Membre Points: 2461 Défis: 24 Message

Citer : Posté le 14/05/2018 22:12 | #


J'utilise un timer qui appelle une fonction ayant des arguments.
Comment je fais pour appeller ma fonction
blabla(Scene* scene, Camera* camera)
depuis un timer ? Il y a des histoires de cast en void mais je suis perdu avec ça... ^^Merci
Lephenixnoir En ligne Administrateur Points: 24232 Défis: 170 Message

Citer : Posté le 15/05/2018 07:50 | #


Le timer de fxlib est pas super bien fait ; la fonction ne peut pas prendre de paramètres. Tu es obligé d'utiliser des variables globales :

Scene *scene_for_the_timer;
Camera *camera_for_the_timer;

void callback(void)
{
  blabla(scene_for_the_timer, camera_for_the_timer);
}

Par complétude, si tu avais utilisé un timer qui te permettait de passer un paramètre void * (ce qui est la norme), tu aurais pu faire ça :

struct argument_for_the_timer
{
  Scene *scene;
  Camera *camera;
}

void callback(void *ptr)
{
  struct argument_for_the_timer *arg = ptr;
  blabla(ptr->scene, ptr->camera);
}

Il aurait toutefois fallu faire attention, au moment d'appeler le timer, à créer une structure scène/caméra qui reste dans la mémoire jusqu'à l'appel du callback. En particulier tu ne peux pas la mettre sur la pile, donc trois solutions se serait présentées (de la meilleure à la pire, à mon goût) :

- Utiliser une variable static pour la structure
- Utiliser malloc()
- Utiliser une variable globale
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Ninestars Hors ligne Membre Points: 2461 Défis: 24 Message

Citer : Posté le 16/05/2018 20:14 | #


Merci !
hmmm pas très beau d'utiliser une struct argument_for_the_timer... et le malloc c'est source d'embrouilles.
Je vais rester avec mes variables globales
Cakeisalie5 En ligne Ancien administrateur Points: 1910 Défis: 11 Message

Citer : Posté le 16/05/2018 20:17 | #


Techniquement, on appelle ce genre de structures/instances de structures des cookies, et c'est super utilisé de partout (autant dire que dans la libcasio j'en ai un paquet). Et ça permet pas mal de choses (même si du coup ça prend le contrôle du fil d'exécution en l'état, donc y a mieux mais c'est déjà très bien), donc franchement c'est pas moche, bien au contraire

Mais ce que je dis ne sert peut-être pas à grand chose

Promotion ordinaire sur les inscriptions sur Planète Casio : en ce moment, c'est gratuit !

Mon blogBesoin d'utilitaires de transfert vers et depuis la calculatrice sous GNU/Linux ?
Ninestars Hors ligne Membre Points: 2461 Défis: 24 Message

Citer : Posté le 16/05/2018 20:24 | #


Par contre j'ai un problème que je ne comprends pas (ça sort un peu du cadre du topic mais c'est l'origine de mon problème...)
Quand je fais
int main()
{
    Scene scene;
}
Mon contructeur de Scene est appelé, tout marche bien (sauf problème du dessus)
Et quand je fais la solution en variables globales
Scene scene;
int main()
{
    ...
}
Je peux lui définir des valeurs à ses attributs scene.bingo = 3 mais le constructeur n'est pas lancé. Je suis obligé de lancer le constructeur moi même scene.Scene();
Lephenixnoir En ligne Administrateur Points: 24232 Défis: 170 Message

Citer : Posté le 16/05/2018 20:26 | #


Tu compiles avec le SDK ? Si oui : tu as bien laissé les histoires de INIT_ADDIN_APPLICATION() à la fin du programme ?
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Ninestars Hors ligne Membre Points: 2461 Défis: 24 Message

Citer : Posté le 16/05/2018 20:26 | #


Cake a écrit :
Mais ce que je dis ne sert peut-être pas à grand chose
Non du tout, merci pour la précision

Ajouté le 16/05/2018 à 20:28 :
Oui, mon code de main.cpp est le suivant
Code
Cliquer pour enrouler
/****************************************************************************/
/*                                                                          */
/*   CASIO fx-9860G SDK Library                                             */
/*                                                                          */
/*   3D engine created by NineStars                                         */
/*                                                                          */
/****************************************************************************/


extern "C"
{
    #include <fxlib.h>
    #include "Time.h"
    #include "input.h"
    #include "MonochromeLib.h"
}
#include "Scene.hpp"
#include "Debug.hpp"


#define SPEED_T 1
#define SPEED_R 2


// importation de toutes les textures et objets
#include "Map.hpp"


int main();
void update_engine(Scene* scene);

// definition de la scene et la camera en variable globale

Scene scene;

//-----------------------------------------------------------------------------
// MAIN
//-----------------------------------------------------------------------------
int main()
{
    //Scene scene;
    Camera camera;
    //scene.Scene();
    scene.Scene();
    // enregistrement des textures speciales
    scene.setup(&tex_hidden, &tex_black, &tex_white);
    // definition de la map
    scene.map.id = 3;
    scene.map.list_object = list_object3;
    scene.map.list_object_size = 1;
    // definition des paramètres de la camera
    camera.set(0, 0, 10, 0, 0); // regarde selon x

    //SetTimer(1, 50, update_engine((void*)scene));
    while(scene.stop_execution == false)
    {
        //camera.yaw += 1;
        // mets à jour les paramètres de la camera
        camera.update();

        // nettoie le z buffer
        camera.clear_z_buffer();

        // efface la vram
        ML_clear_vram();

        // dessine la scene dans la vram
        scene.render_draw(&camera);

        // affiche la scene
        ML_display_vram();
    }
    
    //KillTimer(1);
    unsigned int key;
    while(1) GetKey(&key);
    return 1;
}


/*
//-----------------------------------------------------------------------------
// UPDATE_ENGINE
//-----------------------------------------------------------------------------
void update_engine(Scene* s)
{
    Scene* sc = (Scene*) &s;
    // deplacement des objets
    if (scene.map.id == 1)
    {
        // rien ne bouge
    }
    if (scene.map.id == 2)
    {
        scene.map.list_object[1]->angle -= 0.03;
    }
    if (scene.map.id == 3)
    {
        //scene.map.list_object[0]->angle += 1;
    }

    // deplacement de la camera
    input_update();

    if (input_trigger(K_EXIT) || input_trigger(K_MENU)) scene.stop_execution = true;
    
    if (input_press(K_5)) camera.move_front(SPEED_T);
    if (input_press(K_2)) camera.move_front(-SPEED_T);
    if (input_press(K_4)) camera.move_side(-SPEED_T);
    if (input_press(K_6)) camera.move_side(SPEED_T);
    if (input_press(K_MULT)) camera.move_altitude(SPEED_T);
    if (input_press(K_DIV)) camera.move_altitude(-SPEED_T);
    
    if (input_acceleration(K_RIGHT)) camera.yaw -= SPEED_R;
    if (input_acceleration(K_LEFT)) camera.yaw += SPEED_R;
    if (input_acceleration(K_DOWN)) camera.pitch -= SPEED_R;
    if (input_acceleration(K_UP)) camera.pitch += SPEED_R;
    if (camera.pitch < -90) camera.pitch = -90;
    if (camera.pitch > 90) camera.pitch = 90;
}

*/



//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
extern "C"
{
    int AddIn_main(int isAppli, unsigned short OptionNum)
    {
        return main();
    }

    #pragma section _BR_Size
    unsigned long BR_Size;
    #pragma section
    #pragma section _TOP
    int InitializeSystem(int isAppli, unsigned short OptionNum)
    {
        return INIT_ADDIN_APPLICATION(isAppli, OptionNum);
    }
    #pragma section
    /* AddIn_main(isAppli(1=MAIN,0=eAct), OptionNum(isAppli==0?0~3)) :: int(Done?1:0) */
}
Lephenixnoir En ligne Administrateur Points: 24232 Défis: 170 Message

Citer : Posté le 16/05/2018 20:38 | #


C'est curieux. Définitivement le compilateur devrait insérer un appel au constructeur au bon endroit. GCC n'y manquerait pas par exemple.

Je n'ai pas de solution satisfaisante à t'apporter. Je t'invite à utiliser une macro MANUAL_CTOR ou un truc du même genre pour contrôler l'appel manuel des constructeurs et de la définir pour l'instant.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Ninestars Hors ligne Membre Points: 2461 Défis: 24 Message

Citer : Posté le 16/05/2018 20:42 | #


D'accord, plus un problème de compilateur qu'une erreur de ma part alors. Je vais forcer le constructeur manuellement, merci

Ajouté le 20/11/2018 à 20:22 :
Rebonsoir, j'ai une autre difficulté avec les timers, j'aimerai pouvoir appeler une fonction dans une class
Scene_Title::Scene_Title()
{
    SetTimer(ID_USER_TIMER1, 50, Scene_Title::update);
}

void Scene_Title::update()
{
    // blabla
}
Comment puis-je faire ?

Ajouté le 20/11/2018 à 20:31 :
En fait je peux me débrouiller avec une fonction intermédiaire timer_update() qui appelle scene.update() car Scene_Title scene; en variable globale.
J'ai un problème un peu plus complexe en vérité avec les héritages. Mais comme ça ça fonctionne, je reviendrai si j'ai plus de problème
Lephenixnoir En ligne Administrateur Points: 24232 Défis: 170 Message

Citer : Posté le 20/11/2018 20:33 | #


En fait je peux me débrouiller avec une fonction intermédiaire timer_update() qui appelle scene.update() car Scene_Title scene; en variable globale.

Et tu n'as pas le choix, car pour appeler une méthode il faut lui passer un pointeur vers l'instance (implicite en C++, self en Python, etc), donc tu es obligé de mentionner explicitement scene à un moment.

J'ai un problème un peu plus complexe en vérité avec les héritages. Mais comme ça ça fonctionne, je reviendrai si j'ai plus de problème

Si c'est de l'héritage multiple, hmm...
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Ninestars Hors ligne Membre Points: 2461 Défis: 24 Message

Citer : Posté le 20/11/2018 20:46 | #


D'accord, c'est bien ce qui me semblait, impossible pour lui de savoir quel Scene_Title il doit appeler sachant qu'il peut y avoir plusieurs instances...

L'héritage est une piste d'optimisation, car j'ai plusieurs scenes : scene_title, scene_menu, scene_map, ... et j'aimerai avec une classe mère scene pour mutualiser du code et éviter les cascades de if ^^Je verrai ça un peu plus tard si j'ai le temps.
Lephenixnoir En ligne Administrateur Points: 24232 Défis: 170 Message

Citer : Posté le 20/11/2018 21:06 | #


As-tu pensé aux méthodes virtuelles ? Je dis ça un peu au hasard, ça a l'air de correspondre à ton besoin.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Ninestars Hors ligne Membre Points: 2461 Défis: 24 Message

Citer : Posté le 20/11/2018 21:12 | #


Oui j'ai commencé à toucher à virtual. Je suis arrivé à un point où ça plantait au lancement du jeu (pas à la complilation). Je chercherai plus tard ce n'est pas ma priorité

Ajouté le 17/06/2019 à 19:51 :
Je reviens avec un petit problème avec les timers que j'avais en partie évoqué au dessus.
Scene_Title* scene_title = new Scene_Title;
SetTimer(ID_USER_TIMER1, 50, scene_title->update());
while(scene == SCENE_TITLE)
{
    scene_title->draw();
}
KillTimer(ID_USER_TIMER1);
delete scene_title;
Avec
void Scene_Title::update()
{...}


J'aimerai pouvoir appeler scene_title->update avec le timer mais j'ai une erreur de compilation Argument of type "void" is incompatible with parameter of type "void (*)()"
Je tente des trucs, mais je n'arrive pas à me dépatouiller de cette erreur
Lephenixnoir En ligne Administrateur Points: 24232 Défis: 170 Message

Citer : Posté le 17/06/2019 20:08 | #


void update_scene_title(void)
{
  scene_title->update();
}

SetTimer(ID_USER_TIMER1, 50, update_scene_title);

Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Ninestars Hors ligne Membre Points: 2461 Défis: 24 Message

Citer : Posté le 17/06/2019 20:15 | #


Merci pour la réponse Lephé, c'est mot pour mot ce que j'avais jusqu'à présent.
Sauf que là, et excuse moi je n'avais pas précisé, scene_title n'est pas une variable globale, mais définie dans main.
L'objectif serait justement me passer de cette fonction intermédiaire Est-ce possible ?
Lephenixnoir En ligne Administrateur Points: 24232 Défis: 170 Message

Citer : Posté le 17/06/2019 20:21 | #


Tu ne peux pas ; une méthode n'est pas une fonction. Justement le code compilé de Scene_Title::update prend un paramètre (le pointeur vers la scène), et ça SetTimer() ne te le donne pas.

Normalement les APIs de timers te donnent un pointeur générique type void * à côté de la fonction à appeler, et le passent en paramètre. Comme ça, tu peux envoyer ce que tu veux. Mais comme SetTimer() ne le fait pas, tu n'as pas ce choix.

Tu pourrais être tenté de mettre la fonction dans le main() pour en faire une clôture ; ça marcherait, sous GCC. Mais c'est une pratique bof en termes de sécurité.

Après tu peux cacher ta variable globale en statique dans un module qui offre une fonction pour la modifier. C'est plus clean. Ça dépend de ton usage après, si tu m'en dis plus je pourrai peut-être proposer une solution plus appropriée.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Ninestars Hors ligne Membre Points: 2461 Défis: 24 Message

Citer : Posté le 17/06/2019 20:49 | #


D'accord merci. C'est plus clair !
Je vais garder en variable globale avec une fonction intermédiaire dans ce cas.
En fait j'ai plusieurs scènes Scene_Title, Scene_Map, .... et j'aimerai mutualiser du code, comme cela, ajouter une scène se fait de façon transparente dans main. Faire quelque chose de plus propre.
Aujourd'hui j'ai ceci :
(rappel : appel du constructeur forcé car bug SDK)
int main()
{
    game.Game();
    windmill.Windmill(&tex_black, &tex_white);
    scene_title.Scene_Title();
    scene_map.Scene_Map();
    scene_map_debug.Scene_Map_Debug();

    //scene = SCENE_TITLE;
    scene = SCENE_MAP;
    //scene = SCENE_MAP_DEBUG;
    game.new_game();


    while (scene != EXIT)
    {
        if (scene == SCENE_TITLE)
        {
            scene_title.launch();
            SetTimer(ID_USER_TIMER1, 50, update_scene_title);
            update_scene_title();
            while(scene == SCENE_TITLE)
            {
                ML_clear_vram();
                scene_title.draw();
                ML_display_vram();
            }
            KillTimer(ID_USER_TIMER1);
            scene_title.terminate();
        }
        if (scene == SCENE_MAP)
        {
            scene_map.launch();
            SetTimer(ID_USER_TIMER1, 50, update_scene_map);
            update_scene_map();
            while(scene == SCENE_MAP)
            {
                ML_clear_vram();
                scene_map.draw();
                ML_display_vram();
            }
            KillTimer(ID_USER_TIMER1);
            scene_map.terminate();
        }
        if (scene == SCENE_MAP_DEBUG)
        {
            scene_map_debug.launch();
            SetTimer(ID_USER_TIMER1, 50, update_scene_map_debug);
            update_scene_map_debug();
            while(scene == SCENE_MAP_DEBUG)
            {
                ML_clear_vram();
                scene_map_debug.draw();
                ML_display_vram();
            }
            KillTimer(ID_USER_TIMER1);
            scene_map_debug.terminate();
        }
    }

    unsigned int key;
    GetKey(&key);

    return 1;
}

De plus, quand je change de scene, un dernier callback est appelé après le KillTimer malgré le KillTimer, j'ai fortement l'impression. Est-ce possible ? Parce que j'ai eu des bugs : un appelle de upadate_scene_map alors que scene = SCENE_TITLE
Lephenixnoir En ligne Administrateur Points: 24232 Défis: 170 Message

Citer : Posté le 17/06/2019 21:20 | #


Hmm, je vois. Tu peux pouvoir changer de scène facilement pendant l'exécution, c'est bien ça ?

Mon conseil serait simplement de changer une variable globale (cachée) via un appel de fonction et de laisser tourner le timer (ou bien tu l'arrêtes, tu nettoies, et tu le relances depuis la même fonction).

Ton bug peut se produire oui, typiquement si tu rentres dans le while(scene == SCENE_TITLE) et que le timer se lance pendant que tu nettoies la VRAM, tu peux changer de scène pendant la mise à jour de la position et quand même réapparaître dans la boucle qui dessine le titre.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Ninestars Hors ligne Membre Points: 2461 Défis: 24 Message

Citer : Posté le 17/06/2019 21:46 | #


Exactement.
D'accord, merci pour ton avis
Darkjura Hors ligne Membre Points: 389 Défis: 0 Message

Citer : Posté le 31/10/2020 09:49 | #


Et que risque-t-il de se passer si, dans la fonction qu'exécute le timer, on KillTimer ?

Exemple :
SetTimer(1,100,verif);

void verif(void)
{
     if(a==0) KillTimer(1);
     affiche(map);
}

Là, il se passe quoi si a vaut 0 ?
→ Ça beugue
→ Ça stoppe le timer normalement et la map ne se dessine pas
→ Ça stoppe le timer mais la fonction se termine et dessine la map

LienAjouter une imageAjouter une vidéoAjouter un lien vers un profilAjouter du codeCiterAjouter un spoiler(texte affichable/masquable par un clic)Ajouter une barre de progressionItaliqueGrasSoulignéAfficher du texte barréCentréJustifiéPlus petitPlus grandPlus de smileys !
Cliquez pour épingler Cliquez pour détacher Cliquez pour fermer
Alignement de l'image: Redimensionnement de l'image (en pixel):
Afficher la liste des membres
:bow: :cool: :good: :love: ^^
:omg: :fusil: :aie: :argh: :mdr:
:boulet2: :thx: :champ: :whistle: :bounce:
valider
 :)  ;)  :D  :p
 :lol:  8)  :(  :@
 0_0  :oops:  :grr:  :E
 :O  :sry:  :mmm:  :waza:
 :'(  :here:  ^^  >:)

Σ π θ ± α β γ δ Δ σ λ
Veuillez donner la réponse en chiffre
Vous devez activer le Javascript dans votre navigateur pour pouvoir valider ce formulaire.

Si vous n'avez pas volontairement désactivé cette fonctionnalité de votre navigateur, il s'agit probablement d'un bug : contactez l'équipe de Planète Casio.

Planète Casio v4.3 © créé par Neuronix et Muelsaco 2004 - 2024 | Il y a 89 connectés | Nous contacter | Qui sommes-nous ? | Licences et remerciements

Planète Casio est un site communautaire non affilié à Casio. Toute reproduction de Planète Casio, même partielle, est interdite.
Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou copyrights.
CASIO est une marque déposée par CASIO Computer Co., Ltd