1 /*
2 * The ManaPlus Client
3 * Copyright (C) 2004-2009 The Mana World Development Team
4 * Copyright (C) 2009-2010 The Mana Developers
5 * Copyright (C) 2011-2019 The ManaPlus Developers
6 * Copyright (C) 2019-2021 Andrei Karas
7 *
8 * This file is part of The ManaPlus Client.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "resources/resourcemanager/resourcemanager.h"
25
26 #ifdef USE_OPENGL
27 #include "resources/image/image.h"
28 #endif // USE_OPENGL
29
30 #include "resources/imageset.h"
31
32 #include "resources/memorymanager.h"
33
34 #include "resources/sprite/spritedef.h"
35
36 #include "utils/cast.h"
37 #include "utils/checkutils.h"
38 #include "utils/foreach.h"
39 #include "utils/stringutils.h"
40
41 #if !defined(DEBUG_DUMP_LEAKS) && !defined(UNITTESTS)
42 #include "resources/resourcetypes.h"
43 #endif // defined(DEBUG_DUMP_LEAKS) || defined(UNITTESTS)
44
45 PRAGMA48(GCC diagnostic push)
46 PRAGMA48(GCC diagnostic ignored "-Wshadow")
47 #ifndef USE_OPENGL
48 #include <SDL_image.h>
49 #endif // USE_OPENGL
50 PRAGMA48(GCC diagnostic pop)
51
52 #include <sstream>
53
54 #include <sys/time.h>
55
56 #include "debug.h"
57
58 namespace ResourceManager
59 {
60
61 std::set<SDL_Surface*> deletedSurfaces;
62 Resources mResources;
63 Resources mOrphanedResources;
64 std::set<Resource*> mDeletedResources;
65 time_t mOldestOrphan = 0;
66 bool mDestruction = false;
67
deleteResourceManager()68 void deleteResourceManager()
69 {
70 mDestruction = true;
71 mResources.insert(mOrphanedResources.begin(), mOrphanedResources.end());
72
73 // Release any remaining spritedefs first because they depend on image sets
74 ResourceIterator iter = mResources.begin();
75
76 #ifdef DEBUG_LEAKS
77 #ifdef UNITTESTS
78 bool status(false);
79 #endif // UNITTESTS
80
81 while (iter != mResources.end())
82 {
83 if (iter->second)
84 {
85 if (iter->second->mRefCount)
86 {
87 logger->log(std::string("ResourceLeak: ").append(
88 iter->second->mIdPath).append(" (").append(
89 toString(iter->second->mRefCount)).append(")"));
90 #ifdef UNITTESTS
91 status = true;
92 #endif // UNITTESTS
93 }
94 }
95 ++iter;
96 }
97
98 #ifdef UNITTESTS
99 if (status)
100 reportAlways("Found leaked resources.")
101 #endif // UNITTESTS
102
103 iter = mResources.begin();
104 #endif // DEBUG_LEAKS
105
106 while (iter != mResources.end())
107 {
108 #ifdef DEBUG_LEAKS
109 if (iter->second && iter->second->mRefCount)
110 {
111 ++iter;
112 continue;
113 }
114 #endif // DEBUG_LEAKS
115
116 if (dynamic_cast<SpriteDef*>(iter->second) != nullptr)
117 {
118 cleanUp(iter->second);
119 const ResourceIterator toErase = iter;
120 ++iter;
121 mResources.erase(toErase);
122 }
123 else
124 {
125 ++iter;
126 }
127 }
128
129 // Release any remaining image sets first because they depend on images
130 iter = mResources.begin();
131 while (iter != mResources.end())
132 {
133 #ifdef DEBUG_LEAKS
134 if (iter->second && iter->second->mRefCount)
135 {
136 ++iter;
137 continue;
138 }
139 #endif // DEBUG_LEAKS
140
141 if (dynamic_cast<ImageSet*>(iter->second) != nullptr)
142 {
143 cleanUp(iter->second);
144 const ResourceIterator toErase = iter;
145 ++iter;
146 mResources.erase(toErase);
147 }
148 else
149 {
150 ++iter;
151 }
152 }
153
154 // Release remaining resources, logging the number of dangling references.
155 iter = mResources.begin();
156 while (iter != mResources.end())
157 {
158 #ifdef DEBUG_LEAKS
159 if (iter->second && iter->second->mRefCount)
160 {
161 ++iter;
162 continue;
163 }
164 #endif // DEBUG_LEAKS
165
166 if (iter->second != nullptr)
167 {
168 cleanUp(iter->second);
169 const ResourceIterator toErase = iter;
170 ++iter;
171 mResources.erase(toErase);
172 }
173 else
174 {
175 ++iter;
176 }
177 }
178 clearDeleted(true);
179 clearScheduled();
180 mDestruction = false;
181 }
182
cleanUp(Resource * const res)183 void cleanUp(Resource *const res)
184 {
185 if (res == nullptr)
186 return;
187
188 const unsigned refCount = res->mRefCount;
189 if (refCount > 0)
190 {
191 logger->log("ResourceManager::~ResourceManager() cleaning up %u "
192 "reference%s to %s",
193 refCount,
194 (refCount == 1) ? "" : "s",
195 res->mIdPath.c_str());
196 }
197
198 delete res;
199 #ifdef DEBUG_LEAKS
200 cleanOrphans(true);
201 #endif // DEBUG_LEAKS
202 }
203
cleanProtected()204 void cleanProtected()
205 {
206 ResourceIterator iter = mResources.begin();
207 while (iter != mResources.end())
208 {
209 Resource *const res = iter->second;
210 if (res == nullptr)
211 {
212 ++ iter;
213 continue;
214 }
215 if (res->mProtected)
216 {
217 res->mProtected = false;
218 res->decRef();
219 iter = mResources.begin();
220 continue;
221 }
222
223 ++ iter;
224 }
225 }
226
cleanOrphans(const bool always)227 bool cleanOrphans(const bool always)
228 {
229 timeval tv;
230 gettimeofday(&tv, nullptr);
231 // Delete orphaned resources after 30 seconds.
232 time_t oldest = static_cast<time_t>(tv.tv_sec);
233 const time_t threshold = oldest - 30;
234
235 if (mOrphanedResources.empty() || (!always && mOldestOrphan >= threshold))
236 return false;
237
238 bool status(false);
239 ResourceIterator iter = mOrphanedResources.begin();
240 while (iter != mOrphanedResources.end())
241 {
242 Resource *const res = iter->second;
243 if (res == nullptr)
244 {
245 ++iter;
246 continue;
247 }
248 const time_t t = res->mTimeStamp;
249 if (!always && t >= threshold)
250 {
251 if (t < oldest)
252 oldest = t;
253 ++ iter;
254 }
255 else
256 {
257 logResource(res);
258 const ResourceIterator toErase = iter;
259 ++iter;
260 mOrphanedResources.erase(toErase);
261 delete res; // delete only after removal from list,
262 // to avoid issues in recursion
263 status = true;
264 }
265 }
266
267 mOldestOrphan = oldest;
268 return status;
269 }
270
logResource(const Resource * const res)271 void logResource(const Resource *const res)
272 {
273 if (res == nullptr)
274 return;
275 #ifdef USE_OPENGL
276 const Image *const image = dynamic_cast<const Image *>(res);
277 if (image != nullptr)
278 {
279 std::string src = image->mSource;
280 const int count = image->mRefCount;
281 if (count != 0)
282 src.append(" ").append(toString(count));
283 logger->log("resource(%s, %u) %s", res->mIdPath.c_str(),
284 image->getGLImage(), src.c_str());
285 }
286 else
287 {
288 std::string src = res->mSource;
289 const int count = res->mRefCount;
290 if (count > 0)
291 src.append(" ").append(toString(count));
292 logger->log("resource(%s) %s", res->mIdPath.c_str(), src.c_str());
293 }
294 #else // USE_OPENGL
295
296 logger->log("resource(%s)", res->mIdPath.c_str());
297 #endif // USE_OPENGL
298 }
299
logResources(const std::string & msg)300 void logResources(const std::string &msg)
301 {
302 logger->log("start of resources: " + msg);
303 logger->log("resources");
304 FOR_EACH(ResourceIterator, it, mResources)
305 {
306 logResource((*it).second);
307 }
308 logger->log("orphaned resources");
309 FOR_EACH(ResourceIterator, it, mOrphanedResources)
310 {
311 logResource((*it).second);
312 }
313 logger->log("deleted resources");
314 FOR_EACH(std::set<Resource*>::iterator, it, mDeletedResources)
315 {
316 logResource(*it);
317 }
318 logger->log("end of resources");
319 }
320
clearDeleted(const bool full)321 void clearDeleted(const bool full)
322 {
323 bool status(true);
324 logger->log1("clear deleted");
325 while (status)
326 {
327 status = false;
328 std::set<Resource*>::iterator resDelIter = mDeletedResources.begin();
329 while (resDelIter != mDeletedResources.end())
330 {
331 if ((*resDelIter)->mRefCount == 0U)
332 {
333 status = true;
334 Resource *res = *resDelIter;
335 logResource(res);
336 mDeletedResources.erase(resDelIter);
337 delete res;
338 break;
339 }
340 ++ resDelIter;
341 }
342 }
343 if (full && !mDeletedResources.empty())
344 {
345 logger->log1("leaks in deleted");
346 std::set<Resource*>::iterator resDelIter = mDeletedResources.begin();
347 while (resDelIter != mDeletedResources.end())
348 {
349 logResource(*resDelIter);
350
351 // for debug only
352 // delete *resDelIter;
353 // for debug only
354
355 ++ resDelIter;
356 }
357 }
358 }
359
addResource(const std::string & idPath,Resource * const resource)360 bool addResource(const std::string &idPath,
361 Resource *const resource)
362 {
363 if (resource != nullptr)
364 {
365 resource->incRef();
366 resource->mIdPath = idPath;
367 #ifdef DEBUG_IMAGES
368 logger->log("set name %p, %s", static_cast<void*>(resource),
369 resource->mIdPath.c_str());
370 #endif // DEBUG_IMAGES
371
372 mResources[idPath] = resource;
373 return true;
374 }
375 return false;
376 }
377
getFromCache(const std::string & filename,const int variant)378 Resource *getFromCache(const std::string &filename,
379 const int variant)
380 {
381 std::stringstream ss;
382 ss << filename << "[" << variant << "]";
383 return getFromCache(ss.str());
384 }
385
isInCache(const std::string & idPath)386 bool isInCache(const std::string &idPath)
387 {
388 const ResourceCIterator &resIter = mResources.find(idPath);
389 return (resIter != mResources.end() && (resIter->second != nullptr));
390 }
391
getTempResource(const std::string & idPath)392 Resource *getTempResource(const std::string &idPath)
393 {
394 const ResourceCIterator &resIter = mResources.find(idPath);
395 if (resIter != mResources.end())
396 {
397 Resource *const res = resIter->second;
398 if (resIter->second != nullptr)
399 return res;
400 }
401 return nullptr;
402 }
403
getFromCache(const std::string & idPath)404 Resource *getFromCache(const std::string &idPath)
405 {
406 // Check if the id exists, and return the value if it does.
407 ResourceIterator resIter = mResources.find(idPath);
408 if (resIter != mResources.end())
409 {
410 if (resIter->second != nullptr)
411 resIter->second->incRef();
412 return resIter->second;
413 }
414
415 resIter = mOrphanedResources.find(idPath);
416 if (resIter != mOrphanedResources.end())
417 {
418 Resource *const res = resIter->second;
419 mResources.insert(*resIter);
420 mOrphanedResources.erase(resIter);
421 if (res != nullptr)
422 res->incRef();
423 return res;
424 }
425 return nullptr;
426 }
427
get(const std::string & idPath,generator fun,const void * const data)428 Resource *get(const std::string &idPath,
429 generator fun,
430 const void *const data)
431 {
432 #ifndef DISABLE_RESOURCE_CACHING
433 Resource *resource = getFromCache(idPath);
434 if (resource != nullptr)
435 return resource;
436 resource = fun(data);
437
438 if (resource != nullptr)
439 {
440 resource->incRef();
441 resource->mIdPath = idPath;
442 #ifdef DEBUG_IMAGES
443 logger->log("set name %p, %s", static_cast<void*>(resource),
444 resource->mIdPath.c_str());
445 #endif // DEBUG_IMAGES
446
447 mResources[idPath] = resource;
448 }
449 else
450 {
451 reportAlways("Error loading resource: %s", idPath.c_str())
452 }
453 #else // DISABLE_RESOURCE_CACHING
454
455 Resource *resource = fun(data, idPath);
456
457 if (resource)
458 {
459 resource->incRef();
460 resource->mIdPath = idPath;
461 #ifdef DEBUG_IMAGES
462 logger->log("set name %p, %s", static_cast<void*>(resource),
463 resource->mIdPath.c_str());
464 #endif // DEBUG_IMAGES
465 }
466 else
467 {
468 reportAlways("Error loading resource: " + idPath)
469 }
470 #endif // DISABLE_RESOURCE_CACHING
471
472 // Returns nullptr if the object could not be created.
473 return resource;
474 }
475
release(Resource * const res)476 void release(Resource *const res)
477 {
478 if ((res == nullptr) || mDestruction)
479 return;
480
481 #ifndef DISABLE_RESOURCE_CACHING
482 std::set<Resource*>::iterator resDelIter = mDeletedResources.find(res);
483 if (resDelIter != mDeletedResources.end())
484 {
485 // we found zero counted image in deleted list. deleting it and exit.
486 mDeletedResources.erase(resDelIter);
487 delete res;
488 return;
489 }
490
491 ResourceIterator resIter = mResources.find(res->mIdPath);
492
493 if (resIter == mResources.end())
494 {
495 // +++ need reenable after Resource will have type field
496 // reportAlways("no resource in cache: %s",
497 // res->mIdPath.c_str())
498 delete res;
499 return;
500 }
501 if (resIter->second != res)
502 {
503 // +++ need reenable after Resource will have type field
504 // reportAlways("in cache other image: %s",
505 // res->mIdPath.c_str())
506 delete res;
507 return;
508 }
509
510 timeval tv;
511 gettimeofday(&tv, nullptr);
512 const time_t timestamp = static_cast<time_t>(tv.tv_sec);
513
514 res->mTimeStamp = timestamp;
515 if (mOrphanedResources.empty())
516 mOldestOrphan = timestamp;
517
518 mOrphanedResources.insert(*resIter);
519 mResources.erase(resIter);
520 #else // DISABLE_RESOURCE_CACHING
521
522 delete res;
523 #endif // DISABLE_RESOURCE_CACHING
524 }
525
moveToDeleted(Resource * const res)526 void moveToDeleted(Resource *const res)
527 {
528 if (res == nullptr)
529 return;
530
531 bool found(false);
532 const int count = res->mRefCount;
533 if (count == 1)
534 logResource(res);
535 res->decRef();
536 ResourceIterator resIter = mResources.find(res->mIdPath);
537 if (resIter != mResources.end() && resIter->second == res)
538 {
539 mResources.erase(resIter);
540 found = true;
541 }
542 else
543 {
544 resIter = mOrphanedResources.find(res->mIdPath);
545 if (resIter != mOrphanedResources.end() && resIter->second == res)
546 {
547 mOrphanedResources.erase(resIter);
548 found = true;
549 }
550 }
551 if (found)
552 {
553 if (count > 1)
554 mDeletedResources.insert(res);
555 else
556 delete res;
557 }
558 }
559
decRefDelete(Resource * const res)560 void decRefDelete(Resource *const res)
561 {
562 if (res == nullptr)
563 return;
564
565 const int count = res->mRefCount;
566 if (count == 1)
567 {
568 logResource(res);
569
570 ResourceIterator resIter = mResources.find(res->mIdPath);
571 if (resIter != mResources.end() && resIter->second == res)
572 {
573 mResources.erase(resIter);
574 }
575 else
576 {
577 resIter = mOrphanedResources.find(res->mIdPath);
578 if (resIter != mOrphanedResources.end() && resIter->second == res)
579 mOrphanedResources.erase(resIter);
580 }
581
582 delete res;
583 }
584 else
585 {
586 res->decRef();
587 }
588 }
589
deleteInstance()590 void deleteInstance()
591 {
592 #ifdef DUMP_LEAKED_RESOURCES
593 logger->log1("clean orphans start");
594 ResourceManager::cleanProtected();
595 while (ResourceManager::cleanOrphans(true))
596 continue;
597 logger->log1("clean orphans end");
598 ResourceIterator iter = ResourceManager::mResources.begin();
599
600 #ifdef UNITTESTS
601 bool status(false);
602 #endif // UNITTESTS
603
604 while (iter != ResourceManager::mResources.end())
605 {
606 const Resource *const res = iter->second;
607 if (res != nullptr)
608 {
609 if (res->mRefCount != 0U)
610 {
611 logger->log(std::string("ResourceLeak: ").append(
612 res->mIdPath).append(" (").append(toString(
613 res->mRefCount)).append(")"));
614 #ifdef UNITTESTS
615 status = true;
616 #endif // UNITTESTS
617 }
618 }
619 ++iter;
620 }
621 #ifdef UNITTESTS
622 if (status)
623 reportAlways("Found leaked resources.")
624 #endif // UNITTESTS
625 #endif // DUMP_LEAKED_RESOURCES
626
627 deleteResourceManager();
628 }
629
scheduleDelete(SDL_Surface * const surface)630 void scheduleDelete(SDL_Surface *const surface)
631 {
632 deletedSurfaces.insert(surface);
633 }
634
clearScheduled()635 void clearScheduled()
636 {
637 BLOCK_START("ResourceManager::clearScheduled")
638 FOR_EACH (std::set<SDL_Surface*>::iterator, i, deletedSurfaces)
639 MSDL_FreeSurface(*i);
640 deletedSurfaces.clear();
641 BLOCK_END("ResourceManager::clearScheduled")
642 }
643
clearCache()644 void clearCache()
645 {
646 cleanProtected();
647 while (cleanOrphans(true))
648 continue;
649 }
650
calcMemoryLocal()651 int calcMemoryLocal()
652 {
653 int sz = 24;
654 FOR_EACH (std::set<SDL_Surface*>::iterator, it, deletedSurfaces)
655 {
656 sz += MemoryManager::getSurfaceSize(*it);
657 }
658 return sz;
659 }
660
calcMemoryChilds(const int level)661 int calcMemoryChilds(const int level)
662 {
663 int sz = 0;
664 FOR_EACH (ResourceCIterator, it, mResources)
665 {
666 sz += static_cast<int>((*it).first.capacity());
667 sz += (*it).second->calcMemory(level + 1);
668 }
669 FOR_EACH (ResourceCIterator, it, mOrphanedResources)
670 {
671 sz += static_cast<int>((*it).first.capacity());
672 sz += (*it).second->calcMemory(level + 1);
673 }
674 FOR_EACH (std::set<Resource*>::const_iterator, it, mDeletedResources)
675 {
676 sz += (*it)->calcMemory(level + 1);
677 }
678 return sz;
679 }
680
calcMemory(const int level)681 int calcMemory(const int level)
682 {
683 const int sumLocal = calcMemoryLocal();
684 const int sumChilds = calcMemoryChilds(0);
685 MemoryManager::printMemory("resource manager",
686 level,
687 sumLocal,
688 sumChilds);
689 return sumLocal + sumChilds;
690 }
691
size()692 int size() noexcept2
693 {
694 return CAST_S32(mResources.size());
695 }
696
697 #if defined(DEBUG_DUMP_LEAKS) || defined(UNITTESTS)
getResources()698 Resources &getResources()
699 {
700 return mResources;
701 }
702
getOrphanedResources()703 Resources &getOrphanedResources()
704 {
705 return mOrphanedResources;
706 }
707
getDeletedResources()708 const std::set<Resource*> &getDeletedResources()
709 {
710 return mDeletedResources;
711 }
712 #endif // defined(DEBUG_DUMP_LEAKS) || defined(UNITTESTS)
713
714 } // namespace ResourceManager
715