/* * Copyright (C) 2007-2010 Xagasoft, All rights reserved. * * This file is part of the libbu++ library and is released under the * terms of the license contained in the file LICENSE. */ #ifndef BU_CACHE_H #define BU_CACHE_H // #include "bu/cptr.h" #include "bu/hash.h" #include "bu/list.h" #include "bu/cachestore.h" #include "bu/cachecalc.h" #include "bu/trace.h" namespace Bu { // template // keytype __cacheGetKey( obtype *&pObj ); template keytype __cacheGetKey( const obtype *pObj ) { return pObj->getKey(); } template class Cache { public: /** * Cache Pointer - Provides access to data that is held within the * cache. This provides safe, refcounting access to data stored in * the cache, with support for lazy loading. */ class Ptr { friend class Bu::Cache; private: Ptr( Cache *pCache, obtype *pData, const keytype &kId ) : pCache( pCache ), pData( pData ), kId( kId ) { if( pCache ) pCache->incRef( kId ); } Ptr( Cache *pCache, const keytype &kId ) : pCache( pCache ), pData( NULL ), kId( kId ) { } public: Ptr( const Ptr &rSrc ) : pCache( rSrc.pCache ), pData( rSrc.pData ), kId( rSrc.kId ) { if( pCache && pData ) pCache->incRef( kId ); } Ptr() : pCache( 0 ), pData( 0 ) { } virtual ~Ptr() { if( pCache && pData ) pCache->decRef( kId ); } obtype &operator*() { checkPtr(); return *pData; } const obtype &operator*() const { checkPtr(); return *pData; } obtype *operator->() { checkPtr(); return pData; } const obtype *operator->() const { checkPtr(); return pData; } bool isValid() const { return pCache != NULL; } bool isBound() const { return pData != NULL; } const keytype &getKey() const { return kId; } void unbind() { if( pCache && pData ) pCache->decRef( kId ); pData = NULL; } void clear() { unbind(); pCache = NULL; } void unset() { clear(); } Ptr &operator=( const Ptr &rRhs ) { if( pCache && pData ) pCache->decRef( kId ); pCache = rRhs.pCache; pData = rRhs.pData; kId = rRhs.kId; if( pCache && pData ) pCache->incRef( kId ); return *this; } bool operator==( const Ptr &rRhs ) const { return pData == rRhs.pData; } bool operator!=( const Ptr &rRhs ) const { return pData != rRhs.pData; } private: void checkPtr() const { if( pCache && !pData ) { pData = pCache->getRaw( kId ); pCache->incRef( kId ); } } private: Bu::Cache *pCache; mutable obtype *pData; mutable keytype kId; }; private: typedef Bu::CacheStore Store; typedef Bu::List StoreList; typedef Bu::CacheCalc Calc; typedef struct CacheEntry { obtype *pData; int iRefs; time_t tLastSync; } CacheEntry; typedef Bu::Hash CidHash; public: typedef keytype Key; Cache( Calc *pCalc, Store *pStore ) : pCalc( pCalc ), pStore( pStore ) { TRACE(); pCalc->setCache( this ); } virtual ~Cache() { TRACE(); // Better safe than sorry, better try a sync before anything // else happens. sync(); // Cycle through and unload all objects from the system. for( typename CidHash::iterator i = hEnt.begin(); i != hEnt.end(); i++ ) { if( i.getValue().iRefs > 0 ) { // TODO: Throw an error in this case? iRefs != 0 for an // object when the Cache is destroyed. throw Bu::ExceptionBase("iRefs not zero."); } pStore->unload( i.getValue().pData, i.getKey() ); } delete pCalc; delete pStore; } Ptr insert( obtype *pData ) { TRACE( pData ); if( pStore->has( __cacheGetKey( pData ) ) ) throw Bu::ExceptionBase("Key already exists in cache."); CacheEntry e = {pData, 0, 0}; keytype k = pStore->create( pData ); hEnt.insert( k, e ); pCalc->onLoad( pData, k ); return Ptr( this, pData, k ); } bool has( const keytype &cId ) { return hEnt.has( cId ) || pStore->has( cId ); } /** * Retrieve an object from the cache and return a pointer to it. * The object returned may be loaded from backend storage if needed, * or the currently live object will be returned. *@param cId The id of the object to load. *@returns A pointer to the object. */ Ptr get( const keytype &cId ) { TRACE( cId ); try { return Ptr( this, hEnt.get( cId ).pData, cId ); } catch( Bu::HashException &e ) { CacheEntry e = {pStore->load( cId ), 0, time( NULL )}; pCalc->onLoad( e.pData, cId ); hEnt.insert( cId, e ); return Ptr( this, e.pData, cId ); } } /** * Retrieve a handle to an object without loading it now. This function * will return a pointer that has not yet been "realized" but can be * used normally. Upon initial use in any way the object will be * loaded from the cache, either linking against the already loaded * object or loading it fresh from the backend storage. The advantage * of this is that you recieve a usable handle to the data, but it * does not count as a reference yet, meaning that the data is loaded * when you need it, not before. */ Ptr getLazy( const keytype &cId ) { TRACE( cId ); return Ptr( this, cId ); } int getRefCount( const keytype &cId ) { TRACE( cId ); return hEnt.get( cId ).iRefs; } void unload( const keytype &cId ) { TRACE( cId ); try { if( hEnt.get( cId ).iRefs > 0 ) { printf("Shouldn't unload, references still exist!\n"); return; } } catch( Bu::HashException &e ) { // It's not here? Eh, return. return; } obtype *pObj = hEnt.get( cId ).pData; pCalc->onUnload( pObj, cId ); hEnt.erase( cId ); // The unload has to happen last just in case cId is a reference // to data that is about to be deleted from memory by the unload. pStore->unload( pObj, cId ); } void erase( const keytype &cId ) { TRACE( cId ); try { if( hEnt.get( cId ).iRefs > 0 ) { printf("Shouldn't erase, references still exist!\n"); return; } obtype *pObj = hEnt.get( cId ).pData; pCalc->onDestroy( pObj, cId ); hEnt.erase( cId ); pStore->destroy( pObj, cId ); } catch( Bu::HashException &e ) { pCalc->onDestroy( cId ); if( hEnt.has( cId ) ) { // The object was loaded by onDestroy erase( cId ); } else { pStore->destroy( cId ); } } } typedef Bu::List KeyList; KeyList getKeys() { return pStore->getKeys(); } KeyList getActiveKeys() { return hEnt.getKeys(); } int getSize() { return pStore->getSize(); } /** * Make sure all currently loaded but not-in-use objects are synced to * the store. */ void sync() { TRACE(); int iSynced = 0; for( typename CidHash::iterator i = hEnt.begin(); i != hEnt.end(); i++ ) { if( i.getValue().iRefs == 0 ) { if( pCalc->shouldSync( i.getValue().pData, i.getKey(), i.getValue().tLastSync ) ) { pStore->sync( i.getValue().pData, i.getKey() ); iSynced++; i.getValue().tLastSync = time( NULL ); } } } if( iSynced > 0 ) { pStore->sync(); } } private: void incRef( const keytype &cId ) { TRACE( cId ); hEnt.get( cId ).iRefs++; } void decRef( const keytype &cId ) { TRACE( cId ); CacheEntry &e = hEnt.get( cId ); e.iRefs--; } obtype *getRaw( const keytype &cId ) { TRACE( cId ); try { return hEnt.get( cId ).pData; } catch( Bu::HashException &e ) { CacheEntry e = {pStore->load( cId ), 0, time( NULL )}; pCalc->onLoad( e.pData, cId ); hEnt.insert( cId, e ); return e.pData; } } private: CidHash hEnt; Calc *pCalc; Store *pStore; }; }; #endif