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