SDL: Hry nejen pro Linux (11)

3. 5. 2005
Doba čtení: 7 minut

Sdílet

Na konci minulého dílu jsme nakousli základní práci s událostmi, dnes budeme pokračovat. Tento článek je primárně věnován práci s frontou událostí, ale jelikož ještě nevíme nic o unionu SDL_Event, bude částečně probrán i on.

Základem zpracování událostí jsou v SDL union SDL_Event a funkce, které načítají tyto objekty z fronty zpráv. Nejdříve bude v rychlosti probrána zmíněná datová struktura a pak se budeme celý zbytek článku věnovat práci s frontou událostí.

Union SDL_Event

Pro ty, kteří už zapomněli… Datový typ union je podobný klasické struktuře, rozdíl mezi nimi spočívá v tom, že v určitém okamžiku může v jeho vnitřku existovat vždy jen jedna z deklarovaných položek. Při vytváření se alokuje paměť o velikosti největší z nich.

Union SDL_Event je pravděpodobně, hned po SDL_Surface, druhým nejdůležitějším a nejpoužívanějším ze všech SDL datových typů. Jak už bylo několikrát zmíněno, poskytuje programátorovi rozhraní pro práci s událostmi.

typedef union
{
    Uint8 type;         // Typ události

    SDL_ActiveEvent     active; // (De)aktivace okna
    SDL_KeyboardEvent   key;    // Klávesnice
    SDL_MouseMotionEvent    motion; // Myš
    SDL_MouseButtonEvent    button;
    SDL_JoyAxisEvent    jaxis;  // Joystick
    SDL_JoyBallEvent    jball;
    SDL_JoyHatEvent     jhat;
    SDL_JoyButtonEvent  jbutton;
    SDL_ResizeEvent     resize; // Změna velikosti okna
    SDL_ExposeEvent     expose; // Požadavek na překreslení
    SDL_QuitEvent       quit;   // Požadavek na ukončení
    SDL_UserEvent       user;   // Uživatelská událost
    SDL_SywWMEvent      syswm;  // Systémově závislá
} SDL_Event; 

Jak to všechno funguje? Když uživatel například změní velikost okna, SDL vygeneruje objekt SDL_Event, atribut type nastaví na hodnotu SDL_VIDEORESIZE a do jeho parametrů (podobjekt resize) uloží nové rozměry okna. Celý objekt je pak vložen do fronty událostí.

Detekuje-li aplikace příchod zprávy, podle parametru type zjistí, že se jedná o změnu velikosti okna, a v resize.w, resize.h najde nové rozměry. V závislosti na nich pak provede odpovídající akci – například překreslí scénu nebo aktualizuje OpenGL perspektivu.

Proměnná type může nabývat hodnot uvedených v následující tabulce v levém sloupci. Vpravo se pak nachází odpovídající struktura, ve které se hledají podrobnosti o události.

sdl11
Typ události Odpovídající struktura
SDL_ACTIVEEVENT SDL_ActiveEvent
SDL_KEYDOWN/UP SDL_KeyboardEvent
SDL_MOUSEMOTION SDL_MouseMoti­onEvent
SDL_MOUSEBUTTON­DOWN/UP SDL_MouseButto­nEvent
SDL_JOYAXISMOTION SDL_JoyAxisEvent
SDL_JOYBALLMOTION SDL_JoyBallEvent
SDL_JOYHATMOTION SDL_JoyHatEvent
SDL_JOYBUTTON­DOWN/UP SDL_JoyButtonEvent
SDL_QUIT SDL_QuitEvent
SDL_VIDEORESIZE SDL_ResizeEvent
SDL_VIDEOEXPOSE SDL_ExposeEvent
SDL_USEREVENT SDL_UserEvent
SDL_SYSWMEVENT SDL_SysWMEvent

Protože popis těchto struktur a všeho, co s nimi souvisí, zabere několik následujících článků, budeme se nejprve věnovat funkcím, které vyzvedávají události z fronty, a pak až konkrétnímu popisu jednotlivých zpráv.

Načítání událostí z fronty

Existují dva základní způsoby, jak načíst událost z fronty, v SDL je reprezentují funkce SDL_PollEvent() a SDL_WaitEvent(). Obě vezmou událost, která je zrovna na řadě, zkopírují její data do předaného parametru a odstraní ji z fronty. Co se stane s událostí dál, záleží na programátorovi, který vytváří aplikaci.

int SDL_PollEvent(SDL_Event *event);
int SDL_WaitEvent(SDL_Event *event); 

Rozdíl mezi těmito funkcemi se projeví až tehdy, když je fronta prázdná. Jak už z názvu SDL_WaitEvent() vyplývá, tato funkce čeká libovolně dlouho, dokud nějaká zpráva nedorazí. SDL_PollEvent() se na rodíl od toho v případě prázdné fronty ihned ukončí a nulovým návratovým kódem oznámí, že nebylo načteno nic. V ostatních případech vrátí jedničku, která vyjadřuje, že byla nějaká zpráva načtena a má se zpracovat. SDL_WaitEvent() naproti tomu vrátí nulu, pouze pokud nastane nějaká chyba.

Události se typicky zpracovávají v cyklu, který se ukončí, když je fronta prázdná.

SDL_Event event;
while(SDL_PollEvent(&event))
{
    // Zpracování události
}

// Všechny události zpracovány 

Může vyvstat otázka, kterou z funkcí je lepší používat. Dá se říci, že v 99 procentech případů sáhne programátor po SDL_PollEvent() a SDL_WaitEvent() použije pouze ve výjimečných případech. Důvodem je, že program potřebuje neustále provádět určitou činnost, jako jsou animace a herní logika. Systémové časovače však nemusí být pro tento typ úloh zrovna nejlepší volbou, protože jsou většinou výrazně pomalejší než cyklus, který se provádí neustále dokola. U jednoduchých her je v podstatě jedno, co se použije, nicméně u trochu složitějších bývají s rychlostí velké problémy.

Události se načítají ze vstupních zařízení funkcí SDL_PumpEvents(), bez ní by nikdy aplikaci nepřišla žádná zpráva. V SDL_PollEvent() a SDL_WaitEvent() je volána automaticky, při jiném způsobu načítání zpráv musí být použita explicitně.

void SDL_PumpEvents(void); 

SDL dokumentace uvádí, že SDL_PumpEvents() nesmí být použito v jiném vláknu, než ve kterém bylo voláno SDL_SetVideoMode().

Vkládání událostí do fronty

Událostní systém v SDL není pouze jednosměrný, ale může být použit i k dialogu mezi různými částmi aplikace. Funkce SDL_PushEvent() přebírá ukazatel na objekt události, který umístí do fronty (resp. jeho kopii), v případě úspěchu vrátí 0, jinak –1.

int SDL_PushEvent(SDL_Event *event); 

Většinou se posílají uživatelské události (SDL_USEREVENT), ale jelikož ještě nebyly vysvětleny, ukážeme si poslání na SDL_QUIT. Tato událost zprostředkovává programu požadavek na ukončení a nepřebírá žádné parametry.

void PushQuitEvent()
{
    SDL_Event event;
    event.type = SDL_QUIT;
    SDL_PushEvent(&event);
} 

Kdykoli by byla v programu zavolána tato funkce, aplikace by byla ukončena (předpokládá se standardní ukončení aplikace po příchodu SDL_QUIT).

Obecná práce s frontou

Všechny činnosti s frontou zpráv, které byly právě probrány, a také některé další, mohou být provedeny pomocí SDL_PeepEvents(). Tato obecná funkce přebírá v prvních dvou parametrech ukazatel na pole událostí a samozřejmě jeho velikost.

int SDL_PeepEvents(SDL_Event *events, int numevents,
        SDL_eventaction action, Uint32 mask); 

Parametr action specifikuje, co se má vykonat. Je-li nastaven na SDL_ADDEVENT, jsou události z pole vloženy do fronty, v případě SDL_PEEKEVENT budou vráceny, ale ne vymazány. K vrácení a následnému vymazání z fronty slouží SDL_GETEVENT.

Poslední parametr definuje masku událostí, se kterými se má pracovat. Jedná se o ANDované flagy makra SDL_EVENTMASK(typ_u­dálosti). Také se mohou použít přímé názvy masek, lze je najít v hlavičkovém souboru SDL_events.h. Maska pro libovolné události nese označení SDL_ALLEVENTS.

Návratová hodnota představuje počet vložených/načtených událostí, v případě chyby je vráceno –1.

Zákaz generování některých událostí

Pomocí funkce SDL_EventState() lze specifikovat, jestli se mají události daného typu vkládat do fronty, nebo ne. První parametr specifikuje typ události a druhý určuje činnost funkce. Je-li předán flag SDL_IGNORE, události se do fronty vkládat nebudou, v případě SDL_ENABLE se zpracovávají normálně a SDL_QUERY slouží k dotazům. Vrácen je vždy stav po modifikaci.

Uint8 SDL_EventState(Uint8 type, int state); 

Tato funkce se uplatní především při používání technik, které zjišťují stav vstupních zařízení přímo, a bylo by tedy zbytečné generovat události. Například funkce SDL_GetKeyState() umožňuje programátorovi dotázat se na stisk klávesy. K tomu však až příště.

Pomocí následujících dvou řádků lze zakázat generování všech událostí klávesnice.

SDL_EventState(SDL_KEYUP, SDL_IGNORE);
SDL_EventState(SDL_KEYDOWN, SDL_IGNORE); 

Filtr událostí

O něco flexibilnější než předchozí funkce je SDL_SetEventFil­ter(), pomocí níž lze do SDL předat ukazatel na rutinu, která může filtrovat události nejen podle typu, ale i podle ostatních parametrů.

void SDL_SetEventFilter(SDL_EventFilter filter);
SDL_EventFilter SDL_GetEventFilter(void); 

Typ SDL_EventFilter je definován jako ukazatel na funkci, které je v parametru předán ukazatel na událost. Vrátí-li tato funkce číslo jedna, bude událost vložena do fronty, v případě nuly nebude.

typedef int (*SDL_EventFilter)(const SDL_Event *event); 

Jediná připomínka vzniká u události SDL_QUIT. Filtr je pro ni volán pouze tehdy, pokud správce oken vyžaduje zavřít okno aplikace. V takovém případě vrácená jednička říká, aby bylo okno zavřeno, cokoli jiného ponechá okno otevřené (je-li to možné). Pokud je vyvoláno SDL_QUIT kvůli signálu o přerušení, filtr volán nebude a zpráva je vždy doručena přímo aplikaci.

A ještě dvě krátké poznámky: Filtr není volán na události, které vkládá do fronty sama aplikace (SDL_PushEvent(), SDL_PeepEvents()), a jelikož může být vykonáván v jiném vláknu, měli byste si dávat pozor, co v něm děláte.

V následujícím příkladu má SDL zakázáno generovat události klávesnice, pokud se nejedná o klávesu abecedního znaku nebo čísla. Je to vlastně analogie příkladu u funkce SDL_EventState(), nicméně zde byl výběr navíc omezen podle parametrů. Všimněte si, že filtr má podobnou strukturu jako samotné zpracovávání událostí.

bitcoin_skoleni

#include <ctype.h>// Kvůli isalnum()

int EventFilter(const SDL_Event *event)
{
    switch(event->type)
    {
    case SDL_KEYDOWN:
        // Abecední znak nebo číslo?
        if(isalnum(event->key.keysym.sym))
            return 1;// Vložit
        else
            return 0;// Nevkládat
        break;

    default:
        break;
    }

    return 1;// Vložit
}

// Zapnutí filtru např. v Init()
SDL_SetEventFilter(EventFilter); 

Ukázkové programy

Fronta událostí

Program demonstruje nejčastější techniky používané při práci s frontou událostí. Jsou jimi načítání a vkládání zpráv z/do fronty a filtrování událostí. Pomocí mezerníku lze zapínat a vypínat filtrování zpráv o uvolnění kláves (při příchodu se vypíše oznámení do konzole), zprávy týkající se myši se vždy ignorují. Stejně jako u příkladu z minula, ani zde se nic nevykresluje. (Zdrojový kód se zvýrazněním syntaxe.)

SDL 11

Download

Pokračování

Dnešním dílem jsme postavili spíše teoretické základy pro zpracování událostí. Příští díl už bude konkrétnější, je věnován zpracování vstupů z klávesnice.

Autor článku

Backend programátor ve společnosti Avast, kde vyvíjí a spravuje BigData systém pro příjem a analýzu událostí odesílaných z klientských aplikací založený na Apache Kafka a Apache Hadoop.