J'ai du code que je ne veux exécuter qu'une seule fois, même si les circonstances qui déclenchent ce code peuvent se produire plusieurs fois.
Par exemple, lorsque l'utilisateur clique sur la souris, je veux cliquer sur la chose:
void Update() {
if(mousebuttonpressed) {
ClickTheThing(); // I only want this to happen on the first click,
// then never again.
}
}
Cependant, avec ce code, chaque fois que je clique sur la souris, la chose est cliquée. Comment puis-je y arriver une seule fois?
architecture
events
MichaelHouse
la source
la source
Réponses:
Utilisez un drapeau booléen.
Dans l'exemple illustré, vous modifieriez le code pour qu'il ressemble à ce qui suit:
De plus, si vous voulez pouvoir répéter l'action, mais limitez la fréquence de l'action (c'est-à-dire le temps minimum entre chaque action). Vous utiliseriez une approche similaire, mais réinitialiseriez le drapeau après un certain temps. Voir ma réponse ici pour plus d'idées à ce sujet.
la source
onStart() { delegate = doSomething; } ... if(mousebuttonpressed) { delegate.Execute(); }
etvoid doSomething() { doStuff(); delegate = funcDoNothing; }
mais à la fin toutes les options finissent par avoir un drapeau d'une sorte que vous définissez / désélectionnez ... et déléguer dans ce cas n'est rien d'autre (sauf si vous avez plus de deux options que faire peut-être?).Si le drapeau booléen ne suffit pas ou si vous souhaitez améliorer la lisibilité * du code dans la
void Update()
méthode, vous pouvez envisager d' utiliser des délégués (pointeurs de fonction):Pour les délégués "exécuter une seule fois" simples sont exagérés, je suggère donc d'utiliser plutôt le drapeau booléen.
Cependant, si vous aviez besoin de fonctionnalités plus compliquées, les délégués sont probablement un meilleur choix. Par exemple, si vous vouliez exécuter plusieurs actions différentes en chaîne: une au premier clic, une autre au deuxième et une autre au troisième, vous pourriez simplement faire:
au lieu de tourmenter votre code avec des dizaines de drapeaux différents.
* au prix d'une maintenance réduite du reste du code
J'ai fait un profilage avec des résultats à peu près comme je m'y attendais:
Le premier test a été exécuté sur Unity 5.1.2, mesuré avec
System.Diagnostics.Stopwatch
un projet construit en 32 bits (pas dans le concepteur!). L'autre sur Visual Studio 2015 (v140) compilé en mode de sortie 32 bits avec l'indicateur / Ox. Les deux tests ont été exécutés sur un processeur Intel i5-4670K à 3,4 GHz, avec 10 000 000 d'itérations pour chaque implémentation. code:conclusion: Alors que le compilateur Unity fait un bon travail lors de l'optimisation des appels de fonction, donnant à peu près le même résultat pour l'indicateur positif et les délégués (21 et 25 ms respectivement), la mauvaise prédiction de branche ou l'appel de fonction est encore assez cher (remarque: le délégué doit être supposé en cache dans ce test).
Fait intéressant, le compilateur Unity n'est pas assez intelligent pour optimiser la branche lorsqu'il y a 99 millions d'erreurs consécutives, de sorte que l'annulation manuelle du test donne une amélioration des performances donnant le meilleur résultat de 5 ms. La version C ++ ne montre aucune amélioration des performances pour la condition de négation, mais la surcharge globale de l'appel de fonction est nettement inférieure.
le plus important: la différence est à peu près hors de propos pour tout scénario du monde réel
la source
Pour être complet
(Ne recommande pas réellement de faire cela car il est en fait assez simple d'écrire simplement quelque chose comme
if(!already_called)
, mais il serait "correct" de le faire.)Sans surprise, la norme C ++ 11 a idiomisé le problème plutôt trivial d'appeler une fonction une fois, et l'a rendue super explicite:
}
Certes, la solution standard est quelque peu supérieure à la triviale en présence de threads car elle garantit toujours que toujours exactement un appel se produit, jamais quelque chose de différent.
Cependant, vous n'exécutez pas normalement une boucle d'événements multithread et n'appuyez pas sur les boutons de plusieurs souris en même temps, de sorte que la sécurité des threads est un peu superflue pour votre cas.
En termes pratiques, cela signifie qu'en plus de l'initialisation thread-safe d'un booléen local (que C ++ 11 garantit de toute façon thread-safe), la version standard doit également effectuer une opération atomic test_set avant d'appeler ou de ne pas appeler la fonction.
Pourtant, si vous aimez être explicite, il y a la solution.
EDIT:
Huh. Maintenant que je suis assis ici, à regarder ce code pendant quelques minutes, je suis presque enclin à le recommander.
En fait, ce n'est pas aussi stupide que cela puisse paraître à première vue, en effet, il communique très clairement et sans ambiguïté votre intention ... sans doute mieux structuré et plus lisible que toute autre solution impliquant une
if
telle ou telle chose .la source
Certaines suggestions varient en fonction de l'architecture.
Créez clickThisThing () en tant que pointeur de fonction / variante / DLL / so / etc ... et assurez-vous qu'il est initialisé à votre fonction requise lorsque l'objet / composant / module / etc ... est instancié.
Dans le gestionnaire à chaque fois que vous appuyez sur le bouton de la souris, appelez le pointeur de la fonction clickThisThing (), puis remplacez immédiatement le pointeur par un autre pointeur de fonction NOP (aucune opération).
Pas besoin de drapeaux et de logique supplémentaire pour que quelqu'un se trompe plus tard, il suffit de l'appeler et de le remplacer à chaque fois et comme c'est un NOP, le NOP ne fait rien et est remplacé par un NOP.
Ou vous pouvez utiliser l'indicateur et la logique pour ignorer l'appel.
Ou vous pouvez déconnecter la fonction Update () après l'appel afin que le monde extérieur l'oublie et ne l'appelle plus jamais.
Ou vous avez le clickThisThing () lui-même utiliser l'un de ces concepts pour ne répondre qu'une fois avec un travail utile. De cette façon, vous pouvez utiliser un objet / composant / module clickThisThingOnce () de base et l'instancier partout où vous avez besoin de ce comportement et aucun de vos mises à jour n'a besoin d'une logique spéciale partout.
la source
Utilisez des pointeurs de fonction ou des délégués.
La variable contenant le pointeur de fonction n'a pas besoin d'être explicitement examinée jusqu'à ce qu'un changement soit nécessaire. Et lorsque vous n'avez plus besoin de ladite logique pour fonctionner, remplacez-la par une référence à une fonction vide / sans opération. Comparer avec la vérification conditionnelle de chaque image pendant toute la durée de vie de votre programme - même si cet indicateur n'était nécessaire que dans les premières images après le démarrage! - Impur et inefficace. (Le point de vue de Boreal sur la prédiction est cependant reconnu à cet égard.)
Les conditions imbriquées, qu'elles constituent souvent, sont encore plus coûteuses que de devoir vérifier un seul conditionnel à chaque image, car la prédiction de branche ne peut plus faire sa magie dans ce cas. Cela signifie des retards réguliers de l'ordre de 10s de cycles - les arrêts de pipeline par excellence . La nidification peut avoir lieu au - dessus ou dessous de ce drapeau booléen. Je n'ai guère besoin de rappeler aux lecteurs à quel point les boucles de jeu complexes peuvent devenir rapidement.
Les pointeurs de fonction existent pour cette raison - utilisez-les!
la source
Dans certains langages de script comme javascript ou lua, il est facile de tester la référence de la fonction. À Lua ( love2d ):
la source
Dans un ordinateur Von Neumann Architecture , la mémoire est l'endroit où les instructions et les données de votre programme sont stockées. Si vous souhaitez que le code ne s'exécute qu'une seule fois, vous pouvez faire en sorte que le code de la méthode écrase la méthode avec les NOP une fois la méthode terminée. De cette façon, si la méthode devait être exécutée à nouveau, rien ne se passerait.
la source
En python si vous envisagez d'être un con pour d'autres personnes sur le projet:
ce script montre le code:
la source
Voici une autre contribution, elle est un peu plus générale / réutilisable, légèrement plus lisible et maintenable, mais probablement moins efficace que d'autres solutions.
Encapsulons notre logique à exécution unique dans une classe, Once:
L'utiliser comme l'exemple fourni ressemble à ceci:
la source
Vous pouvez envisager d'utiliser une variable statique.
Vous pouvez le faire dans
ClickTheThing()
fonction elle-même, ou créer une autre fonction et appelerClickTheThing()
le corps if.Il est similaire à l'idée d'utiliser un indicateur comme suggéré dans d'autres solutions, mais l'indicateur est protégé contre tout autre code et ne peut pas être modifié ailleurs car il est local à la fonction.
la source
En fait, je suis tombé sur ce dilemme avec l'entrée de contrôleur Xbox. Bien que ce ne soit pas exactement le même, c'est assez similaire. Vous pouvez changer le code dans mon exemple pour l'adapter à vos besoins.
Edit: Votre situation utiliserait ceci ->
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagrawmouse
Et vous pouvez apprendre à créer une classe d'entrée brute via ->
https://docs.microsoft.com/en-us/windows/desktop/inputdev/raw-input
Mais .. maintenant sur l'algorithme super génial ... pas vraiment, mais bon .. c'est plutôt cool :)
* Donc ... nous pouvons stocker les états de chaque bouton et qui sont enfoncés, relâchés et maintenus enfoncés !!! Nous pouvons également vérifier le temps d'attente, mais cela nécessite une seule instruction if et peut vérifier n'importe quel nombre de boutons, mais il existe certaines règles, voir ci-dessous pour ces informations.
Evidemment, si nous voulons vérifier si quelque chose est pressé, relâché, etc. ismousepressed "sera en fait faux lors de votre prochaine vérification.
Code complet ici: https://github.com/JeremyDX/DX_B/blob/master/DX_B/XGameInput.cpp
Comment ça fonctionne..
Donc, je ne suis pas sûr des valeurs que vous recevez lorsque vous décrivez si un bouton est enfoncé ou non, mais fondamentalement, lorsque je charge dans XInput, j'obtiens une valeur de 16 bits entre 0 et 65535, cela a 15 bits possibles pour "Pressé".
Le problème était à chaque fois que je vérifiais cela. Cela me donnerait simplement l'état actuel des informations. J'avais besoin d'un moyen de convertir l'état actuel en valeurs Pressed, Released et Hold.
Donc ce que j'ai fait est le suivant.
Nous créons d'abord une variable "CURRENT". Chaque fois que nous vérifions ces données, nous définissons le "CURRENT" sur une variable "PREVIOUS", puis stockons les nouvelles données sur "Current" comme on le voit ici ->
Avec ces informations, voici où ça devient passionnant !!
Nous pouvons maintenant déterminer si un bouton est enfoncé!
Ce que cela fait, c'est essentiellement qu'il compare les deux valeurs et toutes les pressions sur les boutons qui sont affichées dans les deux resteront 1 et tout le reste réglé sur 0.
C'est-à-dire (1 | 2 | 4) & (2 | 4 | 8) donnera (2 | 4).
Maintenant que nous avons les boutons "HELD" enfoncés. Nous pouvons obtenir le reste.
Pressé est simple .. nous prenons notre état "COURANT" et supprimons tous les boutons enfoncés.
Publié est le même que nous le comparons à notre DERNIER état à la place.
Donc, en regardant la situation pressée. Si disons Actuellement, nous en avions 2 | 4 | 8 pressé. Nous avons constaté que 2 | 4 lieu. Lorsque nous supprimons les bits maintenus, il ne nous reste que 8. Il s'agit du bit nouvellement pressé pour ce cycle.
La même chose peut être appliquée pour Released. Dans ce scénario, "LAST" a été défini sur 1 | 2 | 4. Ainsi, lorsque nous supprimons le 2 | 4 bits. Il nous reste 1. Alors le bouton 1 a été relâché depuis la dernière image.
Ce scénario ci-dessus est probablement la situation la plus idéale que vous pouvez offrir pour la comparaison de bits et il fournit 3 niveaux de données sans instructions if ou pour les boucles, juste 3 calculs de bits rapides.
Je voulais également documenter les données de retenue, donc bien que ma situation ne soit pas parfaite ... ce qu'elle fait, nous définissons essentiellement les bits de retenue que nous voulons vérifier.
Ainsi, chaque fois que nous définissons nos données de presse / libération / attente, nous vérifions si les données de conservation sont toujours égales à la vérification actuelle du bit de conservation. Si ce n'est pas le cas, nous réinitialisons l'heure à l'heure actuelle. Dans mon cas, je le définis sur des index de trame, donc je sais pour combien de trames il a été maintenu enfoncé.
L'inconvénient de cette approche est que je ne peux pas obtenir de temps d'attente individuel, mais vous pouvez vérifier plusieurs bits à la fois. C'est-à-dire si je mets le bit de maintien à 1 | 16 si 1 ou 16 ne sont pas tenus, cela échouera. Il faut donc que TOUS ces boutons soient maintenus enfoncés pour continuer à cocher.
Ensuite, si vous regardez dans le code, vous verrez tous les appels de fonction soignés.
Ainsi, votre exemple serait réduit à simplement vérifier si une pression sur un bouton s'est produite et une pression sur un bouton ne peut se produire qu'une seule fois avec cet algorithme. Lors de la prochaine vérification, il n'existera pas, car vous ne pouvez pas appuyer plus d'une fois que vous devrez relâcher avant de pouvoir appuyer à nouveau.
la source
Sortie stupide et bon marché mais vous pouvez utiliser une boucle for
la source