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