Wie war es vorher?
Ein Namespace BSMath, der diverse Klassen und Funktionen enthalten hat. Jede Klasse hatte eine Header und eine CPP inkl. umhschließendem ifndef-define-endif Konstrukt um doppelte Inkludierungen zu vermeiden. Die Klassen wurden innerhalb der Lib gegenseitig verwendet, z.B. hat Matrix von Vector4 Gebraucht gemacht und Triangle von Vector3 etc.
Wie sollte es nach dem Umbau sein?
Es sollte ein Modul BSMath geben, das alle Klassen zur Verfügung stellt. Zusätzlich dazu sollte jede Klasse ein eigenes Submodul bilden, so dass man nicht unbedingt die ganze Bibliothek importieren muss, sondern die Wahl hat. Später könnte man dann ggfs. auch zugehörige Klassen in sinnvollere Submodule aufteilen, z.B. Vector und Matrix in ein Modul stecken, statt beide einzeln zu haben oder Geometrieobjekte in ein Submodul packen, wie Triangle, Plane, etc. Für den Anfang sollte aber alles erstmal einzeln sein, möglichst nah an der Ausgangssituation mit den klassischen Headern.
Projekteinstellungen:
Bevor wir die Modules nutzen können, sind zwei Projekteinstellungen im VisualStudio-Projekt nötig:
C/C++ => Sprache => C++-Sprachstandard: Vorschau - Features aus dem aktuellen C++ Arbeitsentwurf (/std:c++latest)
C/C++ => Erweitert => Alle Quellen auf Modulabhängigkeiten überprüfen: Ja
Aufbau der des Moduls BSMath:
Eine Datei Math.ixx mit folgendem Code:
Code: Alles auswählen
export module BSMath; // Modul definieren und exportieren, damit es per "import BSMath" genutzt werden kann
// Hier kommt der Inhalt des Moduls, der lediglich daraus besteht alle Submodule nach außen "durchzureichen"
export import BSMath.AABB;
export import BSMath.Base;
export import BSMath.Frustum;
export import BSMath.Matrix;
export import BSMath.Noise;
export import BSMath.Plane;
export import BSMath.Quaternion;
export import BSMath.RandomFloat;
export import BSMath.Sphere;
export import BSMath.Triangle;
export import BSMath.Vector2;
export import BSMath.Vector3;
export import BSMath.Vector4;
AABB.ixx
Code: Alles auswählen
export module BSMath.AABB; // Submodul definieren und exportieren
// Nötige andere Module importieren
import BSMath.Vector3;
import BSMath.Frustum;
// Der Namespace wird absichtlich nicht exportiert, da ich mir die Möglichkeit offen halten möchte
// Strukturen zu definieren, die von außen nicht sichtbar sind, Stichwort Visibility/Reachability
namespace BSMath
{
// Klasse definieren und exportieren nicht vergessen
export class AABB
{
public:
Vector3 middle, min, max, corner[8];
void create(Vector3*, Vector3*);
bool isVisible(Frustum&);
bool pointInside(Vector3*);
bool aabbInside(AABB*);
bool collision(AABB*);
};
}
Ergo gibt es zu der AABB.ixx auch eine passenden AABB.cpp:
Code: Alles auswählen
// Definition unseres Submoduls
// Achtung: hier kein export! Wir wollen dem Compiler nur mitteilen, dass diese Datei zum Modul BSMath.AABB gehört.
// Wer möchte, kann so die Implementierungen sogar auf mehrere CPPs aufteilen.
module BSMath.AABB;
// Nötige Importe von anderen Submodulen
import BSMath.Vector3;
import BSMath.Frustum;
// Unser Namespace mit den Implemtnierungen - kein Hexenwerk und auch kein Modulzeugs mehr ab hier
namespace BSMath
{
// Implementierungen der Methoden (ich verzichte hier aus Platzgründen auf den eigentlichen Code)
void AABB::create(Vector3* minimum, Vector3* maximum)
{
}
bool AABB::isVisible(Frustum& viewfrustum)
{
}
bool AABB::pointInside(Vector3* point)
{
}
bool AABB::aabbInside(AABB* aabb)
{
}
bool BSMath::AABB::collision(AABB* aabb)
{
}
Code: Alles auswählen
// Modulteil um Header zu inkludieren
module;
#include <stdlib.h>
#include <cmath>
// Hier nun unsere Submoduldefinition
export module BSMath.Base;
// Notwendige Importe
import BSMath.Vector2;
import BSMath.Vector3;
import BSMath.Vector4;
// Und direkte Implementierungen der Funktionen in unserem Namespace
namespace BSMath
{
const float pi = 3.141592654f;
export float degreeToRadian(float degree)
{
}
export float radianToDegree(float radian)
{
}
export unsigned int color(unsigned int r, unsigned int g, unsigned int b, unsigned int a)
{
}
export unsigned int colorFromVector(Vector4& v)
{
}
export void calculateTangentAndBitangent(Vector3& v0, Vector3& v1, Vector3& v2, Vector2& tu0, Vector2& tu1, Vector2& tu2, Vector3& tangent, Vector3& bitangent)
{
}
export float randomNumber(float bottom, float top)
{
}
}
Ich fand es ehrlich gesagt nicht sehr intuitiv, aber nach einiger Zeit hat man es raus wann und wo das export, import und module Keyword hin muss.
Es gibt natürlich noch ne ganze Menge mehr zu wissen, aber dazu vielleicht ein anderes Mal mehr.
Fallstricke:
Wie bereits gesagt, ging mir der Umbau nicht gerade leicht von der Hand. Das mag zum einen an meiner recht langen C++ Abstinenz gelegen haben (beruflich bin ich mit PHP unterwegs und da bleibt leider wenig Zeit fürs Hobby), aber zum anderen sicherlich an der bisher doch recht dürftigen Unterstützung seitens VisualStudio.
Kompilierungsfehler werden scheinbar willkürlich geworfen. Man korrigiert Fehler A, kompiliert neu, dann taucht Fehler B auf, von dem vorher jede Spur fehlte. Nach dessen Korrektur taucht plötzlich Fehler C an einer Stelle auf, wo vorher noch alles bestens war und so geht das leider immer weiter. Ich hatte mehrfach das Gefühl, dass VisualStudio sich da selbst ziemlich verhaspelt und verschluckt und eigentlich noch gar nicht so wirklich richtig mit den Modulen klarkommt. Ein weiteres Graus ist leider Intellisense, das euch permanent überall irgendwelche Fehler anmäkelt, die aber tatsächlich nicht existieren. 5min später - ohne Änderungen gemacht zu haben - sind sie dann weg und es werden andere angezeigt. Mein PC ist übrigens hardwaretechnisch recht Highend und unausgelastet, daran kann es also eher nicht liegen.
Meine Tipps:
- Tastet euch langsam heran; Baut Klasse für Klasse um und kompiliert häufig neu
- Bereinigt das Projekt häufig, ggfs. vor jedem Kompilieren. Ansonsten schleppt ihr Altlasten mit und VS verschluckt sich garantiert
- Lasst euch von Intellisense nicht kirre machen; das letzte Wort hat schließlich der Compiler
- In meinem Fall (C++11 zu C++20) kamen noch einige neue Fehlermeldungen hinzu, die ich noch nicht kannte. Es mag Sinn machen, sich vor den Modulen vielleicht mal mit den Neuerungen aus C++17 und C++20 vertraut zu machen.
Ausblicke:
Als nächstes wird die 3D-Engine dann umgestellt. Mal schauen was da noch für Überraschungen auf mich warten. Aktuell bin ich aber sehr froh, dass alles wieder läuft und der neue Deferred Renderer das tut was er soll: