//Window App
- Appentwicklung für Windows (C++)
- Game Engine Architektur, Strings, Memory Allocator, Logging, Unit Tests
- 1 Teammitglied
- Zeitraum: 01/2023 - heute
Zusammenfassung
Inspiriert von Jason Gregorys Game Engine Architecture arbeite ich seit einiger Zeit an einer
Anwendung, in der ich mein theoretisches Wissen testen und meine neuen Ideen ausprobieren kann. In dieser Sandbox
entwickle ich eine Anwendung, die viele Komponenten einer Game Engine beinhalten soll.
So möchte ich nach und nach immer mehr über die Entwicklung von Spielen lernen und mir neue Fähigkeiten aneignen.
Ich habe mich durch diese Anwendung mit der Windows API vertraut gemacht und einiges über die Architektur von
Windows Applikationen sowie Game Engines gelernt. Auf einige der Komponenten möchte ich hier eingehen.
Grundlegender Aufbau
Mein Ziel ist es, dass die Anwendung ein Fenster öffnet, auf dem Text sowie 2D/3D Grafiken und UI angezeigt werden kann.
In einem solchen Fall bietet sich der WinMain Einstiegspunkt an. Da ich jedoch Debug-Informationen
nicht nur in die Visual Studio Konsole ausgeben möchte, habe ich mich für eine Konsolenanwendung entschieden. Diese
öffnet dann im Anschluss kontrolliert ein Fenster und initialisiert die Message Pump.
Debugging && Logging
Um meine Anwendung korrekt entwickeln zu können muss natürlich ein gutes Logging System her. Dafür habe ich eine
Struktur implementiert, die Strings in die Konsole ausgeben kann. Auf diesem Logging System baut dann das Debugging System
auf, das standardmäßig zusätzlich zur Log-Nachricht die Uhrzeit und einen optionalen Topic-Tag ausgibt. Für Fehler und
Assertions gibt es ebenfalls ein eigenes System. Damit können Fehlermedungen und Warnungen ausgegeben werden, die zusätzlich
auch die File-Location sowie die Zeile des Fehlers ausgeben. Assertions beenden hier auch klassischerweise das Programm, wobei
die Konsole praktischerweise offen bleibt, sodass wertvolle Informationen über die Assertion sichtbar bleiben.
Strings
Da Strings bekanntlich performance-kritisch sind, habe ich eine eigene String Klasse geschrieben, die Strings effizienter
gestalten soll. Nach Jason Gregorys Vorschlag aus Game Engine Architecture beinhaltet die Klasse
eine globale String-Tabelle. Innerhalb der Tabelle werden Strings als Hash und als char* abgebildet.
Somit sind String-Zuweisungen, -Vergleiche und das Erstellen neuer Strings mit bereits hinterlegtem Text deutlich performanter.
Für das Hashing wird ein 64bit Fast CRC-Hash Algorithmus verwendet.
Später könnte die String-Klasse beispielsweise die verwendeten Strings zur Compiletime Serialisieren und könnte dann im besten Fall
ausschließlich mittels der Hash-Werte betrieben werden.
Der Source-Code der String-Klasse steht hier zur Einsicht zur Verfügung.
Unit Tests
Bereits zu beginn der Entwicklung habe ich großen Wert auf Unit-Tests gelegt. Dass Unit-Tests eine wertvolle Angewohnheit sind,
habe ich sowohl in meinem Studium (Game Engine Programming, Systems Engineering and Management, ...) gelernt, als auch in diesem
Projekt. Ähnlich wie Assertions falsche Annahmen bzw. fehlerhafte Daten konsequent aufdecken, haben mir die Unit-Tests effektiv
gezeigt, wo sich Fehler eingeschlichen haben. Die Implementierung von Tests war zwar sehr zeitintensiv, hat sich jedoch (fast)
immer ausgezahlt. Die Tests werden über das Kommandozeilenargument -test aktiviert und ausgeführt.
Zukünftige Anpassungen meiner Tests sollen die Routine um Function-Tests und randomisierte Tests erweitern.
Memory Allocation
Für die Anwendung habe ich zwei eigene Memory Allocator implementiert. Einen Stack-Allocator und einen Pool Allocator.
Der Stack-Allocator ist mit einem einfachen Pointer auf die oberste Speicheradresse des Stacks umgesetzt. Es können außerdem
beliebig Marker gesetzt werden, um den Speicher wieder freizugeben. Der Pool Allocator erzeugt eine Anzahl an Speicher-Blöcken,
deren Größe und Anzahl bei der Initialisierung des Allocators angegeben werden. In Zukunft sollen beide Allocator noch mit einer
automatischen Alignment-Funktion ausgestattet werden, sodass das Lesen und Schreiben möglichst effizient ist. Beide Allocator
sind in die Unit-Tests eingebunden, um Fehlverhalten frühzeitig zu erkennen.
Der Source-Code des Stack-Allocators steht hier zur Einsicht zur Verfügung.
Der Source-Code des Pool-Allocators steht hier zur Einsicht zur Verfügung.
...
Für die Zukunft sind noch viele weitere Strukturen, Klassen und Subsysteme geplant, die in die Anwendung einfließen sollen.
Diese werde ich hier zu gegebenem Anlass nachtragen. Themen, die mich interessieren und die dann auch auf dieser Seite veröffentlicht
werden, sind Multithreading (insbesondere ein Job System), D3D11/12 und oder
Vulkan Rendering, ein Entity-Component-System und viele weitere.