Ich habe gerade zum ersten Mal in meinem ganzen Leben eine vollständige 3D-Szene in XML gespeichert und erfolgreich wieder geladen. Deshalb nun gleich noch ein kurzer Abriss über das Serialisierungssystem.
An unterster Stelle steht ein Typverzeichnis, das registrierten Typen (
int,
float, ...) eine eindeutige Identifikationsnummer aus
[0, n] zuordnet. Das Typverzeichnis wird bei Programmstart automatisch durch die dynamischer Initialisierung statischer Typindex-Konstanten aufgebaut, die sich dezentral verstreut in verschiedenen Übersetzungseinheiten finden, wenn sie dort benötigt werden. Das sähe bei rohem Zugriff in etwa so aus:
Code: Alles auswählen
const uint4 IntTypeIndex = GetTypeIndex().Add("int"); // Fiktiver direkter Zugriff auf Typ-Index, so nicht im Quelltext
Daneben gibt es ein Text-Serialisierer-Verzeichnis, das in einem einfachen Vektor entsprechend den Typ-Identifikationsnummern Zeiger auf Text-Serialisierer-Objekten speichert. Diese Text-Serialisierer-Objekte wiederum liegen beliebig verteilt als statische Objekte in einzelnen Übersetzungseinheiten, wo sie vollkommen dezentral durch noch mehr statische Hilfsobjekte bei deren dynamischen Initialisierung (also automatisch bei Programmstart, sehr praktisch) in das globale Text-Serialisierer-Verzeichnis eingetragen werden.
Code: Alles auswählen
class Serializer;
struct Hilfskonstrukt
{
Serializer serializer;
Hilfskonstrukt()
{
// Fiktiver direkter Zugriff auf Serializer-Index, so nicht im Quelltext
GetTextSerialization().AddSerializer(&serializer);
}
};
const Hilfsklasse Hilfsobjekt;
Da das alles für jeden Wertetyp ziemlich umständlich wäre, wurde das ganze am Ende in bequeme Templates zusammengefasst, die das intern erledigen:
Dasselbe Muster gibt es nun noch einmal eine Stufe höher für Entity- und Controller-Typen. Statische Entity- und Controller-Serialisierer-Objekte werden bei Programmstart durch statische Hilfsobjekte in entsprechende globale Entity- oder Controller-Serialisierer-Verzeichnisse eingetragen.
Wird nun ein Asset oder eine Welt geladen, so wird zunächst für jedes Entity entsprechend eines "
type"-XML-Attributes der passende Serialisierer aus dem Verzeichnis gefischt und damit das Entity geladen. Der Entity-Serialisierer fischt seinerseits für jeden Controller entsprechend seines "
type"-XML-Attributes weitere Controller-Serialisierer aus dem Verzeichnis, lädt auch diese, und fügt sie in die Controller-Liste des jeweils geladenen Entities ein. Die meisten Serialisierer greifen dabei auch noch auf das generische Property-Serialisierungssystem zurück, welches in der Regel aufgrund gegebener Property-Reflection-Informationen (siehe ältere Posts) mithilfe des eingangs beschriebenen Werte-Typverzeichnisses alle numerischen Objekteigenschaften voll automatisch laden kann.
Übrig bleiben für die Controller-Serialisierer nur strukturspezifische Aufgaben. So muss der Mesh Controller beispielsweise neben generischen Objekteigenschaften auch Mesh-Subsets und deren Materialzuordnungen laden. Materialien haben mir lange Zeit großes Kopfzerbrechen bereitet, weil es mir widerstrebt hätte, wenn alle Materialien in extra Materialdateien abgelegt werden müssten. Einfach alle Materialien aus dem Material Cache, die über keine Dateizuordnung verfügen, mit in die Map-Datei zu schreiben, erschien mir jedoch ebenso unschön. Schlussendlich habe ich heute noch in den gesamten Serialisierungsprozess eine Serialisierungswarteschlage integriert, welche von beliebigen Serialisierer-Objekten zu beliebiger Zeit mit zusätzlichen Serialisierungsaufgaben befüllt werden kann, welche dann nach allen regulären Serialisieurngsvorgängen (im Moment nur Entities und Controllers) ausgeführt werden. Mit einer zusätzlichen Materialserialisierungsaufgabe in der Warteschlange, die im Laufe der regulären Serialisierung alle referenzierten Materialien sammelt (jedes Material nur einmal!), und diese am Ende der Serialisierung mit in die Map-Datei hineinspeichert, bin ich auch dieses Problem los. Natürlich müssen diese Materialien dann auch vor allen Entities und Controllers wieder geladen werden. Die Lösung liegt nach all den statischen Systemen nahe, eine weitere globale Serialisierungswarteschlange, die bei Programmstart durch statische Hilfsobjekte mit Serialisierungsaufgaben gefüllt wird, erledigt auch dies.
Im Moment sieht das Ergebnis so aus:
Code: Alles auswählen
<world name="Some Scene" nextPersistentID="2">
<entities>
<e name="Barn" id="0" type="Entity">
<properties>
<p n="cell">0;0;0</p>
<p n="position">0.984296083;1.52951145;1.46267819</p>
<p n="orientation">0.982881844;-0.0469291434;0.178160235;0.0734675601;0.986638784;-0.145418867;-0.16895543;0.15601857;0.973197043</p>
<p n="scaling">1;1;1</p>
</properties>
<controllers>
<c type="MeshController" mesh="C:/Development/Graphics/breeze 2/Bin/Data/Meshes/Static/Barn/Barn1.mesh" materialName="beMeshController.Material"/>
</controllers>
</e>
<e name="Light" id="1" type="Entity">
<properties>
<p n="cell">0;0;0</p>
<p n="position">0;-1.86264515e-009;-7.4505806e-009</p>
<p n="orientation">1;0;0;0;1;0;0;0;1</p>
<p n="scaling">1;1;1</p>
</properties>
<controllers>
<c type="DirectionalLightController">
<properties>
<p n="color">1;1;1;1</p>
<p n="attenuation">1</p>
<p n="attenuation offset">1</p>
<p n="range">2.00000007e+032</p>
<p n="shadow">0</p>
<p n="shadow resolution">1024</p>
</properties>
</c>
</controllers>
</e>
</entities>
<materials>
<m name="beMeshController.Material" effect="C:/Development/Graphics/breeze 2/Bin/Data/Effects/2.0/Materials/Simple.fx">
<setup>
<properties>
<p n="Diffuse">1;1;1;0.0529999994</p>
<p n="Specular">1;1;1;0</p>
</properties>
</setup>
<setup effect="C:/Development/Graphics/breeze 2/Bin/Data/Effects/2.0/Prototypes/Shadow.fx"/>
<setup effect="C:/Development/Graphics/breeze 2/Bin/Data/Effects/2.0/Prototypes/Feedback.fx"/>
<technique name="Geometry" idx="0" setup="0"/>
<technique name="Shadow" idx="1" setup="1"/>
<technique name="ObjectIDs" idx="2" setup="2"/>
</m>
</materials>
</world>