GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/resourcemanager/resourcemanager.cpp Lines: 182 267 68.2 %
Date: 2021-03-17 Branches: 95 210 45.2 %

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


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

9
            src.append(" ").append(toString(count));
292
9752
        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");
304
    FOR_EACH(ResourceIterator, it, mResources)
305
    {
306
        logResource((*it).second);
307
    }
308
    logger->log("orphaned resources");
309
    FOR_EACH(ResourceIterator, it, mOrphanedResources)
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
442
void clearDeleted(const bool full)
322
{
323
442
    bool status(true);
324
442
    logger->log1("clear deleted");
325
884
    while (status)
326
    {
327
442
        status = false;
328
442
        std::set<Resource*>::iterator resDelIter = mDeletedResources.begin();
329
443
        while (resDelIter != mDeletedResources.end())
330
        {
331
1
            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

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

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

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

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

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

3
        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
1
        delete res;
583
    }
584
    else
585
    {
586
        res->decRef();
587
    }
588
}
589
590
440
void deleteInstance()
591
{
592
#ifdef DUMP_LEAKED_RESOURCES
593
440
    logger->log1("clean orphans start");
594
440
    ResourceManager::cleanProtected();
595
1052
    while (ResourceManager::cleanOrphans(true))
596
        continue;
597
440
    logger->log1("clean orphans end");
598
440
    ResourceIterator iter = ResourceManager::mResources.begin();
599
600
#ifdef UNITTESTS
601
440
    bool status(false);
602
#endif  // UNITTESTS
603
604
440
    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
440
    if (status)
623
        reportAlways("Found leaked resources.")
624
#endif  // UNITTESTS
625
#endif  // DUMP_LEAKED_RESOURCES
626
627
440
    deleteResourceManager();
628
440
}
629
630
void scheduleDelete(SDL_Surface *const surface)
631
{
632
    deletedSurfaces.insert(surface);
633
}
634
635
440
void clearScheduled()
636
{
637
    BLOCK_START("ResourceManager::clearScheduled")
638
880
    FOR_EACH (std::set<SDL_Surface*>::iterator, i, deletedSurfaces)
639
        MSDL_FreeSurface(*i);
640
440
    deletedSurfaces.clear();
641
    BLOCK_END("ResourceManager::clearScheduled")
642
440
}
643
644
void clearCache()
645
{
646
    cleanProtected();
647
    while (cleanOrphans(true))
648
        continue;
649
}
650
651
int calcMemoryLocal()
652
{
653
    int sz = 24;
654
    FOR_EACH (std::set<SDL_Surface*>::iterator, it, deletedSurfaces)
655
    {
656
        sz += MemoryManager::getSurfaceSize(*it);
657
    }
658
    return sz;
659
}
660
661
int calcMemoryChilds(const int level)
662
{
663
    int sz = 0;
664
    FOR_EACH (ResourceCIterator, it, mResources)
665
    {
666
        sz += static_cast<int>((*it).first.capacity());
667
        sz += (*it).second->calcMemory(level + 1);
668
    }
669
    FOR_EACH (ResourceCIterator, it, mOrphanedResources)
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
692
int size() noexcept2
693
{
694
    return CAST_S32(mResources.size());
695
}
696
697
#if defined(DEBUG_DUMP_LEAKS) || defined(UNITTESTS)
698
98
Resources &getResources()
699
{
700
98
    return mResources;
701
}
702
703
49
Resources &getOrphanedResources()
704
{
705
49
    return mOrphanedResources;
706
}
707
708
45
const std::set<Resource*> &getDeletedResources()
709
{
710
45
    return mDeletedResources;
711
}
712
#endif  // defined(DEBUG_DUMP_LEAKS) || defined(UNITTESTS)
713
714

3
}  // namespace ResourceManager