Lekce 12 - SDL - Obecná práce s událostmi
V minulé lekci, SDL - Události myši, jsme se zaměřili na myš a ukázali si zachytávání událostí jednotlivých tlačítek a pohybu.
Tentokrát se podíváme na události ze širšího úhlu. Řekneme si něco o filozofii událostí, dále se podíváme na frontu událostí a práci s ní. V příštím díle budeme pokračovat a povíme si o tom, jak si můžeme vytvořit vlastní události a jak události filtrovat.
Filozofie událostí v SDL
Filozofie událostí v SDL je trochu rozdílná od událostí, na které jsme zvyklí z ostatních technologií. Pro představu uvedu Windows Forms (WF), Windows Presentation Foundation (WPF) nebo také webový vývoj. Ve zmíněných technologiích fungují události na principu callbacku, který se volá ihned, jakmile se událost registruje. Často se tyto událostí volají odděleně od hlavního proudu programu a můžou běžet i v samostatném vlákně. Hlavním aspektem je zde tedy callback, který se spustí ihned po tom, co se událost zaregistruje.
Z předchozích dílů je patrné, že SDL funguje jinak. SDL pracuje s
frontou událostí, která je rozdílová oproti poslednímu volání. Aby to
nebylo tak jednoduché, SDL používá fronty dvě. První je systémová a
ukládá do ní operační systém. Po zavolání funkce SDL_PumpEvents
se události přesunou z první fronty do druhé, která už přísluší SDL. S
touto frontou pracujeme v programu. Také je potřeba počítat s tím, že do
interní fronty se přidá od každého typu události pouze jedna událost.
Bude to rozebráno v dalších odstavcích. Dosud jsme žádné
SDL_PumpEvents
nepoužili. Jak jsme tedy události z fronty
získávali? Funkce SDL_PollEvent
, kterou jsme používali do teď,
v sobě interně SDL_PumpEvents
volá, proto nebylo potřeba volat
funkci explicitně.
Než se blíže podíváme na práci s interní frontou, bude potřeba
probrat ještě pár věcí. Zmínil jsem rozdílové události. To znamená,
že dostaneme informace o tom, co se změnilo od posledního volání
SDL_PumpEvents
. Uděláme simulaci složitého výpočtu a vlákno
na sekundu uspíme (SDL_Delay
). I když to vypadá, že okno
neodpovídá (což je vlastně pravda), operační systém stále zachytává
události, které aplikaci později předá. Když budeme myší pohybovat přes
celé okno, událost nás bude informovat o přesunu, ale na aktuální místo.
Pohneme-li třeba myší na stranu a poté se vrátíme do stejného místa,
bude vyvolána SDL_MOUSEMOTION
událost, ale xrel
i
yrel
se budou rovnat nule (jako by se myš nepohnula). Proto se po
zavolání SDL_WarpMouseInWindow
můžeme téměř s jistotou
spolehnout na promazání další události typu SDL_MOUSEMOTION
,
protože veškerý pohyb myši byl již v předešlé události (tak jak jsme to
dělali v minulém díle). Jestliže používáme SDL_PeepEvents
,
máme jistotu stoprocentní, ale o tom si povíme až v dalších
odstavcích.
Zajímavá vlastnost, kdy se po zavolání SDL_PumpEvents
vrátí od každého typu události pouze jedna instance, má i své nevýhody.
Hlavně to pocítíme u SDL_KEYDOWN
události. Jestliže během
doby, kdy je vlákno uspané, zmáčkneme 4 různé klávesy. V každém cyklu
dostaneme pouze jednu událost typu SDL_KEYDOWN
. Pro získání
událostí nad všemi čtyřmi klávesami potřebuje program projít hlavní
smyčku 4x. Osobně si myslím, že je to chyba a SDL_PumpEvents
by
mělo vrátit všechny 4 události hned v prvním průchodu. Musíme se smířit
s tím, že ne vždy funguje podle našich představ (pokud nechceme SDL sami
přepsat a zkompilovat). Opět se to týká pouze SDL_PeepEvents
.
Při použití SDL_PollEvent
se s touto chybou nesetkáme.
Práce s frontou událostí
Protože funkce SDL_PumpEvents
je interně volána v
SDL_PollEvent
i v SDL_WaitEvent
, je patrné, že ji
budeme volat ve spojení s jinou funkcí. Tato funkce je SDL_PeepEvents.
Definice je následující:
int SDL_PeepEvents(SDL_Event* events, int numevents, SDL_eventaction action, Uint32 minType, Uint32 maxType);
Jako první parametr předáváme buffer, do kterého se budou události
ukládat, popřípadě odkud se budou události brát –
SDL_PeepEvents
můžeme použít i k přidání událostí do
fronty. Druhým parametrem je maximální počet událostí, které chceme z
fronty vzít, popřípadě přidat. Parametr action
může nabývat
tří různých konstant – SDL_ADDEVENT
pro přidání událostí
nebo SDL_PEEKEVENT
pro získání událostí z fronty. Při
použití SDL_PEEKEVENT
se události z fronty nesmažou, ale
zůstávají v ní. Pokud bychom chtěli událost z fronty i odstranit,
použijeme konstantu SDL_GETEVENT
. minType
a
maxType
je rozpětí událostí, které chceme získat. Pro
všechny události můžeme použít konstanty SDL_FIRSTEVENT
a
SDL_LASTEVENT
. Jako u většiny funkcí v SDL, návratová hodnota
je při erroru negativní. V obvyklých případech vrací počet událostí,
které funkce zpracovala.
S nově nabitými znalostmi se můžeme podívat na část kódu, která
bezpečně vymaže SDL_MOUSEMOTION
událost po
SDL_WarpMouseInWindow
. events
je v tomhle případě
pole typu SDL_Event
o velikosti 255 prvků.
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); } }
Nejdříve přesuneme všechny události (pokud ještě nějaké čekají) do interní fronty. Poté myš přesuneme. Výsledek bude ten, že událost pro přesun myši bude skutečně první v externí frontě. Všechny události, které proběhly do přesunu, si uložíme v bufferu. Nyní může následovat nějaká dlouhá operace. Poté z externí fronty získáme opět všechny události, mezi nimiž bude i událost vyvolaná přesunem myši. Protože víme, že je událost uložena jako první, vrátíme kromě ní všechny události zpět do fronty.
Klasické funkce pro práci s kontejnerem
Funkce pro získání událostí jsme již probrali, nyní se zbývá
podívat na zbytek funkcí, které jsou typické pro práci s jakýmkoliv
kontejnerem. Jeden způsob přidání událostí do fronty jsme již viděli
(SDL_PeepEvents
). Jestliže máme pouze jednu událost, bude
jednodušší použít SDL_PushEvent.
Tato funkce přidá jednu událost do fronty a vrací zápornou hodnotu při
neúspěchu, nulu jestliže byla událost vyfiltrována a 1 při úspěchu.
Také se někdy hodí frontu vyčistit. Použijeme funkci SDL_FlushEvent pro jeden typ události (nebo ORované hodnoty), popřípadě SDL_FlushEvents pro rozpětí událostí. Stejně fungují i funkce SDL_HasEvent a SDL_HasEvents, které ověřují, zda se nějaká událost právě vyskytuje ve frontě. Zmíněné metody pracují pouze s interní frontou, nemají tedy efekt na frontu externí.
Někdy musíme na událost v programu čekat. K tomu slouží funkce SDL_WaitEvent
a SDL_WaitEventTimeout.
První zmíněná čeká na událost neomezeně dlouho (aplikace zamrzne),
druhá přijímá jako druhý parametr čas, který má čekat (v
milisekundách). Obě funkce fungují podobně jako SDL_PollEvent
,
ale uživateli se bude zdát, že se program zasekl, proto použití těchto
funkcí nedoporučuji.
Zajímavostí je funkce SDL_QuitRequested.
Nedělá nic jiného, než že se podívá, zda je ve frontě událost typu
SDL_QUIT
. Můžeme zjednodušit hlavní smyčku a místo proměnné
použijeme tuto funkci.
Ukázkový program k této lekci je (z důvodů návaznosti) v další lekci.
V příští lekci, SDL - Vlastní události a filtrování, si vytvoříme vlastní událost a vložíme si ji do fronty událostí. Také se blíže podíváme na filtrování událostí.