Aurore
A Switch-like interface for your app, on every screen
Loading...
Searching...
No Matches
cache_helper.hpp
Go to the documentation of this file.
1/*
2 Copyright 2022 xfangfang
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15*/
16
17#pragma once
18
19#include <functional>
20#include <list>
21#include <stdexcept>
22#include <unordered_map>
23
25
26namespace aurore
27{
28
29template <typename K, typename T>
30struct Node
31{
32 K key;
34
36 bool dirty = false;
37
39 size_t count = 1;
40
42 size_t bytes = 0;
43
44 Node(K k, T v)
45 : key(k)
46 , value(v)
47 {
48 }
49};
50
56template <typename K, typename T>
58{
59 public:
60 typedef typename std::list<Node<K, T>>::iterator CacheIter;
61#define DIRTY "_$dirty$"
62 inline static size_t DEFAULT_CAPACITY = 400;
63 inline static bool ALWAYS_CACHE_LOCAL_FILE = true;
64
65 LRUCache(size_t c, T defaultValue)
66 : capacity(c)
67 , defaultValue(defaultValue)
68 {
69 if (c < 1)
70 throw std::logic_error("Cache capacity cannot less than 1.");
71 }
72
73 T get(K key)
74 {
75 if (!isCacheHit(key))
76 {
77 // Cache not hit
78 return defaultValue;
79 }
80
81 // Cache hit
82 cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);
83 cacheMap[key] = cacheList.begin();
84 valueMap[cacheMap[key]->value] = cacheList.begin();
85 cacheMap[key]->count++;
86 return cacheMap[key]->value;
87 }
88
89 void set(K key, T value)
90 {
91 if (isCacheHit(key))
92 {
93 throw std::logic_error("Can not cache the same key twice.");
94 }
95
96 // Check capacity limits
97 if (cacheList.size() >= capacity)
98 {
99 deleteCache(cacheList.size() - capacity);
100 }
101 // Add new cache
102 cacheList.push_front(Node<K, T>(key, value));
103 if (sizeOf)
104 {
105 cacheList.front().bytes = sizeOf(value);
106 totalBytes += cacheList.front().bytes;
107 }
108
109 // Update the values of two maps
110 cacheMap[key] = cacheList.begin();
111 valueMap[cacheMap[key]->value] = cacheList.begin();
112
113 trimToBudget();
114 }
115
121 void setMemoryBudget(size_t bytes, std::function<size_t(T)> sizeOf)
122 {
123 if (sizeOf && !this->sizeOf)
124 for (auto& i : cacheList)
125 {
126 i.bytes = sizeOf(i.value);
127 totalBytes += i.bytes;
128 }
129 this->budget = bytes;
130 this->sizeOf = sizeOf;
131 trimToBudget();
132 }
133
134 size_t getTotalBytes() const { return totalBytes; }
135 size_t getSize() const { return cacheList.size(); }
136
140 void remove(T value)
141 {
142 if (!isExisted(value))
143 {
144 return;
145 }
146 // Released more than taken: kept at zero (an unsigned count wrapped
147 // to its maximum and the entry could never be freed)
148 if (valueMap[value]->count > 0)
149 valueMap[value]->count--;
150 // Nothing holds it any more: freed now if over the budget
151 if (valueMap[value]->count == 0)
152 trimToBudget();
153 }
154
158 void update(T old_val, T new_val)
159 {
160 if (!isExisted(old_val))
161 {
162 return;
163 }
164 valueMap[old_val]->value = new_val;
165 valueMap[new_val] = valueMap[old_val];
166 valueMap.erase(old_val);
167 if (sizeOf)
168 {
169 auto& node = *valueMap[new_val];
170 totalBytes -= node.bytes;
171 node.bytes = sizeOf(new_val);
172 totalBytes += node.bytes;
173 }
174 }
175
176 void setCapacity(int c)
177 {
178 if (c < 1)
179 throw std::logic_error("Cache capacity cannot less than 1.");
180 // The setting standard of the DEFAULT_CAPACITY should be the upper limit of the number of all pictures.
181 c += DEFAULT_CAPACITY;
182 this->capacity = c;
183 if (cacheList.size() > capacity)
184 {
185 deleteCache(cacheList.size() - capacity);
186 }
187 }
188
193 void markDirty(T value)
194 {
195 if (!isExisted(value))
196 {
197 return;
198 }
199
200 auto item = valueMap[value];
201 if (item->dirty)
202 return;
203
204 // modify existing Key.
205 cacheMap.erase(item->key);
206 item->key += DIRTY;
207 item->dirty = true;
208 }
209
211 {
212 for (auto& i : cacheList)
213 {
214 if (i.dirty)
215 continue;
216 cacheMap.erase(i.key);
217 i.key += DIRTY;
218 i.dirty = true;
219 }
220 }
221
227 {
229 for (auto i = cacheList.begin(); i != cacheList.end();)
230 {
231 if (i->count <= 0)
232 {
233 nvgDeleteImage(vg, i->value);
234 cacheMap.erase(i->key);
235 valueMap.erase(i->value);
236 totalBytes -= i->bytes;
237 i = cacheList.erase(i);
238 }
239 else
240 {
241 ++i;
242 }
243 }
244 }
245
246 std::list<Node<K, T>>& getCacheList() { return cacheList; }
247
248 void debug()
249 {
250 printf("===== cache size: %zu =====\n", cacheList.size());
251 for (auto& i : cacheList)
252 {
253 printf("count: %zu, dirty: %d, value: %zu, key: %s\n", i.count,
254 i.dirty, i.value, i.key.c_str());
255 }
256 }
257
258 private:
259 size_t capacity = 1;
260 size_t budget = 0;
261 size_t totalBytes = 0;
262 std::function<size_t(T)> sizeOf;
263 T defaultValue;
264
267 void trimToBudget()
268 {
269 if (budget == 0 || totalBytes <= budget || cacheList.size() < 2)
270 return;
272 auto i = cacheList.end();
273 while (totalBytes > budget && i != std::next(cacheList.begin()))
274 {
275 --i;
276 if (i->count <= 0)
277 {
278 nvgDeleteImage(vg, i->value);
279 cacheMap.erase(i->key);
280 valueMap.erase(i->value);
281 totalBytes -= i->bytes;
282 i = cacheList.erase(i);
283 }
284 }
285 }
286 std::list<Node<K, T>> cacheList;
287 std::unordered_map<K, CacheIter> cacheMap;
288 std::unordered_map<T, CacheIter> valueMap;
289
296 size_t deleteCache(size_t num)
297 {
298 // todo: The current data structure will increase the complexity of each
299 // new cache from O(1) to O(N) after the cache is full.
300 if (num <= 0)
301 return 0;
302 // From the least recently used. (A reverse iterator erased through
303 // its base was invalidated, and the next entry was skipped.)
305 auto i = cacheList.end();
306 while (num > 0 && i != cacheList.begin())
307 {
308 --i;
309 if (i->count <= 0)
310 {
311 num--;
312 nvgDeleteImage(vg, i->value);
313 cacheMap.erase(i->key);
314 valueMap.erase(i->value);
315 totalBytes -= i->bytes;
316 i = cacheList.erase(i);
317 }
318 }
319 return num;
320 }
321
322 bool isExisted(K key) { return cacheMap.find(key) != cacheMap.end(); }
323
324 bool isExisted(T val) { return valueMap.find(val) != valueMap.end(); }
325
326 bool isCacheHit(K key)
327 {
328 if (isExisted(key))
329 return !cacheMap[key]->dirty;
330 return false;
331 }
332};
333
334class TextureCache : public Singleton<TextureCache>
335{
336 public:
338 {
340 [this]()
341 { this->cache.markAllDirty(); });
342
344 { this->clean(); });
345 }
346
347 int getCache(const std::string& key) { return cache.get(key); }
348
352 void addCache(const std::string& key, size_t texture)
353 {
354 if (texture <= 0)
355 return;
356 cache.set(key, texture);
357 }
358
363 void removeCache(size_t texture)
364 {
365 if (texture <= 0)
366 return;
367 cache.remove(texture);
368 }
369
370 void markDirty(size_t texture)
371 {
372 if (texture <= 0)
373 return;
374 cache.markDirty(texture);
375 }
376
380 void updateCache(size_t old_tex, size_t new_tex)
381 {
382 if (old_tex <= 0 || new_tex <= 0)
383 return;
384 cache.update(old_tex, new_tex);
385 }
386
392 void setMemoryBudget(size_t bytes)
393 {
394 cache.setMemoryBudget(bytes, [](size_t texture) -> size_t
395 {
396 int w = 0, h = 0;
397 nvgImageSize(aurore::Application::getNVGContext(), (int)texture, &w, &h);
398 return (size_t)w * (size_t)h * 4; });
399 }
400
401 size_t getTotalBytes() const { return cache.getTotalBytes(); }
402 size_t getSize() const { return cache.getSize(); }
403
404 void clean()
405 {
407 for (auto& i : cache.getCacheList())
408 {
409 nvgDeleteImage(vg, i.value);
410 }
411 }
412
418
419 void debug() { cache.debug(); }
420
422};
423
424}
#define DIRTY
Definition cache_helper.hpp:61
static VoidEvent * getWindowSizeChangedEvent()
Fired when the window's size changed.
static NVGcontext * getNVGContext()
The nanovg context every view draws with.
static VoidEvent * getExitEvent()
Fired when the app starts to exit, before the activities are deleted.
Subscription subscribe(Callback cb)
Definition event.hpp:81
Definition cache_helper.hpp:58
T get(K key)
Definition cache_helper.hpp:73
std::list< Node< K, T > > & getCacheList()
Definition cache_helper.hpp:246
void update(T old_val, T new_val)
Definition cache_helper.hpp:158
size_t getSize() const
Definition cache_helper.hpp:135
void markDirty(T value)
Definition cache_helper.hpp:193
void debug()
Definition cache_helper.hpp:248
void setMemoryBudget(size_t bytes, std::function< size_t(T)> sizeOf)
Definition cache_helper.hpp:121
static bool ALWAYS_CACHE_LOCAL_FILE
Definition cache_helper.hpp:63
void remove(T value)
Definition cache_helper.hpp:140
LRUCache(size_t c, T defaultValue)
Definition cache_helper.hpp:65
size_t getTotalBytes() const
Definition cache_helper.hpp:134
std::list< Node< K, T > >::iterator CacheIter
Definition cache_helper.hpp:60
void trimUnused()
Definition cache_helper.hpp:226
void setCapacity(int c)
Definition cache_helper.hpp:176
void markAllDirty()
Definition cache_helper.hpp:210
static size_t DEFAULT_CAPACITY
Definition cache_helper.hpp:62
void set(K key, T value)
Definition cache_helper.hpp:89
Definition singleton.hpp:24
Definition cache_helper.hpp:335
size_t getSize() const
Definition cache_helper.hpp:402
size_t getTotalBytes() const
Definition cache_helper.hpp:401
TextureCache()
Definition cache_helper.hpp:337
int getCache(const std::string &key)
Definition cache_helper.hpp:347
LRUCache< std::string, size_t > cache
Definition cache_helper.hpp:421
void updateCache(size_t old_tex, size_t new_tex)
Definition cache_helper.hpp:380
void trimUnused()
Definition cache_helper.hpp:417
void removeCache(size_t texture)
Definition cache_helper.hpp:363
void debug()
Definition cache_helper.hpp:419
void addCache(const std::string &key, size_t texture)
Definition cache_helper.hpp:352
void setMemoryBudget(size_t bytes)
Definition cache_helper.hpp:392
void clean()
Definition cache_helper.hpp:404
void markDirty(size_t texture)
Definition cache_helper.hpp:370
Definition actions.hpp:25
aurore::Box * key(const char *letter, float size=24)
Definition cache_helper.hpp:31
size_t count
Reference count, 1 for each cache hit.
Definition cache_helper.hpp:39
bool dirty
Cache entries marked as dirty will be refreshed when fetching the cache next time.
Definition cache_helper.hpp:36
T value
Definition cache_helper.hpp:33
size_t bytes
What the entry weighs (bytes), for a budget in bytes.
Definition cache_helper.hpp:42
K key
Definition cache_helper.hpp:32
Node(K k, T v)
Definition cache_helper.hpp:44