Ticket #1167: colladacache+archivebuild_fixes-02172012.patch

File colladacache+archivebuild_fixes-02172012.patch, 19.1 KB (added by historic_bruno, 12 years ago)
  • source/graphics/ColladaManager.cpp

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2012 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    2121
    2222#include "graphics/ModelDef.h"
    2323#include "lib/fnv_hash.h"
     24#include "maths/MD5.h"
     25#include "ps/CacheLoader.h"
    2426#include "ps/CLogger.h"
    2527#include "ps/CStr.h"
    2628#include "ps/DllLoader.h"
     
    6264    int (*convert_dae_to_psa)(const char* dae, Collada::OutputFn psa_writer, void* cb_data);
    6365
    6466public:
    65     CColladaManagerImpl()
    66         : dll("Collada")
     67    CColladaManagerImpl(const PIVFS& vfs)
     68        : dll("Collada"), m_VFS(vfs)
    6769    {
    6870    }
    6971
     
    109111            set_logger(ColladaLog, static_cast<void*>(&skeletonPath));
    110112
    111113            CVFSFile skeletonFile;
    112             if (skeletonFile.Load(g_VFS, skeletonPath) != PSRETURN_OK)
     114            if (skeletonFile.Load(m_VFS, skeletonPath) != PSRETURN_OK)
    113115            {
    114116                LOGERROR(L"Failed to read skeleton definitions");
    115117                dll.Unload();
     
    123125                dll.Unload();
    124126                return false;
    125127            }
    126 
    127             // TODO: the cached PMD/PSA files should probably be invalidated when
    128             // the skeleton definition file is changed, else people will get confused
    129             // as to why it's not picking up their changes
    130128        }
    131129
    132130        // Set the filename for the logger to report
     
    137135        CStr daeData;
    138136        {
    139137            CVFSFile daeFile;
    140             if (daeFile.Load(g_VFS, daeFilename) != PSRETURN_OK)
     138            if (daeFile.Load(m_VFS, daeFilename) != PSRETURN_OK)
    141139                return false;
    142140            daeData = daeFile.GetAsString();
    143141        }
    144142
    145143        // Do the conversion into a memory buffer
     144        // We need to check the result, as archive builder needs to know if the source dae
     145        //  was sucessfully converted to .pmd/psa
     146        int result = -1;
    146147        WriteBuffer writeBuffer;
    147148        switch (type)
    148149        {
    149         case CColladaManager::PMD: convert_dae_to_pmd(daeData.c_str(), ColladaOutput, &writeBuffer); break;
    150         case CColladaManager::PSA: convert_dae_to_psa(daeData.c_str(), ColladaOutput, &writeBuffer); break;
     150        case CColladaManager::PMD:
     151            result = convert_dae_to_pmd(daeData.c_str(), ColladaOutput, &writeBuffer);
     152            break;
     153        case CColladaManager::PSA:
     154            result = convert_dae_to_psa(daeData.c_str(), ColladaOutput, &writeBuffer);
     155            break;
    151156        }
    152157
    153158        // don't create zero-length files (as happens in test_invalid_dae when
     
    155160        // logic warns when asked to load such.
    156161        if (writeBuffer.Size())
    157162        {
    158             Status ret = g_VFS->CreateFile(pmdFilename, writeBuffer.Data(), writeBuffer.Size());
     163            Status ret = m_VFS->CreateFile(pmdFilename, writeBuffer.Data(), writeBuffer.Size());
    159164            ENSURE(ret == INFO::OK);
    160165        }
    161166
    162         return true;
     167        return (result == 0);
    163168    }
     169
     170private:
     171    PIVFS m_VFS;
    164172};
    165173
    166 CColladaManager::CColladaManager()
    167 : m(new CColladaManagerImpl())
     174CColladaManager::CColladaManager(const PIVFS& vfs)
     175: m(new CColladaManagerImpl(vfs)), m_VFS(vfs)
    168176{
    169177}
    170178
     
    173181    delete m;
    174182}
    175183
    176 VfsPath CColladaManager::GetLoadableFilename(const VfsPath& pathnameNoExtension, FileType type)
     184void CColladaManager::PrepareCacheKey(MD5& hash, u32& version)
    177185{
     186    // Include skeletons.xml file info in the hash
     187    VfsPath skeletonPath("art/skeletons/skeletons.xml");
     188    FileInfo fileInfo;
     189    if (m_VFS->GetFileInfo(skeletonPath, &fileInfo) != INFO::OK)
     190    {
     191        // This shouldn't occur for any sensible reasons
     192        LOGERROR(L"Failed to stat '%ls' for DAE caching", skeletonPath.string().c_str());
     193        // We can continue, something else will break if we try loading a skeletal model
     194    }
     195    else
     196    {
     197        u64 skeletonsModifyTime = (u64)fileInfo.MTime() & ~1; // skip lowest bit, since zip and FAT don't preserve it
     198        u64 skeletonsSize = (u64)fileInfo.Size();   
     199        hash.Update((const u8*)&skeletonsModifyTime, sizeof(skeletonsModifyTime));
     200        hash.Update((const u8*)&skeletonsSize, sizeof(skeletonsSize));
     201    }
     202
     203    // Add converter version to the hash
     204    int converterVersion = COLLADA_CONVERTER_VERSION;
     205    hash.Update((const u8*)&converterVersion, sizeof(converterVersion));
     206
     207    // Arbitrary version number - change this if we update the code and
     208    // need to invalidate old users' caches
     209    version = 1;
     210}
     211
     212VfsPath CColladaManager::GetLoadablePath(const VfsPath& pathnameNoExtension, FileType type)
     213{
    178214    std::wstring extn;
    179215    switch (type)
    180216    {
     
    186222    /*
    187223
    188224    If there is a .dae file:
    189         * Calculate a hash to identify it.
    190         * Look for a cached .pmd file matching that hash.
    191         * If it exists, load it. Else, convert the .dae into .pmd and load it.
    192     Otherwise, if there is a (non-cache) .pmd file:
    193         * Load it.
    194     Else, fail.
     225        * Calculate hash of skeletons.xml and converter version.
     226        * Use CCacheLoader to check for archived or loose cached .pmd/psa.
     227        * If it exists:
     228            * Return pathname of .pmd/psa.
     229        * Else:
     230            * Convert the .dae into .pmd/psa.
     231            * Return pathname of .pmd/psa.
     232    Else, if there is a (non-cached) .psa/pmd file:
     233        * Return pathname of .pmd/psa.
     234    Else:
     235        * Fail (return empty string).
    195236
    196     The hash calculation ought to be fast, since normally (during development)
    197     the .dae file will exist but won't have changed recently and so the cache
    198     would be used. Hence, just hash the file's size, mtime, and the converter
    199     version number (so updates of the converter can cause regeneration of .pmds)
    200     instead of the file's actual contents.
     237    Since we use CCacheLoader which automatically hashes file size and mtime,
     238    and handles archived files and loose cache, when preparing the cache key
     239    we add converter version number (so updates of the converter cause
     240    regeneration of the .pmd/psa) and the global skeletons.xml file size and
     241    mtime, as modelers frequently change the contents of skeletons.xml and get
     242    perplexed if the in-game models haven't updated as expected (we don't know
     243    which models were affected by the skeletons.xml change, if any, so we just
     244    regenerate all of them)
    201245
    202     TODO (maybe): The .dae -> .pmd conversion may fail (e.g. if the .dae is
     246    TODO (maybe): The .dae -> .pmd/psa conversion may fail (e.g. if the .dae is
    203247    invalid or unsupported), but it may take a long time to start the conversion
    204248    then realise it's not going to work. That will delay the loading of the game
    205249    every time, which is annoying, so maybe it should cache the error message
     
    208252
    209253    */
    210254
    211     // (TODO: the comments and variable names say "pmd" but actually they can
    212     // be "psa" too.)
    213 
    214     VfsPath dae(pathnameNoExtension.ChangeExtension(L".dae"));
    215     if (! VfsFileExists(dae))
     255    // Check if the .dae source exists
     256    VfsPath sourcePath = pathnameNoExtension.ChangeExtension(L".dae");
     257    if (m_VFS->GetFileInfo(sourcePath, 0) != INFO::OK)
    216258    {
    217         // No .dae - got to use the .pmd, assuming there is one
     259        // No .dae source file - have to use the uncached .pmd/psa if it exists
     260        // (cached .pmd/psa are only created by converting DAEs)
    218261        return pathnameNoExtension.ChangeExtension(extn);
    219262    }
    220263
    221     // There is a .dae - see if there's an up-to-date cached copy
     264    // Now we're looking for cached files
     265    CCacheLoader cacheLoader(m_VFS, extn);
     266    MD5 hash;
     267    u32 version;
     268    PrepareCacheKey(hash, version);
    222269
    223     FileInfo fileInfo;
    224     if (g_VFS->GetFileInfo(dae, &fileInfo) < 0)
     270    VfsPath loadPath;
     271    Status ret = cacheLoader.TryLoadingCached(sourcePath, hash, version, loadPath);
     272    if (ret == INFO::OK)
    225273    {
    226         // This shouldn't occur for any sensible reasons
    227         LOGERROR(L"Failed to stat DAE file '%ls'", dae.string().c_str());
    228         return VfsPath();
     274        // Found a cached version
     275        return loadPath;
    229276    }
     277    else if (ret == INFO::SKIPPED)
     278    {
     279        // No cached version was found - we'll need to create it
     280    }
     281    else
     282    {
     283        ENSURE(ret < 0);
     284        // This shouldn't happen, we already checked that the .dae exists
     285        return L"";
     286    }
    230287
    231     // Build a struct of all the data we want to hash.
    232     // (Use ints and not time_t/off_t because we don't care about overflow
    233     // but do care about the fields not being 64-bit aligned)
    234     // (Remove the lowest bit of mtime because some things round it to a
    235     // resolution of 2 seconds)
    236 #pragma pack(push, 1)
    237     struct { int version; int mtime; int size; } hashSource
    238         = { COLLADA_CONVERTER_VERSION, (int)fileInfo.MTime() & ~1, (int)fileInfo.Size() };
    239     cassert(sizeof(hashSource) == sizeof(int) * 3); // no padding, because that would be bad
    240 #pragma pack(pop)
     288    // Cached version isn't up to date with DAE or skeletons.xml, so regenerate it
     289    if (! m->Convert(sourcePath, loadPath, type))
     290        return L"";
    241291
    242     // Calculate the hash, convert to hex
    243     u32 hash = fnv_hash(static_cast<void*>(&hashSource), sizeof(hashSource));
    244     wchar_t hashString[9];
    245     swprintf_s(hashString, ARRAY_SIZE(hashString), L"%08x", hash);
    246     std::wstring extension(L"_");
    247     extension += hashString;
    248     extension += extn;
     292    return loadPath;
     293}
    249294
    250     // realDaePath_ is "[..]/mods/whatever/art/meshes/whatever.dae"
    251     OsPath realDaePath_;
    252     Status ret = g_VFS->GetRealPath(dae, realDaePath_);
    253     ENSURE(ret == INFO::OK);
    254     wchar_t realDaeBuf[PATH_MAX];
    255     wcscpy_s(realDaeBuf, ARRAY_SIZE(realDaeBuf), realDaePath_.string().c_str());
    256     std::replace(realDaeBuf, realDaeBuf+ARRAY_SIZE(realDaeBuf), '\\', '/');
    257     const wchar_t* realDaePath = wcsstr(realDaeBuf, L"mods/");
    258 
    259     // cachedPmdVfsPath is "cache/mods/whatever/art/meshes/whatever_{hash}.pmd"
    260     VfsPath cachedPmdVfsPath = VfsPath("cache") / realDaePath;
    261     cachedPmdVfsPath = cachedPmdVfsPath.ChangeExtension(extension);
    262 
    263     // If it's not in the cache, we'll have to create it first
    264     if (! VfsFileExists(cachedPmdVfsPath))
     295bool CColladaManager::GenerateCachedFile(const VfsPath& sourcePath, FileType type, VfsPath& archiveCachePath)
     296{
     297    std::wstring extn;
     298    switch (type)
    265299    {
    266         if (! m->Convert(dae, cachedPmdVfsPath, type))
    267             return L""; // failed to convert
     300    case PMD: extn = L".pmd"; break;
     301    case PSA: extn = L".psa"; break;
     302        // no other alternatives
    268303    }
    269304
    270     return cachedPmdVfsPath;
     305    CCacheLoader cacheLoader(m_VFS, extn);
     306    MD5 hash;
     307    u32 version;
     308    PrepareCacheKey(hash, version);
     309
     310    archiveCachePath = cacheLoader.ArchiveCachePath(sourcePath);
     311
     312    return m->Convert(sourcePath, VfsPath("cache") / archiveCachePath, type);
    271313}
  • source/graphics/ColladaManager.h

     
    1 /* Copyright (C) 2009 Wildfire Games.
     1/* Copyright (C) 2012 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    1818#ifndef INCLUDED_COLLADAMANAGER
    1919#define INCLUDED_COLLADAMANAGER
    2020
     21#include "lib/file/vfs/vfs.h"
    2122#include "lib/file/vfs/vfs_path.h"
    2223
    2324class CStr8;
    2425class CColladaManagerImpl;
     26class MD5;
    2527
    2628class CColladaManager
    2729{
    2830public:
    2931    enum FileType { PMD, PSA };
    3032
    31     CColladaManager();
     33    CColladaManager(const PIVFS& vfs);
    3234    ~CColladaManager();
    3335
    3436    /**
     
    4244     * @return full VFS path (including extension) of file to load; or empty
    4345     * string if there was a problem and it could not be loaded.
    4446     */
    45     VfsPath GetLoadableFilename(const VfsPath& pathnameNoExtension, FileType type);
     47    VfsPath GetLoadablePath(const VfsPath& pathnameNoExtension, FileType type);
    4648
     49    /**
     50     * Converts DAE to archive cached .pmd/psa and outputs the resulting path
     51     * (used by archive builder)
     52     *
     53     * @param sourcePath[in] path of the .dae to load
     54     * @param type[in] FileType, .pmd or .psa
     55     * @param archiveCachePath[out] output path of the cached file
     56     *
     57     * @return true if COLLADA converter completed successfully; or false if it failed
     58     */
     59    bool GenerateCachedFile(const VfsPath& sourcePath, FileType type, VfsPath& archiveCachePath);
     60
    4761private:
     62    /**
     63     * Creates MD5 hash key from skeletons.xml info and COLLADA converter version,
     64     * used to invalidate cached .pmd/psas
     65     *
     66     * @param hash[out] resulting MD5 hash
     67     * @param version[out] version passed to CCacheLoader, used if code change should force
     68     *        cache invalidation
     69     */
     70    void PrepareCacheKey(MD5& hash, u32& version);
     71
    4872    CColladaManagerImpl* m;
     73    PIVFS m_VFS;
    4974};
    5075
    5176#endif // INCLUDED_COLLADAMANAGER
  • source/graphics/GameView.cpp

     
    4141#include "maths/Matrix3D.h"
    4242#include "maths/Quaternion.h"
    4343#include "ps/ConfigDB.h"
     44#include "ps/Filesystem.h"
    4445#include "ps/Game.h"
    4546#include "ps/Globals.h"
    4647#include "ps/Hotkey.h"
     
    152153public:
    153154    CGameViewImpl(CGame* game)
    154155        : Game(game),
    155         ColladaManager(), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager),
     156        ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager),
    156157        ObjectManager(MeshManager, SkeletonAnimManager, *game->GetSimulation2()),
    157158        LOSTexture(*game->GetSimulation2()),
    158159        TerritoryTexture(*game->GetSimulation2()),
  • source/graphics/MeshManager.cpp

     
    4949
    5050    PROFILE("load mesh");
    5151
    52     VfsPath pmdFilename = m_ColladaManager.GetLoadableFilename(name, CColladaManager::PMD);
     52    VfsPath pmdFilename = m_ColladaManager.GetLoadablePath(name, CColladaManager::PMD);
    5353
    5454    if (pmdFilename.empty())
    5555    {
  • source/graphics/SkeletonAnimManager.cpp

     
    6161    CSkeletonAnimDef* def = NULL;
    6262
    6363    // Find the file to load
    64     VfsPath psaFilename = m_ColladaManager.GetLoadableFilename(name, CColladaManager::PSA);
     64    VfsPath psaFilename = m_ColladaManager.GetLoadablePath(name, CColladaManager::PSA);
    6565
    6666    if (psaFilename.empty())
    6767    {
  • source/graphics/tests/test_MeshManager.h

     
    1 /* Copyright (C) 2009 Wildfire Games.
     1/* Copyright (C) 2012 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    102102    void setUp()
    103103    {
    104104        initVfs();
    105         colladaManager = new CColladaManager();
     105        colladaManager = new CColladaManager(g_VFS);
    106106        meshManager = new CMeshManager(*colladaManager);
    107107    }
    108108
     
    166166        copyFile(srcDAE, testDAE);
    167167        copyFile(srcSkeletonDefs, testSkeletonDefs);
    168168
    169         VfsPath daeName1 = colladaManager->GetLoadableFilename(testBase, CColladaManager::PMD);
    170         VfsPath daeName2 = colladaManager->GetLoadableFilename(testBase, CColladaManager::PMD);
     169        VfsPath daeName1 = colladaManager->GetLoadablePath(testBase, CColladaManager::PMD);
     170        VfsPath daeName2 = colladaManager->GetLoadablePath(testBase, CColladaManager::PMD);
    171171        TS_ASSERT(!daeName1.empty());
    172172        TS_ASSERT_PATH_EQUALS(daeName1, daeName2);
    173173        // TODO: it'd be nice to test that it really isn't doing the DAE->PMD
  • source/ps/ArchiveBuilder.cpp

     
    2020#include "ArchiveBuilder.h"
    2121
    2222#include "graphics/TextureManager.h"
     23#include "graphics/ColladaManager.h"
    2324#include "lib/tex/tex_codec.h"
    2425#include "lib/file/archive/archive_zip.h"
    2526#include "lib/file/vfs/vfs_util.h"
     
    4142
    4243    DeleteDirectory(m_TempDir/"_archivecache"); // clean up in case the last run failed
    4344
    44     m_VFS->Mount(L"cache/", m_TempDir/"_archivecache/");
     45    m_VFS->Mount(L"cache/", m_TempDir/"_archivecache"/"");
    4546
    4647    m_VFS->Mount(L"", mod/"", VFS_MOUNT_MUST_EXIST | VFS_MOUNT_KEEP_DELETED);
    4748
     
    7677
    7778    // Use CTextureManager instead of CTextureConverter directly,
    7879    // so it can deal with all the loading of settings.xml files
    79     CTextureManager texman(m_VFS, true, true);
     80    CTextureManager textureManager(m_VFS, true, true);
    8081
     82    CColladaManager colladaManager(m_VFS);
     83
    8184    CXeromyces xero;
    8285
    8386    for (size_t i = 0; i < m_Files.size(); ++i)
     
    99102        {
    100103            VfsPath cachedPath;
    101104            debug_printf(L"Converting texture %ls\n", realPath.string().c_str());
    102             bool ok = texman.GenerateCachedTexture(path, cachedPath);
     105            bool ok = textureManager.GenerateCachedTexture(path, cachedPath);
    103106            ENSURE(ok);
    104107
    105108            OsPath cachedRealPath;
     
    113116            continue;
    114117        }
    115118
    116         // TODO: should cache DAE->PMD and DAE->PSA conversions too
     119        // Cache PMD conversions of DAE models
     120        if (boost::algorithm::starts_with(path.string(), L"art/meshes/") &&
     121            path.Extension() == L".dae"
     122        )
     123        {
     124            VfsPath cachedPath;
     125            debug_printf(L"Converting model %ls\n", realPath.string().c_str());
     126            bool ok = colladaManager.GenerateCachedFile(path, CColladaManager::PMD, cachedPath);
     127           
     128            // The DAE might fail to convert for whatever reason, and in that case
     129            //  it can't be used in the game, so we just exclude it
     130            //  (alternatively we could throw release blocking errors on useless files)
     131            if (ok)
     132            {
     133                OsPath cachedRealPath;
     134                ret = m_VFS->GetRealPath(VfsPath("cache")/cachedPath, cachedRealPath);
     135                ENSURE(ret == INFO::OK);
    117136
     137                writer->AddFile(cachedRealPath, cachedPath);
     138            }
     139
     140            // We don't want to store the original file too (since it's a
     141            // large waste of space), so skip to the next file
     142            continue;
     143        }
     144
     145        // Cache PSA conversions of DAE animations
     146        if (boost::algorithm::starts_with(path.string(), L"art/animation/") &&
     147            path.Extension() == L".dae"
     148        )
     149        {
     150            VfsPath cachedPath;
     151            debug_printf(L"Converting animation %ls\n", realPath.string().c_str());
     152            bool ok = colladaManager.GenerateCachedFile(path, CColladaManager::PSA, cachedPath);
     153           
     154            // The DAE might fail to convert for whatever reason, and in that case
     155            //  it can't be used in the game, so we just exclude it
     156            //  (alternatively we could throw release blocking errors on useless files)
     157            if (ok)
     158            {
     159                OsPath cachedRealPath;
     160                ret = m_VFS->GetRealPath(VfsPath("cache")/cachedPath, cachedRealPath);
     161                ENSURE(ret == INFO::OK);
     162
     163                writer->AddFile(cachedRealPath, cachedPath);
     164            }
     165
     166            // We don't want to store the original file too (since it's a
     167            // large waste of space), so skip to the next file
     168            continue;
     169        }
     170
    118171        debug_printf(L"Adding %ls\n", realPath.string().c_str());
    119172        writer->AddFile(realPath, path);
    120173
  • source/tools/atlas/GameInterface/ActorViewer.cpp

     
    3737#include "graphics/UnitManager.h"
    3838#include "graphics/Overlay.h"
    3939#include "maths/MathUtil.h"
     40#include "ps/Filesystem.h"
    4041#include "ps/Font.h"
    4142#include "ps/CLogger.h"
    4243#include "ps/GameSetup/Config.h"
     
    6162    ActorViewerImpl() :
    6263        Entity(INVALID_ENTITY),
    6364        Terrain(),
    64         ColladaManager(),
     65        ColladaManager(g_VFS),
    6566        MeshManager(ColladaManager),
    6667        SkeletonAnimManager(ColladaManager),
    6768        UnitManager(),