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.
RM | Resource Manager - Systém správy zdrojů se stará o
nahrávání a uvolňování datových zdrojů z paměti. |
GUI | Grafické uživatelské rozhraní má na starost
vizualizaci dějů ve hře a vstup uživatele. |
NET | Síťové rozhraní zprostředkovává komunikaci mezi
programy běžícími na více počítačích propojených do sítě. |
WORLD | World implementuje pravidla hry 8 Kingdoms a
definuje rozhraní pro komunikaci s hrou. |
AI | Modul 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
/ai | Zdrojové soubory pro modul umělé inteligence |
/bin | Přeložený projekt. Potřebné knihovny(Win32) |
/common | Obecné programátorská rozhraní: logování, message systém, alokace paměti,... |
/common/rm | Zdrojové soubory pro resource manager. |
/common/tcl | Zdrojové soubory rozhraní pro TCL. |
/common/xml | Zdrojové soubory rozhraní pro XML. |
/doc | Dokumentace. |
/editor | Zdrojové soubory, knihovny a data potřebná pro editor. |
/editor/bin | Spustitelný soubor editoru a potřebné dll knihovny. |
/external | Knihovny, dll a hlavičkové soubory. Hlavně pro Win32. |
/gui | Zdrojové soubory uživatelského rozhraní. |
/net | Zdrojové soubory pro síťové rozhraní. |
/log | Adresář pro logovací soubory. |
/projects | Adresář obsahující projekty pro různé překladače a prostředí. |
/res | Adresář pro data. |
/savegame | Adresář pro uložené hry. |
/tests | Adresář pro testovací programy. |
/world | Zdrojové 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.

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):
- různé verze funkce printf(vsnprinf, vsnwprinf)
- různé verze názvy funkce unlink v knihovnách překladače od Borlandu, MS,
a gcc
- různé způsoby pro práci s adresáři na Unixu a MS Windows
- stricmp a strcasecmp
- socket API je řešeno přímo v modulu NET, ale patří do této kategorie
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.
RESOURCES_DIR | Adresář s daty projektu. Na OS Windows
vetšinou relativní cesta vzhledem k programu. Na OS Unix cesta někam do
/usr/local/share/... |
K8_LOG | Zdali má být zapnuto logování. |
LOG_DIR | Adresář 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é:
Obecné |
K8_MM_DEBUG | Ladění paměti. |
K8_MM_NEWDEBUG | Ladění new/delete, náročné. |
K8_MM_DADEBUG | Ladění DA, ještě náročnější. |
MEMDBG_FILE | Soubor s výpisem informací o nedealokované paměti. |
K8_MM_MALLOCCHECK | Kontrola alokace paměti. |
GUI |
STEERING | Experimentální pohyb jednotek. |
SHADOWMAPPING | Vrhání stínů shadowmappingem. |
SIMPLE_SHADOWS | Jednoduché stíny. |
FULLTROTTLE | Vyřadí pasivní čekání ve vykreslovací smyčce. |
SHOW_DBG_STATES | Aktivuje vypisování informací o stavu hry. |
DEBUG_STEERING | Aktivuje kreslení překážek ve steeringu. |
DEBUG_DRAW_AABB | Aktivuje vykreslování AABB u TSceneModelObjectů. |
DEBUG_DRAW_PATH | Aktivuje vykreslování cesty. |
DEBUG_SCENE | Vykreslování obsahu scény z pohledu "druhé" kamery. |
DEBUG_DRAW_GATES | Aktivuje vykreslování trasy kolem budov. |
DEBUG_DRAW_ATTACKPOSITIONS | Aktivuje vykreslování útočných pozic. |
DRAW_DEBUG | Aktivuje vykreslování směrových gizm. |
Síť |
COMPRESS_ACTIVE | Aktivuje kompresi XML dat posílaných přes síť. Pozn.:na klientech i serveru musí být stejná hodnota. |
WORLD |
CLIENT_AUTOPLAY | Režim pro automatickou hru. |
SERVER_AUTOSAVE | Automatické ukládání na konci kola. |
AI |
AI_STRATEGIZER_ENABLED | Makro určující, zdali se má volat tah umělé inteligence - má čistě ladící význam. |
AI_LOGGING | Makro 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.
Tabulka 1.5: Tabulka použitých knihoven.