12. diel - SDĽ - Všeobecná práca s udalosťami
Tentoraz sa pozrieme na udalosti zo širšieho uhla. Povieme si niečo o filozofiu udalostí, ďalej sa pozrieme na front udalostí a prácu s ňou. V budúcom dieli budeme pokračovať a povieme si o tom, ako si môžeme vytvoriť vlastné udalosti a ako udalosti filtrovať.
Filozofia udalostí v SDL
Filozofia udalostí v SDL je trochu rozdielna od udalostí, na ktoré sme zvyknutí z ostatných technológií. Pre predstavu uvediem Windows Forms (WF), Windows Presentation Foundation (WPF) alebo tiež webový vývoj. Vo spomínaných technológiách fungujú udalosti na princípe callbacku, ktorý sa volá ihneď, ako sa udalosť registruje. Často sa tieto udalostí volajú oddelene od hlavného prúdu programu a môžu bežať aj v samostatnom vlákne. Hlavným aspektom je tu teda callback, ktorý sa spustí ihneď po tom, čo sa udalosť zaregistruje.
Z predchádzajúcich dielov je zrejmé, že SDL funguje inak. SDL pracuje s
frontom udalosťou, ktorá je rozdielová oproti poslednému volanie. Aby to
nebolo také jednoduché, SDL používa fronty dve. Prvá je systémová a
ukladá do nej operačný systém. Po zavolaní funkcie SDL_PumpEvents
sa udalosti presunú z prvej fronty do druhej, ktorá už prislúcha SDL. S
touto frontom pracujeme v programe. Tiež je potrebné počítať s tým, že do
internej fronty sa pridá od každého typu udalosti iba jedna udalosť. Bude to
rozobrané v ďalších odsekoch. Doteraz sme žiadne
SDL_PumpEvents
nepoužili. Ako sme teda udalosti z frontu
získavali? Funkcia SDL_PollEvent
, ktorú sme používali do teraz,
v sebe interne SDL_PumpEvents
volá, preto nebolo potrebné volať
funkciu explicitne.
Než sa bližšie pozrieme na prácu s internou frontom, bude potrebné
prebrať ešte pár vecí. Spomenul som rozdielové udalosti. To znamená, že
dostaneme informácie o tom, čo sa zmenilo od posledného volania
SDL_PumpEvents
. Urobíme simuláciu zložitého výpočtu a vlákno
na sekundu uspí (SDL_Delay
). Aj keď to vyzerá, že okno
neodpovedá (čo je vlastne pravda), operačný systém stále zachytáva
udalosti, ktoré aplikáciu neskôr odovzdá. Keď budeme myšou pohybovať cez
celé okno, udalosť nás bude informovať o presune, ale na aktuálnu miesto.
Pohneme Ak treba myšou na stranu a potom sa vrátime do rovnakého miesta, bude
vyvolaná SDL_MOUSEMOTION
udalosť, ale xrel
aj
yrel
sa budú rovnať nule (ako by sa myš nepohla). Preto sa po
zavolaní SDL_WarpMouseInWindow
môžeme takmer s istotou
spoľahnúť na premazanie ďalšie udalosti typu SDL_MOUSEMOTION
,
pretože všetok pohyb myši bol už v predchádzajúcej udalosti (tak ako sme
to robili v minulom diele). Ak používame SDL_PeepEvents
, máme
istotu stopercentné, ale o tom si povieme až v ďalších odsekoch.
Zaujímavá vlastnosť, kedy sa po zavolaní SDL_PumpEvents
vráti z každého typu udalosti len jedna inštancia, má aj svoje nevýhody.
Hlavne to pocítime u SDL_KEYDOWN
udalosti. Ak počas doby, keď je
vlákno uspané, stlačíme 4 rôzne klávesy. V každom cykle dostaneme iba
jednu udalosť typu SDL_KEYDOWN
. Pre získanie udalostí nad
všetkými štyrmi klávesmi potrebuje program prejsť hlavné slučku 4x.
Osobne si myslím, že je to chyba a SDL_PumpEvents
by malo
vrátiť všetky 4 udalosti hneď v prvom prechode. Musíme sa zmieriť s tým,
že nie vždy funguje podľa našich predstáv (ak nechceme SDL sami prepísať
a skompilovať). Opäť sa to týka iba SDL_PeepEvents
. Pri
použití SDL_PollEvent
sa s touto chybou nestretneme.
Práca s frontom udalosťou
Pretože funkcia SDL_PumpEvents
je interne volaná v
SDL_PollEvent
aj v SDL_WaitEvent
, je zrejmé, že ju
budeme volať v spojení s inou funkciou. Táto funkcia je
SDL_PeepEvents. Definícia je nasledujúci:
int SDL_PeepEvents(SDL_Event* events, int numevents, SDL_eventaction action, Uint32 minType, Uint32 maxType);
Ako prvý parameter odovzdávame buffer, do ktorého sa budú udalosti
ukladať, prípadne odkiaľ sa budú udalosti brať -
SDL_PeepEvents
môžeme použiť ik pridanie udalostí do fronty.
Druhým parametrom je maximálny počet udalostí, ktoré chceme z frontu
vziať, prípadne pridať. Parameter action
môže nadobúdať
troch rôznych konštánt - SDL_ADDEVENT
pre pridanie udalostí
alebo SDL_PEEKEVENT
na získanie udalostí z frontu. Pri použití
SDL_PEEKEVENT
sa udalosti z frontu neodstránia, ale zostávajú v
nej. Ak by sme chceli udalosť z frontu aj odstrániť, použijeme konštantu
SDL_GETEVENT
. minType
a maxType
je
rozpätie udalostí, ktoré chceme získať. Pre všetky udalosti môžeme
použiť konštanty SDL_FIRSTEVENT
a SDL_LASTEVENT
.
Ako u väčšiny funkcií v SDL, vrátená hodnota je pri error negatívny. V
obvyklých prípadoch vracia počet udalostí, ktoré funkcie spracovala.
S novo nabitými znalosťami sa môžeme pozrieť na časť kódu, ktorá
bezpečne vymaže SDL_MOUSEMOTION
udalosť po
SDL_WarpMouseInWindow
. events
je v tomto prípade
poľa typu SDL_Event
o veľkosti 255 prvkov.
while (SDL_PollEvent(events)) { if (events[0].type == SDL_KEYDOWN && events[0].key.keysym.scancode == SDL_SCANCODE_X) { SDL_PumpEvents(); //Přesun myši SDL_WarpMouseInWindow(MainWindow,400,300); //Získá zbývající události int NumOfOld = SDL_PeepEvents(events, 255, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION); SDL_Delay(1000); //simulace dlouho trvající úlohy //získání nových událostí SDL_PumpEvents(); int NumOfNew = SDL_PeepEvents(events+NumOfOld,255-NumOfOld,SDL_GETEVENT,SDL_MOUSEMOTION, SDL_MOUSEMOTION); //vrácení událostí zpět do fronty SDL_PeepEvents(events, NumOfOld, SDL_ADDEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION); if (NumOfNew > 1) SDL_PeepEvents(events+NumOfOld+1, NumOfNew-1, SDL_ADDEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION); } }
Najskôr presunieme všetky udalosti (ak ešte nejaké čakajú) do internej fronty. Potom myš presunieme. Výsledok bude ten, že udalosť pre presun myši bude skutočne prvý v externej fronte. Všetky udalosti, ktoré prebehli do presunu, si uložíme v bufferu. Teraz môže nasledovať nejaká dlhá operácia. Potom z externej frontu získame opäť všetky udalosti, medzi ktorými bude aj udalosť vyvolaná presunom myši. Pretože vieme, že je udalosť uložená ako prvá, vrátime okrem nej všetky udalosti späť do fronty.
Klasické funkcie pre prácu s kontajnerom
Funkcie pre získanie udalostí sme už prebrali, teraz sa zostáva pozrieť
na zvyšok funkcií, ktoré sú typické pre prácu s akýmkoľvek kontajnerom.
Jeden spôsob pridanie udalostí do fronty sme už videli
(SDL_PeepEvents
). Ak máme len jednu udalosť, bude jednoduchšie
použiť SDL_PushEvent.
Táto funkcia pridá jednu udalosť do fronty a vracia zápornú hodnotu pri
neúspechu, nulu ak bola udalosť vyfiltrované a 1 pri úspechu.
Tiež sa niekedy hodia front vyčistiť. Použijeme funkciu SDL_FlushEvent pre jeden typ udalosti (alebo ORované hodnoty), prípadne SDL_FlushEvents pre rozpätie udalostí. Rovnako fungujú aj funkcia SDL_HasEvent a SDL_HasEvents, ktoré overujú, či sa nejaká udalosť práve vyskytuje vo fronte. Spomínané metódy pracujú iba s internou frontom, nemajú teda efekt na front externé.
Niekedy musíme na udalosť v programe čakať. K tomu slúži funkcia
SDL_WaitEvent a SDL_WaitEventTimeout. Prvá spomínaná
čaká na udalosť neobmedzene dlho (aplikácia zamrzne), druhá prijíma ako
druhý parameter čas, ktorý má čakať (v milisekundách). Obe funkcie
fungujú podobne ako SDL_PollEvent
, ale užívateľovi sa bude
zdať, že sa program zasekol, preto použitie týchto funkcií
neodporúčam.
Zaujímavosťou je funkcia SDL_QuitRequested. Nerobí nič iné,
než že sa pozrie, či je vo fronte udalosť typu SDL_QUIT
.
Môžeme zjednodušiť hlavné slučku a miesto premenné použijeme túto
funkciu.
V budúcom diele si vytvoríme vlastný udalosť a vložíme si ju do fronty udalostí. Tiež sa bližšie pozrieme na filtrovanie udalostí. Ukážkový program k tejto lekcii je (z dôvodov nadväznosti) v ďalšej lekcii.