Podczas jednego z moich dzisiejszych wykładów na temat Unity omawialiśmy aktualizację naszej pozycji gracza, sprawdzając każdą klatkę, czy użytkownik ma wciśnięty przycisk. Ktoś powiedział, że to było nieefektywne i zamiast tego powinniśmy użyć detektora zdarzeń.
Moje pytanie brzmi: niezależnie od języka programowania lub sytuacji, w której jest stosowany, jak działa detektor zdarzeń?
Moja intuicja zakłada, że detektor zdarzeń stale sprawdza, czy zdarzenie zostało uruchomione, co oznacza, że w moim scenariuszu nie będzie inaczej niż sprawdzanie każdej klatki, jeśli zdarzenie zostało uruchomione.
Na podstawie dyskusji w klasie wydaje się, że detektor zdarzeń działa w inny sposób.
Jak działa detektor zdarzeń?
event-programming
Gary Holiday
źródło
źródło
Odpowiedzi:
W przeciwieństwie do podanego przykładu odpytywania (gdzie przycisk jest sprawdzany w każdej ramce), detektor zdarzeń nie sprawdza, czy przycisk jest w ogóle wciśnięty. Zamiast tego jest wywoływany po naciśnięciu przycisku.
Być może rzuca cię termin „detektor zdarzeń”. Termin ten sugeruje, że „słuchacz” aktywnie robi coś, aby słuchać, podczas gdy w rzeczywistości nic nie robi. „Detektor” jest jedynie funkcją lub metodą subskrybującą zdarzenie. Po uruchomieniu zdarzenia wywoływana jest metoda detektora („moduł obsługi zdarzeń”).
Zaletą schematu zdarzeń jest to, że dopóki przycisk nie zostanie wciśnięty, nie ma żadnych kosztów. Zdarzenie można obsłużyć w ten sposób bez monitorowania, ponieważ pochodzi ono z tak zwanego „przerwania sprzętowego”, które przez krótki czas zapobiega uruchomieniu uruchomionego kodu.
Niektóre interfejsy użytkownika i gry wykorzystują coś, co nazywa się „pętlą komunikatów”, która kolejkuje zdarzenia w celu ich wykonania w późniejszym (zwykle krótkim) okresie, ale nadal potrzebujesz przerwania sprzętowego, aby uzyskać to zdarzenie w pętli komunikatów.
źródło
Słuchacz wydarzeń podobny do subskrypcji biuletynu e-mailowego (rejestrujesz się, aby otrzymywać aktualizacje, których transmisja jest później inicjowana przez nadawcę), zamiast niekończącego się odświeżania strony internetowej (gdzie to Ty inicjujesz przesyłanie informacji).
System zdarzeń jest implementowany przy użyciu obiektów zdarzeń, które zarządzają listą subskrybentów. Zainteresowane obiekty (zwane subskrybentami , słuchaczami , delegatami itp.) Mogą subskrybować się, aby otrzymywać informacje o zdarzeniu, wywołując metodę, która subskrybuje się do zdarzenia, co powoduje, że zdarzenie dodaje je do swojej listy. Ilekroć zdarzenie jest uruchamiane (terminologia może również obejmować: wywoływane , wywoływane , wywoływane , uruchamiane itp.) , Wywołuje odpowiednią metodę dla każdego z subskrybentów, aby poinformować ich o zdarzeniu, przekazując wszelkie informacje kontekstowe, które muszą zrozumieć co się stało.
źródło
Krótka, niezadowalająca odpowiedź jest taka, że aplikacja odbiera sygnał (zdarzenie) i że procedura jest wywoływana tylko w tym momencie.
Dłuższe wyjaśnienie jest nieco bardziej zaangażowane.
Skąd pochodzą wydarzenia dla klientów?
Każda nowoczesna aplikacja † ma wewnętrzną, zwykle częściowo ukrytą „pętlę zdarzeń”, która rozsyła zdarzenia do odpowiednich komponentów, które powinny je odbierać. Na przykład zdarzenie „kliknięcie” jest wysyłane do przycisku, którego powierzchnia jest widoczna przy bieżących współrzędnych myszy. To jest na najprostszym poziomie. W rzeczywistości system operacyjny wykonuje większość tego wysyłania, ponieważ niektóre zdarzenia i niektóre komponenty będą odbierać wiadomości bezpośrednio.
Skąd pochodzą zdarzenia związane z aplikacją?
Systemy operacyjne wywołują zdarzenia w miarę ich występowania. Robią to reaktywnie, powiadamiani przez własnych kierowców.
Jak kierowcy generują zdarzenia?
Nie jestem ekspertem, ale na pewno niektórzy używają przerwań procesora: kontrolowany przez nich sprzęt podnosi pin procesora, gdy dostępne są nowe dane; CPU odpala sterownik, który obsługuje przychodzące dane, które ostatecznie generują (kolejkę) zdarzeń do wysłania, a następnie zwracają kontrolę z powrotem do systemu operacyjnego.
Jak więc widzisz, twoja aplikacja tak naprawdę nie działa przez cały czas. Jest to szereg procedur, które są uruchamiane przez system operacyjny (w pewnym sensie), gdy zdarzają się zdarzenia, ale przez resztę czasu nic nie robi.
† istnieją godne uwagi wyjątki, np. Gry raz, które mogą robić coś inaczej
źródło
Terminologia
zdarzenie : rodzaj rzeczy, która może się zdarzyć.
odpalanie zdarzenia : konkretne wystąpienie zdarzenia; wydarzenie się dzieje.
detektor zdarzeń : Coś, co zwraca uwagę na wystrzeliwanie zdarzeń.
moduł obsługi zdarzeń : coś, co występuje, gdy detektor zdarzeń wykrywa uruchamianie zdarzenia.
subskrybent zdarzenia : odpowiedź, którą ma wywołać procedura obsługi zdarzenia.
Te definicje nie zależą od implementacji, dlatego można je realizować na różne sposoby.
Niektóre z tych terminów są często mylone z synonimami, ponieważ użytkownicy często nie muszą ich rozróżniać.
Typowe scenariusze
Programowanie zdarzeń logicznych.
Wydarzenie jest, gdy jakaś metoda jest wywoływana.
Wypalania zdarzenie jest szczególnym wezwaniem do tej metody.
Detektor zdarzeń jest haczyk w metodzie zdarzeń, który nazywa się na każdym odpaleniem zdarzenia, które wywołuje procedurę obsługi zdarzeń.
Moduł obsługi zdarzeń wywołuje kolekcję subskrybentów zdarzenia.
Abonent (a) wydarzenie wykonać wszelkie czynności (s) system oznacza wydarzy w odpowiedzi zaistnienia zdarzenia.
Wydarzenia zewnętrzne
Wydarzenie jest wydarzeniem zewnętrznym, które można wywieść z obserwabli.
Wypalania wydarzenie jest, gdy dzieje, że zewnętrzna może być uznane za mające miejsce.
Detektor zdarzeń jakoś wykrywa zapaleń zdarzeń, często przez odpytywanie obserwowalny (s), a następnie wywołuje procedurę obsługi zdarzenia po wykryciu zdarzenia wypalania.
Moduł obsługi zdarzeń wywołuje kolekcję subskrybentów zdarzenia.
Abonent (a) wydarzenie wykonać wszelkie czynności (s) system oznacza wydarzy w odpowiedzi zaistnienia zdarzenia.
Odpytywanie a wstawianie haków do mechanizmu strzelania zdarzenia
Chodzi o to, że sondaże często nie są konieczne. Wynika to z faktu, że detektory zdarzeń można wdrożyć przez automatyczne wyzwalanie zdarzeń, które jest wywoływane przez procedurę obsługi zdarzeń, co jest często najbardziej wydajnym sposobem implementacji rzeczy, gdy zdarzenia mają miejsce na poziomie systemu.
Analogicznie nie musisz codziennie sprawdzać skrzynki pocztowej, czy pracownik pocztowy puka do twoich drzwi i przekazuje pocztę bezpośrednio do ciebie.
Jednak nasłuchiwanie zdarzeń może również działać przez odpytywanie. Sondowanie niekoniecznie musi sprawdzać określoną wartość lub inną możliwą do zaobserwowania; może być bardziej skomplikowane. Ale ogólnie rzecz biorąc, celem odpytywania jest wnioskowanie, gdy wystąpiło jakieś zdarzenie, na które można zareagować.
Analogicznie, musisz sprawdzać swoją skrzynkę pocztową każdego dnia, gdy pracownik pocztowy po prostu wrzuca do niej pocztę. Nie musiałbyś wykonywać tej ankiety, gdybyś mógł poinstruować pracownika pocztowego, aby zapukał do twoich drzwi, ale często nie jest to możliwe.
Łańcuchowa logika zdarzeń
W wielu językach programowania możesz napisać zdarzenie, które jest wywoływane po naciśnięciu klawisza na klawiaturze lub w określonym czasie. Mimo że są to zdarzenia zewnętrzne, nie trzeba ich odpytywać. Dlaczego?
To dlatego, że system operacyjny odpytuje za ciebie. Na przykład system Windows sprawdza takie rzeczy, jak zmiany stanu klawiatury, a jeśli je wykryje, wywoła subskrybentów zdarzeń. Tak więc, gdy subskrybujesz wydarzenie związane z naciśnięciem klawisza, faktycznie subskrybujesz wydarzenie, które samo w sobie jest subskrybentem wydarzenia, które sonduje.
Analogicznie powiedzmy, że mieszkasz w kompleksie mieszkaniowym, a pracownik poczty wysyła pocztę do wspólnego obszaru odbioru poczty. Następnie pracownik podobny do systemu operacyjnego może sprawdzić tę pocztę dla wszystkich, dostarczając pocztę do mieszkań tych, którzy coś otrzymali. To oszczędza wszystkim innym kłopotów z koniecznością przeszukania obszaru odbioru poczty.
Jak podejrzewasz, zdarzenie może działać poprzez odpytywanie. A jeśli zdarzenie jest w jakiś sposób powiązane z wydarzeniami zewnętrznymi, np. Naciśnięcie klawisza klawiatury, to w pewnym momencie musi nastąpić odpytywanie.
Jest również prawdą, że zdarzenia niekoniecznie muszą obejmować odpytywanie. Na przykład, jeśli zdarzenie ma miejsce po naciśnięciu przycisku, to detektor zdarzenia tego przycisku jest metodą, którą środowisko GUI może wywołać, gdy ustali, że kliknięcie myszą uderzy w przycisk. W tym przypadku nadal trzeba było przeprowadzić odpytywanie, aby można było wykryć kliknięcie myszą, ale detektor myszy jest bardziej pasywnym elementem połączonym z prymitywnym mechanizmem odpytywania poprzez łańcuch zdarzeń.
Aktualizacja: przy odpytywaniu sprzętu na niskim poziomie
Okazuje się, że urządzenia USB i inne nowoczesne protokoły komunikacyjne mają dość fascynujący, podobny do sieci zestaw protokołów interakcji, umożliwiając urządzeniom I / O, w tym klawiaturom i myszom, angażowanie się w topologie ad hoc .
Co ciekawe, „ zakłócenia ” są dość imperatywnymi, synchronicznymi rzeczami, więc nie obsługują topologii sieci ad hoc . Aby to naprawić, „ przerwań ” uogólniono w asynchroniczne pakiety o wysokim priorytecie zwane „ transakcjami przerwań ” (w kontekście USB) lub „ przerwańami sygnalizowanymi komunikatem ” (w kontekście PCI). Ten protokół jest opisany w specyfikacji USB:
Istotą wydaje się być to, że urządzenia I / O i komponenty komunikacyjne (takie jak koncentratory USB) w zasadzie działają jak urządzenia sieciowe. Wysyłają więc wiadomości, które wymagają odpytywania portów i tym podobnych. Zmniejsza to zapotrzebowanie na dedykowane linie sprzętowe.
Wydaje się, że systemy operacyjne, takie jak Windows, same obsługują proces odpytywania, np. Jak opisano w dokumentacji MSDN dla tych
USB_ENDPOINT_DESCRIPTOR
, która opisuje, jak kontrolować, jak często Windows odpytuje kontroler hosta USB w poszukiwaniu komunikatów przerywających / izochronicznych:Nowsze protokoły połączeń monitora, takie jak DisplayPort, wydają się robić to samo:
Ta abstrakcja pozwala na kilka ciekawych funkcji, takich jak uruchamianie 3 monitorów z jednego połączenia:
Pod względem koncepcyjnym należy oderwać się od tego, że mechanizmy odpytywania pozwalają na bardziej uogólnioną komunikację szeregową, co jest niesamowite, gdy potrzebujesz bardziej ogólnej funkcjonalności. Sprzęt i system operacyjny wykonują więc wiele sondowań dla systemu logicznego. Następnie klienci, którzy subskrybują wydarzenia, mogą cieszyć się szczegółami obsługiwanymi przez system niższego poziomu bez konieczności pisania własnych protokołów odpytywania / przekazywania wiadomości.
Ostatecznie zdarzenia takie jak naciśnięcia klawiszy wydają się przechodzić przez dość interesującą serię zdarzeń, zanim dotrą do imperatywnego mechanizmu wyzwalania zdarzeń na poziomie oprogramowania.
źródło
Pull vs Push
Istnieją dwie główne strategie sprawdzania, czy zdarzenie się wydarzyło lub czy osiągnięto określony stan. Na przykład wyobraź sobie, że czekasz na ważną dostawę:
Metoda ściągania (zwana również odpytywaniem) jest prostsza: możesz ją wdrożyć bez żadnych specjalnych funkcji. Z drugiej strony jest to często mniej wydajne, ponieważ ryzykujesz dodatkowe kontrole bez niczego do pokazania.
Z drugiej strony, metoda wypychania jest ogólnie bardziej wydajna: kod działa tylko wtedy, gdy ma coś do zrobienia. Z drugiej strony wymaga mechanizmu rejestrowania słuchacza / obserwatora / wywołania zwrotnego 1 .
1 Mój listonosz zazwyczaj nie ma takiego mechanizmu, niestety.
źródło
O jedności w konkretnym przypadku - nie ma innego sposobu sprawdzenia wkładu gracza, niż odpytywanie go w każdej klatce. Aby utworzyć detektor zdarzeń, nadal potrzebujesz obiektu takiego jak „system zdarzeń” lub „menedżer zdarzeń”, aby wykonać odpytywanie, więc tylko popchnęłoby problem do innej klasy.
To prawda, że gdy masz już menedżera zdarzeń, masz tylko jedną klasę odpytującą dane wejściowe w każdej ramce, ale nie daje to żadnych oczywistych korzyści w zakresie wydajności, ponieważ teraz ta klasa musi iterować nad słuchaczami i wywoływać je, które, w zależności od twojej gry projekt (jak w, ile jest nasłuchujących i jak często odtwarzacz wykorzystuje dane wejściowe), może faktycznie być bardziej kosztowny.
Poza tym pamiętajcie o złotej zasadzie - przedwczesna optymalizacja jest źródłem wszelkiego zła , co jest szczególnie prawdziwe w grach wideo, w których często proces renderowania każdej klatki kosztuje tyle, że małe optymalizacje skryptu są zupełnie nieistotne
źródło
Chyba że masz jakieś wsparcie w swoim systemie operacyjnym / frameworku, które obsługuje zdarzenia, takie jak naciśnięcie przycisku lub przepełnienie timera lub nadejście wiadomości - będziesz musiał zaimplementować ten sposób nasłuchiwania zdarzeń za pomocą odpytywania (gdzieś poniżej).
Ale nie odwracaj się od tego wzorca projektowego tylko dlatego, że nie od razu zyskujesz na wydajności. Oto powody, dla których powinieneś go używać bez względu na to, czy masz wsparcie dla obsługi zdarzeń, czy nie.
Wniosek - miałeś szczęście uczestniczyć w dyskusji i nauczyłeś się jednej alternatywy dla ankiet. Poszukaj okazji do zastosowania tej koncepcji w praktyce, a docenisz, jak elegancki może być kod.
źródło
Większość pętli zdarzeń jest zbudowanych powyżej prymitywnego multipleksowania z odpytywaniem udostępnianego przez system operacyjny. W Linuksie ta prymityw jest często
poll
(2) wywołaniem systemowym (ale może być starymselect
). W aplikacjach GUI serwer wyświetlania (np. Xorg lub Wayland ) komunikuje się (przez gniazdo (7) lub potok (7) ) z twoją aplikacją. Przeczytaj także o protokołach i architekturze systemu X Window .Takie operacje podstawowe odpytywania są wydajne; jądro w praktyce obudzi Twój proces, gdy zostanie wykonane jakieś wejście (a niektóre przerwania zostaną obsłużone).
Konkretnie, biblioteka narzędzi widgetów komunikuje się z serwerem wyświetlania, czekając na wiadomości i wysyłając te wiadomości do widgetów. Biblioteki zestawów narzędzi, takie jak Qt lub GTK, są dość złożone (miliony linii kodu źródłowego). Klawiatura i mysz są obsługiwane tylko przez proces serwera wyświetlania (który tłumaczy takie dane wejściowe na komunikaty zdarzeń wysyłane do aplikacji klienckich).
(Upraszczam; w rzeczywistości rzeczy są znacznie bardziej złożone)
źródło
W systemie opartym wyłącznie na sondowaniu podsystem, który może chcieć wiedzieć, kiedy nastąpi określone działanie, będzie musiał uruchomić jakiś kod za każdym razem, gdy może ono nastąpić. Jeśli istnieje wiele podsystemów, z których każdy musiałby zareagować w ciągu 10 ms od wystąpienia niekoniecznie wyjątkowego zdarzenia, wszyscy musieliby sprawdzać co najmniej 100 razy / sekundę, czy ich zdarzenie miało miejsce. Jeśli te podsystemy są w różnych procesach wątkowych (lub, co gorsza, procesach), wymagałoby to przełączania w każdym takim wątku lub procesie 100x / sekundę.
Jeśli wiele rzeczy, na które będą patrzeć aplikacje, jest raczej podobnych, bardziej efektywnym może być scentralizowany podsystem monitoringu - być może sterowany tabelą - który może obserwować wiele rzeczy i obserwować, czy któraś z nich się zmieniła. Jeśli na przykład są 32 przełączniki, platforma może mieć funkcję odczytu wszystkich 32 przełączników jednocześnie w słowo, umożliwiając kodowi monitora sprawdzenie, czy między przełącznikami zmieniły się jakiekolwiek ankiety, a jeśli nie, to nie martw się, jaki kod może być nimi zainteresowany.
Jeśli istnieje wiele podsystemów, które chciałyby powiadamiać, gdy coś się zmieni, dedykowany podsystem monitorowania powiadamia inne podsystemy, gdy wystąpią zdarzenia, które są nimi zainteresowane, mogą być bardziej wydajne niż to, że każdy podsystem sonduje własne zdarzenia. Utworzenie dedykowanego podsystemu monitorowania w przypadkach, w których nikt nie jest zainteresowany żadnymi zdarzeniami, stanowiłoby jednak czystą stratę zasobów. Jeśli istnieje tylko kilka podsystemów, które są zainteresowane zdarzeniami, koszt ich obejrzenia dla wydarzeń, którymi są zainteresowani, może być mniejszy niż koszt utworzenia dedykowanego podsystemu monitorowania ogólnego przeznaczenia, ale próg rentowności punkt różni się znacznie między różnymi platformami.
źródło
Odbiornik zdarzenia jest jak ucho oczekujące na wiadomość. Po wystąpieniu zdarzenia podprogram wybrany jako detektor zdarzeń działa przy użyciu argumentów zdarzenia.
Zawsze są dwa ważne dane: moment, w którym zdarzenie ma miejsce i obiekt, w którym to zdarzenie ma miejsce. Kolejnym argumentem są dodatkowe dane o tym, co się stało.
Detektor zdarzeń określa reakcję na wystąpienie.
źródło
Detektor zdarzeń stosuje się do wzorca publikowania / subskrybowania (jako subskrybent)
W najprostszej formie obiekt publikujący przechowuje listę instrukcji subskrybentów, które należy wykonać, gdy trzeba coś opublikować.
Będzie miał jakąś
subscribe(x)
metodę, gdzie x zależy od tego, jak procedura obsługi zdarzeń jest zaprojektowana do obsługi zdarzenia. Po wywołaniu funkcji subscribe (x) x jest dodawane do listy wydawców instrukcji / referencji subskrybentów.Wydawca może zawierać całą logikę do obsługi zdarzenia lub jej część. Może po prostu wymagać odwołań do subskrybentów, aby powiadomić ich / przekształcić za pomocą określonej logiki, gdy wystąpi zdarzenie. Może nie zawierać logiki i wymagać obiektów subskrybenta (metod / detektorów zdarzeń), które mogą obsłużyć zdarzenie. Najprawdopodobniej zawiera mieszankę obu.
W przypadku wystąpienia zdarzenia wydawca wykona iterację i wykona logikę dla każdego elementu na liście instrukcji / referencji subskrybentów.
Bez względu na to, jak skomplikowany jest moduł obsługi zdarzeń, u jego podstaw leży ten prosty wzór.
Przykłady
Na przykład detektora zdarzeń podajesz metodę / funkcji / instrukcji / detektora zdarzeń do metody subscribe () procedury obsługi zdarzeń. Procedura obsługi zdarzeń dodaje metodę do swojej listy wywołań zwrotnych subskrybenta. Gdy wystąpi zdarzenie, moduł obsługi zdarzeń wykonuje iterację po liście i wykonuje każde wywołanie zwrotne.
Na przykład w świecie rzeczywistym, kiedy subskrybujesz biuletyn na Stack Exchange, odniesienie do twojego profilu zostanie dodane do tabeli subskrybentów bazy danych. Kiedy nadejdzie czas opublikowania biuletynu, odnośnik zostanie wykorzystany do wypełnienia szablonu biuletynu i zostanie wysłany na Twój e-mail. W tym przypadku x jest po prostu odniesieniem do ciebie, a wydawca ma zestaw instrukcji wewnętrznych używanych dla wszystkich subskrybentów.
źródło