Was mich aber nicht entspannt, sind Bugs – das weiß jeder, der den Jammer-Thread verfolgt. Und die hat SoC auch nach vier Patches noch satt. (Spezifisches später.)
Mein guter Vorsatz für 2016 war: Alles ändern, was mich stört – statt nur drüber zu jammern. SoCs Quelltext ist geleakt, das waren also beste Voraussetzungen. Ich bin’s angegangen.
Disclaimer: Ich werde mich oft über die Quelltextqualität beschweren. Das Projekt war jedoch viele Jahre in Arbeit – unter finanziell und personell schwierigen Bedingungen. So sehr ich auch stänkere, ich hätte es selber nicht besser hinbekommen.
Kompilieren
Shiiiiit. Der Quelltext ist auf kurz nach dem letzten Patch datiert (wahrscheinlich der Zeitpunkt, an dem alle Arbeiten eingestellt und der Nachfänger begonnen wurde). Und die native Entwicklungsumgebung war … Borland C++. Sie dürften während eines Patches auf Visual C++ 2003 umgestiegen sein, also war es nicht komplett aussichtslos. Ich habe einfach mal alle Projektdateien in eine Solution gehauen und losgelegt.
Erstes Problem: Die Hälfte davon hat nichts mit dem eigentlichen Spiel zu tun. Da sind Projekte wie
- der Multiplayer-Server (mir egal)
- die GameSpy-Multiplayer-API (mir egal)
- der Level-Editor, Actor Editor, Shader Editor, Particle Editor, … (mir egal)
- ein Endkundenprogramm zum Design eigener Charakter im Online-Modus (interessant, aber MFC-Alptraum)
- eine Version der Engine zum statischen Linken (das Spiel selber ist aus zig DLLs ausgeliefert; Gründe später)
- der Absturzdialog, der Absturzberichte via E-Mail an den Publisher schickt (weg damit)
- …
- die Physik-Engine (modifizierte Open Dynamics Engine)
- zlib (wo ist das NICHT drin?!)
- boost, loki, und STLPort (sie haben wirklich ALLES ausprobiert!)
- SDKs für Maya, 3DStudio Max, … (für die Editoren)
- …
- das XR_3DA-Projekt kompilieren (so heißt die EXE, die das Spiel startet)
- warten, bis sich der Linker über eine Abhängigkeit beschwert
- falls die Abhängigkeit wie eines der Projekte heißt, das Projekt ebenfalls kompilierbar machen – sonst nach der richtigen Drittbibliothek suchen
- GOTO 1
In der zweiten Nacht begegnete ich meinem Nemesis.
Jeder Traum endet mit einem Monster
Das – von template ganz oben bis zur geschweiften Klammer ganz unten – ist die Deklaration einer Klasse. Die Klasse an sich realisiert einen A*-Algorithmus, also Wegfindung für die KI. Und sie kompiliert nicht. Jede Spezialisierung schmeißt einen 30 Zeilen langen, aber ansonsten nichtssagenden Syntaxfehler.
Die etlichen Template-Parameter sind alles, was man an dem Pfad konfigurierbar machen kann. Z.B. will man bei menschlichen Gegnern pro Pfadknoten Deckungsmöglichkeiten und Gefahr abwägen, während Hunde einfach nur von A nach B rennen sollen. Dann übergibt man als entsprechenden Parameter einen Vertex-Typ mit Variable für Deckung oder eben nicht. Und diese Vertices will man wieder unterschiedlich allokieren, also macht man noch einen Template-Parameter für Vertex-Allokatoren, usw. Der Entwickler hatte wohl gerade Andrei Alexandrescus Policy-based Design durchgearbeitet und wollte wissen, wie weit man es treiben kann.
Es hat mich einen halben Tag gekostet, den Ficker zu finden. Der aktuelle C++-Standard erfordert ein zusätzliches template vor einem bestimmten geschachtelten Parameternamen. Die Details habe ich nun auch vergessen (ich hätte damals nicht gedacht, ein Post-Mortem zu schreiben).
Der Algorithmus wird – wie gesagt – für verschiedene Situationen spezialisiert. Jedes „Monster“ (eine Beschreibung für alle Lebewesen im Spiel – vom Spieler über andere Stalker und Monster bis hin zu Krähen) instanziert eine Version des Algorithmus zur Wegfindung. Dummerweise bedeutete das auch, dass ich das Schlüsselwort in hunderten Deklarationen nachtragen musste.
Als ich fast fertig war, hatte ich ein Déjà-Vu. Der Dateiname kam mir bekannt vor. Hmmm. Nachgeschaut, und – Scheiße an der Latte. Sie nutzen den Algorithmus in zwei verschiedenen Projekten, mussten aber irgendein Detail ändern, und haben dafür die dutzenden Dateien einfach kopiert. Ein erheblicher Teil der Dateien liegt als Duplikat mit marginalen Änderungen vor. Schlimmer konnte es wirklich nicht mehr werden.
Wurde es auch nicht! Ich hab’s zu 90 % kompilierbar gekriegt. Danach kamen noch Probleme mit den Allokatoren, aber das ging recht schnell:
SoC nutzt sieben oder acht verschiedene Algorithmen zur Speicherverwaltung. Von new über malloc() und direkte Borland-Aufrufe plus Placement new bis hin zu komplett eigenen Low Fragementation Heaps.
Ich habe einfach alle gelöscht und durch new ersetzt.
Moore’s Law, Schmoore’s Law
Das viele Kompilieren macht einen kirre. Ich habe so schnell wie möglich Visual C++’ Multi-Threaded Compiler aktiviert, aber trotzdem dauert ein Rebuild fast 15 Minuten mit acht Kernen à 3 GHz, und verbraucht über 3 GiB RAM. Im Jahr 2006 haben die Entwickler sicher den ganzen Tag kompilieren müssen.
Warum ist das so? Weil nichts strukturiert wurde. Alles #include irgendwelche Monster-Header, und die wiederum andere, und letzten Endes landet alles bei boost. Vorkompilierte Header retten da auch nichts mehr (kompilieren teils fast eine Minute). Außerdem ist alles inline – damals konnten Compiler noch nicht global optimieren, und wollte man wichtige Funktionen inlinen, musste man eben tatsächlich ihren Quelltext #includen.
Ich schätze, dass die Projektstruktur mit den unzähligen DLLs ein verzweifelter Versuch ist, nicht immer alles neu kompilieren zu müssen. (Mit Strukturierung hatte es jedenfalls nichts zu tun – dass die DLL xrGame.dll heißt, bedeutet nicht, dass da auch wirklich nur Spiellogik drin ist.)
Wird der Fenstermodus endlich richtig funktionieren? Werde ich meine 64-Bit-Version kriegen? Geduld! Nächstes Mal gucken wir, ob es überhaupt startet!