Format de fichier flexible
Souvent j'ai pu me rendre compte que l'évolution des formats de fichier est quelque chose d'assez problématique et cela pour plusieurs raisons :- Chaque nouvelle version remet totalement en cause le format e.g. ajout d'une information (car souvent on accède une section de donnée relativement à la précédente), augmentation ou diminution de la taille d'une donnée, etc.
- Le réexport au nouveau format de toutes les données du projet est souvent quelquechose d'assez compliqué.
Dans ce post je vais uniquement discuter du premier point.
Dans n'importe quel moteur on peut trouver le code suivant :
int Version = 0;
pfile->read(&Version, sizeof(int));
if (Version == 1)
{
SHeaderV1 Header;
pfile->read(&Header, sizeof(SHeaderV1));
// read sequentially & use first data section
// read sequentially & use second data section
// ...
}
else if (Version == 2)
{
SHeaderV2 Header;
pfile->read(&Header, sizeof(SHeaderV2));
// read sequentially & use first data section
// read sequentially & use second data section
// ...
}
else
{
assert(false);
}
Pour ma part je n'aime pas ce genre de code car beaucoup de copier/coller, manque de lisibilité, etc.
Voici une tentative pour abstraire l'accès à un format de fichier :
// Description d'une section de données
struct SEntry
{
int m_Id; // Name of the entry
int m_Offset; // File absolute offset
int m_Size; // Full entry size
};
// Header générique pour les formats de fichier
struct SFileHeader
{
int m_Version;
int m_EntryCount;
SEntry * m_pEntryList;
}
// Classe générique permettant d'accéder aux différentes sections du fichier
class CFileFormat
{
// ....
}
#define MY_FORMAT_HEADER_ID 1
#define MY_FORMAT_VERTEXLIST_ID 2
#define MY_FORMAT_NORMALLIST_ID 3
CFileFormat MyFormat(pMemFile);
SFileHeader MyHeader;
MyFormat.GetEntryValue<SFileHeader>(MY_FORMAT_HEADER_ID, &MyHeader);
int const VertexCount = MyFormat.GetEntryCount<SVertex>(MY_FORMAT_VERTEXLIST_ID);
SEntryAccessor<SVertex> VertexListAccessor = MyFormat.GetEntryAccessor<SVertex>(MY_FORMAT_VERTEXLIST_ID);
SEntryAccessor<SNormal> NormalListAccessor = MyFormat.GetEntryAccessor<SVertex>MY_FORMAT_NORMALLIST_ID);
assert(VertexCount == MyFormat.GetEntryCount<SVertex>(MY_FORMAT_NORMALLIST_ID));
SVertex * pVerticesList = VertexListAccessor.GetData();
SNormal * pNormalList = NormalListAccessor.GetData();
for (int VertexIter = 0 ; VertexIter < VertexCount ; ++VertexIter)
{
// Do things
}
Donc, même si on ajoute des données entre la liste de normales et de vertices ou que l'on modifie la taille du header, le code est toujours valable. Il est vrai que cette abstraction ne solutionne pas tous les cas de figure (on aura toujours des if(Version==1){ /*...*/} ) mais elle permet au moins de répondre à certaines problématiques (normaliser l'accès aux formats de fichiers, les rendre plus accessibles et clairs, etc.).
L'autre composant qui complète cette solution est bien entendu le process de build des données, sujet dont je parlerai dans un autre post.

Commentaires