1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
|
// Resource ObjectCache for OpenMW, forked from osgDB ObjectCache by Robert Osfield, see copyright notice below.
// Changes:
// - removeExpiredObjectsInCache no longer keeps a lock while the unref happens.
// - template allows customized KeyType.
// - objects with uninitialized time stamp are not removed.
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* OpenSceneGraph Public License for more details.
*/
#ifndef OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE
#define OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE
#include "cachestats.hpp"
#include <osg/Node>
#include <osg/Referenced>
#include <osg/ref_ptr>
#include <algorithm>
#include <map>
#include <mutex>
#include <optional>
#include <string>
#include <vector>
namespace osg
{
class Object;
class State;
class NodeVisitor;
class Stats;
}
namespace Resource
{
struct GenericObjectCacheItem
{
osg::ref_ptr<osg::Object> mValue;
double mLastUsage;
};
template <typename KeyType>
class GenericObjectCache : public osg::Referenced
{
public:
/*
* @brief Updates usage timestamps and removes expired items
*
* Updates the lastUsage timestamp of cached non-nullptr items that have external references.
* Initializes lastUsage timestamp for new items.
* Removes items that haven't been referenced for longer than expiryDelay.
*
* \note
* Last usage might be updated from other places so nullptr items
* that are not referenced elsewhere are not always removed.
*
* @param referenceTime the timestamp indicating when the item was most recently used
* @param expiryDelay the delay after which the cache entry for an item expires
*/
void update(double referenceTime, double expiryDelay)
{
std::vector<osg::ref_ptr<osg::Object>> objectsToRemove;
{
const double expiryTime = referenceTime - expiryDelay;
std::lock_guard<std::mutex> lock(mMutex);
std::erase_if(mItems, [&](auto& v) {
Item& item = v.second;
// update last usage timestamp if item is being referenced externally
// or initialize if not set
if ((item.mValue != nullptr && item.mValue->referenceCount() > 1) || item.mLastUsage == 0)
item.mLastUsage = referenceTime;
// skip items that have been accessed since expiryTime
if (item.mLastUsage > expiryTime)
return false;
++mExpired;
// just mark for removal here so objects can be removed in bulk outside the lock
if (item.mValue != nullptr)
objectsToRemove.push_back(std::move(item.mValue));
return true;
});
}
// remove expired items from cache
objectsToRemove.clear();
}
/** Remove all objects in the cache regardless of having external references or expiry times.*/
void clear()
{
std::lock_guard<std::mutex> lock(mMutex);
mItems.clear();
}
/** Add a key,object,timestamp triple to the Registry::ObjectCache.*/
template <class K>
void addEntryToObjectCache(K&& key, osg::Object* object, double timestamp = 0.0)
{
std::lock_guard<std::mutex> lock(mMutex);
const auto it = mItems.find(key);
if (it == mItems.end())
mItems.emplace_hint(it, std::forward<K>(key), Item{ object, timestamp });
else
it->second = Item{ object, timestamp };
}
/** Remove Object from cache.*/
void removeFromObjectCache(const auto& key)
{
std::lock_guard<std::mutex> lock(mMutex);
const auto itr = mItems.find(key);
if (itr != mItems.end())
mItems.erase(itr);
}
/** Get an ref_ptr<Object> from the object cache*/
osg::ref_ptr<osg::Object> getRefFromObjectCache(const auto& key)
{
std::lock_guard<std::mutex> lock(mMutex);
if (Item* const item = find(key))
return item->mValue;
return nullptr;
}
std::optional<osg::ref_ptr<osg::Object>> getRefFromObjectCacheOrNone(const auto& key)
{
const std::lock_guard<std::mutex> lock(mMutex);
if (Item* const item = find(key))
return item->mValue;
return std::nullopt;
}
/** Check if an object is in the cache, and if it is, update its usage time stamp. */
bool checkInObjectCache(const auto& key, double timeStamp)
{
std::lock_guard<std::mutex> lock(mMutex);
if (Item* const item = find(key))
{
item->mLastUsage = timeStamp;
return true;
}
return false;
}
/** call releaseGLObjects on all objects attached to the object cache.*/
void releaseGLObjects(osg::State* state)
{
std::lock_guard<std::mutex> lock(mMutex);
for (const auto& [k, v] : mItems)
v.mValue->releaseGLObjects(state);
}
/** call node->accept(nv); for all nodes in the objectCache. */
void accept(osg::NodeVisitor& nv)
{
std::lock_guard<std::mutex> lock(mMutex);
for (const auto& [k, v] : mItems)
if (osg::Object* const object = v.mValue.get())
if (osg::Node* const node = dynamic_cast<osg::Node*>(object))
node->accept(nv);
}
/** call operator()(KeyType, osg::Object*) for each object in the cache. */
template <class Functor>
void call(Functor&& f)
{
std::lock_guard<std::mutex> lock(mMutex);
for (const auto& [k, v] : mItems)
f(k, v.mValue.get());
}
template <class K>
std::optional<std::pair<KeyType, osg::ref_ptr<osg::Object>>> lowerBound(K&& key)
{
const std::lock_guard<std::mutex> lock(mMutex);
const auto it = mItems.lower_bound(std::forward<K>(key));
if (it == mItems.end())
return std::nullopt;
return std::pair(it->first, it->second.mValue);
}
CacheStats getStats() const
{
const std::lock_guard<std::mutex> lock(mMutex);
return CacheStats{
.mSize = mItems.size(),
.mGet = mGet,
.mHit = mHit,
.mExpired = mExpired,
};
}
protected:
using Item = GenericObjectCacheItem;
std::map<KeyType, Item, std::less<>> mItems;
mutable std::mutex mMutex;
std::size_t mGet = 0;
std::size_t mHit = 0;
std::size_t mExpired = 0;
Item* find(const auto& key)
{
++mGet;
const auto it = mItems.find(key);
if (it == mItems.end())
return nullptr;
++mHit;
return &it->second;
}
};
}
#endif
|