Asset-Management mit Archiven



  • Hallo miteinander,

    ich habe mir jetzt eine Bibliothek geschrieben um meine Spieldateien wie Maps, Scripts und Sounds zu packen. Dabei habe ich mich am VPK-Format orientiert und habe eine Indexdatei und durchnummerierte Archive im Stil von Game.index und Game.000 , Game.001 , ...

    So theoretisch könnte ich alles in ein Archiv packen, kann aber bei Updates nicht wirklich schön selektiv nur Teile updaten, sondern muss ein großes Archiv updaten. Außerdem bin ich mir noch nicht sicher wie ich die Teilarchive eigentlich erstellen lassen soll - große Soundfiles aufteilen in mehrere kleine Teilarchive und den Rest in ein eigenes oder was?

    Prinzipiell geht es hier darum, ob sich performancemäßig was ändert, wenn ich ständig Streams öffne um entweder aus (ganz beispielhaft) unterschidlichen 10MB Dateien was rauszulesen oder immer wieder eine große 1GB Datei öffnen muss und da mein Zeug suchen muss.

    In meinem Index kann ich durch Angabe des gesuchten Assets das genaue Archiv, den Offset und die Länge erhalten um evtl. direkt da reinspringen zu können.

    Habt ihr eine Idee wie ich da am Besten vorgehen sollte?


  • Mod

    es waere wichtig zu wissen weshalb du archive nutzen willst, davon haengt ab wie man damit umgeht. was meinst du mit 'update', eine neue datei hinzufuegen? eine bestehende modifizieren (ohne die groesse zu aendern?), und wozu?
    wozu willst du es ueberhaupt aufteilen, hast du mehr daten als ein archive von dir verwalten kann? oder werden die parallel erstellt und dadurch ist der build prozess schneller? oder?



  • Beim Update beziehe ich mich drauf, dass ich beispielsweise neuen Content anbieten will, der automatisch runtergeladen wird. Hierbei ist es viel einfacher diesen neuen Content in bspws. einer Game.003 anzubieten und eine neue Indexdatei mitzuliefern anstelle eine riesige Game.dat jedes Mal herunterladen zu müssen. Sozusagen ein "inkrementelles Update". Alternativ könnte man sagen: wenn beispielsweise eine neue Map zum Spiel dazukommt, dann wird nur das Archiv mit alles Maps geupdated und das Archive mit anderen Dateien kann ignoriert werden.

    Das Erzeugen dieser Archive ist für den Moment noch sehr einfach gestrickt und sequentiell implementiert - könnte aber durchaus parallelisiert werden, wie du meinst. Mir geht es in erster Linie darum herauszufinden, ob etwas dafür spricht, nicht nur bei neuen Releases/neuem Content zu Splitten, sondern auch so mit dem Hintergedanken, ob es denn effizienter ist große Datei als Stream zu öffnen und sie bis zum Ende offen zu lassen oder ständig kleine Dateien zu öffnen, den Inhalt zu laden und dann wieder zu schließen.

    Ist jetzt evtl. klarer, was ich im Sinn hatte?



  • Eine große Datei ist effizienter, aber wirklich merken wirst du das nur, wenn du sehr, sehr viele sehr kleine Dateien hast. Ab 128KB/Datei wird's wahrscheinlich kaum noch einen Unterschied machen. game.dat Dateien kann man allerdings wesentlich intelligenter patchen: Man speichert Stellen + Werte mit denen man die Original-Datei xoren muss. Damit wird der Patch klein, und wenn man ihn zwei mal anwendet wird er wieder rückgängig gemacht.



  • Ok, dann werde ich anfangs mal probieren das zusammenzuhalten. Das mit dem xoren von Dateien nutze ich aktuell um das Archiv wenigstens bisschen zu "verschlüsseln" um einen direkten Zugriff zu vermeiden - wie funktioniert das denn beim Patchen? Ich kann mir vorstellen, dass es etwas komplizierter werden kann, wenn die Dateigrößen unterschiedlich sind vor/nach dem Patchen. Hast du ein kurzes Beispiel oder einen Link im Netz?


  • Mod

    Christian Ivicevic schrieb:

    Beim Update beziehe ich mich drauf, dass ich beispielsweise neuen Content anbieten will, der automatisch runtergeladen wird. Hierbei ist es viel einfacher diesen neuen Content in bspws. einer Game.003 anzubieten und eine neue Indexdatei mitzuliefern anstelle eine riesige Game.dat jedes Mal herunterladen zu müssen. Sozusagen ein "inkrementelles Update". Alternativ könnte man sagen: wenn beispielsweise eine neue Map zum Spiel dazukommt, dann wird nur das Archiv mit alles Maps geupdated und das Archive mit anderen Dateien kann ignoriert werden.

    tjo, hat es irgendwelche vorteile wenn du archive updatest? ansonsten klingt ein neues archive doch ausreichend. wobei ich mir nicht sicher bin was du mit index datei mitliefern meinst, es hat doch eh immer jedes archive eine index datei, sonst kann man mit den daten darin wohl nichts anfangen, wuerde ich annehmen.

    Das Erzeugen dieser Archive ist für den Moment noch sehr einfach gestrickt und sequentiell implementiert - könnte aber durchaus parallelisiert werden, wie du meinst. Mir geht es in erster Linie darum herauszufinden, ob etwas dafür spricht, nicht nur bei neuen Releases/neuem Content zu Splitten, sondern auch so mit dem Hintergedanken, ob es denn effizienter ist große Datei als Stream zu öffnen und sie bis zum Ende offen zu lassen oder ständig kleine Dateien zu öffnen, den Inhalt zu laden und dann wieder zu schließen.

    eine grosse datei die du per memory mapped file aufmachst ist wohl das effizienteste falls genug addressraum vorhanden ist, aber ob du 5x 100MB aufmachst oder 1x500MB wird nicht merkbar sein, eher wenn du 500000x100kb aufmachen wuerdest.
    das OS kuemmert sich dann z.b. drum dass daten read-only sind und schreibt sie nicht zurueck, sondern invalidiert nur den eintrag in der eigenen tabelle. wenn du per hand speicher allokierst und dann freigibst um mit den dateien zu arbeiten, hast du schlechtere speichernutzung und musst es explizit machen, waerend das OS einfach den speicher nutzt den das host system zur verfuegung hat.



  • Es hängt auch davon ab, was genau du mit den Dateien machen wirst. Wenn du einfach beim Start einmal jede öffnest und in einem Stück von vorn bis hinten durchliest und das wars, dann wird Memoy Mapped I/O z.B. auch kaum einen Unterschied machen...


  • Mod

    cooky451 schrieb:

    ... wesentlich intelligenter patchen: Man speichert Stellen + Werte mit denen man die Original-Datei xoren muss. Damit wird der Patch klein, und wenn man ihn zwei mal anwendet wird er wieder rückgängig gemacht.

    sobald sich eine dateigroesse aendert ist das ganze system auf die nase gefallen. aenlichkeiten zwischen zwei GB grossen archiven zu finden ist nicht sonderlich trivial, gibt firmen die nichts anderes als solche "backup" systeme entwickeln (und die meisten sind wohl von Sun,IBM,HP,DELL,etc. aufgekauft worden).

    gibt auch ein paar kostenfreie patcher, aber ich weiss nicht wie effizient die solche deltas finden, problematisch bei sowas ist aber oft dass sie wirklich nur zwischen zwei versionen funktionieren, wenn du ein spiel hast wo sich die leute den content selbst aussuchen, hast du viel mehr permutationen als du vermutlich patches erstellen koenntest.


  • Mod

    dot schrieb:

    Es hängt auch davon ab, was genau du mit den Dateien machen wirst. Wenn du einfach beim Start einmal jede öffnest und in einem Stück von vorn bis hinten durchliest und das wars, dann wird Memoy Mapped I/O z.B. auch kaum einen Unterschied machen...

    im worst case also nicht schlecht? klingt doch gut 🙂



  • tjo, hat es irgendwelche vorteile wenn du archive updatest? ansonsten klingt ein neues archive doch ausreichend. wobei ich mir nicht sicher bin was du mit index datei mitliefern meinst, es hat doch eh immer jedes archive eine index datei, sonst kann man mit den daten darin wohl nichts anfangen, wuerde ich annehmen.

    Also im Moment ist es so, dass ich die Game.index einlese und bspws. beim Zugriff auf die Datei /Foo/Bar/Baz.bla zurückbekomme, dass diese Datei in Archiv Nummer 2, also der Datei Game.002 , ab Byte x mit Länge y zu finden ist. Dieser Index erstreckt sich also über ALLE Archive.

    Soviel wie ich mitbekommen habe, scheint aber Memory-Mapping für mich insofern praktisch, dass ich den Index eh nur einmal einlese und dementsprechend direkt den gesuchten Teil on-thy-fly rausextrahieren und verarbeiten kann. Hierbei müsste ich noch herausfinden, wie ich am Geschickstesten mit Memory-Streams (?) arbeiten kann um nicht ständig die extrahierten Dateien auf Platte zu schreiben, was inperfomant wäre.


  • Mod

    Christian Ivicevic schrieb:

    tjo, hat es irgendwelche vorteile wenn du archive updatest? ansonsten klingt ein neues archive doch ausreichend. wobei ich mir nicht sicher bin was du mit index datei mitliefern meinst, es hat doch eh immer jedes archive eine index datei, sonst kann man mit den daten darin wohl nichts anfangen, wuerde ich annehmen.

    Also im Moment ist es so, dass ich die Game.index einlese und bspws. beim Zugriff auf die Datei /Foo/Bar/Baz.bla zurückbekomme, dass diese Datei in Archiv Nummer 2, also der Datei Game.002 , ab Byte x mit Länge y zu finden ist. Dieser Index erstreckt sich also über ALLE Archive.

    ob du jetzt eine game.index hast, oder dir die informationen aus allen archiven rausholst die du oeffnest, duerfte egal sein. zweiteres ist aber flexibler.

    Soviel wie ich mitbekommen habe, scheint aber Memory-Mapping für mich insofern praktisch, dass ich den Index eh nur einmal einlese und dementsprechend direkt den gesuchten Teil on-thy-fly rausextrahieren und verarbeiten kann. Hierbei müsste ich noch herausfinden, wie ich am Geschickstesten mit Memory-Streams (?) arbeiten kann um nicht ständig die extrahierten Dateien auf Platte zu schreiben, was inperfomant wäre.

    memory streams? auf platte extrahieren? wieso nutzt du nicht einfach pointer? dafuer sind memory mapped files da, ist doch nicht java hier 😛



  • Ich habe bisher noch nie mit MMF gearbeitet und werde sicher viel Spaß haben, meine Methoden um meine Daten zu (de)serialisieren (ich habe hier Boost::Serialization als "Light"-Variante nachgebaut für mich) an MMF anzupassen, aber dies wird definitiv viel effizienter werden, soviel wie ich bisher gelesen habe. Danke auf jeden Fall schon mal für den Hinweis auf MMF - wahrscheinlich unter Windows eine tolle Angelegenheit 😃


  • Mod

    Christian Ivicevic schrieb:

    Ich habe bisher noch nie mit MMF gearbeitet und werde sicher viel Spaß haben, meine Methoden um meine Daten zu (de)serialisieren (ich habe hier Boost::Serialization als "Light"-Variante nachgebaut für mich) an MMF anzupassen, aber dies wird definitiv viel effizienter werden, soviel wie ich bisher gelesen habe. Danke auf jeden Fall schon mal für den Hinweis auf MMF - wahrscheinlich unter Windows eine tolle Angelegenheit 😃

    so als tipp, so 'deserialisiere' ich eine textur

    CTexture* pTexture = reinterpret_cast<CTexture*>(&Archive[TextureOffset]);
    

    so ein mesh

    CMesh* pMesh = reinterpret_cast<CMesh*>(&Archive[MeshOffset]);
    

    so als meine 'light' variante 😉



  • Es ging mir darum, dass ich das in einer eigenen Bibliothek habe und auch mit dem ganzen Kram mit überladenen Operatoren wie & definiere wie ein struct oder eine Klasse deserialisiert wird.

    template<typename TArchive>
    TArchive& Serialize(TArchive& ar, GCFFILE::Ptr& pFile) {
    	ar & pFile->strFilename;
    	if(pFile->strFilename != String::empty) {
    		ar & pFile->dwCrc32;
    		ar & pFile->qwReserved1;
    		ar & pFile->wArchiveIndex;
    		ar & pFile->dwOffset;
    		ar & pFile->qwReserved2;
    		ar & pFile->dwSize;
    		ar & pFile->qwReserved3;
    		ar & pFile->wTerminator;
    	}
    	return ar;
    }
    

    Und aktuell habe ich für die erste Variante mit regulären fstreams Ausdrücke wie z.B.

    this->pImpl->m_Stream.read(reinterpret_cast<GCFLib_RawPtr>(&object), sizeof(GCFLib_DWord));
    

    und respektive write, je nachdem in welche Richtung das geht. Aber die MMF-Variante wie du sie da beschreibst sieht definitiv viel komfortabler aus!


Log in to reply