GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/resourcemanager/resourcemanager.cpp Lines: 182 267 68.2 %
Date: 2018-07-14 Branches: 95 214 44.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
1
std::set<SDL_Surface*> deletedSurfaces;
61
1
Resources mResources;
62
1
Resources mOrphanedResources;
63
1
std::set<Resource*> mDeletedResources;
64
time_t mOldestOrphan = 0;
65
bool mDestruction = false;
66
67
449
void deleteResourceManager()
68
{
69
449
    mDestruction = true;
70
1347
    mResources.insert(mOrphanedResources.begin(), mOrphanedResources.end());
71
72
    // Release any remaining spritedefs first because they depend on image sets
73
449
    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
449
    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
449
    iter = mResources.begin();
130
449
    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
449
    iter = mResources.begin();
155
449
    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
449
    clearDeleted(true);
178
449
    clearScheduled();
179
449
    mDestruction = false;
180
449
}
181
182
1
void cleanUp(Resource *const res)
183
{
184
1
    if (res == nullptr)
185
        return;
186
187
1
    const unsigned refCount = res->mRefCount;
188
1
    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
1
    delete res;
198
#ifdef DEBUG_LEAKS
199
    cleanOrphans(true);
200
#endif  // DEBUG_LEAKS
201
}
202
203
450
void cleanProtected()
204
{
205
450
    ResourceIterator iter = mResources.begin();
206
3277
    while (iter != mResources.end())
207
    {
208
2827
        Resource *const res = iter->second;
209
2827
        if (res == nullptr)
210
        {
211
            ++ iter;
212
            continue;
213
        }
214
2827
        if (res->mProtected)
215
        {
216
1
            res->mProtected = false;
217
1
            res->decRef();
218
1
            iter = mResources.begin();
219
1
            continue;
220
        }
221
222
        ++ iter;
223
    }
224
450
}
225
226
1273
bool cleanOrphans(const bool always)
227
{
228
    timeval tv;
229
1273
    gettimeofday(&tv, nullptr);
230
    // Delete orphaned resources after 30 seconds.
231
1273
    time_t oldest = static_cast<time_t>(tv.tv_sec);
232
1273
    const time_t threshold = oldest - 30;
233
234


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

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

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

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

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

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

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

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

3
}  // namespace ResourceManager