GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/resourcemanager/resourcemanager.cpp Lines: 180 267 67.4 %
Date: 2018-05-24 20:11:55 Branches: 86 198 43.4 %

Line Branch Exec Source
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-2018  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
23
#include "resources/resourcemanager/resourcemanager.h"
24
25
#ifdef USE_OPENGL
26
#include "resources/image/image.h"
27
#endif  // USE_OPENGL
28
29
#include "resources/imageset.h"
30
31
#include "resources/memorymanager.h"
32
33
#include "resources/sprite/spritedef.h"
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)
41
#include "resources/resourcetypes.h"
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
2
std::set<SDL_Surface*> deletedSurfaces;
61
2
Resources mResources;
62
2
Resources mOrphanedResources;
63
2
std::set<Resource*> mDeletedResources;
64
time_t mOldestOrphan = 0;
65
bool mDestruction = false;
66
67
898
void deleteResourceManager()
68
{
69
898
    mDestruction = true;
70
2694
    mResources.insert(mOrphanedResources.begin(), mOrphanedResources.end());
71
72
    // Release any remaining spritedefs first because they depend on image sets
73
898
    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
898
    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
898
    iter = mResources.begin();
130
898
    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
898
    iter = mResources.begin();
155
898
    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
898
    clearDeleted(true);
178
898
    clearScheduled();
179
898
    mDestruction = false;
180
898
}
181
182
2
void cleanUp(Resource *const res)
183
{
184
2
    if (res == nullptr)
185
        return;
186
187
2
    const unsigned refCount = res->mRefCount;
188
2
    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
2
    delete res;
198
#ifdef DEBUG_LEAKS
199
    cleanOrphans(true);
200
#endif  // DEBUG_LEAKS
201
}
202
203
900
void cleanProtected()
204
{
205
900
    ResourceIterator iter = mResources.begin();
206
6554
    while (iter != mResources.end())
207
    {
208
5654
        Resource *const res = iter->second;
209
5654
        if (res == nullptr)
210
        {
211
            ++ iter;
212
            continue;
213
        }
214
5656
        if (res->mProtected)
215
        {
216
2
            res->mProtected = false;
217
2
            res->decRef();
218
2
            iter = mResources.begin();
219
2
            continue;
220
        }
221
222
        ++ iter;
223
    }
224
900
}
225
226
2546
bool cleanOrphans(const bool always)
227
{
228
    timeval tv;
229
2546
    gettimeofday(&tv, nullptr);
230
    // Delete orphaned resources after 30 seconds.
231
2546
    time_t oldest = static_cast<time_t>(tv.tv_sec);
232
2546
    const time_t threshold = oldest - 30;
233
234

2546
    if (mOrphanedResources.empty() || (!always && mOldestOrphan >= threshold))
235
        return false;
236
237
1424
    bool status(false);
238
1424
    ResourceIterator iter = mOrphanedResources.begin();
239
23458
    while (iter != mOrphanedResources.end())
240
    {
241
22034
        Resource *const res = iter->second;
242
22034
        if (res == nullptr)
243
        {
244
            ++iter;
245
            continue;
246
        }
247
22034
        const time_t t = res->mTimeStamp;
248
22034
        if (!always && t >= threshold)
249
        {
250
            if (t < oldest)
251
                oldest = t;
252
            ++ iter;
253
        }
254
        else
255
        {
256
22034
            logResource(res);
257
22034
            const ResourceIterator toErase = iter;
258
22034
            ++iter;
259
22034
            mOrphanedResources.erase(toErase);
260
22034
            delete res;  // delete only after removal from list,
261
                         // to avoid issues in recursion
262
22034
            status = true;
263
        }
264
    }
265
266
1424
    mOldestOrphan = oldest;
267
1424
    return status;
268
}
269
270
22040
void logResource(const Resource *const res)
271
{
272
22040
    if (res == nullptr)
273
        return;
274
#ifdef USE_OPENGL
275
22040
    const Image *const image = dynamic_cast<const Image *>(res);
276
22040
    if (image != nullptr)
277
    {
278
35730
        std::string src = image->mSource;
279
11910
        const int count = image->mRefCount;
280
11910
        if (count != 0)
281
            src.append(" ").append(toString(count));
282
47640
        logger->log("resource(%s, %u) %s", res->mIdPath.c_str(),
283
            image->getGLImage(), src.c_str());
284
    }
285
    else
286
    {
287
30390
        std::string src = res->mSource;
288
10130
        const int count = res->mRefCount;
289
10130
        if (count > 0)
290

18
            src.append(" ").append(toString(count));
291
30390
        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
902
void clearDeleted(const bool full)
321
{
322
902
    bool status(true);
323
902
    logger->log1("clear deleted");
324
1804
    while (status)
325
    {
326
902
        status = false;
327
902
        std::set<Resource*>::iterator resDelIter = mDeletedResources.begin();
328
904
        while (resDelIter != mDeletedResources.end())
329
        {
330
2
            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

1804
    if (full && !mDeletedResources.empty())
343
    {
344
2
        logger->log1("leaks in deleted");
345
2
        std::set<Resource*>::iterator resDelIter = mDeletedResources.begin();
346
4
        while (resDelIter != mDeletedResources.end())
347
        {
348
2
            logResource(*resDelIter);
349
350
            // for debug only
351
//            delete *resDelIter;
352
            // for debug only
353
354
            ++ resDelIter;
355
        }
356
    }
357
902
}
358
359
2
bool addResource(const std::string &idPath,
360
                 Resource *const resource)
361
{
362
2
    if (resource != nullptr)
363
    {
364
2
        resource->incRef();
365
4
        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
2
        mResources[idPath] = resource;
372
2
        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
12
bool isInCache(const std::string &idPath)
386
{
387
12
    const ResourceCIterator &resIter = mResources.find(idPath);
388

18
    return (resIter != mResources.end() && (resIter->second != nullptr));
389
}
390
391
6
Resource *getTempResource(const std::string &idPath)
392
{
393
6
    const ResourceCIterator &resIter = mResources.find(idPath);
394
6
    if (resIter != mResources.end())
395
    {
396
2
        Resource *const res = resIter->second;
397
2
        if (resIter->second != nullptr)
398
            return res;
399
    }
400
    return nullptr;
401
}
402
403
44884
Resource *getFromCache(const std::string &idPath)
404
{
405
    // Check if the id exists, and return the value if it does.
406
44884
    ResourceIterator resIter = mResources.find(idPath);
407
44884
    if (resIter != mResources.end())
408
    {
409
22732
        if (resIter->second != nullptr)
410
22732
            resIter->second->incRef();
411
22732
        return resIter->second;
412
    }
413
414
22152
    resIter = mOrphanedResources.find(idPath);
415
22152
    if (resIter != mOrphanedResources.end())
416
    {
417
106
        Resource *const res = resIter->second;
418
212
        mResources.insert(*resIter);
419
106
        mOrphanedResources.erase(resIter);
420
106
        if (res != nullptr)
421
106
            res->incRef();
422
        return res;
423
    }
424
    return nullptr;
425
}
426
427
44876
Resource *get(const std::string &idPath,
428
              generator fun,
429
              const void *const data)
430
{
431
#ifndef DISABLE_RESOURCE_CACHING
432
44876
    Resource *resource = getFromCache(idPath);
433
44876
    if (resource != nullptr)
434
        return resource;
435
22046
    resource = fun(data);
436
437
22046
    if (resource != nullptr)
438
    {
439
22046
        resource->incRef();
440
44092
        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
22046
        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
22046
    return resource;
473
}
474
475
22152
void release(Resource *const res)
476
{
477

22152
    if ((res == nullptr) || mDestruction)
478
6
        return;
479
480
#ifndef DISABLE_RESOURCE_CACHING
481
22152
    std::set<Resource*>::iterator resDelIter = mDeletedResources.find(res);
482
22152
    if (resDelIter != mDeletedResources.end())
483
    {
484
        // we found zero counted image in deleted list. deleting it and exit.
485
6
        mDeletedResources.erase(resDelIter);
486
6
        delete res;
487
        return;
488
    }
489
490
44292
    ResourceIterator resIter = mResources.find(res->mIdPath);
491
492
22146
    if (resIter == mResources.end())
493
    {
494
        reportAlways("no resource in cache: %s",
495
            res->mIdPath.c_str());
496
        delete res;
497
        return;
498
    }
499
22146
    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
22146
    gettimeofday(&tv, nullptr);
509
22146
    const time_t timestamp = static_cast<time_t>(tv.tv_sec);
510
511
22146
    res->mTimeStamp = timestamp;
512
22146
    if (mOrphanedResources.empty())
513
578
        mOldestOrphan = timestamp;
514
515
44292
    mOrphanedResources.insert(*resIter);
516
22146
    mResources.erase(resIter);
517
#else  // DISABLE_RESOURCE_CACHING
518
519
    delete res;
520
#endif  // DISABLE_RESOURCE_CACHING
521
}
522
523
10
void moveToDeleted(Resource *const res)
524
{
525
10
    if (res == nullptr)
526
        return;
527
528
10
    bool found(false);
529
10
    const int count = res->mRefCount;
530
10
    if (count == 1)
531
2
        logResource(res);
532
10
    res->decRef();
533
20
    ResourceIterator resIter = mResources.find(res->mIdPath);
534

16
    if (resIter != mResources.end() && resIter->second == res)
535
    {
536
6
        mResources.erase(resIter);
537
6
        found = true;
538
    }
539
    else
540
    {
541
8
        resIter = mOrphanedResources.find(res->mIdPath);
542

8
        if (resIter != mOrphanedResources.end() && resIter->second == res)
543
        {
544
4
            mOrphanedResources.erase(resIter);
545
4
            found = true;
546
        }
547
    }
548
    if (found)
549
    {
550
10
        if (count > 1)
551
            mDeletedResources.insert(res);
552
        else
553
4
            delete res;
554
    }
555
}
556
557
2
void decRefDelete(Resource *const res)
558
{
559
2
    if (res == nullptr)
560
        return;
561
562
2
    const int count = res->mRefCount;
563
2
    if (count == 1)
564
    {
565
2
        logResource(res);
566
567
4
        ResourceIterator resIter = mResources.find(res->mIdPath);
568

4
        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
2
        delete res;
580
    }
581
    else
582
    {
583
        res->decRef();
584
    }
585
}
586
587
898
void deleteInstance()
588
{
589
#ifdef DUMP_LEAKED_RESOURCES
590
898
    logger->log1("clean orphans start");
591
898
    ResourceManager::cleanProtected();
592
2182
    while (ResourceManager::cleanOrphans(true))
593
        continue;
594
898
    logger->log1("clean orphans end");
595
898
    ResourceIterator iter = ResourceManager::mResources.begin();
596
597
#ifdef UNITTESTS
598
898
    bool status(false);
599
#endif  // UNITTESTS
600
601
898
    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
898
    if (status)
620
        reportAlways("Found leaked resources.");
621
#endif  // UNITTESTS
622
#endif  // DUMP_LEAKED_RESOURCES
623
624
898
    deleteResourceManager();
625
898
}
626
627
void scheduleDelete(SDL_Surface *const surface)
628
{
629
    deletedSurfaces.insert(surface);
630
}
631
632
898
void clearScheduled()
633
{
634
    BLOCK_START("ResourceManager::clearScheduled")
635
1796
    FOR_EACH (std::set<SDL_Surface*>::iterator, i, deletedSurfaces)
636
        MSDL_FreeSurface(*i);
637
898
    deletedSurfaces.clear();
638
    BLOCK_END("ResourceManager::clearScheduled")
639
898
}
640
641
void clearCache()
642
{
643
    cleanProtected();
644
    while (cleanOrphans(true))
645
        continue;
646
}
647
648
int calcMemoryLocal()
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
689
int size() noexcept2
690
{
691
    return CAST_S32(mResources.size());
692
}
693
694
#if defined(DEBUG_DUMP_LEAKS) || defined(UNITTESTS)
695
196
Resources &getResources()
696
{
697
196
    return mResources;
698
}
699
700
98
Resources &getOrphanedResources()
701
{
702
98
    return mOrphanedResources;
703
}
704
705
90
const std::set<Resource*> &getDeletedResources()
706
{
707
90
    return mDeletedResources;
708
}
709
#endif  // defined(DEBUG_DUMP_LEAKS) || defined(UNITTESTS)
710
711

6
}  // namespace ResourceManager