1 Moduly a Programátorská rozhraní
1.1 Moduly
Projekt 8 Kingdoms je vzhledem k jeho složitosti a rozsáhlosti rozložen na několik relativně samostatných a nezávislých částí. Tyto části jsme nazvali moduly a bude na ně v dokumentaci tak odkazováno. Jejich výčet společně s popisem obsahuje tabulka 1.
RMResource Manager - Systém správy zdrojů se stará o nahrávání a uvolňování datových zdrojů z paměti.
GUIGrafické uživatelské rozhraní má na starost vizualizaci dějů ve hře a vstup uživatele.
NETSíťové rozhraní zprostředkovává komunikaci mezi programy běžícími na více počítačích propojených do sítě.
WORLDWorld implementuje pravidla hry 8 Kingdoms a definuje rozhraní pro komunikaci s hrou.
AIModul umělé inteligence zajišťuje co nejlepší hru počítačového hráče v prostředí světa 8 Kingdoms.
Tabulka 1.1: Moduly projektu 8 Kingdoms.
Jejich implementace je zhruba rozdělena do stejnojmenných adresářů ve stromě zdrojových souborů projektu. Toto schéma zjednodušilo průběh vývoje, protože se jednotlivé moduly daly vyvíjet nezávisle na množství a funkčnosti implementovaného kódu ostatních modulů. Jediné společné systémy, které všechny moduly používají jsou: systém na výměnu zpráv, logování, funkce pro alokaci paměti a konstrukce řešící kompatibilitu s různými systémy. Tyto budou popsány v následujících kapitolách této části.
1.2 Adresářová struktura
/aiZdrojové soubory pro modul umělé inteligence
/binPřeložený projekt. Potřebné knihovny(Win32)
/commonObecné programátorská rozhraní: logování, message systém, alokace paměti,...
/common/rmZdrojové soubory pro resource manager.
/common/tclZdrojové soubory rozhraní pro TCL.
/common/xmlZdrojové soubory rozhraní pro XML.
/docDokumentace.
/editorZdrojové soubory, knihovny a data potřebná pro editor.
/editor/binSpustitelný soubor editoru a potřebné dll knihovny.
/externalKnihovny, dll a hlavičkové soubory. Hlavně pro Win32.
/guiZdrojové soubory uživatelského rozhraní.
/netZdrojové soubory pro síťové rozhraní.
/logAdresář pro logovací soubory.
/projectsAdresář obsahující projekty pro různé překladače a prostředí.
/resAdresář pro data.
/savegameAdresář pro uložené hry.
/testsAdresář pro testovací programy.
/worldZdrojové soubory pro modul World.
Tabulka 1.2: Adresářová struktura.
1.3 Systém zpráv
Systém zpráv je integrální součástí projektu 8 Kingdoms. Je implementován v souborech Msg.cpp/h. Slouží pro komunikaci jednotlivých modulů mezi sebou. Na počátku, hned po spuštění, se inicializuje globální fronta příjemců zpráv typu TTransceiverQueue funkcí KInitGlobalTransceiverQueue. Poté si každý modul registruje pomocí funkce KRegisterGlobalTransceiver a struktury TMessageTransceiver handler typu RVAL(*HANDLER)(MESSAGE_ID,SENDER,PARAM), do kterého bude dostávat zprávy, které pošlou jiné moduly. Zprávy jsou pak posílány funkcí KSendMessage. Systém zpráv se nestará o zablokování ani o vlákna. Tvoří jen jakési rozhraní pro volání funkcí. Je to tak proto, že se předem nedá odhadnout, jak náročná reakce na zprávu bude, aby se tím případně příliš nezatěžoval systém při volání většího počtu zpráv. Diagram 1 názorně zachycuje fungování systému zpráv.
Funkce systému zpráv
Diagram 1.1: Funkce systému zpráv.
1.4 Logování zpráv
Aby se usnadnil průběh vývoje a odhalování chyb při ostrém běhu projektu, byl navržen jednoduchý systém pro logování. Je implementován v Log.cpp/h. Zdrojový soubor, který chce použít logování musí na začátku uvést direktivu:
              
    #include "common/Log.h"
  
K logování se používá logovací třída TLog. Logovat je možné buď do souborů, nebo na standardní výstup. Typ výstupu se ovlivní parametry konstruktoru. Logovací soubory mohou mít buď speciální jména, nebo jména vygenerovaná systémem TLog. Adresář, kam se zapisují logovací soubory je v makru LOG_DIR. Maximální délka logovacích zpráv je dána makrem MAXLOGBUFFER_LEN.
              
    #include "common/Log.h"
    
    //loguj do souboru mujlog.log
    TLog mylog("mujlog.log");
    
    //loguj na standardni vystup
    TLog mylog(1);

    //loguj do vygenerovaneho souboru
    TLog mylog();
  
Samotné logování se provádí voláním metody LogMsg, která má stejné parametry jako funkce printf. Dále je možné logovat s prioritou metodou LogMsgId. Tímto způsobem je možné logování škálovat v rámci jednoho souboru. Logují se jen ty zprávy, které mají větší prioritu, než je priorita logu nastavitelná parametrem konstruktoru. Defaultní hodnota priority je definována makrem DEFAULT_PRIORITY_LOG.
              
    #include "common/Log.h"
        
    //loguj na standardni vystup s prioritou 5
    static TLog mylog(1,5);

    //doporuceny zpusob pouzivani vlastnicho logu
    #define MYLOG mylog.LogMsg
    
    void myfunc()
    {
      ...
      MYLOG("System spustil funkci myfunc");
      ...
      MYLOG("myfunc: Vysledek: %d, Chybovy kod: %d, Zprava: %s", v, c, s);
      ..
      mylog.LogMsgId(6,"tato zprava se zaloguje");
      mylog.LogMsgId(2,"tato zprava se nezaloguje");
    }    
  
Logovací systém definuje jeden primární log, tzn globální instanci TLog, který je aktivní podle toho, jestli je definováno makro K8_LOG. Tento log loguje do souboru, nebo na obrazovku podle toho jestli je makro GLOBALLOG_STDOUT nastaveno 1, nebo 0. Samotné logování se musí provádět pomocí maker GLOBALLOG a GLOBALLOGID.
              
    #include "common/Log.h"
        
    void myfunc()
    {
      ...
      GLOBALLOG("System spustil funkci myfunc");
      ...
      GLOBALLOGID(4,"myfunc: Vysledek: %d, Chybovy kod: %d, Zprava: %s", v, c, s);
    }    
  
1.5 Alokace paměti
Aby se snáze odstranily problémy s neodalokovanou pamětí, používají se v projektu 8 Kingdoms speciální techniky na její alokaci. Tyto techniky umožňují dohledat zdrojové soubory, ve kterých se neodalokovaná paměť alokovala. Pro používání tohoto systému musí být na začátku hlavičkového souboru Cčkového modulu makro.
              
    #include "common/mm.h"
  
Všechno další zajistí používání funkcí KMemAlloc a KMemFree pro alokaci struktur. Alokace tříd je složitější, musí se na ně volat new. Operátor new není předefinován globálně. Místo toho každá třída je potomkem DebugClassHelper, která předefinovává operátor new pro tuto třídu. Dědění se provádí makry CLSDBG1, CLSDBG2. Viz příklad.
              
    #include "common/mm.h"
    
    struct mystruct
    {
      ...
    };

    class myclass CLSDBG1
    {
      ...
    };
    
    void myfunc(){
      mystruct* ms;
      ms = (mystruct*)KMemAlloc(sizeof(mystruct));
      
      myclass* mc = new myclass();
    }
  
Extra kód zajišťující udržování záznamů o alokovaných blocích paměti se zkompiluje pokud je definováno makro K8_MM_DEBUG. Tyto záznamy se zapíší do speciálního logu mimo Logovací systém ve chvíli kdy se zavolá DumpAllocMem(). Nejčastěji se tak děje před ukončením programu.
Dobrým programátorským zvykem je kontrolovat, zda byla paměť alokována. MM obsahuje pro tento případ náhražky za funkce malloc, realloc a operátor new, které tuto kontrolu provádějí v ostré verzi centrálně. Tyto metody se použijí při překladu ostré verze(release) s definovaným makrem K8_MM_MALLOCCHECK. Standardně není tato kontrola zapnuta, protože se má zato, že jde o zbytečný overhead. Pokud dojde i virtuální paměť, program umírá a znamená to, že problém je někde jinde, a operační systém umírá vzápětí také. V našem případě není okamžité ukončení programu kritické a ztráta dat důležitá.
MM ještě obsahuje funkce pro realokaci polí, KMemRealloc a trošku chytřejší realokaci polí, která ukusuje pamět po celých mocninách dvojky KExtArrayRealloc. Mezi užitečné funkce patří také KInitString a KReleaseString pro alokaci a nastavení řetězců.
V poslední fázi vývoje se začaly využívat inteligentní pointery. Některé objekty jsou vlastněny více rodičovskými objekty a není u nich zajištěno pořadí, ve kterém se dealokují a neví se kdo má vlastně podřízený objekt jako poslední a může jej bezpečně odstranit z paměti. Tyto objekty dědí třídu TRefCounter. Při jejich alokaci je počet referencí nastaven na jedna. Voláním metod getRef a ungetRef se počet referencí zvyšuje a snižuje podle toho, na kolika místech je uložen ukazatel na objekt. Jakmile klesne počet referencí na 0, objekt se sám odstraní z paměti.
1.6 Kompatibilita
Jelikož portabilita je jedním z cílu projektu 8 Kingdoms, je nutné si poradit s některými odlišnostmi různých OS, přinejmenším do doby než se najde lepší a přenositelnější řešení. Proto ty účely existuje modul compatibility.cpp/h ve kterém se řeší některé systémově specifické věci. Většinou jde o nestandardní názvy běžných funkcí na různých platformách, které neměly to štěstí, aby se dostaly do specifikace ANSI C. (Čímž se staly prostředkem pro výrobce překladačů, aby do věci zavedli ještě větší zmatek):
1.7 Výjimky
Některá, hlavně vysokoúrovňová, rozhraní používají pro pro hlášení výjimečných stavů a chyb kromě chybových kódů a návratových hodnot C++ výjimky. Pro tyto výjimky platí, že každá je odvozena od E_8K nebo od některého z jejich potomků. Tato třída obsahuje základní datové položky pro přenos informace o chybě, čísla řádku a jména souboru. Všechny výjimky jsou definovány v souboru exc.cpp/h. Pro usnadnění vytváření výjimek je možné použít makro COMMON_EXCEPTION.
                 
    /// definice vyjimky
    class E_8K_MYEXCEPTION : public E_8K {
    public:
      E_8K_GUI(const char* d):E_8K(d){};
      E_8K_GUI(const char* d,const char* f,int l):E_8K(d,f,l){};
    };
    
    /// alternativne
    COMMON_EXCEPTION(E_8K, E_8K_MYEXCEPTION);    
  
Pro volání výjimek se pak používá speciální makro THROW, které dosadí do konstruktorů výjimek číslo řádku a jméno souboru, takže lze vznik výjimky snadno dohledat. Uživatelským parametrem výjimky je textový řetězec popisující vzniklou chybu.
                 
    void func() {
      ...
      if(a<b)
        THROW(E_8K_MYEXCEPTION,"impossible: a less then b\n");
      ...
    }
    
    ...
    try {
      func();
    }
    catch(E_8K_MYEXCEPTION e) {
      //s tim se neda nic delat
      exit(-1);
    }
  
1.8 Konfigurace před překladem
V kořenovém adresáři projektu se nalézá soubor config.h, který obsahuje nejvyšší úroveň parametrů pro správné fungování projektu. Tento soubor je na platformách Unix výstupním souborem nástrojů pro konfiguraci a kompilaci projektů(autoconf, automake, autoheader). Při překladu na platformách Windows mohou být parametry nastaveny ručně. Soubor config.h pak includuje každý zdrojový soubor, který chce tyto parametry použít. Tabulka 3 shrnuje tyto parametry.
MakroPopis
RESOURCES_DIRAdresář s daty projektu. Na OS Windows vetšinou relativní cesta vzhledem k programu. Na OS Unix cesta někam do /usr/local/share/...
K8_LOGZdali má být zapnuto logování.
LOG_DIRAdresář pro logovací soubory. Na OS Windows cesta relativně ke spustitelnému souboru. Na OS Unix cesta do nějakého /tmp/...
VERSIONŘetězcové konstanta označující verzi projektu.
Tabulka 1.3: Makra pro konfiguraci projektu před překladem.
Některé moduly mohou mít skryté, neoficiální vlastnosti, které se aktivují jedině makrem definovaným při překladu. Tabulka 4 uvádí takové:
ModulMakroPopis
Obecné K8_MM_DEBUGLadění paměti.
K8_MM_NEWDEBUGLadění new/delete, náročné.
K8_MM_DADEBUGLadění DA, ještě náročnější.
MEMDBG_FILESoubor s výpisem informací o nedealokované paměti.
K8_MM_MALLOCCHECKKontrola alokace paměti.
GUI STEERINGExperimentální pohyb jednotek.
SHADOWMAPPINGVrhání stínů shadowmappingem.
SIMPLE_SHADOWSJednoduché stíny.
FULLTROTTLEVyřadí pasivní čekání ve vykreslovací smyčce.
SHOW_DBG_STATESAktivuje vypisování informací o stavu hry.
DEBUG_STEERINGAktivuje kreslení překážek ve steeringu.
DEBUG_DRAW_AABBAktivuje vykreslování AABB u TSceneModelObjectů.
DEBUG_DRAW_PATHAktivuje vykreslování cesty.
DEBUG_SCENEVykreslování obsahu scény z pohledu "druhé" kamery.
DEBUG_DRAW_GATESAktivuje vykreslování trasy kolem budov.
DEBUG_DRAW_ATTACKPOSITIONSAktivuje vykreslování útočných pozic.
DRAW_DEBUGAktivuje vykreslování směrových gizm.
Síť COMPRESS_ACTIVEAktivuje kompresi XML dat posílaných přes síť. Pozn.:na klientech i serveru musí být stejná hodnota.
WORLD CLIENT_AUTOPLAYRežim pro automatickou hru.
SERVER_AUTOSAVEAutomatické ukládání na konci kola.
AI AI_STRATEGIZER_ENABLEDMakro určující, zdali se má volat tah umělé inteligence - má čistě ladící význam.
AI_LOGGINGMakro určující, zdali má AI logovat své akce.
Tabulka 1.4: Makra pro konfiguraci překladu skrytých vlastností.
1.9 Použité knihovny
K překladu 8 Kingdoms jsou potřeba externí knihovny shrnuté v tabulce 4. Přičemž platí, že projekt je tak přenositelný, jak jsou přenositelné tyto knihovny.
Knihovna Verze Zdroj
OpenGL 1.1(1.2) Součást OS.
http://www.mesa3d.org/
http://www.opengl.org/
SDL 1.29 http://www.libsdl.org/
SDL_mixer 1.2 http://www.libsdl.org/projects/SDL_mixer/
Expat 1.95.9 http://expat.sourceforge.net/
Tcl 8.4.12 http://www.tcl.tk/
zlib 1.2.3 http://www.zlib.net/
Tabulka 1.5: Tabulka použitých knihoven.