Containerdateien/Virtuelle Dateisysteme



  • Jedes neuere Spiel nutzt ja große Containerdatein in denen die ganzen eigentlichen Dateien lagern. Sowas wolte ich gern mal selbst schreiben. Mein Problem ist das ich auch nach längerem Suchen keine wirkliche Übersicht gefudnen habe die mal kurz und knapp erklärt wie man die Dateien am besten in dem Container ablegt, also wie so ein Container intern aufgebaut wird. Raus holt man sie ja als Virtuelle Datei nur eben das Lagern im Container bereitet mir Kopfzerbrechen. Hat da evtl jemand was zur Hand was es kurz und Knapp erklärt oder kann mir ein paar Ansatzpunkte nennen anhand derer ich das ganze mal anschaun kann? Zum Thema Virtuelle dateien hätte ich noch folgende Frage, gehe ich richtig in der Annahme das die meisten Spiele die Virtuellen Dateien im RAM halten um so im falle eines neu ladens nach einem Tab schneller das Spiel neu zu laden, der unetrschied zwischen Taben und Spiel starten ist bei größeren Spielen ja Teilweise recht groß.



  • es reicht doch, wenn du aus den dateinamen fortlaufende integers machst und eine lut baust, die diese integers in fileoffsets übersetzt.
    typischer fall von sachen, die erst kurz vorm brennen gemacht werden sollten.
    performancetechnisch sollte das wenig ausmachen, ob du ein mapping mit hunderten von views anguckst oder hunderte von filemappings mit je einem view. gerade das nicht-ins-ram-zwingen könnte performance bringen, könnte ich mir vorstellen.



  • volkard schrieb:

    es reicht doch, wenn du aus den dateinamen fortlaufende integers machst und eine lut baust, die diese integers in fileoffsets übersetzt.
    typischer fall von sachen, die erst kurz vorm brennen gemacht werden sollten.

    Dh also Kurz an den Dateianfang ein Chunk/Block mit der Dateiliste und dann ein Chunk/Block pro Datei und dann einfach sagen ich brauch datei 13 also wandere ich bis ich Chunk nr13 habe und den Stopfe ich dann in ne Virtuelle Datei?

    volkard schrieb:

    performancetechnisch sollte das wenig ausmachen, ob du ein mapping mit hunderten von views anguckst oder hunderte von filemappings mit je einem view. gerade das nicht-ins-ram-zwingen könnte performance bringen, könnte ich mir vorstellen.

    Das verstehe ich nicht Hunderprozentig. Nehme ich mal ein konkretes Beispiel, ich habe eine Containerdatei auf der Festplatte, Inhalt sind 5Dateien, die lese ich in den RAM ein, dh ich habe 5 virtuelel Dateien im RAM und einen Container auf der Festplatte, es würde dann also kaum einen unterschied bringen bzw wäre evtl sogar schneller wenn ich die Dateien immer wieder neu vom Container anfordere? Oder hab ich da was falsch verstanden?



  • Warum nicht einfach ein .zip?
    Ich habe auch einen ResourceManager, in den ich .zip-Dateien und Pfade mappen kann und der dann entsprechend bei Anforderungen losrennt und die Datei sucht und dann ein Speicherhandle darauf zurückgibt. Man sogar einstellen, ob man zuerst richtige Pfade durchsuchen möchte oder die zips. So kann man während der Entwicklung gut switchen und doppelte Dateien werden automatisch aufgelöst. Die Datei im Speicher zu lesen ist dann ja das kleinere Problem und moderne Libs um z.Bsp. Bilder zu laden akzeptieren auch nen Stück RAM als Quelle.
    (Ich hoffe ich hab deine Intension richtig verstanden)

    Hier noch ein wenig COde in den Raum geworfen:

    NLMemoryHandle* NLZipReaderImpl::extractToMemory(const std::string& file )
    {
        if ( this->isOpen() )
        {
            char current_file[MAX_PATH];
            int done = unzGoToFirstFile(m_zip);        
            while ( done == UNZ_OK )
            {
                unz_file_info file_info;
                unzGetCurrentFileInfo(m_zip, &file_info, current_file, sizeof(current_file), 0, 0, 0, 0);
                if ( file == current_file )
                {
                    if ( unzOpenCurrentFilePassword( m_zip, m_password.length() == 0 ? NULL : m_password.c_str() )  == UNZ_OK )
                    {
                        int size = file_info.uncompressed_size;
                        unsigned char* p = new unsigned char[size];
                        memset(p, 0, sizeof(unsigned char)*size);
                        unzReadCurrentFile(m_zip, p, size);
                        assert ( p != NULL );
                        NLMemoryHandle* handle = new NLMemoryHandle(p, size, file, false);
                        unzCloseCurrentFile(m_zip);
                        return handle;
                    }
                }
                done=unzGoToNextFile(m_zip);
            }
        }
        //throw(NLFileNotFoundException(NL_CRITICAL_NO));
        return NULL;
    }
    

    rya.

    edit:
    ps.: Diese Funktionen mit "unz" findest du in der minizip-lib und die findest du im contrib-ordner der zLib.



  • Ja die Intention hast du richtig getroffen. Das viele Moderne Funktionen RAM-Stücken akzeptieren weiß ich, ua stellt ja DirectX für alles auch ne RAM Version zur verfügung.

    Ja zip wäre eine Idee. Mich hat nur Interessiert wie es in soner Datei aussieht, ich weis gerne wie es Funktioniert bevor ich es benutze.



  • Xebov schrieb:

    Dh also Kurz an den Dateianfang ein Chunk/Block mit der Dateiliste und dann ein Chunk/Block pro Datei und dann einfach sagen ich brauch datei 13 also wandere ich bis ich Chunk nr13 habe und den Stopfe ich dann in ne Virtuelle Datei?

    jo.
    aber der witz an der geschichte ist, daß du im c++-code statt mit loadPng("img/scrne/terrain/grass.png") mit loadPng("FILEID_IMG_SCENE_TERRAIN_GRASS_PNG") arbeitest.
    und die FILEIDs haste forlaufend oder wenigstens recht dicht.
    da haste dann zwei zugriffsmöglichkeiten. beim entwickweln haste einfach ein dickes array von strings im ram, das die IDs zu filenames mapt und die filezugriffe sind per dateisystem (natürlich filemapping). und ohne den anwendungscode zu ändern, kannste dir eine fette datei zusammenkopieren, und dir offen lassen und in einer tabelle statt die filenames zu suchen, nun die startpositionen und längen in der fetten datei nehmen. kann mir vorstellen, daß das bei sauvielen files gar nicht so dumm ist.

    Xebov schrieb:

    Nehme ich mal ein konkretes Beispiel, ich habe eine Containerdatei auf der Festplatte, Inhalt sind 5Dateien, die lese ich in den RAM ein, dh ich habe 5 virtuelel Dateien im RAM und einen Container auf der Festplatte, es würde dann also kaum einen unterschied bringen bzw wäre evtl sogar schneller wenn ich die Dateien immer wieder neu vom Container anfordere? Oder hab ich da was falsch verstanden?

    am schnellsten ist natürlich, eine datei aus dem ram anzufordern. aber ist es auch immer gut, die ins ram zu drücken, wobei evtl andere wichtige sachen rausfallen? kennst du CreateFilemapping und konsorten?



  • volkard schrieb:

    jo.
    aber der witz an der geschichte ist, daß du im c++-code statt mit loadPng("img/scrne/terrain/grass.png") mit loadPng("FILEID_IMG_SCENE_TERRAIN_GRASS_PNG") arbeitest.
    und die FILEIDs haste forlaufend oder wenigstens recht dicht.
    da haste dann zwei zugriffsmöglichkeiten. beim entwickweln haste einfach ein dickes array von strings im ram, das die IDs zu filenames mapt und die filezugriffe sind per dateisystem (natürlich filemapping). und ohne den anwendungscode zu ändern, kannste dir eine fette datei zusammenkopieren, und dir offen lassen und in einer tabelle statt die filenames zu suchen, nun die startpositionen und längen in der fetten datei nehmen. kann mir vorstellen, daß das bei sauvielen files gar nicht so dumm ist.

    Hier gehen unsere Gedanken etwas auseinander. Ich dachte dabei daran mehrere dateien zu ersteleln die sehr groß sind. Die Dateien bekommen einen Kopf der ein Array aus Namen und Pfaden enthält. Die datei wird geöffnet und die Pfadliste gespeichert. Dann schickt man seine gewünschte Datei und den Pfad an den loader der kann dann Prüfen ob die Datei außerhalb des Containers da ist oder eben im Container, wenn sie nicht da ist gibts eben nen Fehler.

    Die Variante ein Array aus Zahlen und Pfaden zu erstellen halte ich nicht für vorteilhaft. Der Grund ist das abzählen. Wenn ich im Container zB 5 Dateien habe und lösche Datei 3 dann rutschen die dateien 4 und 5 auf die Plätze 3 und 4 in der Liste, ich muß aber beim ändern auf nichts weiter achten, da durch das löschen von Chunk 3 ja auch die Chunks dann stimmen.
    Habe ich aber reine zahlen dann habe ich den Nachteil das ich jedes Chunk prüfen muß ob es nun das richtige ist, das wäre zumindest mein momentaner Gedankengang.

    volkard schrieb:

    am schnellsten ist natürlich, eine datei aus dem ram anzufordern. aber ist es auch immer gut, die ins ram zu drücken, wobei evtl andere wichtige sachen rausfallen? kennst du CreateFilemapping und konsorten?

    Nein das kenn ich nicht.

    Wobei wichtiges rausfallen ja Relativ ist, wenn die Dateien nicht zuviele und nicht zu groß sind wäre es ja ansich kein Problem sich mal 100MB aus dem RAM zu greifen und die Dateien einzulagern, wobei dein gedanken ansatz natürlich schon in sich richtig ist.

    Die Frage nach dem lagern im RAM stellte sich mir aus einem bestimtmen Grund. Nehme ich ein aktuelels Spiel und starte es dauert das schonmal ein paar Minuten bis es geladen hat, Tabe ich aber raus und 10min später rein geht das laden sehr viel schneller als wenn iche s neu starten würde, und diese Tatsache hat ja nen Ursprung.



  • Xebov schrieb:

    Die Variante ein Array aus Zahlen und Pfaden zu erstellen halte ich nicht für vorteilhaft. Der Grund ist das abzählen. Wenn ich im Container zB 5 Dateien habe und lösche Datei 3 dann rutschen die dateien 4 und 5 auf die Plätze 3 und 4 in der Liste

    nee!
    dann wird in platz3 ein leerer chunk geschrieben.
    das meinte ich mit "und die FILEIDs haste forlaufend oder wenigstens recht dicht." eine datei, die es auf der platte nicht gibt, oder eine ID, zu der es keinen dateinamen gibt, erzeugt bei dieser ID einfach einen leerern chunk. falls die chunks beschrieben werden mit offset+länge kannste als länge -1 nehmen für nichtexistenz.

    ist aber nur, wie ich das machen würde. andere machen es anders. zum beispiel hab ich gerade den Landwirtschafts-Simulator 2008 angeschaut, der macht es genauso wie Scorcher24. ob ein ding ein verzeichnis ist oder ein zipfile ist dem völlig egal, wird alles zur laufzeit umgesetzt.



  • volkard schrieb:

    nee!
    dann wird in platz3 ein leerer chunk geschrieben.
    das meinte ich mit "und die FILEIDs haste forlaufend oder wenigstens recht dicht." eine datei, die es auf der platte nicht gibt, oder eine ID, zu der es keinen dateinamen gibt, erzeugt bei dieser ID einfach einen leerern chunk. falls die chunks beschrieben werden mit offset+länge kannste als länge -1 nehmen für nichtexistenz.

    ist aber nur, wie ich das machen würde. andere machen es anders. zum beispiel hab ich gerade den Landwirtschafts-Simulator 2008 angeschaut, der macht es genauso wie Scorcher24. ob ein ding ein verzeichnis ist oder ein zipfile ist dem völlig egal, wird alles zur laufzeit umgesetzt.

    Ich würde gar keine leeren Chunks machen. Ich würde es mit eienr einfachen Liste machen, also eine Tabelle mit den Pfaden und dateien die im Container enthalten sind. er durch sucht die Liste nach einer übereinstimmung, findet er keine ist die datei nicht da, findet er eine schaut er welche nr die Angabe ind er Liste hat, also welchen Platz, zB Platz 5, dann flitzt er los und zählt 5 Chunks ab und den 5. nimmt er dann. Da bräuchte man dann eigentlich keine umsetzung Dateinamen<->Integer.

    So ähnlich wie Scorcher das geschrieben hatte habe ich mir das auch gedacht, allerdings gings mir ja eher darum wie man sone Container selebr machen kann, also einfahc mal die Technik zu sehen die dahinter steckt ohne gleich den einfachen weg über existierende Formate wie zip zu gehen.



  • Einen Container hab ich mal vor Jahren entwickelt als ANSI-C gelernt habe.
    Ist nicht ganz so schwer, je nachdem wieviel Features man möchte.
    Ich hab im Endeffekt einen HEader aufgebaut:

    MAGIC_NUMMER - immer 99999 als Identifikation des Dateiformats

    NUM_ENTRIES(sizeof(int)) Anzahl der Eintragsblöcke

    FILENAME(sizeof(char)*255) - Der erste Dateiname mit einer fixen größe
    FILEADRESS(sizeof(int)) - Die Adresse in der Datei, bzw der Offset für fseek()
    FILELENGTH(sizeof(int)) - Die Länge der Datei

    Das wiederholt sich dann bis alle Dateien eingetragen sind. Danach werden die Dateien einfach ganz simpel hinten dran hingeschrieben. Das ist nicht besonders toll oder performat, war damals nur eine Übung. Aber zeigt doch ganz gut, wie man sowas aufbauen kann. Die Daten die man hinten dranhängt, jagt man vorher einfach nur durch inflate() in der zlib, dann ist das ganze auch komprimiert.
    Dann sollte man aber im Header auch die ungezippte Größe vermerken.

    edit: Dafür empfehle ich dir die "Goldenen Regeln der Spieleprogrammierung" von Martin Brownlow, das hat einen Abschnitt darüber mit dabei. HAb eben nachgesehen :D.
    rya.



  • Scorcher24 schrieb:

    Einen Container hab ich mal vor Jahren entwickelt als ANSI-C gelernt habe.
    Ist nicht ganz so schwer, je nachdem wieviel Features man möchte.
    Ich hab im Endeffekt einen HEader aufgebaut:

    MAGIC_NUMMER - immer 99999 als Identifikation des Dateiformats

    NUM_ENTRIES(sizeof(int)) Anzahl der Eintragsblöcke

    FILENAME(sizeof(char)*255) - Der erste Dateiname mit einer fixen größe
    FILEADRESS(sizeof(int)) - Die Adresse in der Datei, bzw der Offset für fseek()
    FILELENGTH(sizeof(int)) - Die Länge der Datei

    Das wiederholt sich dann bis alle Dateien eingetragen sind. Danach werden die Dateien einfach ganz simpel hinten dran hingeschrieben. Das ist nicht besonders toll oder performat, war damals nur eine Übung. Aber zeigt doch ganz gut, wie man sowas aufbauen kann. Die Daten die man hinten dranhängt, jagt man vorher einfach nur durch inflate() in der zlib, dann ist das ganze auch komprimiert.
    Dann sollte man aber im Header auch die ungezippte Größe vermerken.

    edit: Dafür empfehle ich dir die "Goldenen Regeln der Spieleprogrammierung" von Martin Brownlow, das hat einen Abschnitt darüber mit dabei. HAb eben nachgesehen :D.
    rya.

    reicht ja schon.
    und bei programmstart liest man den header und jagt ihn in eine std::map<string,pair<FILEADRESS,FILENAME>>, dann kann man immer nach blitzschnell dateinamen suchen.



  • Ich Quote mal euch beide.

    Ich hätte ja folgendes gemacht.

    Dateichunk <- Identifikation
    Stringliste <- Dateinamen und Pfade
    chunks <-1 chunk pro Datei

    Dann hätte ich einfach die angeforderte Datei mit strcmp mit dem eintrag nacheinander verglichen.

    Alternativ könnte ich mir allerdings auch einen Baum vorstellen. Die unterpfade bekommen nummern anhand derer sich die Chunknr finden läst (da wäre dann ne simple Addition nötig) und dann springt das Programm in der Datei von Chunk zu Chunk bis es den richtigen gefunden hat. Also als bsp man braucht Dateichunk 17 also springt das programm ab den Dateichunks 17 Chunks vorwärts.

    Allerdings hab ich von Maps und Bäumen nicht so viel Ahnung, denke aber da da ein Baum schneller wäre da er den Dateipfad nachbilden kann.



  • pfade?
    du brauchst kein echtes dateisystem nachzubauen. denn dein virtuelles dateisystem ist readonly! das macht alles VIEL einfacher.



  • volkard schrieb:

    pfade?
    du brauchst kein echtes dateisystem nachzubauen. denn dein virtuelles dateisystem ist readonly! das macht alles VIEL einfacher.

    Ja es ist Readonly, aber ohne Pfade sehe ich ein kleines Problem. Mal angenommen du forderst eine Datei an "\Dateien\Texturen\Umgebung\wiese.bmp". Nun hast du ein System das erstmal schaut ob es die Datei auf der Platte gibt und danach in den Container schaut. Für die Festplatte brauchst du den Pfad. Wenn du ihn im Container wegläst und dort nur "wiese.bmp" hast dann haste das Problem das es "wiese.bmp" nur einmal geben darf, wenn es aber in verschiedenen Ordnern 2x oder öffter vorkommt wäre das Ergebniss aus dem Container das erst beste, also müsste die Datei im Container auch über den vollen Pfad abgeglichen werden, der Container wäre in diesem moment ja im Grunde nichts anderes als ein Abbild der Festplatte wenn er entpackt wäre nur das er eben eine Datei ist und Komprimiert/verschlüsselt sein kann.



  • Xebov schrieb:

    also müsste die Datei im Container auch über den vollen Pfad abgeglichen werden, der Container wäre in diesem moment ja im Grunde nichts anderes als ein Abbild der Festplatte wenn er entpackt wäre nur das er eben eine Datei ist und Komprimiert/verschlüsselt sein kann.

    ja. ich kanns mir gar nicht anders denken.



  • Dh also doch Baum der die Pfade nachbildet oder eben endlose strcmp Sessions, der Baum wäre da wohl schneller.



  • Xebov schrieb:

    Dh also doch Baum der die Pfade nachbildet oder eben endlose strcmp Sessions, der Baum wäre da wohl schneller.

    oder std::map!
    da ist doch schon ein baum drinne. ein schnelelr baum. der splittet nicht nur bei verzeichnissen, sondern irgendwo und überall, so daß der baum ausgeglichen ist.



  • Achso ok, das wusste ich nicht, ich kenn mich mit den "Lagermöglichkeiten" aus der std nicht so gut aus. Das hieße dann aber im gegenzug das ich jeder Datei eine Nummer zuweisen müsste damit dieser Baum funktioniert, sehe ich das so richtig?



  • Du kannst auch nen string nehmen. Als bsp:

    typedef std::map<std::string, std::string> myMap;
    

    Jetzt kannst du über

    myMap files;
    files["TheFolder"]
    

    die Dateien suchen.



  • Gut zu wissen. Dann wären meine Fragen soweit geklärt dann kann ich mich direkt mal in Ruhe über die Sache hermachen.


Log in to reply