ManaPlus
resourcemanager.cpp
Go to the documentation of this file.
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 
25 
26 #ifdef USE_OPENGL
27 #include "resources/image/image.h"
28 #endif // USE_OPENGL
29 
30 #include "resources/imageset.h"
31 
33 
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)
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;
64 std::set<Resource*> mDeletedResources;
65 time_t mOldestOrphan = 0;
66 bool mDestruction = false;
67 
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 
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 
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 
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 
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 
300 void logResources(const std::string &msg)
301 {
302  logger->log("start of resources: " + msg);
303  logger->log("resources");
305  {
306  logResource((*it).second);
307  }
308  logger->log("orphaned resources");
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
591 {
592 #ifdef DUMP_LEAKED_RESOURCES
593  logger->log1("clean orphans start");
595  while (ResourceManager::cleanOrphans(true))
596  continue;
597  logger->log1("clean orphans end");
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 
628 }
629 
630 void scheduleDelete(SDL_Surface *const surface)
631 {
632  deletedSurfaces.insert(surface);
633 }
634 
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 
645 {
646  cleanProtected();
647  while (cleanOrphans(true))
648  continue;
649 }
650 
652 {
653  int sz = 24;
654  FOR_EACH (std::set<SDL_Surface*>::iterator, it, deletedSurfaces)
655  {
657  }
658  return sz;
659 }
660 
661 int calcMemoryChilds(const int level)
662 {
663  int sz = 0;
665  {
666  sz += static_cast<int>((*it).first.capacity());
667  sz += (*it).second->calcMemory(level + 1);
668  }
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 
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 
693 {
694  return CAST_S32(mResources.size());
695 }
696 
697 #if defined(DEBUG_DUMP_LEAKS) || defined(UNITTESTS)
698 Resources &getResources()
699 {
700  return mResources;
701 }
702 
703 Resources &getOrphanedResources()
704 {
705  return mOrphanedResources;
706 }
707 
708 const std::set<Resource*> &getDeletedResources()
709 {
710  return mDeletedResources;
711 }
712 #endif // defined(DEBUG_DUMP_LEAKS) || defined(UNITTESTS)
713 
714 } // namespace ResourceManager
#define CAST_S32
Definition: cast.h:30
#define reportAlways(...)
Definition: checkutils.h:253
void log(const char *const log_text,...)
Definition: logger.cpp:269
void log1(const char *const log_text)
Definition: logger.cpp:238
static int getSurfaceSize(const SDL_Surface *const surface)
static void printMemory(const std::string &name, const int level, const int localSum, const int childsSum)
bool mProtected
Definition: resource.h:88
std::string mIdPath
Definition: resource.h:84
virtual void incRef()
Definition: resource.cpp:38
virtual void decRef()
Definition: resource.cpp:50
std::string mSource
Definition: resource.h:85
unsigned int mRefCount
Definition: resource.h:87
time_t mTimeStamp
Definition: resource.h:82
#define MSDL_FreeSurface(surface)
Definition: debug.h:54
#define FOR_EACH(type, iter, array)
Definition: foreach.h:25
#define noexcept2
Definition: localconsts.h:50
#define PRAGMA48(str)
Definition: localconsts.h:199
Logger * logger
Definition: logger.cpp:89
uint32_t data
bool msg(InputEvent &event)
Definition: chat.cpp:39
std::string toString(T const &value)
converts any type to a string
Definition: catch.hpp:1774
void deleteResourceManager()
Resource *(&) generator(const void *const data)
void release(Resource *const res)
Resources::const_iterator ResourceCIterator
Definition: resourcetypes.h:38
void logResources(const std::string &msg)
void clearDeleted(const bool full)
bool isInCache(const std::string &idPath)
std::set< Resource * > mDeletedResources
Resource * getFromCache(const std::string &filename, const int variant)
void decRefDelete(Resource *const res)
void scheduleDelete(SDL_Surface *const surface)
int calcMemory(const int level)
void logResource(const Resource *const res)
std::set< SDL_Surface * > deletedSurfaces
bool cleanOrphans(const bool always)
void moveToDeleted(Resource *const res)
std::map< std::string, Resource * > Resources
Definition: resourcetypes.h:36
Resources mOrphanedResources
int calcMemoryChilds(const int level)
Resource * get(const std::string &idPath, generator fun, const void *const data)
bool addResource(const std::string &idPath, Resource *const resource)
Resources::iterator ResourceIterator
Definition: resourcetypes.h:37
void cleanUp(Resource *const res)
Resource * getTempResource(const std::string &idPath)
#define BLOCK_END(name)
Definition: perfomance.h:80
#define BLOCK_START(name)
Definition: perfomance.h:79