Containerdateien/Virtuelle Dateisysteme



  • 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.



  • ich nehme mal keinen header, sondern ein indexfile der form

    img/terrein/wiese.png 0 1564
    img/terrein/pflaster.png 1564 100
    script/traktor.loa 1664 345
    

    und

    struct Beschreiber{
       int offset;
       int laenge;
    };
    ...
    //indexfile laden
    map<string,Beschreiber> lagerFiles;
    ifstream in("files.idx");
    string name;
    Beschreiber beschreiber;
    for(;;){
       file>>name;
       file>>beschreiber.offset>>beschreiber.laenge;
       if(in.eof())
          break;
       lagerfiles[name]=beschreiber;
    }
    

    und zum laden

    char* loadFile(string filename){
    Beschreiber& b=lagerFiles[filename);//ja, das ist lecker schnell!
    void* mem=operator new(beschreiber.laenge);
    offenesLagerfile.seek(beschreiber.laenge);
    offenesLagerfile.read(mem,beschreiber.laenge);
    return mem;
    }
    

    oder so. nur prinzipskizze.



  • volkard schrieb:

    ich nehme mal keinen header, sondern ein indexfile der form

    img/terrein/wiese.png 0 1564
    img/terrein/pflaster.png 1564 100
    script/traktor.loa 1664 345
    

    und

    struct Beschreiber{
       int offset;
       int laenge;
    };
    ...
    

    oder so. nur prinzipskizze.

    Ich würde es so machen:

    string
    string
    string
    

    Dann würde ich die strings einlesen und beim einlesen eine Nummer verpassen. Chunks haben den Vorteil das man sie ja überspringen kann weil sie ihre eigene Länge beinhalten. Der Vorteil an der Version ist das man wenn sich der Inhalt der Datei ändert nicht alles Aktualisieren muß, sondern man einfach den string und den Chunk an den richtigen Stellen entfernt oder einfügt, man muß somit nicht bei jeder änderung des Containers für jede Datei die Offsets aktualisieren, weil das programm sich die selbst zusammen sucht.



  • Xebov schrieb:

    Ich würde es so machen:

    auch ok. war ja nur ne skizze, um die verwendung der map zu verdeutlichen.


  • Mod

    Scorcher24 schrieb:

    Warum nicht einfach ein .zip?

    so machen es die meisten spiele. that's the way to go



  • rapso schrieb:

    Scorcher24 schrieb:

    Warum nicht einfach ein .zip?

    so machen es die meisten spiele. that's the way to go

    Was ich jetzt noch gefunden habe und was ich sehr cool finde, ist PhysFS:
    http://icculus.org/pipermail/physfs/2009-March/000697.html
    Das ist ein System zum laden von Resourcen, das jeglichen Zugriff ausserhalb der eigenen Resourcen unterbindet. Wie genau weiss ich noch nicht, aber ich habs mal geladen und werd da ein wenig reinpeeken. Vllt integrier ich das sogar bei mir.. mal schauen.
    Gute Nacht.
    rya.


  • Mod

    volkard schrieb:

    es reicht doch, wenn du aus den dateinamen fortlaufende integers machst und eine lut baust, die diese integers in fileoffsets übersetzt.

    ja, und man kann auch alle variablen einfach durchnummerieren, mit nur einem namen, foo0 bis foo65535 😮

    typischer fall von sachen, die erst kurz vorm brennen gemacht werden sollten.

    ich weiss nicht bei welchen spielen du solche erfahrungen gesammelt hast, aber von allen produkten bei denen ich mitgearbeitet habe kenne ich das man bei jedem nightly-build alle resourcen so zusammengefasst hat.
    gerade so primitive dinge sollten von anfang an implementiert und getestet sein, damit man nicht am ende boese ueberraschungen erlebt. simpelstes beispiel ist gross-kleinschreibung, je nach entwicklungsplattform ist das wichtig, essentiell oder egal. baut man am ende ein archivsystem ein was anders funktioniert (weil man was fertiges nimmt) kann das einiges an zeit dauern bis man es sauber zum laufen bekommt.
    da waere es gut es schon lange getestet zu haben.

    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.

    es bringt gerade beim oeffnen von dateien sehr viel performance, da man normalerweise alles vom OS umgeht was bremsen koennte und es bringt performance beim lesen von langsamen laufwerken, weil z.b. bei zip, die kompression kleinere dateien erlaubt.

    btw. ja ich hab gelesen was du noch geschrieben hast, und das rad was du da neu erfindest ist was .zip macht, nur sagt dein erster beitrag das irgendwie anders.



  • rapso schrieb:

    btw. ja ich hab gelesen was du noch geschrieben hast, und das rad was du da neu erfindest ist was .zip macht, nur sagt dein erster beitrag das irgendwie anders.

    ich will das entzippen vom installer erledigen lassen die daten ausgepackt auf der platte leben lassen. in einem fetten file.

    das laden einer datei will ich im prinzip weg haben. zur verdeutlichung nochmal mit indexfile und datenfile getrennt.

    //indexfile und datafile komplett in den speicher gemapt
    Range<char*,char*> loadFile(u32 fileID){
       Range<u32,u32>* descriptor=theIndexFileContent[fileID];
       return makeRange(theDataFileContent+descriptor.first,theDataFileContent+descriptor.second);
    }
    

    das macht beim file-aufsuchen O(1). und verdient darüberhinaus sogar noch das prädikat 'sauschnell'. die verwaltungskosten sind bei wenigen dutzend takten anzusiedeln. fertig und schluß.
    falls geladen werden muß, muß halt geladen werden. dann kostet es was. weil die platte lahm ist. diese kosten kommen auch auf einen zu, weil das vorher teuer ausgepackte zeug in die auslagerungsdatei ausgelagert werden mußte.
    ich hab halt quasi null verwaltungskosten, während dieses einzel-datei-geanfasse beim anwender laufzeit und programmierer entwicklungszeit kostet und bei beiden nicht gerade wenig.

    abstriche habe ich gemacht, um dem fragesteller den einsteig zu erleichtern.


Anmelden zum Antworten