Mam kod, który chcę uruchomić tylko raz, nawet jeśli okoliczności, które go uruchomiły, mogą się zdarzyć wiele razy.
Na przykład, gdy użytkownik kliknie myszą, chcę kliknąć rzecz:
void Update() {
if(mousebuttonpressed) {
ClickTheThing(); // I only want this to happen on the first click,
// then never again.
}
}
Jednak z tym kodem za każdym razem, gdy klikam myszą, rzecz zostaje kliknięta. Jak mogę to zrobić tylko raz?
architecture
events
MichaelHouse
źródło
źródło
Odpowiedzi:
Użyj flagi logicznej.
W pokazanym przykładzie zmodyfikujesz kod tak, aby wyglądał następująco:
Ponadto, jeśli chcesz móc powtarzać akcję, ale ogranicz częstotliwość akcji (tj. Minimalny czas między kolejnymi akcjami). Zastosowałbyś podobne podejście, ale zresetowałeś flagę po pewnym czasie. Zobacz moją odpowiedź tutaj, aby uzyskać więcej pomysłów na ten temat.
źródło
onStart() { delegate = doSomething; } ... if(mousebuttonpressed) { delegate.Execute(); }
ivoid doSomething() { doStuff(); delegate = funcDoNothing; }
, ale w końcu wszystkie opcje w końcu o flagę niektórych rodzajów masz włączony / wyłączony ... i delegata w tym przypadku to nic innego (z wyjątkiem, jeśli masz więcej niż dwie opcje co się dzieje być może?).Jeśli flaga bool nie wystarczy lub chcesz poprawić czytelność * kodu w
void Update()
metodzie, możesz rozważyć użycie delegatów (wskaźników funkcji):W przypadku zwykłego polecenia „wykonaj raz” delegaci są przesadzeni, dlatego sugerowałbym użycie flagi bool.
Jeśli jednak potrzebujesz bardziej skomplikowanej funkcjonalności, delegaci są prawdopodobnie lepszym wyborem. Na przykład, jeśli chcesz wykonać więcej różnych działań: jedno przy pierwszym kliknięciu, drugie na drugim i jeszcze jedno na trzecim, możesz po prostu zrobić:
zamiast dręczyć swój kod dziesiątkami różnych flag.
* kosztem niższej możliwości konserwacji pozostałej części kodu
Zrobiłem trochę profilowania z wynikami, tak jak się spodziewałem:
Pierwszy test został przeprowadzony na Unity 5.1.2, mierzonym
System.Diagnostics.Stopwatch
w 32-bitowym projekcie (nie w projektancie!). Drugi na Visual Studio 2015 (v140) skompilowany w 32-bitowym trybie wydania z flagą / Ox. Oba testy przeprowadzono na procesorze Intel i5-4670K przy częstotliwości 3,4 GHz, z 10 000 000 iteracji dla każdej implementacji. kod:wniosek: Podczas gdy kompilator Unity wykonuje dobrą robotę, optymalizując wywołania funkcji, dając mniej więcej taki sam wynik zarówno dla flagi dodatniej, jak i delegatów (odpowiednio 21 i 25 ms), niepoprawne przewidywanie gałęzi lub wywołanie funkcji jest wciąż dość drogie (uwaga: należy przyjąć, że pamięć podręczna jest delegowana w tym teście).
Co ciekawe, kompilator Unity nie jest wystarczająco inteligentny, aby zoptymalizować gałąź, gdy pojawi się 99 milionów nieprzewidzianych błędów, więc ręczne negowanie testu daje pewien wzrost wydajności, dając najlepszy wynik 5 ms. Wersja C ++ nie wykazuje żadnego wzrostu wydajności w celu zanegowania warunku, jednak ogólny koszt wywołania funkcji jest znacznie niższy.
co najważniejsze: różnica jest praktycznie nieistotna dla każdego scenariusza w świecie rzeczywistym
źródło
Dla pełności
(Właściwie nie polecam tego robić, ponieważ napisanie czegoś takiego jest dość proste
if(!already_called)
, ale byłoby to „poprawne”.)Nic dziwnego, że standard C ++ 11 z idiomizował dość trywialny problem wywołania funkcji raz i sprawił, że była bardzo wyraźna:
}
Trzeba przyznać, że standardowe rozwiązanie jest nieco lepsze od trywialnego w obecności wątków, ponieważ wciąż gwarantuje, że zawsze dzieje się dokładnie jedno wywołanie, nigdy coś innego.
Jednak zwykle nie uruchamiasz wielowątkowej pętli zdarzeń i naciskasz przyciski kilku myszy jednocześnie, więc bezpieczeństwo wątków jest nieco zbędne w twoim przypadku.
W praktyce oznacza to, że oprócz bezpiecznej dla wątków inicjalizacji lokalnej wartości logicznej (która C ++ 11 i tak gwarantuje, że jest bezpieczna dla wątków), wersja standardowa musi również wykonać atomową operację test_set przed wywołaniem lub nie wywołaniem funkcja.
Jeśli jednak lubisz wyrażać się otwarcie, istnieje rozwiązanie.
EDYCJA:
Huh. Teraz, kiedy siedzę tutaj i wpatruję się w ten kod przez kilka minut, jestem prawie skłonny go polecić.
W rzeczywistości nie jest to tak głupie, jak mogłoby się początkowo wydawać, w rzeczywistości bardzo jasno i jednoznacznie przekazuje twoje zamiary ... prawdopodobnie lepiej ustrukturyzowane i bardziej czytelne niż jakiekolwiek inne rozwiązanie, które obejmuje takie
if
czy inne.źródło
Niektóre sugestie będą się różnić w zależności od architektury.
Utwórz clickThisThing () jako wskaźnik funkcji / wariant / DLL / so / etc ... i upewnij się, że jest on inicjalizowany do wymaganej funkcji, gdy instancja obiektu / komponentu / modułu / etc ...
W module obsługi po każdym naciśnięciu przycisku myszy wywołaj wskaźnik funkcji clickThisThing (), a następnie natychmiast zamień wskaźnik na inny wskaźnik funkcji NOP (brak operacji).
Nie ma potrzeby używania flag i dodatkowej logiki, aby ktoś mógł później spieprzyć, wystarczy zadzwonić i wymienić za każdym razem, a ponieważ jest to NOP, NOP nic nie robi i zostaje zastąpiony NOP.
Lub możesz użyć flagi i logiki, aby pominąć połączenie.
Lub możesz odłączyć funkcję Update () po wywołaniu, aby świat zewnętrzny zapomniał o tym i nigdy więcej go nie wywoływał.
Lub masz sam clickThisThing () używa jednej z tych koncepcji, aby odpowiedzieć użyteczną pracą tylko raz. W ten sposób możesz użyć bazowego obiektu / komponentu / modułu clickThisThingOnce () i utworzyć go w dowolnym miejscu, w którym potrzebujesz tego zachowania, a żaden z twoich programów aktualizujących nie potrzebuje specjalnej logiki w tym miejscu.
źródło
Użyj wskaźników funkcji lub delegatów.
Zmienna zawierająca wskaźnik funkcji nie musi być jawnie badana, dopóki nie trzeba wprowadzić zmiany. A kiedy już nie potrzebujesz, aby uruchomić tę logikę, zamień ją na odwołanie do funkcji pusta / brak operacji. Porównaj z wykonywaniem warunkowej kontroli każdej klatki przez całe życie programu - nawet jeśli ta flaga była potrzebna tylko w pierwszych kilku ramkach po uruchomieniu! - Nieczyste i nieefektywne. (Punkt Boreala na temat przewidywania jest jednak potwierdzony w tym względzie.)
Zagnieżdżone warunki warunkowe, które często stanowią, są nawet bardziej kosztowne niż konieczność sprawdzania jednego warunku w każdej ramce, ponieważ przewidywanie gałęzi nie może już wtedy wykonać swojej magii. Oznacza to regularne opóźnienia rzędu 10s cykli - przeciągnięcia rurociągów par excellence . Zagnieżdżanie może wystąpić powyżej lub poniżej tej flagi boolowskiej. Nie potrzebuję przypominać czytelnikom, jak szybko mogą stać się złożone pętle gier.
Z tego powodu istnieją wskaźniki funkcji - używaj ich!
źródło
W niektórych językach skryptowych, takich jak javascript lub lua, można łatwo wykonać testowanie odwołania do funkcji. W Lua ( love2d ):
źródło
W komputerze z architekturą Von Neumann w pamięci przechowywane są instrukcje i dane programu. Jeśli chcesz, aby kod był uruchamiany tylko raz, możesz mieć kod w metodzie, który zastąpi metodę NOP po jej zakończeniu. W ten sposób, jeśli metoda miałaby zostać uruchomiona ponownie, nic by się nie stało.
źródło
W python, jeśli planujesz być palantem dla innych osób w projekcie:
ten skrypt demonstruje kod:
źródło
Oto kolejny wkład, jest trochę bardziej ogólny / wielokrotnego użytku, nieco bardziej czytelny i łatwy do utrzymania, ale prawdopodobnie mniej wydajny niż inne rozwiązania.
Podsumujmy naszą jednorazową logikę w klasie Raz:
Używanie go jak w podanym przykładzie wygląda następująco:
źródło
Możesz rozważyć użycie zmiennej jako zmiennej.
Możesz to zrobić w
ClickTheThing()
samej funkcji lub utworzyć inną funkcję i wywołaćClickTheThing()
w treści if.Jest podobny do pomysłu używania flagi, jak sugerowano w innych rozwiązaniach, ale flaga jest chroniona przed innym kodem i nie można jej zmienić gdzie indziej, ponieważ jest lokalna dla funkcji.
źródło
Ten dylemat spotkałem z wejściem kontrolera Xbox. Chociaż nie dokładnie tak samo, jest cholernie podobny. Możesz zmienić kod w moim przykładzie, aby dostosować go do swoich potrzeb.
Edycja: Twoja sytuacja wykorzystałaby to ->
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagrawmouse
I możesz dowiedzieć się, jak utworzyć surową klasę wejściową za pomocą ->
https://docs.microsoft.com/en-us/windows/desktop/inputdev/raw-input
Ale ... teraz na super niesamowity algorytm ... nie bardzo, ale hej .. to całkiem fajne :)
* Więc ... możemy przechowywać stany każdego przycisku, które są wciśnięte, zwolnione i wstrzymane !!! Możemy również sprawdzić czas wstrzymania, ale wymaga to pojedynczego wyciągu if i możemy sprawdzić dowolną liczbę przycisków, ale dla niektórych informacji można znaleźć poniższe zasady.
Oczywiście, jeśli chcemy sprawdzić, czy coś jest wciśnięte, zwolnione itp., Zrobilibyśmy „If (This) {}”, ale pokazuje to, w jaki sposób możemy uzyskać stan prasy, a następnie wyłączyć następną klatkę, aby… ismousepressed ”będzie faktycznie fałszywy przy następnym sprawdzeniu.
Pełny kod tutaj: https://github.com/JeremyDX/DX_B/blob/master/DX_B/XGameInput.cpp
Jak to działa..
Więc nie jestem pewien, jakie wartości otrzymujesz, gdy obrazujesz, czy przycisk jest wciśnięty, czy nie, ale w zasadzie, gdy ładuję w XInput, otrzymuję 16-bitową wartość między 0 a 65535, to ma 15 bitów możliwych stanów dla „wciśniętego”.
Problem występował za każdym razem, gdy to sprawdzałem. Po prostu dawał mi aktualny stan informacji. Potrzebowałem sposobu, aby przekonwertować bieżący stan na wartości wyciskane, zwolnione i wstrzymane.
Więc to, co zrobiłem, jest następujące.
Najpierw tworzymy zmienną „CURRENT”. Za każdym razem, gdy sprawdzamy te dane, ustawiamy „CURRENT” na zmienną „PREVIOUS”, a następnie przechowujemy nowe dane na „Current”, jak pokazano tutaj ->
Dzięki tym informacjom jest to ekscytujące !!
Możemy teraz dowiedzieć się, czy przycisk jest ODŁĄCZONY!
To robi w zasadzie porównuje dwie wartości i wszystkie naciśnięcia przycisków, które są pokazane w obu pozostaną 1, a wszystko inne ustawione na 0.
Tj. (1 | 2 | 4) i (2 | 4 | 8) da (2 | 4).
Teraz, kiedy już mamy, które przyciski są „HELD” w dół. Możemy zdobyć resztę.
Naciśnięcie jest proste. Przyjmujemy stan „AKTUALNY” i usuwamy wszystkie przytrzymane przyciski.
Wydany jest taki sam, tylko porównujemy go do naszego OSTATNEGO stanu.
Patrząc na sytuację z tłoczeniem. Jeśli powiedzmy, że obecnie mieliśmy 2 | 4 | 8 wciśnięty. Okazało się, że 2 | 4, gdzie odbyła się. Po usunięciu bitów trzymanych zostaje nam tylko 8. To nowo wciśnięty bit dla tego cyklu.
To samo można zastosować do wydania. W tym scenariuszu „OSTATNI” został ustawiony na 1 | 2 | 4. Więc kiedy usuniemy 2 | 4 bity. Pozostaje nam 1. Więc przycisk 1 został zwolniony od ostatniej klatki.
Powyższy scenariusz jest prawdopodobnie najbardziej idealną sytuacją, jaką możesz zaoferować do porównania bitów i zapewnia 3 poziomy danych bez instrukcji if lub pętli tylko 3 szybkie obliczenia bitów.
Chciałem również udokumentować dane wstrzymania, więc chociaż moja sytuacja nie jest idealna ... to w zasadzie ustawiamy ograniczenia, które chcemy sprawdzić.
Dlatego za każdym razem, gdy ustawiamy nasze dane Press / Release / Hold, sprawdzamy, czy dane hold nadal odpowiadają bieżącemu testowi bitów hold. Jeśli nie, resetujemy czas do aktualnego czasu. W moim przypadku ustawiam go na indeksy klatek, więc wiem, dla ilu klatek zostało wstrzymane.
Minusem tego podejścia jest to, że nie mogę uzyskać indywidualnych czasów wstrzymania, ale możesz sprawdzić wiele bitów jednocześnie. To znaczy, jeśli ustawię bit wstrzymania na 1 | 16, jeśli 1 lub 16 nie zostaną utrzymane, to się nie powiedzie. Wymaga to przytrzymania WSZYSTKICH tych przycisków, aby kontynuować tykanie.
Następnie, jeśli spojrzysz na kod, zobaczysz wszystkie fajne wywołania funkcji.
Twój przykład sprowadza się zatem do sprawdzenia, czy naciśnięcie przycisku miało miejsce, a naciśnięcie przycisku może wystąpić tylko raz przy użyciu tego algorytmu. Przy następnym sprawdzeniu, aby nacisnąć, nie będzie istnieć, ponieważ nie można nacisnąć więcej niż raz, aby zwolnić, zanim będzie można nacisnąć ponownie.
źródło
Głupie tanie wyjście, ale możesz użyć pętli for
źródło