- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files
- Directories
- Examples

# PtexExtractor/ptex/PtexCache.cpp

    /*
    PTEX SOFTWARE
    Copyright 2009 Disney Enterprises, Inc.  All rights reserved

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:

      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.

      * Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in
        the documentation and/or other materials provided with the
        distribution.

      * The names "Disney", "Walt Disney Pictures", "Walt Disney Animation
        Studios" or the names of its contributors may NOT be used to
        endorse or promote products derived from this software without
        specific prior written permission from Walt Disney Pictures.

    Disclaimer: THIS SOFTWARE IS PROVIDED BY WALT DISNEY PICTURES AND
    CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
    BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
    FOR A PARTICULAR PURPOSE, NONINFRINGEMENT AND TITLE ARE DISCLAIMED.
    IN NO EVENT SHALL WALT DISNEY PICTURES, THE COPYRIGHT HOLDER OR
    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND BASED ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
    */

    #include "PtexPlatform.h"
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdlib.h>
    #include <iostream>
    #include <limits.h>
    #include "Ptexture.h"
    #include "PtexReader.h"
    #include "PtexCache.h"

    #ifdef GATHER_STATS
    namespace PtexInternal {
        CacheStats::~CacheStats() {
        if (getenv("PTEX_STATS"))
            print();
        }

        void CacheStats::print()
        {
        if (nfilesOpened || ndataAllocated || nblocksRead) {
            printf("Ptex Stats:\n");
            printf("  nfilesOpened:   %6d\n", nfilesOpened);
            printf("  nfilesClosed:   %6d\n", nfilesClosed);
            printf("  ndataAllocated: %6d\n", ndataAllocated);
            printf("  ndataFreed:     %6d\n", ndataFreed);
            printf("  nblocksRead:    %6d\n", nblocksRead);
            printf("  nseeks:         %6d\n", nseeks);
            if (nblocksRead)
            printf("  avgReadSize:    %6d\n", int(nbytesRead/nblocksRead));
            if (nseeks)
            printf("  avgSeqReadSize: %6d\n", int(nbytesRead/nseeks));
            printf("  MbytesRead:     %6.2f\n", nbytesRead/(1024.0*1024.0));
        }
        }

        CacheStats stats;
    }
    #endif

    PtexCacheImpl::~PtexCacheImpl()
    {
        // explicitly pop all unused items so that they are
        // destroyed while cache is still valid
        AutoLockCache locker(cachelock);
        while (_unusedData.pop());
        while (_unusedFiles.pop());
    }

    void PtexCacheImpl::setFileInUse(PtexLruItem* file)
    {
        assert(cachelock.locked());
        _unusedFiles.extract(file);
        _unusedFileCount--;
    }

    void PtexCacheImpl::setFileUnused(PtexLruItem* file)
    {
        assert(cachelock.locked());
        _unusedFiles.push(file);
        _unusedFileCount++;
    }

    void PtexCacheImpl::removeFile()
    {
        // cachelock should be locked, but might not be if cache is being deleted
        _unusedFileCount--;
        STATS_INC(nfilesClosed);
    }

    void PtexCacheImpl::setDataInUse(PtexLruItem* data, int size)
    {
        assert(cachelock.locked());
        _unusedData.extract(data);
        _unusedDataCount--;
        _unusedDataSize -= size;
    }

    void PtexCacheImpl::setDataUnused(PtexLruItem* data, int size)
    {
        assert(cachelock.locked());
        _unusedData.push(data);
        _unusedDataCount++;
        _unusedDataSize += size;
    }

    void PtexCacheImpl::removeData(int size) {
        // cachelock should be locked, but might not be if cache is being deleted
        _unusedDataCount--;
        _unusedDataSize -= size;
        STATS_INC(ndataFreed);
    }

    class PtexReaderCache : public PtexCacheImpl
    {
    public:
        PtexReaderCache(int maxFiles, int maxMem, bool premultiply, PtexInputHandler* handler)
        : PtexCacheImpl(maxFiles, maxMem),
          _io(handler), _cleanupCount(0), _premultiply(premultiply)
        {}

        ~PtexReaderCache()
        {
        // orphan all files since we're about to delete the file table
        // and we don't want the base dtor to try to access it
        purgeAll();
        }

        virtual void setSearchPath(const char* path)
        {
        // get the open lock since the path is used during open operations
        AutoMutex locker(openlock);

        // record path
        _searchpath = path ? path : "";

        // split into dirs
        _searchdirs.clear();
        char* buff = strdup(path);
        char* pos = 0;
        char* token = strtok_r(buff, ":", &pos);
        while (token) {
            if (token[0]) _searchdirs.push_back(token);
            token = strtok_r(0, ":", &pos);
        }
        free(buff);
        }

        virtual const char* getSearchPath()
        {
        // get the open lock since the path is used during open operations
        AutoMutex locker(openlock);
        return _searchpath.c_str();
        }

        virtual PtexTexture* get(const char* path, Ptex::String& error);

        virtual void purge(PtexTexture* texture)
        {
        PtexReader* reader = dynamic_cast<PtexReader*>(texture);
        if (!reader) return;
        purge(reader->path());
        }

        virtual void purge(const char* filename)
        {
        AutoLockCache locker(cachelock);
        FileMap::iterator iter = _files.find(filename);
        if (iter != _files.end()) {
            PtexReader* reader = iter->second;
            if (reader && intptr_t(reader) != -1) {
            reader->orphan();
            iter->second = 0;
            }
            _files.erase(iter);
        }
        }

        virtual void purgeAll()
        {
        AutoLockCache locker(cachelock);
        FileMap::iterator iter = _files.begin();
        while (iter != _files.end()) {
            PtexReader* reader = iter->second;
            if (reader && intptr_t(reader) != -1) {
            reader->orphan();
            iter->second = 0;
            }
            iter = _files.erase(iter);
        }
        }

        void removeBlankEntries()
        {
        // remove blank file entries to keep map size in check
        for (FileMap::iterator i = _files.begin(); i != _files.end();) {
            if (i->second == 0) i = _files.erase(i);
            else i++;
        }
        }

    private:
        PtexInputHandler* _io;
        std::string _searchpath;
        std::vector<std::string> _searchdirs;
        typedef PtexDict<PtexReader*> FileMap;
        FileMap _files;
        int _cleanupCount;
        bool _premultiply;
    };

    PtexTexture* PtexReaderCache::get(const char* filename, Ptex::String& error)
    {
        AutoLockCache locker(cachelock);

        // lookup reader in map
        PtexReader* reader = _files[filename];
        if (reader) {
        // -1 means previous open attempt failed
        if (intptr_t(reader) == -1) return 0;
        reader->ref();
        return reader;
        }
        else {
        bool ok = true;

        // get open lock and make sure we still need to open
        // temporarily release cache lock while we open acquire open lock
        cachelock.unlock();
        AutoMutex openlocker(openlock);
        cachelock.lock();

        // lookup entry again (it might have changed in another thread)
        PtexReader** entry = &_files[filename];

        if (*entry) {
            // another thread opened it while we were waiting
            if (intptr_t(*entry) == -1) return 0;
            (*entry)->ref();
            return *entry;
        }

        // make a new reader
        reader = new PtexReader((void**)entry, this, _premultiply, _io);

        // temporarily release cache lock while we open the file
        cachelock.unlock();
        if (!_io) {
            char tmppath[PATH_MAX+1];
            if (filename[0] != '/' && !_searchdirs.empty()) {
            // file is relative, search in searchpath
            bool found = false;
            struct stat statbuf;
            for (size_t i = 0, size = _searchdirs.size(); i < size; i++) {
                snprintf(tmppath, sizeof(tmppath), "%s/%s", _searchdirs[i].c_str(), filename);
                if (stat(tmppath, &statbuf) == 0) {
                found = true;
                filename = tmppath;
                break;
                }
            }
            if (!found) {
                std::string errstr = "Can't find ptex file: ";
                errstr += filename;
                error = errstr.c_str();
                ok = false;
            }
            }
        }
        if (ok) ok = reader->open(filename, error);

        // reacquire cache lock
        cachelock.lock();

        if (!ok) {
            // open failed, clear parent ptr and unref to delete
            *entry = reader; // to pass parent check in orphan()
            reader->orphan();
            reader->unref();
            *entry = (PtexReader*)-1; // flag for future lookups
            return 0;
        }

        // successful open, record in _files map entry
        *entry = reader;

        // clean up unused files
        purgeFiles();

        // Cleanup map every so often so it doesn't get HUGE
        // from being filled with blank entries from dead files.
        // Note: this must be done while we still have the open lock!
        if (++_cleanupCount >= 1000) {
            _cleanupCount = 0;
            removeBlankEntries();
        }
        }
        return reader;
    }

    PtexCache* PtexCache::create(int maxFiles, int maxMem, bool premultiply,
                     PtexInputHandler* handler)
    {
        // set default files to 100
        if (maxFiles <= 0) maxFiles = 100;

        // set default memory to 100 MB
        const int MB = 1024*1024;
        if (maxMem <= 0) maxMem = 100 * MB;

        // if memory is < 1 MB, warn
        if (maxMem < 1 * MB) {
        std::cerr << "Warning, PtexCache created with < 1 MB" << std::endl;
        }

        return new PtexReaderCache(maxFiles, maxMem, premultiply, handler);
    }