ScummVM API documentation
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
resource_cache.h
1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 //=============================================================================
23 //
24 // ResourceCache is an abstract storage that tracks use history with MRU list.
25 // Cache is limited to a certain size, in bytes.
26 // When a total size of items reaches the limit, and more items are put into,
27 // the Cache uses MRU list to find the least used items and disposes them
28 // one by one until the necessary space is freed.
29 // ResourceCache's implementations must provide a method for calculating an
30 // item's size.
31 //
32 // Supports copyable and movable items, have 2 variants of Put function for
33 // each of them. This lets it store both std::shared_ptr and std::unique_ptr.
34 //
35 // TODO: support data Priority, which tells which items may be disposed
36 // when adding new item and surpassing the cache limit.
37 //
38 // TODO: as an option, consider to have Locked items separate from the normal
39 // cache limit, and probably have their own limit setting as a safety measure.
40 // (after reaching this limit ResourceCache would simply ignore any further
41 // Lock commands until some items are unlocked.)
42 // Rethink this when it's time to design a better resource handling in AGS.
43 //
44 // TODO: as an option, consider supporting a specialized container type that
45 // has an associative container's interface, but is optimized for having most
46 // keys allocated in large continious sequences by default.
47 // This may be suitable for e.g. sprites, and may (or may not?) save some mem.
48 //
49 // Only for the reference: one of the ideas is for container to have a table
50 // of arrays of fixed size internally. When getting an item the hash would be
51 // first divided on array size to find the array the item resides in, then the
52 // item is taken from item from slot index = (hash - arrsize * arrindex).
53 // Find out if there is already a hash table kind that follows similar
54 // principle.
55 //
56 //=============================================================================
57 
58 #ifndef AGS_SHARED_UTIL_RESOURCE_CACHE_H
59 #define AGS_SHARED_UTIL_RESOURCE_CACHE_H
60 
61 #include "common/std/list.h"
62 #include "common/std/map.h"
63 #include "ags/shared/util/string.h"
64 
65 namespace AGS3 {
66 namespace AGS {
67 namespace Shared {
68 
69 template<typename TKey, typename TValue,
70  typename TSize = size_t, typename HashFn = std::hash<TKey> >
72 public:
73  // Flags determine management rules for the particular item
74  enum ItemFlags {
75  // Locked items are temporarily saved from disposal when freeing cache space;
76  // they still count towards the cache size though.
77  kCacheItem_Locked = 0x0001,
78  // External items are managed strictly by the external user;
79  // do not count towards the cache size, do not prevent caching normal items.
80  // They cannot be locked or released (considered permanently locked),
81  // only removed by request.
82  kCacheItem_External = 0x0002,
83  };
84 
85  ResourceCache(TSize max_size = 0u)
86  : _maxSize(max_size), _sectionLocked(_mru.end()) {}
87 
88  // Get the MRU cache size limit
89  inline size_t GetMaxCacheSize() const { return _maxSize; }
90  // Get the current total MRU cache size
91  inline size_t GetCacheSize() const { return _cacheSize; }
92  // Get the summed size of locked items (included in total cache size)
93  inline size_t GetLockedSize() const { return _lockedSize; }
94  // Get the summed size of external items (excluded from total cache size)
95  inline size_t GetExternalSize() const { return _externalSize; }
96 
97  // Set the MRU cache size limit
98  void SetMaxCacheSize(TSize size) {
99  _maxSize = size;
100  FreeMem(0u); // makes sure it does not exceed max size
101  }
102 
103  // Tells if particular key is in the cache
104  bool Exists(const TKey &key) const {
105  return _storage.find(key) != _storage.end();
106  }
107 
108  // Gets the item with the given key if it exists;
109  // reorders the item as recently used.
110  const TValue &Get(const TKey &key) {
111  auto it = _storage.find(key);
112  if (it == _storage.end())
113  return _dummy; // no such key
114 
115  // Unless locked, move the item ref to the beginning of the MRU list
116  const auto &item = it->second;
117  if ((item.Flags & kCacheItem_Locked) == 0)
118  _mru.splice(_mru.begin(), _mru, item.MruIt);
119  return item.Value;
120  }
121 
122  // Add particular item into the cache, disposes existing item if such key is already taken.
123  // If a new item will exceed the cache size limit, cache will remove oldest items
124  // in order to free mem.
125  void Put(const TKey &key, const TValue &value, uint32_t flags = 0u) {
126  if (_maxSize == 0)
127  return; // cache is disabled
128  auto it = _storage.find(key);
129  if (it != _storage.end()) {
130  // Remove previous cached item
131  RemoveImpl(it);
132  }
133  PutImpl(key, TValue(value), flags); // make a temp local copy for safe std::move
134  }
135 
136  void Put(const TKey &key, TValue &&value, uint32_t flags = 0u) {
137  if (_maxSize == 0)
138  return; // cache is disabled
139  auto it = _storage.find(key);
140  if (it != _storage.end()) {
141  // Remove previous cached item
142  RemoveImpl(it);
143  }
144  PutImpl(key, std::move(value), flags);
145  }
146 
147  // Locks the item with the given key,
148  // temporarily excluding it from MRU disposal rules
149  void Lock(const TKey &key) {
150  auto it = _storage.find(key);
151  if (it == _storage.end())
152  return; // no such key
153  auto &item = it->second;
154  if ((item.Flags & kCacheItem_Locked) != 0)
155  return; // already locked
156 
157  // Lock item and move to the locked section
158  item.Flags |= kCacheItem_Locked;
159  _mru.splice(_sectionLocked, _mru, item.MruIt); // CHECKME: TEST!!
160  _sectionLocked = item.MruIt;
161  _lockedSize += item.Size;
162  }
163 
164  // Releases (unlocks) the item with the given key,
165  // adds it back to MRU disposal rules
166  void Release(const TKey &key) {
167  auto it = _storage.find(key);
168  if (it == _storage.end())
169  return; // no such key
170 
171  auto &item = it->second;
172  if ((item.Flags & kCacheItem_External) != 0)
173  return; // never release external data, must be removed by user
174  if ((item.Flags & kCacheItem_Locked) == 0)
175  return; // not locked
176 
177  // Unlock, and move the item to the beginning of the MRU list
178  item.Flags &= ~kCacheItem_Locked;
179  if (_sectionLocked == item.MruIt)
180  _sectionLocked = std::next(item.MruIt);
181  _mru.splice(_mru.begin(), _mru, item.MruIt); // CHECKME: TEST!!
182  _lockedSize -= item.Size;
183  }
184 
185  // Deletes the cached item
186  void Dispose(const TKey &key) {
187  auto it = _storage.find(key);
188  if (it == _storage.end())
189  return; // no such key
190  RemoveImpl(it);
191  }
192 
193  // Removes the item from the cache and returns to the caller.
194  TValue Remove(const TKey &key) {
195  auto it = _storage.find(key);
196  if (it == _storage.end())
197  return TValue(); // no such key
198  TValue value = std::move(it->second.Value);
199  RemoveImpl(it);
200  return value;
201  }
202 
203  // Disposes all items that are not locked or external
204  void DisposeFreeItems() {
205  for (auto mru_it = _sectionLocked; mru_it != _mru.end(); ++mru_it) {
206  auto it = _storage.find(*mru_it);
207  assert(it != _storage.end());
208  auto &item = it->second;
209  _cacheSize -= item.Size;
210  _storage.erase(it);
211  _mru.erase(mru_it);
212  }
213  }
214 
215  // Clear the cache, dispose all items
216  void Clear() {
217  _storage.clear();
218  _mru.clear();
219  _sectionLocked = _mru.end();
220  _cacheSize = 0u;
221  _lockedSize = 0u;
222  _externalSize = 0u;
223  }
224 
225 protected:
226  struct TItem;
227  // MRU list type
228  typedef std::list<TKey> TMruList;
229  // MRU list reference type
230  typedef typename TMruList::iterator TMruIt;
231  // Storage type
233 
234  struct TItem {
235  TMruIt MruIt; // MRU list reference
236  TValue Value;
237  TSize Size = 0u;
238  uint32_t Flags = 0u; // flags determine management rules for this item
239 
240  TItem() = default;
241  TItem(const TItem &item) = default;
242  TItem(TItem &&item) = default;
243  TItem(const TMruIt &mru_it, const TValue &value, const TSize size, uint32_t flags)
244  : MruIt(mru_it), Value(value), Size(size), Flags(flags) {}
245  TItem(const TMruIt &mru_it, TValue &&value, const TSize size, uint32_t flags)
246  : MruIt(mru_it), Value(std::move(value)), Size(size), Flags(flags) {}
247  TItem &operator=(const TItem &item) = default;
248  TItem &operator=(TItem &&item) = default;
249  };
250 
251  // Calculates item size; expects to return 0 if an item is invalid
252  // and should not be added to the cache.
253  virtual TSize CalcSize(const TValue &item) = 0;
254 
255 private:
256  // Add particular item into the cache.
257  // If a new item will exceed the cache size limit, cache will remove oldest items
258  // in order to free mem.
259  void PutImpl(const TKey &key, TValue &&value, uint32_t flags) {
260  // Request item's size, and test if it's a valid item
261  TSize size = CalcSize(value);
262  if (size == 0u)
263  return; // invalid item
264 
265  if ((flags & kCacheItem_External) == 0) {
266  // clear up space before adding
267  if (_cacheSize + size > _maxSize)
268  FreeMem(size);
269  _cacheSize += size;
270  } else {
271  // always mark external data as locked, easier to handle
272  flags |= kCacheItem_Locked;
273  _externalSize += size;
274  }
275 
276  // Prepare a MRU slot, then add an item
277  TMruIt mru_it = _mru.end();
278  // only normal items are added to MRU at all
279  if ((flags & kCacheItem_External) == 0) {
280  if ((flags & kCacheItem_Locked) == 0) {
281  // normal item, add to the list
282  mru_it = _mru.insert(_mru.begin(), key);
283  } else {
284  // locked item, add to the dedicated list section
285  mru_it = _mru.insert(_sectionLocked, key);
286  _sectionLocked = mru_it;
287  _lockedSize += size;
288  }
289  }
290  TItem item = TItem(mru_it, std::move(value), size, flags);
291  _storage[key] = std::move(item);
292  }
293  // Removes the item from the container
294  void RemoveImpl(typename TStorage::iterator it) {
295  auto &item = it->second;
296  // normal items are removed from MRU, and discounted from cache size
297  if ((item.Flags & kCacheItem_External) == 0) {
298  TMruIt mru_it = item.MruIt;
299  if (_sectionLocked == mru_it)
300  _sectionLocked = std::next(mru_it);
301  _cacheSize -= item.Size;
302  if ((item.Flags & kCacheItem_Locked) != 0)
303  _lockedSize -= item.Size;
304  _mru.erase(mru_it);
305  } else {
306  _externalSize -= item.Size;
307  }
308  _storage.erase(it);
309  }
310  // Remove the oldest (least recently used) item in cache
311  void DisposeOldest() {
312  assert(_mru.begin() != _sectionLocked);
313  if (_mru.begin() == _sectionLocked)
314  return;
315  // Remove from the storage and mru list
316  auto mru_it = std::prev(_sectionLocked);
317  auto it = _storage.find(*mru_it);
318  assert(it != _storage.end());
319  auto &item = it->second;
320  assert((item.Flags & (kCacheItem_Locked | kCacheItem_External)) == 0);
321  _cacheSize -= item.Size;
322  _storage.erase(it);
323  _mru.erase(mru_it);
324  }
325  // Keep disposing oldest elements until cache has at least the given free space
326  void FreeMem(size_t space) {
327  // TODO: consider sprite cache's behavior where it would just clear
328  // whole cache in case disposing one by one were taking too much iterations
329  while ((_mru.begin() != _sectionLocked) && (_cacheSize + space > _maxSize)) {
330  DisposeOldest();
331  }
332  }
333 
334  // Size of tracked data stored in this cache;
335  // note that this is an abstract value, which may or not refer to an
336  // actual size in bytes, and depends on the implementation.
337  TSize _cacheSize = 0u;
338  // Size of data locked (forbidden from disposal),
339  // this size is *included* in _cacheSize; provided for stats.
340  TSize _lockedSize = 0u;
341  // Size of the external data, that is - data that does not count towards
342  // cache limit, and which is not our reponsibility; provided for stats.
343  TSize _externalSize = 0u;
344  // Maximal size of tracked data.
345  // When the inserted item increases the cache size past this limit,
346  // the cache will try to free the space by removing oldest items.
347  // "External" data does not count towards this limit.
348  TSize _maxSize = 0u;
349  // MRU list: the way to track which items were used recently.
350  // When clearing up space for new items, cache first deletes the items
351  // that were last time used long ago.
352  TMruList _mru;
353  // A locked section border iterator, points to the *last* locked item
354  // starting from the end of the list, or equals _mru.end() if there's none.
355  TMruIt _sectionLocked;
356  // Key-to-mru lookup map
357  TStorage _storage;
358  // Dummy value, return in case of a missing key
359  TValue _dummy;
360 };
361 
362 } // namespace Shared
363 } // namespace AGS
364 } // namespace AGS3
365 
366 #endif // AGS_SHARED_UTIL_RESOURCE_CACHE_H
Definition: achievements_tables.h:27
void clear(bool shrinkArray=0)
Definition: hashmap.h:427
Definition: lobject.h:73
Definition: resource_cache.h:234
Definition: lobject.h:323
Definition: lobject.h:59
iterator end()
Definition: list.h:279
iterator begin()
Definition: list.h:266
Definition: resource_cache.h:71
void clear()
Definition: list.h:245
Out move(In first, In last, Out dst)
Definition: algorithm.h:109
Definition: geometry.h:148
void erase(iterator entry)
Definition: hashmap.h:710
Definition: list_intern.h:54
iterator erase(iterator pos)
Definition: list.h:112
Definition: ags.h:40