Ich habe einen Code, den ich nur einmal ausführen möchte, obwohl die Umstände, die diesen Code auslösen, mehrmals auftreten können.
Wenn der Benutzer zum Beispiel mit der Maus klickt, möchte ich auf das Ding klicken:
void Update() {
if(mousebuttonpressed) {
ClickTheThing(); // I only want this to happen on the first click,
// then never again.
}
}
Mit diesem Code wird jedoch jedes Mal, wenn ich mit der Maus klicke, das Ding angeklickt. Wie kann ich das nur einmal machen?
architecture
events
MichaelHouse
quelle
quelle
Antworten:
Verwenden Sie eine Boolesche Flagge.
In dem gezeigten Beispiel würden Sie den Code folgendermaßen ändern:
Außerdem, wenn Sie die Aktion wiederholen möchten, aber die Häufigkeit der Aktion begrenzen möchten (dh die Mindestzeit zwischen den einzelnen Aktionen). Sie würden einen ähnlichen Ansatz verwenden, aber das Flag nach einer bestimmten Zeit zurücksetzen. Siehe meine Antwort hier für weitere Ideen dazu.
quelle
onStart() { delegate = doSomething; } ... if(mousebuttonpressed) { delegate.Execute(); }
undvoid doSomething() { doStuff(); delegate = funcDoNothing; }
aber am Ende haben alle Optionen eine Markierung, die Sie gesetzt / deaktiviert haben ... und delegieren ist in diesem Fall nichts anderes (außer wenn Sie mehr als zwei Optionen haben, was tun?).Sollte das bool-Flag nicht ausreichen oder Sie möchten die Lesbarkeit * des Codes in der
void Update()
Methode verbessern , können Sie die Verwendung von Delegaten (Funktionszeigern) in Betracht ziehen :Für einfache "Execute Once" -Delegierte ist der Overkill, daher würde ich vorschlagen, stattdessen das Bool-Flag zu verwenden.
Wenn Sie jedoch komplexere Funktionen benötigen, sind die Delegierten wahrscheinlich die bessere Wahl. Wenn Sie beispielsweise mehrere verschiedene Aktionen hintereinander ausführen möchten: eine beim ersten Klicken, eine beim zweiten und eine beim dritten Klicken, können Sie einfach Folgendes tun:
anstatt Ihren Code mit Dutzenden verschiedener Flags zu quälen.
* zu Lasten einer geringeren Wartbarkeit des restlichen Codes
Ich habe ein paar Profile mit Ergebnissen erstellt, so wie ich es erwartet hatte:
Der erste Test wurde unter Unity 5.1.2 ausgeführt, gemessen mit einem
System.Diagnostics.Stopwatch
32-Bit-Projekt (nicht in Designer!). Die andere Version von Visual Studio 2015 (v140) wurde im 32-Bit-Veröffentlichungsmodus mit dem Flag / Ox kompiliert. Beide Tests wurden auf einer Intel i5-4670K-CPU mit 3,4 GHz und 10.000.000 Iterationen für jede Implementierung ausgeführt. Code:Fazit: Während der Unity-Compiler bei der Optimierung von Funktionsaufrufen gute Arbeit leistet und sowohl für positive Flags als auch für Delegates ungefähr dasselbe Ergebnis liefert (21 bzw. 25 ms), ist die falsche Vorhersage des Zweigs oder der Funktionsaufruf immer noch recht teuer (Anmerkung: Als Delegate sollte der Cache angenommen werden in diesem Test).
Interessanterweise ist der Unity-Compiler nicht schlau genug, um den Zweig zu optimieren, wenn 99 Millionen aufeinanderfolgende Fehlvorhersagen vorliegen. Das manuelle Negieren des Tests führt also zu einer Leistungssteigerung, die das beste Ergebnis von 5 ms ergibt. Die C ++ - Version zeigt keine Leistungssteigerung bei negierender Bedingung, der Gesamtaufwand für Funktionsaufrufe ist jedoch erheblich geringer.
am wichtigsten: der unterschied ist so ziemlich irrelevat für jedes reale Szenario
quelle
Zur Vollständigkeit
(Es wird nicht empfohlen, dies zu tun, da es eigentlich ziemlich einfach ist, einfach so etwas zu schreiben
if(!already_called)
, aber es wäre "richtig", dies zu tun.)Es ist nicht überraschend, dass der C ++ 11-Standard das eher triviale Problem des einmaligen Aufrufs einer Funktion identifiziert und sehr deutlich gemacht hat:
}
Zugegebenermaßen ist die Standardlösung der trivialen Lösung bei vorhandenen Threads etwas überlegen, da sie weiterhin garantiert, dass immer genau ein Aufruf erfolgt, niemals etwas anderes.
Normalerweise führen Sie jedoch keine Multithread-Ereignisschleife aus und drücken gleichzeitig die Tasten mehrerer Mäuse, sodass die Threadsicherheit für Ihren Fall etwas überflüssig ist.
In der Praxis bedeutet dies, dass die Standardversion neben der thread-sicheren Initialisierung eines lokalen Booleschen Werts (der in C ++ 11 ohnehin als thread-sicher gilt) auch eine atomare test_set-Operation ausführen muss, bevor der Aufruf oder Nichtaufruf erfolgt die Funktion.
Wenn Sie dennoch explizit sein möchten, gibt es die Lösung.
EDIT:
Huh. Jetzt, da ich hier sitze und einige Minuten auf diesen Code gestarrt habe, bin ich fast geneigt, ihn zu empfehlen.
Tatsächlich ist es nicht annähernd so albern, wie es auf den ersten Blick scheinen mag, tatsächlich kommuniziert es sehr klar und eindeutig Ihre Absicht ... wohl besser strukturiert und lesbarer als jede andere Lösung, die eine
if
oder eine solche beinhaltet.quelle
Einige Vorschläge variieren je nach Architektur.
Erstellen Sie clickThisThing () als Funktionszeiger / variant / DLL / so / etc ... und stellen Sie sicher, dass es für die gewünschte Funktion initialisiert wird, wenn das Objekt / die Komponente / das Modul / etc ... instanziiert wird.
Rufen Sie im Handler bei jedem Drücken der Maustaste den Funktionszeiger clickThisThing () auf und ersetzen Sie den Zeiger sofort durch einen anderen Funktionszeiger NOP (keine Operation).
Keine Notwendigkeit für Flags und zusätzliche Logik, damit jemand später Fehler macht. Rufen Sie es einfach auf und ersetzen Sie es jedes Mal. Da es sich um eine NOP handelt, unternimmt die NOP nichts und wird durch eine NOP ersetzt.
Oder Sie können das Flag und die Logik verwenden, um den Anruf zu überspringen.
Oder Sie können die Update () - Funktion nach dem Aufruf trennen, damit die Außenwelt das vergisst und es nie wieder aufruft.
Oder Sie lassen clickThisThing () selbst eines dieser Konzepte verwenden, um nur einmal mit nützlicher Arbeit zu antworten. Auf diese Weise können Sie ein ClickThisThingOnce () - Basisobjekt / eine Basiskomponente / ein Basismodul verwenden und es an jedem Ort instanziieren, an dem Sie dieses Verhalten benötigen, und keiner Ihrer Updater benötigt überall eine spezielle Logik.
quelle
Verwenden Sie Funktionszeiger oder Delegaten.
Die Variable, die den Funktionszeiger enthält, muss nicht explizit untersucht werden, bis eine Änderung vorgenommen werden muss. Und wenn Sie diese Logik nicht mehr benötigen, ersetzen Sie sie durch einen Verweis auf eine leere / no-op-Funktion. Compare with doing conditionals überprüft jeden Frame während der gesamten Lebensdauer Ihres Programms - auch wenn dieses Flag nur in den ersten Frames nach dem Start benötigt wurde! - Unrein und ineffizient. (Boreals Standpunkt zur Vorhersage wird jedoch in dieser Hinsicht anerkannt.)
Verschachtelte Bedingungen, die diese häufig darstellen, sind sogar teurer als die Prüfung einer einzelnen Bedingung pro Frame, da die Verzweigungsvorhersage in diesem Fall nicht mehr ihre Wirkung entfalten kann. Das bedeutet regelmäßige Verzögerungen in der Größenordnung von 10 Zyklen - Pipeline-Stände par excellence . Die Verschachtelung kann über oder unter dieser Booleschen Flagge erfolgen. Ich muss die Leser kaum daran erinnern, wie schnell komplexe Spielschleifen entstehen können.
Aus diesem Grund gibt es Funktionszeiger - verwenden Sie diese!
quelle
In einigen Skriptsprachen wie Javascript oder Lua kann das Testen der Funktionsreferenz einfach durchgeführt werden. In Lua ( love2d ):
quelle
In einem Von Neumann Architecture- Computer befinden sich im Speicher die Anweisungen und Daten Ihres Programms. Wenn Code nur einmal ausgeführt werden soll, kann Code in der Methode die Methode nach Abschluss der Methode mit NOPs überschreiben. Auf diese Weise würde nichts passieren, wenn die Methode erneut ausgeführt würde.
quelle
In Python, wenn Sie vorhaben, anderen Leuten am Projekt einen Ruck zuzufügen:
Dieses Skript demonstriert den Code:
quelle
Hier ist ein weiterer Beitrag: Er ist etwas allgemeiner / wiederverwendbarer, etwas lesbarer und wartbarer, aber wahrscheinlich weniger effizient als andere Lösungen.
Lassen Sie uns unsere einmal ausgeführte Logik in einer Klasse zusammenfassen:
Die Verwendung wie im Beispiel sieht folgendermaßen aus:
quelle
Sie können statische Variablen verwenden.
Sie können dies in der
ClickTheThing()
Funktion selbst tun oder eine andere Funktion erstellen undClickTheThing()
den if-Body aufrufen .Es ähnelt der Idee, ein Flag zu verwenden, wie in anderen Lösungen vorgeschlagen, aber das Flag ist vor anderem Code geschützt und kann nicht an anderer Stelle geändert werden, da es für die Funktion lokal ist.
quelle
Ich bin tatsächlich mit Xbox Controller Input auf dieses Dilemma gestoßen. Obwohl es nicht genau das gleiche ist, ist es ziemlich ähnlich. Sie können den Code in meinem Beispiel an Ihre Bedürfnisse anpassen.
Bearbeiten: Ihre Situation würde dies verwenden ->
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagrawmouse
Und Sie können lernen, wie Sie eine rohe Eingabeklasse über -> erstellen
https://docs.microsoft.com/en-us/windows/desktop/inputdev/raw-input
Aber ... jetzt zu dem super genialen Algorithmus ... nicht wirklich, aber hey ... es ist ziemlich cool :)
* Also ... wir können die Zustände jeder Taste speichern, die gedrückt, losgelassen und gedrückt gehalten werden !!! Wir können auch die Haltezeit überprüfen. Dies erfordert jedoch eine einzelne if-Anweisung und kann eine beliebige Anzahl von Schaltflächen überprüfen. Für diese Informationen gelten jedoch einige Regeln (siehe unten).
Wenn wir überprüfen möchten, ob etwas gedrückt, freigegeben usw. ist, würden Sie natürlich "If (This) {}" ausführen. Dies zeigt jedoch, wie wir den Druckstatus abrufen und ihn dann beim nächsten Frame ausschalten können, damit Ihr " "ismousepressed" wird beim nächsten Überprüfen tatsächlich falsch sein.
Vollständiger Code hier: https://github.com/JeremyDX/DX_B/blob/master/DX_B/XGameInput.cpp
Wie es funktioniert..
Ich bin mir also nicht sicher, welche Werte Sie erhalten, wenn Sie angeben, ob eine Taste gedrückt wurde oder nicht, aber wenn ich in XInput lade, erhalte ich einen 16-Bit-Wert zwischen 0 und 65535. Dies hat 15-Bit-Zustände für "Gedrückt".
Das Problem war jedes Mal, wenn ich dies überprüfte. Es gab mir einfach den aktuellen Stand der Informationen. Ich brauchte eine Möglichkeit, den aktuellen Status in "Gedrückt", "Freigegeben" und "Werte halten" umzuwandeln.
Also, was ich getan habe, ist das Folgende.
Zuerst erstellen wir eine "CURRENT" -Variable. Jedes Mal, wenn wir diese Daten überprüfen, setzen wir den "CURRENT" auf eine "PREVIOUS" -Variable und speichern die neuen Daten unter "Current", wie hier zu sehen ->
Mit diesen Informationen wird es hier spannend !!
Wir können jetzt herausfinden, ob ein Button heruntergedrückt wird!
Im Grunde werden die beiden Werte verglichen, und alle in beiden angezeigten Tastendrücke bleiben auf 1 und alle anderen auf 0 gesetzt.
Dh (1 | 2 | 4) & (2 | 4 | 8) ergibt (2 | 4).
Nun, da wir welche Tasten haben, sind "HELD" gedrückt. Wir können den Rest besorgen.
Gedrückt ist einfach. Wir nehmen unseren "CURRENT" -Zustand an und entfernen alle gedrückten Knöpfe.
Released ist dasselbe, nur dass wir es stattdessen mit unserem LAST-Status vergleichen.
Also schaut euch die Pressed-Situation an. Sagen wir mal Derzeit hatten wir 2 | 4 | 8 gedrückt. Wir fanden, dass 2 | 4 wo gehalten. Wenn wir die gehaltenen Bits entfernen, bleiben uns nur noch 8 übrig. Dies ist das neu gedrückte Bit für diesen Zyklus.
Gleiches gilt für Freigegeben. In diesem Szenario wurde "LAST" auf 1 | gesetzt 2 | 4. Wenn wir also die 2 | entfernen 4 Bits. Wir bleiben mit 1 zurück. Also wurde die Taste 1 seit dem letzten Frame losgelassen.
Dieses obige Szenario ist wahrscheinlich die idealste Situation, die Sie für den Bitvergleich anbieten können, und bietet 3 Datenebenen ohne if-Anweisungen oder für Schleifen nur 3 schnelle Bitberechnungen.
Ich wollte auch Haltedaten dokumentieren, obwohl meine Situation nicht perfekt ist. Wir haben im Grunde genommen die Haltebits festgelegt, auf die wir prüfen möchten.
Jedes Mal, wenn wir unsere Press / Release / Hold-Daten einstellen, prüfen wir, ob die Hold-Daten immer noch der aktuellen Hold-Bit-Prüfung entsprechen. Wenn dies nicht der Fall ist, wird die Zeit auf die aktuelle Zeit zurückgesetzt. In meinem Fall setze ich es auf Frame-Indizes, damit ich weiß, für wie viele Frames es gedrückt wurde.
Der Nachteil dieses Ansatzes ist, dass ich keine einzelnen Haltezeiten erhalte, aber Sie können mehrere Bits gleichzeitig prüfen. Dh wenn ich das Haltebit auf 1 | setze 16, wenn entweder 1 oder 16 nicht gehalten werden, würde dies fehlschlagen. Daher müssen ALLE diese Schaltflächen gedrückt gehalten werden, um das Ticking fortzusetzen.
Wenn Sie dann in den Code schauen, sehen Sie alle ordentlichen Funktionsaufrufe.
Ihr Beispiel würde sich also darauf beschränken, einfach zu überprüfen, ob ein Tastendruck stattgefunden hat und ein Tastendruck mit diesem Algorithmus nur einmal auftreten kann. Bei der nächsten Überprüfung ist dies nicht der Fall, da Sie nicht mehr als einmal drücken können, bevor Sie erneut drücken können.
quelle
Blöder billiger Ausweg, aber man könnte eine for-Schleife benutzen
quelle