code for DEV-52939: viewer's object geometry cache files are not limited in number, and can also be incorrectly cleared with the VFS cache.
reviewed by andrew.master
parent
2969599880
commit
e29f811d56
|
|
@ -1147,6 +1147,17 @@
|
|||
<key>Value</key>
|
||||
<string />
|
||||
</map>
|
||||
<key>CacheNumberOfRegionsForObjects</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Controls number of regions to be cached for objects, ranges from 16 to 128.</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>U32</string>
|
||||
<key>Value</key>
|
||||
<integer>128</integer>
|
||||
</map>
|
||||
<key>CacheSize</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
|
|||
|
|
@ -3025,14 +3025,6 @@ void LLAppViewer::migrateCacheDirectory()
|
|||
#endif // LL_WINDOWS || LL_DARWIN
|
||||
}
|
||||
|
||||
//static
|
||||
S32 LLAppViewer::getCacheVersion()
|
||||
{
|
||||
static const S32 cache_version = 7;
|
||||
|
||||
return cache_version ;
|
||||
}
|
||||
|
||||
void dumpVFSCaches()
|
||||
{
|
||||
llinfos << "======= Static VFS ========" << llendl;
|
||||
|
|
@ -3071,23 +3063,40 @@ void dumpVFSCaches()
|
|||
SetCurrentDirectory(w_str);
|
||||
#endif
|
||||
}
|
||||
|
||||
//static
|
||||
U32 LLAppViewer::getTextureCacheVersion()
|
||||
{
|
||||
//viewer texture cache version, change if the texture cache format changes.
|
||||
const U32 TEXTURE_CACHE_VERSION = 7;
|
||||
|
||||
return TEXTURE_CACHE_VERSION ;
|
||||
}
|
||||
|
||||
//static
|
||||
U32 LLAppViewer::getObjectCacheVersion()
|
||||
{
|
||||
// Viewer object cache version, change if object update
|
||||
// format changes. JC
|
||||
const U32 INDRA_OBJECT_CACHE_VERSION = 14;
|
||||
|
||||
return INDRA_OBJECT_CACHE_VERSION;
|
||||
}
|
||||
|
||||
bool LLAppViewer::initCache()
|
||||
{
|
||||
mPurgeCache = false;
|
||||
BOOL disable_texture_cache = FALSE ;
|
||||
BOOL read_only = mSecondInstance ? TRUE : FALSE;
|
||||
LLAppViewer::getTextureCache()->setReadOnly(read_only) ;
|
||||
LLVOCache::getInstance()->setReadOnly(read_only);
|
||||
|
||||
if (gSavedSettings.getS32("LocalCacheVersion") != LLAppViewer::getCacheVersion())
|
||||
BOOL texture_cache_mismatch = FALSE ;
|
||||
if (gSavedSettings.getS32("LocalCacheVersion") != LLAppViewer::getTextureCacheVersion())
|
||||
{
|
||||
if(read_only)
|
||||
texture_cache_mismatch = TRUE ;
|
||||
if(!read_only)
|
||||
{
|
||||
disable_texture_cache = TRUE ; //if the cache version of this viewer is different from the running one, this viewer can not use the texture cache.
|
||||
}
|
||||
else
|
||||
{
|
||||
mPurgeCache = true; // Purge cache if the version number is different.
|
||||
gSavedSettings.setS32("LocalCacheVersion", LLAppViewer::getCacheVersion());
|
||||
gSavedSettings.setS32("LocalCacheVersion", LLAppViewer::getTextureCacheVersion());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3138,9 +3147,11 @@ bool LLAppViewer::initCache()
|
|||
const S64 MAX_CACHE_SIZE = 1024*MB;
|
||||
cache_size = llmin(cache_size, MAX_CACHE_SIZE);
|
||||
S64 texture_cache_size = ((cache_size * 8)/10);
|
||||
S64 extra = LLAppViewer::getTextureCache()->initCache(LL_PATH_CACHE, texture_cache_size, disable_texture_cache);
|
||||
S64 extra = LLAppViewer::getTextureCache()->initCache(LL_PATH_CACHE, texture_cache_size, texture_cache_mismatch);
|
||||
texture_cache_size -= extra;
|
||||
|
||||
LLVOCache::getInstance()->initCache(LL_PATH_CACHE, gSavedSettings.getU32("CacheNumberOfRegionsForObjects"), getObjectCacheVersion()) ;
|
||||
|
||||
LLSplashScreen::update(LLTrans::getString("StartupInitializingVFS"));
|
||||
|
||||
// Init the VFS
|
||||
|
|
@ -3303,6 +3314,7 @@ void LLAppViewer::purgeCache()
|
|||
{
|
||||
LL_INFOS("AppCache") << "Purging Cache and Texture Cache..." << llendl;
|
||||
LLAppViewer::getTextureCache()->purgeCache(LL_PATH_CACHE);
|
||||
LLVOCache::getInstance()->removeCache(LL_PATH_CACHE);
|
||||
std::string mask = gDirUtilp->getDirDelimiter() + "*.*";
|
||||
gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""),mask);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ public:
|
|||
static LLImageDecodeThread* getImageDecodeThread() { return sImageDecodeThread; }
|
||||
static LLTextureFetch* getTextureFetch() { return sTextureFetch; }
|
||||
|
||||
static S32 getCacheVersion() ;
|
||||
static U32 getTextureCacheVersion() ;
|
||||
static U32 getObjectCacheVersion() ;
|
||||
|
||||
const std::string& getSerialNumber() { return mSerialNumber; }
|
||||
|
||||
|
|
|
|||
|
|
@ -927,7 +927,7 @@ void LLTextureCache::setReadOnly(BOOL read_only)
|
|||
}
|
||||
|
||||
//called in the main thread.
|
||||
S64 LLTextureCache::initCache(ELLPath location, S64 max_size, BOOL disable_texture_cache)
|
||||
S64 LLTextureCache::initCache(ELLPath location, S64 max_size, BOOL texture_cache_mismatch)
|
||||
{
|
||||
llassert_always(getPending() == 0) ; //should not start accessing the texture cache before initialized.
|
||||
|
||||
|
|
@ -942,20 +942,23 @@ S64 LLTextureCache::initCache(ELLPath location, S64 max_size, BOOL disable_textu
|
|||
sCacheMaxTexturesSize = max_size;
|
||||
max_size -= sCacheMaxTexturesSize;
|
||||
|
||||
if(disable_texture_cache) //the texture cache is disabled
|
||||
{
|
||||
llinfos << "The texture cache is disabled!" << llendl ;
|
||||
setReadOnly(TRUE) ;
|
||||
purgeAllTextures(true);
|
||||
|
||||
return max_size ;
|
||||
}
|
||||
|
||||
LL_INFOS("TextureCache") << "Headers: " << sCacheMaxEntries
|
||||
<< " Textures size: " << sCacheMaxTexturesSize/(1024*1024) << " MB" << LL_ENDL;
|
||||
|
||||
setDirNames(location);
|
||||
|
||||
if(texture_cache_mismatch)
|
||||
{
|
||||
//if readonly, disable the texture cache,
|
||||
//otherwise wipe out the texture cache.
|
||||
purgeAllTextures(true);
|
||||
|
||||
if(mReadOnly)
|
||||
{
|
||||
return max_size ;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mReadOnly)
|
||||
{
|
||||
LLFile::mkdir(mTexturesDirName);
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public:
|
|||
|
||||
void purgeCache(ELLPath location);
|
||||
void setReadOnly(BOOL read_only) ;
|
||||
S64 initCache(ELLPath location, S64 maxsize, BOOL disable_texture_cache);
|
||||
S64 initCache(ELLPath location, S64 maxsize, BOOL texture_cache_mismatch);
|
||||
|
||||
handle_t readFromCache(const std::string& local_filename, const LLUUID& id, U32 priority, S32 offset, S32 size,
|
||||
ReadResponder* responder);
|
||||
|
|
|
|||
|
|
@ -69,13 +69,6 @@
|
|||
#pragma warning(disable:4355)
|
||||
#endif
|
||||
|
||||
// Viewer object cache version, change if object update
|
||||
// format changes. JC
|
||||
const U32 INDRA_OBJECT_CACHE_VERSION = 14;
|
||||
|
||||
// Format string used to construct filename for the object cache
|
||||
static const char OBJECT_CACHE_FILENAME[] = "objects_%d_%d.slc";
|
||||
|
||||
extern BOOL gNoRender;
|
||||
|
||||
const F32 WATER_TEXTURE_SCALE = 8.f; // Number of times to repeat the water texture across a region
|
||||
|
|
@ -214,7 +207,7 @@ LLViewerRegion::LLViewerRegion(const U64 &handle,
|
|||
mProductName("unknown"),
|
||||
mHttpUrl(""),
|
||||
mCacheLoaded(FALSE),
|
||||
mCacheEntriesCount(0),
|
||||
mCacheDirty(FALSE),
|
||||
mCacheID(),
|
||||
mEventPoll(NULL),
|
||||
mReleaseNotesRequested(FALSE),
|
||||
|
|
@ -264,8 +257,6 @@ LLViewerRegion::LLViewerRegion(const U64 &handle,
|
|||
// Create the object lists
|
||||
initStats();
|
||||
|
||||
mCacheStart.append(mCacheEnd);
|
||||
|
||||
//create object partitions
|
||||
//MUST MATCH declaration of eObjectPartitions
|
||||
mObjectPartition.push_back(new LLHUDPartition()); //PARTITION_HUD
|
||||
|
|
@ -324,19 +315,6 @@ LLViewerRegion::~LLViewerRegion()
|
|||
std::for_each(mObjectPartition.begin(), mObjectPartition.end(), DeletePointer());
|
||||
}
|
||||
|
||||
|
||||
const std::string LLViewerRegion::getObjectCacheFilename(U64 mHandle) const
|
||||
{
|
||||
std::string filename;
|
||||
U32 region_x, region_y;
|
||||
|
||||
grid_from_region_handle(mHandle, ®ion_x, ®ion_y);
|
||||
filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,
|
||||
llformat(OBJECT_CACHE_FILENAME, region_x, region_y));
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
void LLViewerRegion::loadObjectCache()
|
||||
{
|
||||
if (mCacheLoaded)
|
||||
|
|
@ -347,77 +325,10 @@ void LLViewerRegion::loadObjectCache()
|
|||
// Presume success. If it fails, we don't want to try again.
|
||||
mCacheLoaded = TRUE;
|
||||
|
||||
LLVOCacheEntry *entry;
|
||||
|
||||
std::string filename = getObjectCacheFilename(mHandle);
|
||||
LL_DEBUGS("ObjectCache") << filename << LL_ENDL;
|
||||
|
||||
LLFILE* fp = LLFile::fopen(filename, "rb"); /* Flawfinder: ignore */
|
||||
if (!fp)
|
||||
if(LLVOCache::hasInstance())
|
||||
{
|
||||
// might not have a file, which is normal
|
||||
return;
|
||||
LLVOCache::getInstance()->readFromCache(mHandle, mCacheID, mCacheMap) ;
|
||||
}
|
||||
|
||||
U32 zero;
|
||||
size_t nread;
|
||||
nread = fread(&zero, sizeof(U32), 1, fp);
|
||||
if (nread != 1 || zero)
|
||||
{
|
||||
// a non-zero value here means bad things!
|
||||
// skip reading the cached values
|
||||
llinfos << "Cache file invalid" << llendl;
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
|
||||
U32 version;
|
||||
nread = fread(&version, sizeof(U32), 1, fp);
|
||||
if (nread != 1 || version != INDRA_OBJECT_CACHE_VERSION)
|
||||
{
|
||||
// a version mismatch here means we've changed the binary format!
|
||||
// skip reading the cached values
|
||||
llinfos << "Cache version changed, discarding" << llendl;
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
|
||||
LLUUID cache_id;
|
||||
nread = fread(&cache_id.mData, 1, UUID_BYTES, fp);
|
||||
if (nread != (size_t)UUID_BYTES || mCacheID != cache_id)
|
||||
{
|
||||
llinfos << "Cache ID doesn't match for this region, discarding"
|
||||
<< llendl;
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
|
||||
S32 num_entries;
|
||||
nread = fread(&num_entries, sizeof(S32), 1, fp);
|
||||
if (nread != 1)
|
||||
{
|
||||
llinfos << "Short read, discarding" << llendl;
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
|
||||
S32 i;
|
||||
for (i = 0; i < num_entries; i++)
|
||||
{
|
||||
entry = new LLVOCacheEntry(fp);
|
||||
if (!entry->getLocalID())
|
||||
{
|
||||
llwarns << "Aborting cache file load for " << filename << ", cache file corruption!" << llendl;
|
||||
delete entry;
|
||||
entry = NULL;
|
||||
break;
|
||||
}
|
||||
mCacheEnd.insert(*entry);
|
||||
mCacheMap[entry->getLocalID()] = entry;
|
||||
mCacheEntriesCount++;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -428,61 +339,22 @@ void LLViewerRegion::saveObjectCache()
|
|||
return;
|
||||
}
|
||||
|
||||
S32 num_entries = mCacheEntriesCount;
|
||||
if (0 == num_entries)
|
||||
if (mCacheMap.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string filename = getObjectCacheFilename(mHandle);
|
||||
LL_DEBUGS("ObjectCache") << filename << LL_ENDL;
|
||||
|
||||
LLFILE* fp = LLFile::fopen(filename, "wb"); /* Flawfinder: ignore */
|
||||
if (!fp)
|
||||
if(LLVOCache::hasInstance())
|
||||
{
|
||||
llwarns << "Unable to write cache file " << filename << llendl;
|
||||
return;
|
||||
LLVOCache::getInstance()->writeToCache(mHandle, mCacheID, mCacheMap, mCacheDirty) ;
|
||||
mCacheDirty = FALSE;
|
||||
}
|
||||
|
||||
// write out zero to indicate a version cache file
|
||||
U32 zero = 0;
|
||||
if (fwrite(&zero, sizeof(U32), 1, fp) != 1)
|
||||
for(LLVOCacheEntry::vocache_entry_map_t::iterator iter = mCacheMap.begin(); iter != mCacheMap.end(); ++iter)
|
||||
{
|
||||
llwarns << "Short write" << llendl;
|
||||
delete iter->second;
|
||||
}
|
||||
|
||||
// write out version number
|
||||
U32 version = INDRA_OBJECT_CACHE_VERSION;
|
||||
if (fwrite(&version, sizeof(U32), 1, fp) != 1)
|
||||
{
|
||||
llwarns << "Short write" << llendl;
|
||||
}
|
||||
|
||||
// write the cache id for this sim
|
||||
if (fwrite(&mCacheID.mData, 1, UUID_BYTES, fp) != (size_t)UUID_BYTES)
|
||||
{
|
||||
llwarns << "Short write" << llendl;
|
||||
}
|
||||
|
||||
if (fwrite(&num_entries, sizeof(S32), 1, fp) != 1)
|
||||
{
|
||||
llwarns << "Short write" << llendl;
|
||||
}
|
||||
|
||||
LLVOCacheEntry *entry;
|
||||
|
||||
for (entry = mCacheStart.getNext(); entry && (entry != &mCacheEnd); entry = entry->getNext())
|
||||
{
|
||||
entry->writeToFile(fp);
|
||||
}
|
||||
|
||||
mCacheMap.clear();
|
||||
mCacheEnd.unlink();
|
||||
mCacheEnd.init();
|
||||
mCacheStart.deleteAll();
|
||||
mCacheStart.init();
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void LLViewerRegion::sendMessage()
|
||||
|
|
@ -1175,7 +1047,6 @@ void LLViewerRegion::cacheFullUpdate(LLViewerObject* objectp, LLDataPackerBinary
|
|||
mCacheMap.erase(local_id);
|
||||
delete entry;
|
||||
entry = new LLVOCacheEntry(local_id, crc, dp);
|
||||
mCacheEnd.insert(*entry);
|
||||
mCacheMap[local_id] = entry;
|
||||
}
|
||||
}
|
||||
|
|
@ -1184,18 +1055,13 @@ void LLViewerRegion::cacheFullUpdate(LLViewerObject* objectp, LLDataPackerBinary
|
|||
// we haven't seen this object before
|
||||
|
||||
// Create new entry and add to map
|
||||
if (mCacheEntriesCount > MAX_OBJECT_CACHE_ENTRIES)
|
||||
if (mCacheMap.size() > MAX_OBJECT_CACHE_ENTRIES)
|
||||
{
|
||||
entry = mCacheStart.getNext();
|
||||
mCacheMap.erase(entry->getLocalID());
|
||||
delete entry;
|
||||
mCacheEntriesCount--;
|
||||
mCacheMap.erase(mCacheMap.begin());
|
||||
}
|
||||
entry = new LLVOCacheEntry(local_id, crc, dp);
|
||||
|
||||
mCacheEnd.insert(*entry);
|
||||
mCacheMap[local_id] = entry;
|
||||
mCacheEntriesCount++;
|
||||
}
|
||||
return ;
|
||||
}
|
||||
|
|
@ -1310,6 +1176,7 @@ void LLViewerRegion::requestCacheMisses()
|
|||
mCacheMissFull.reset();
|
||||
mCacheMissCRC.reset();
|
||||
|
||||
mCacheDirty = TRUE ;
|
||||
// llinfos << "KILLDEBUG Sent cache miss full " << full_count << " crc " << crc_count << llendl;
|
||||
}
|
||||
|
||||
|
|
@ -1327,9 +1194,10 @@ void LLViewerRegion::dumpCache()
|
|||
}
|
||||
|
||||
LLVOCacheEntry *entry;
|
||||
|
||||
for (entry = mCacheStart.getNext(); entry && (entry != &mCacheEnd); entry = entry->getNext())
|
||||
for(LLVOCacheEntry::vocache_entry_map_t::iterator iter = mCacheMap.begin(); iter != mCacheMap.end(); ++iter)
|
||||
{
|
||||
entry = iter->second ;
|
||||
|
||||
S32 hits = entry->getHitCount();
|
||||
S32 changes = entry->getCRCChangeCount();
|
||||
|
||||
|
|
@ -1340,7 +1208,7 @@ void LLViewerRegion::dumpCache()
|
|||
change_bin[changes]++;
|
||||
}
|
||||
|
||||
llinfos << "Count " << mCacheEntriesCount << llendl;
|
||||
llinfos << "Count " << mCacheMap.size() << llendl;
|
||||
for (i = 0; i < BINS; i++)
|
||||
{
|
||||
llinfos << "Hits " << i << " " << hit_bin[i] << llendl;
|
||||
|
|
|
|||
|
|
@ -323,9 +323,6 @@ public:
|
|||
LLDynamicArray<LLUUID> mMapAvatarIDs;
|
||||
|
||||
private:
|
||||
// determine the cache filename for the region from the region handle
|
||||
const std::string getObjectCacheFilename(U64 mHandle) const;
|
||||
|
||||
// The surfaces and other layers
|
||||
LLSurface* mLandp;
|
||||
|
||||
|
|
@ -387,11 +384,8 @@ private:
|
|||
// Regions can have order 10,000 objects, so assume
|
||||
// a structure of size 2^14 = 16,000
|
||||
BOOL mCacheLoaded;
|
||||
typedef std::map<U32, LLVOCacheEntry *> cache_map_t;
|
||||
cache_map_t mCacheMap;
|
||||
LLVOCacheEntry mCacheStart;
|
||||
LLVOCacheEntry mCacheEnd;
|
||||
U32 mCacheEntriesCount;
|
||||
BOOL mCacheDirty;
|
||||
LLVOCacheEntry::vocache_entry_map_t mCacheMap;
|
||||
LLDynamicArray<U32> mCacheMissFull;
|
||||
LLDynamicArray<U32> mCacheMissCRC;
|
||||
// time?
|
||||
|
|
|
|||
|
|
@ -25,10 +25,19 @@
|
|||
*/
|
||||
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
|
||||
#include "llvocache.h"
|
||||
|
||||
#include "llerror.h"
|
||||
#include "llregionhandle.h"
|
||||
|
||||
BOOL check_read(LLAPRFile* apr_file, void* src, S32 n_bytes)
|
||||
{
|
||||
return apr_file->read(src, n_bytes) == n_bytes ;
|
||||
}
|
||||
|
||||
BOOL check_write(LLAPRFile* apr_file, void* src, S32 n_bytes)
|
||||
{
|
||||
return apr_file->write(src, n_bytes) == n_bytes ;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// LLVOCacheEntry
|
||||
|
|
@ -57,26 +66,31 @@ LLVOCacheEntry::LLVOCacheEntry()
|
|||
mDP.assignBuffer(mBuffer, 0);
|
||||
}
|
||||
|
||||
|
||||
static inline void checkedRead(LLFILE *fp, void *data, size_t nbytes)
|
||||
LLVOCacheEntry::LLVOCacheEntry(LLAPRFile* apr_file)
|
||||
{
|
||||
if (fread(data, 1, nbytes, fp) != nbytes)
|
||||
{
|
||||
llwarns << "Short read" << llendl;
|
||||
memset(data, 0, nbytes);
|
||||
S32 size = -1;
|
||||
BOOL success;
|
||||
|
||||
success = check_read(apr_file, &mLocalID, sizeof(U32));
|
||||
if(success)
|
||||
{
|
||||
success = check_read(apr_file, &mCRC, sizeof(U32));
|
||||
}
|
||||
if(success)
|
||||
{
|
||||
success = check_read(apr_file, &mHitCount, sizeof(S32));
|
||||
}
|
||||
if(success)
|
||||
{
|
||||
success = check_read(apr_file, &mDupeCount, sizeof(S32));
|
||||
}
|
||||
|
||||
LLVOCacheEntry::LLVOCacheEntry(LLFILE *fp)
|
||||
if(success)
|
||||
{
|
||||
S32 size;
|
||||
checkedRead(fp, &mLocalID, sizeof(U32));
|
||||
checkedRead(fp, &mCRC, sizeof(U32));
|
||||
checkedRead(fp, &mHitCount, sizeof(S32));
|
||||
checkedRead(fp, &mDupeCount, sizeof(S32));
|
||||
checkedRead(fp, &mCRCChangeCount, sizeof(S32));
|
||||
|
||||
checkedRead(fp, &size, sizeof(S32));
|
||||
success = check_read(apr_file, &mCRCChangeCount, sizeof(S32));
|
||||
}
|
||||
if(success)
|
||||
{
|
||||
success = check_read(apr_file, &size, sizeof(S32));
|
||||
|
||||
// Corruption in the cache entries
|
||||
if ((size > 10000) || (size < 1))
|
||||
|
|
@ -90,11 +104,30 @@ LLVOCacheEntry::LLVOCacheEntry(LLFILE *fp)
|
|||
mBuffer = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(success && size > 0)
|
||||
{
|
||||
mBuffer = new U8[size];
|
||||
success = check_read(apr_file, mBuffer, size);
|
||||
|
||||
mBuffer = new U8[size];
|
||||
checkedRead(fp, mBuffer, size);
|
||||
if(success)
|
||||
{
|
||||
mDP.assignBuffer(mBuffer, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete[] mBuffer ;
|
||||
mBuffer = NULL ;
|
||||
}
|
||||
}
|
||||
|
||||
if(!success)
|
||||
{
|
||||
mLocalID = 0;
|
||||
mCRC = 0;
|
||||
mBuffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
LLVOCacheEntry::~LLVOCacheEntry()
|
||||
{
|
||||
|
|
@ -148,22 +181,466 @@ void LLVOCacheEntry::dump() const
|
|||
<< llendl;
|
||||
}
|
||||
|
||||
static inline void checkedWrite(LLFILE *fp, const void *data, size_t nbytes)
|
||||
BOOL LLVOCacheEntry::writeToFile(LLAPRFile* apr_file) const
|
||||
{
|
||||
if (fwrite(data, 1, nbytes, fp) != nbytes)
|
||||
BOOL success;
|
||||
success = check_write(apr_file, (void*)&mLocalID, sizeof(U32));
|
||||
if(success)
|
||||
{
|
||||
llwarns << "Short write" << llendl;
|
||||
success = check_write(apr_file, (void*)&mCRC, sizeof(U32));
|
||||
}
|
||||
if(success)
|
||||
{
|
||||
success = check_write(apr_file, (void*)&mHitCount, sizeof(S32));
|
||||
}
|
||||
if(success)
|
||||
{
|
||||
success = check_write(apr_file, (void*)&mDupeCount, sizeof(S32));
|
||||
}
|
||||
if(success)
|
||||
{
|
||||
success = check_write(apr_file, (void*)&mCRCChangeCount, sizeof(S32));
|
||||
}
|
||||
if(success)
|
||||
{
|
||||
S32 size = mDP.getBufferSize();
|
||||
success = check_write(apr_file, (void*)&size, sizeof(S32));
|
||||
|
||||
if(success)
|
||||
{
|
||||
success = check_write(apr_file, (void*)mBuffer, size);
|
||||
}
|
||||
}
|
||||
|
||||
void LLVOCacheEntry::writeToFile(LLFILE *fp) const
|
||||
{
|
||||
checkedWrite(fp, &mLocalID, sizeof(U32));
|
||||
checkedWrite(fp, &mCRC, sizeof(U32));
|
||||
checkedWrite(fp, &mHitCount, sizeof(S32));
|
||||
checkedWrite(fp, &mDupeCount, sizeof(S32));
|
||||
checkedWrite(fp, &mCRCChangeCount, sizeof(S32));
|
||||
S32 size = mDP.getBufferSize();
|
||||
checkedWrite(fp, &size, sizeof(S32));
|
||||
checkedWrite(fp, mBuffer, size);
|
||||
return success ;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
//LLVOCache
|
||||
//-------------------------------------------------------------------
|
||||
// Format string used to construct filename for the object cache
|
||||
static const char OBJECT_CACHE_FILENAME[] = "objects_%d_%d.slc";
|
||||
|
||||
const U32 MAX_NUM_OBJECT_ENTRIES = 128 ;
|
||||
const U32 NUM_ENTRIES_TO_PURGE = 16 ;
|
||||
const char* object_cache_dirname = "objectcache";
|
||||
const char* header_filename = "object.cache";
|
||||
|
||||
LLVOCache* LLVOCache::sInstance = NULL;
|
||||
|
||||
//static
|
||||
LLVOCache* LLVOCache::getInstance()
|
||||
{
|
||||
if(!sInstance)
|
||||
{
|
||||
sInstance = new LLVOCache() ;
|
||||
}
|
||||
return sInstance ;
|
||||
}
|
||||
|
||||
//static
|
||||
BOOL LLVOCache::hasInstance()
|
||||
{
|
||||
return sInstance != NULL ;
|
||||
}
|
||||
|
||||
//static
|
||||
void LLVOCache::destroyClass()
|
||||
{
|
||||
if(sInstance)
|
||||
{
|
||||
delete sInstance ;
|
||||
sInstance = NULL ;
|
||||
}
|
||||
}
|
||||
|
||||
LLVOCache::LLVOCache():
|
||||
mInitialized(FALSE),
|
||||
mReadOnly(TRUE),
|
||||
mNumEntries(0)
|
||||
{
|
||||
mLocalAPRFilePoolp = new LLVolatileAPRPool() ;
|
||||
}
|
||||
|
||||
LLVOCache::~LLVOCache()
|
||||
{
|
||||
writeCacheHeader();
|
||||
clearCacheInMemory();
|
||||
delete mLocalAPRFilePoolp;
|
||||
}
|
||||
|
||||
void LLVOCache::setDirNames(ELLPath location)
|
||||
{
|
||||
std::string delem = gDirUtilp->getDirDelimiter();
|
||||
|
||||
mHeaderFileName = gDirUtilp->getExpandedFilename(location, object_cache_dirname, header_filename);
|
||||
mObjectCacheDirName = gDirUtilp->getExpandedFilename(location, object_cache_dirname);
|
||||
}
|
||||
|
||||
void LLVOCache::initCache(ELLPath location, U32 size, U32 cache_version)
|
||||
{
|
||||
if(mInitialized)
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
setDirNames(location);
|
||||
if (!mReadOnly)
|
||||
{
|
||||
LLFile::mkdir(mObjectCacheDirName);
|
||||
}
|
||||
mCacheSize = llmin(size, MAX_NUM_OBJECT_ENTRIES) ;
|
||||
mCacheSize = llmax(mCacheSize, NUM_ENTRIES_TO_PURGE);
|
||||
|
||||
mMetaInfo.mVersion = cache_version;
|
||||
readCacheHeader();
|
||||
mInitialized = TRUE ;
|
||||
|
||||
if(mMetaInfo.mVersion != cache_version)
|
||||
{
|
||||
mMetaInfo.mVersion = cache_version ;
|
||||
if(mReadOnly) //disable cache
|
||||
{
|
||||
clearCacheInMemory();
|
||||
}
|
||||
else //delete the current cache if the format does not match.
|
||||
{
|
||||
removeCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLVOCache::removeCache(ELLPath location)
|
||||
{
|
||||
if(mReadOnly)
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
std::string delem = gDirUtilp->getDirDelimiter();
|
||||
std::string mask = delem + "*";
|
||||
std::string cache_dir = gDirUtilp->getExpandedFilename(location, object_cache_dirname);
|
||||
gDirUtilp->deleteFilesInDir(cache_dir, mask); //delete all files
|
||||
LLFile::rmdir(cache_dir);
|
||||
|
||||
clearCacheInMemory();
|
||||
mInitialized = FALSE ;
|
||||
}
|
||||
|
||||
void LLVOCache::removeCache()
|
||||
{
|
||||
llassert_always(mInitialized) ;
|
||||
if(mReadOnly)
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
std::string delem = gDirUtilp->getDirDelimiter();
|
||||
std::string mask = delem + "*";
|
||||
gDirUtilp->deleteFilesInDir(mObjectCacheDirName, mask);
|
||||
|
||||
clearCacheInMemory() ;
|
||||
writeCacheHeader();
|
||||
}
|
||||
|
||||
void LLVOCache::clearCacheInMemory()
|
||||
{
|
||||
if(!mHeaderEntryQueue.empty())
|
||||
{
|
||||
for(header_entry_queue_t::iterator iter = mHeaderEntryQueue.begin(); iter != mHeaderEntryQueue.end(); ++iter)
|
||||
{
|
||||
delete *iter ;
|
||||
}
|
||||
mHeaderEntryQueue.clear();
|
||||
mHandleEntryMap.clear();
|
||||
mNumEntries = 0 ;
|
||||
}
|
||||
}
|
||||
|
||||
void LLVOCache::getObjectCacheFilename(U64 handle, std::string& filename)
|
||||
{
|
||||
U32 region_x, region_y;
|
||||
|
||||
grid_from_region_handle(handle, ®ion_x, ®ion_y);
|
||||
filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, object_cache_dirname,
|
||||
llformat(OBJECT_CACHE_FILENAME, region_x, region_y));
|
||||
|
||||
return ;
|
||||
}
|
||||
|
||||
void LLVOCache::removeFromCache(U64 handle)
|
||||
{
|
||||
if(mReadOnly)
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
std::string filename;
|
||||
getObjectCacheFilename(handle, filename);
|
||||
LLAPRFile::remove(filename, mLocalAPRFilePoolp);
|
||||
}
|
||||
|
||||
BOOL LLVOCache::checkRead(LLAPRFile* apr_file, void* src, S32 n_bytes)
|
||||
{
|
||||
if(!check_read(apr_file, src, n_bytes))
|
||||
{
|
||||
delete apr_file ;
|
||||
removeCache() ;
|
||||
return FALSE ;
|
||||
}
|
||||
|
||||
return TRUE ;
|
||||
}
|
||||
|
||||
BOOL LLVOCache::checkWrite(LLAPRFile* apr_file, void* src, S32 n_bytes)
|
||||
{
|
||||
if(!check_write(apr_file, src, n_bytes))
|
||||
{
|
||||
delete apr_file ;
|
||||
removeCache() ;
|
||||
return FALSE ;
|
||||
}
|
||||
|
||||
return TRUE ;
|
||||
}
|
||||
|
||||
void LLVOCache::readCacheHeader()
|
||||
{
|
||||
//clear stale info.
|
||||
clearCacheInMemory();
|
||||
|
||||
if (LLAPRFile::isExist(mHeaderFileName, mLocalAPRFilePoolp))
|
||||
{
|
||||
LLAPRFile* apr_file = new LLAPRFile(mHeaderFileName, APR_READ|APR_BINARY, mLocalAPRFilePoolp);
|
||||
|
||||
//read the meta element
|
||||
if(!checkRead(apr_file, &mMetaInfo, sizeof(HeaderMetaInfo)))
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
HeaderEntryInfo* entry ;
|
||||
mNumEntries = 0 ;
|
||||
while(mNumEntries < MAX_NUM_OBJECT_ENTRIES)
|
||||
{
|
||||
entry = new HeaderEntryInfo() ;
|
||||
if(!checkRead(apr_file, entry, sizeof(HeaderEntryInfo)))
|
||||
{
|
||||
delete entry ;
|
||||
return ;
|
||||
}
|
||||
else if(!entry->mTime) //end of the cache.
|
||||
{
|
||||
delete entry ;
|
||||
return ;
|
||||
}
|
||||
|
||||
entry->mIndex = mNumEntries++ ;
|
||||
mHeaderEntryQueue.insert(entry) ;
|
||||
mHandleEntryMap[entry->mHandle] = entry ;
|
||||
}
|
||||
|
||||
delete apr_file ;
|
||||
}
|
||||
else
|
||||
{
|
||||
writeCacheHeader() ;
|
||||
}
|
||||
}
|
||||
|
||||
void LLVOCache::writeCacheHeader()
|
||||
{
|
||||
if(mReadOnly)
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
LLAPRFile* apr_file = new LLAPRFile(mHeaderFileName, APR_CREATE|APR_WRITE|APR_BINARY, mLocalAPRFilePoolp);
|
||||
|
||||
//write the meta element
|
||||
if(!checkWrite(apr_file, &mMetaInfo, sizeof(HeaderMetaInfo)))
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
mNumEntries = 0 ;
|
||||
for(header_entry_queue_t::iterator iter = mHeaderEntryQueue.begin() ; iter != mHeaderEntryQueue.end(); ++iter)
|
||||
{
|
||||
(*iter)->mIndex = mNumEntries++ ;
|
||||
if(!checkWrite(apr_file, (void*)*iter, sizeof(HeaderEntryInfo)))
|
||||
{
|
||||
return ;
|
||||
}
|
||||
}
|
||||
|
||||
mNumEntries = mHeaderEntryQueue.size() ;
|
||||
if(mNumEntries < MAX_NUM_OBJECT_ENTRIES)
|
||||
{
|
||||
HeaderEntryInfo* entry = new HeaderEntryInfo() ;
|
||||
for(S32 i = mNumEntries ; i < MAX_NUM_OBJECT_ENTRIES ; i++)
|
||||
{
|
||||
//fill the cache with the default entry.
|
||||
if(!checkWrite(apr_file, entry, sizeof(HeaderEntryInfo)))
|
||||
{
|
||||
mReadOnly = TRUE ; //disable the cache.
|
||||
return ;
|
||||
}
|
||||
}
|
||||
delete entry ;
|
||||
}
|
||||
delete apr_file ;
|
||||
}
|
||||
|
||||
BOOL LLVOCache::updateEntry(const HeaderEntryInfo* entry)
|
||||
{
|
||||
LLAPRFile* apr_file = new LLAPRFile(mHeaderFileName, APR_WRITE|APR_BINARY, mLocalAPRFilePoolp);
|
||||
apr_file->seek(APR_SET, entry->mIndex * sizeof(HeaderEntryInfo) + sizeof(HeaderMetaInfo)) ;
|
||||
|
||||
return checkWrite(apr_file, (void*)entry, sizeof(HeaderEntryInfo)) ;
|
||||
}
|
||||
|
||||
void LLVOCache::readFromCache(U64 handle, const LLUUID& id, LLVOCacheEntry::vocache_entry_map_t& cache_entry_map)
|
||||
{
|
||||
llassert_always(mInitialized);
|
||||
|
||||
handle_entry_map_t::iterator iter = mHandleEntryMap.find(handle) ;
|
||||
if(iter == mHandleEntryMap.end()) //no cache
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
std::string filename;
|
||||
getObjectCacheFilename(handle, filename);
|
||||
LLAPRFile* apr_file = new LLAPRFile(filename, APR_READ|APR_BINARY, mLocalAPRFilePoolp);
|
||||
|
||||
LLUUID cache_id ;
|
||||
if(!checkRead(apr_file, cache_id.mData, UUID_BYTES))
|
||||
{
|
||||
return ;
|
||||
}
|
||||
if(cache_id != id)
|
||||
{
|
||||
llinfos << "Cache ID doesn't match for this region, discarding"<< llendl;
|
||||
|
||||
delete apr_file ;
|
||||
return ;
|
||||
}
|
||||
|
||||
S32 num_entries;
|
||||
if(!checkRead(apr_file, &num_entries, sizeof(S32)))
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
for (S32 i = 0; i < num_entries; i++)
|
||||
{
|
||||
LLVOCacheEntry* entry = new LLVOCacheEntry(apr_file);
|
||||
if (!entry->getLocalID())
|
||||
{
|
||||
llwarns << "Aborting cache file load for " << filename << ", cache file corruption!" << llendl;
|
||||
delete entry ;
|
||||
break;
|
||||
}
|
||||
cache_entry_map[entry->getLocalID()] = entry;
|
||||
}
|
||||
num_entries = cache_entry_map.size() ;
|
||||
|
||||
delete apr_file ;
|
||||
return ;
|
||||
}
|
||||
|
||||
void LLVOCache::purgeEntries()
|
||||
{
|
||||
U32 limit = mCacheSize - NUM_ENTRIES_TO_PURGE ;
|
||||
while(mHeaderEntryQueue.size() > limit)
|
||||
{
|
||||
header_entry_queue_t::iterator iter = mHeaderEntryQueue.begin() ;
|
||||
HeaderEntryInfo* entry = *iter ;
|
||||
|
||||
removeFromCache(entry->mHandle) ;
|
||||
mHandleEntryMap.erase(entry->mHandle) ;
|
||||
mHeaderEntryQueue.erase(iter) ;
|
||||
delete entry ;
|
||||
}
|
||||
|
||||
writeCacheHeader() ;
|
||||
readCacheHeader() ;
|
||||
mNumEntries = mHandleEntryMap.size() ;
|
||||
}
|
||||
|
||||
void LLVOCache::writeToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry::vocache_entry_map_t& cache_entry_map, BOOL dirty_cache)
|
||||
{
|
||||
llassert_always(mInitialized);
|
||||
|
||||
if(mReadOnly)
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
HeaderEntryInfo* entry;
|
||||
handle_entry_map_t::iterator iter = mHandleEntryMap.find(handle) ;
|
||||
if(iter == mHandleEntryMap.end()) //new entry
|
||||
{
|
||||
if(mNumEntries >= mCacheSize)
|
||||
{
|
||||
purgeEntries() ;
|
||||
}
|
||||
|
||||
entry = new HeaderEntryInfo();
|
||||
entry->mHandle = handle ;
|
||||
entry->mTime = time(NULL) ;
|
||||
entry->mIndex = mNumEntries++ ;
|
||||
mHeaderEntryQueue.insert(entry) ;
|
||||
mHandleEntryMap[handle] = entry ;
|
||||
}
|
||||
else
|
||||
{
|
||||
entry = iter->second ;
|
||||
entry->mTime = time(NULL) ;
|
||||
|
||||
//resort
|
||||
mHeaderEntryQueue.erase(entry) ;
|
||||
mHeaderEntryQueue.insert(entry) ;
|
||||
}
|
||||
|
||||
//update cache header
|
||||
if(!updateEntry(entry))
|
||||
{
|
||||
return ; //update failed.
|
||||
}
|
||||
|
||||
if(!dirty_cache)
|
||||
{
|
||||
return ; //nothing changed, no need to update.
|
||||
}
|
||||
|
||||
//write to cache file
|
||||
std::string filename;
|
||||
getObjectCacheFilename(handle, filename);
|
||||
LLAPRFile* apr_file = new LLAPRFile(filename, APR_CREATE|APR_WRITE|APR_BINARY, mLocalAPRFilePoolp);
|
||||
|
||||
if(!checkWrite(apr_file, (void*)id.mData, UUID_BYTES))
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
S32 num_entries = cache_entry_map.size() ;
|
||||
if(!checkWrite(apr_file, &num_entries, sizeof(S32)))
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
for (LLVOCacheEntry::vocache_entry_map_t::const_iterator iter = cache_entry_map.begin(); iter != cache_entry_map.end(); ++iter)
|
||||
{
|
||||
if(!iter->second->writeToFile(apr_file))
|
||||
{
|
||||
//failed
|
||||
delete apr_file ;
|
||||
removeCache() ;
|
||||
return ;
|
||||
}
|
||||
}
|
||||
|
||||
delete apr_file ;
|
||||
return ;
|
||||
}
|
||||
|
|
@ -36,11 +36,11 @@
|
|||
// Cache entries
|
||||
class LLVOCacheEntry;
|
||||
|
||||
class LLVOCacheEntry : public LLDLinked<LLVOCacheEntry>
|
||||
class LLVOCacheEntry
|
||||
{
|
||||
public:
|
||||
LLVOCacheEntry(U32 local_id, U32 crc, LLDataPackerBinaryBuffer &dp);
|
||||
LLVOCacheEntry(LLFILE *fp);
|
||||
LLVOCacheEntry(LLAPRFile* apr_file);
|
||||
LLVOCacheEntry();
|
||||
~LLVOCacheEntry();
|
||||
|
||||
|
|
@ -50,12 +50,15 @@ public:
|
|||
S32 getCRCChangeCount() const { return mCRCChangeCount; }
|
||||
|
||||
void dump() const;
|
||||
void writeToFile(LLFILE *fp) const;
|
||||
BOOL writeToFile(LLAPRFile* apr_file) const;
|
||||
void assignCRC(U32 crc, LLDataPackerBinaryBuffer &dp);
|
||||
LLDataPackerBinaryBuffer *getDP(U32 crc);
|
||||
void recordHit();
|
||||
void recordDupe() { mDupeCount++; }
|
||||
|
||||
public:
|
||||
typedef std::map<U32, LLVOCacheEntry*> vocache_entry_map_t;
|
||||
|
||||
protected:
|
||||
U32 mLocalID;
|
||||
U32 mCRC;
|
||||
|
|
@ -66,4 +69,81 @@ protected:
|
|||
U8 *mBuffer;
|
||||
};
|
||||
|
||||
//
|
||||
//Note: LLVOCache is not thread-safe
|
||||
//
|
||||
class LLVOCache
|
||||
{
|
||||
private:
|
||||
struct HeaderEntryInfo
|
||||
{
|
||||
HeaderEntryInfo() : mIndex(0), mHandle(0), mTime(0) {}
|
||||
S32 mIndex;
|
||||
U64 mHandle ;
|
||||
U32 mTime ;
|
||||
};
|
||||
|
||||
struct HeaderMetaInfo
|
||||
{
|
||||
HeaderMetaInfo() : mVersion(0){}
|
||||
|
||||
U32 mVersion;
|
||||
};
|
||||
|
||||
struct header_entry_less
|
||||
{
|
||||
bool operator()(const HeaderEntryInfo* lhs, const HeaderEntryInfo* rhs) const
|
||||
{
|
||||
return lhs->mTime < rhs->mTime; // older entry in front of queue (set)
|
||||
}
|
||||
};
|
||||
typedef std::set<HeaderEntryInfo*, header_entry_less> header_entry_queue_t;
|
||||
typedef std::map<U64, HeaderEntryInfo*> handle_entry_map_t;
|
||||
private:
|
||||
LLVOCache() ;
|
||||
|
||||
public:
|
||||
~LLVOCache() ;
|
||||
|
||||
void initCache(ELLPath location, U32 size, U32 cache_version) ;
|
||||
void removeCache(ELLPath location) ;
|
||||
|
||||
void readFromCache(U64 handle, const LLUUID& id, LLVOCacheEntry::vocache_entry_map_t& cache_entry_map) ;
|
||||
void writeToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry::vocache_entry_map_t& cache_entry_map, BOOL dirty_cache) ;
|
||||
|
||||
void setReadOnly(BOOL read_only) {mReadOnly = read_only;}
|
||||
|
||||
private:
|
||||
void setDirNames(ELLPath location);
|
||||
// determine the cache filename for the region from the region handle
|
||||
void getObjectCacheFilename(U64 handle, std::string& filename);
|
||||
void removeFromCache(U64 handle);
|
||||
void readCacheHeader();
|
||||
void writeCacheHeader();
|
||||
void clearCacheInMemory();
|
||||
void removeCache() ;
|
||||
void purgeEntries();
|
||||
BOOL updateEntry(const HeaderEntryInfo* entry);
|
||||
BOOL checkRead(LLAPRFile* apr_file, void* src, S32 n_bytes) ;
|
||||
BOOL checkWrite(LLAPRFile* apr_file, void* src, S32 n_bytes) ;
|
||||
|
||||
private:
|
||||
BOOL mInitialized ;
|
||||
BOOL mReadOnly ;
|
||||
HeaderMetaInfo mMetaInfo;
|
||||
U32 mCacheSize;
|
||||
U32 mNumEntries;
|
||||
std::string mHeaderFileName ;
|
||||
std::string mObjectCacheDirName;
|
||||
LLVolatileAPRPool* mLocalAPRFilePoolp ;
|
||||
header_entry_queue_t mHeaderEntryQueue;
|
||||
handle_entry_map_t mHandleEntryMap;
|
||||
|
||||
static LLVOCache* sInstance ;
|
||||
public:
|
||||
static LLVOCache* getInstance() ;
|
||||
static BOOL hasInstance() ;
|
||||
static void destroyClass() ;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ void LLWorld::destroyClass()
|
|||
LLViewerRegion* region_to_delete = *region_it++;
|
||||
removeRegion(region_to_delete->getHost());
|
||||
}
|
||||
LLVOCache::getInstance()->destroyClass() ;
|
||||
LLViewerPartSim::getInstance()->destroyClass();
|
||||
}
|
||||
|
||||
|
|
@ -256,6 +257,8 @@ void LLWorld::removeRegion(const LLHost &host)
|
|||
|
||||
llwarns << "Disabling region " << regionp->getName() << " that agent is in!" << llendl;
|
||||
LLAppViewer::instance()->forceDisconnect(LLTrans::getString("YouHaveBeenDisconnected"));
|
||||
|
||||
regionp->saveObjectCache() ; //force to save objects here in case that the object cache is about to be destroyed.
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue