GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/resourcemanager/resourcemanager.cpp Lines: 180 267 67.4 %
Date: 2017-11-29 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-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
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
810
void deleteResourceManager()
68
{
69
810
    mDestruction = true;
70
2430
    mResources.insert(mOrphanedResources.begin(), mOrphanedResources.end());
71
72
    // Release any remaining spritedefs first because they depend on image sets
73
810
    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
810
    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
810
    iter = mResources.begin();
130
810
    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
810
    iter = mResources.begin();
155
810
    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
810
    clearDeleted();
178
810
    clearScheduled();
179
810
    mDestruction = false;
180
810
}
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
812
void cleanProtected()
204
{
205
812
    ResourceIterator iter = mResources.begin();
206
5930
    while (iter != mResources.end())
207
    {
208
5118
        Resource *const res = iter->second;
209
5118
        if (res == nullptr)
210
        {
211
            ++ iter;
212
            continue;
213
        }
214
5120
        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
812
}
225
226
2324
bool cleanOrphans(const bool always)
227
{
228
    timeval tv;
229
2324
    gettimeofday(&tv, nullptr);
230
    // Delete orphaned resources after 30 seconds.
231
2324
    time_t oldest = static_cast<time_t>(tv.tv_sec);
232
2324
    const time_t threshold = oldest - 30;
233
234

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

18
            src.append(" ").append(toString(count));
291
27762
        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
814
void clearDeleted(const bool full)
321
{
322
814
    bool status(true);
323
814
    logger->log1("clear deleted");
324
1628
    while (status)
325
    {
326
814
        status = false;
327
814
        std::set<Resource*>::iterator resDelIter = mDeletedResources.begin();
328
816
        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

1628
    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
814
}
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
43492
Resource *getFromCache(const std::string &idPath)
404
{
405
    // Check if the id exists, and return the value if it does.
406
43492
    ResourceIterator resIter = mResources.find(idPath);
407
43492
    if (resIter != mResources.end())
408
    {
409
22658
        if (resIter->second != nullptr)
410
22658
            resIter->second->incRef();
411
22658
        return resIter->second;
412
    }
413
414
20834
    resIter = mOrphanedResources.find(idPath);
415
20834
    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
43484
Resource *get(const std::string &idPath,
428
              generator fun,
429
              const void *const data)
430
{
431
#ifndef DISABLE_RESOURCE_CACHING
432
43484
    Resource *resource = getFromCache(idPath);
433
43484
    if (resource != nullptr)
434
        return resource;
435
20728
    resource = fun(data);
436
437
20728
    if (resource != nullptr)
438
    {
439
20728
        resource->incRef();
440
41456
        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
20728
        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
20728
    return resource;
473
}
474
475
20834
void release(Resource *const res)
476
{
477

20834
    if ((res == nullptr) || mDestruction)
478
6
        return;
479
480
#ifndef DISABLE_RESOURCE_CACHING
481
20834
    std::set<Resource*>::iterator resDelIter = mDeletedResources.find(res);
482
20834
    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
41656
    ResourceIterator resIter = mResources.find(res->mIdPath);
491
492
20828
    if (resIter == mResources.end())
493
    {
494
        reportAlways("no resource in cache: %s",
495
            res->mIdPath.c_str());
496
        delete res;
497
        return;
498
    }
499
20828
    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
20828
    gettimeofday(&tv, nullptr);
509
20828
    const time_t timestamp = static_cast<time_t>(tv.tv_sec);
510
511
20828
    res->mTimeStamp = timestamp;
512
20828
    if (mOrphanedResources.empty())
513
534
        mOldestOrphan = timestamp;
514
515
41656
    mOrphanedResources.insert(*resIter);
516
20828
    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
810
void deleteInstance()
588
{
589
#ifdef DUMP_LEAKED_RESOURCES
590
810
    logger->log1("clean orphans start");
591
810
    ResourceManager::cleanProtected();
592
1962
    while (ResourceManager::cleanOrphans(true))
593
        continue;
594
810
    logger->log1("clean orphans end");
595
810
    ResourceIterator iter = ResourceManager::mResources.begin();
596
597
#ifdef UNITTESTS
598
810
    bool status(false);
599
#endif  // UNITTESTS
600
601
810
    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
810
    if (status)
620
        reportAlways("Found leaked resources.");
621
#endif  // UNITTESTS
622
#endif  // DUMP_LEAKED_RESOURCES
623
624
810
    deleteResourceManager();
625
810
}
626
627
void scheduleDelete(SDL_Surface *const surface)
628
{
629
    deletedSurfaces.insert(surface);
630
}
631
632
810
void clearScheduled()
633
{
634
    BLOCK_START("ResourceManager::clearScheduled")
635
1620
    FOR_EACH (std::set<SDL_Surface*>::iterator, i, deletedSurfaces)
636
        MSDL_FreeSurface(*i);
637
810
    deletedSurfaces.clear();
638
    BLOCK_END("ResourceManager::clearScheduled")
639
810
}
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