GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/resourcemanager/resourcemanager.cpp Lines: 182 267 68.2 %
Date: 2018-09-20 Branches: 95 212 44.8 %

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
451
void deleteResourceManager()
68
{
69
451
    mDestruction = true;
70
1353
    mResources.insert(mOrphanedResources.begin(), mOrphanedResources.end());
71
72
    // Release any remaining spritedefs first because they depend on image sets
73
451
    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
451
    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
451
    iter = mResources.begin();
130
451
    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
451
    iter = mResources.begin();
155
451
    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
451
    clearDeleted(true);
178
451
    clearScheduled();
179
451
    mDestruction = false;
180
451
}
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
452
void cleanProtected()
204
{
205
452
    ResourceIterator iter = mResources.begin();
206
3292
    while (iter != mResources.end())
207
    {
208
2840
        Resource *const res = iter->second;
209
2840
        if (res == nullptr)
210
        {
211
            ++ iter;
212
            continue;
213
        }
214
2840
        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
452
}
225
226
1278
bool cleanOrphans(const bool always)
227
{
228
    timeval tv;
229
1278
    gettimeofday(&tv, nullptr);
230
    // Delete orphaned resources after 30 seconds.
231
1278
    time_t oldest = static_cast<time_t>(tv.tv_sec);
232
1278
    const time_t threshold = oldest - 30;
233
234


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

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

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

11103
    if ((res == nullptr) || mDestruction)
478
3
        return;
479
480
#ifndef DISABLE_RESOURCE_CACHING
481
11103
    std::set<Resource*>::iterator resDelIter = mDeletedResources.find(res);
482
11103
    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
22200
    ResourceIterator resIter = mResources.find(res->mIdPath);
491
492
11100
    if (resIter == mResources.end())
493
    {
494
        reportAlways("no resource in cache: %s",
495
            res->mIdPath.c_str());
496
        delete res;
497
        return;
498
    }
499
11100
    if (resIter->second != res)
500
    {
501
// +++ need reenable after Resource will have type field
502
//        reportAlways("in cache other image: %s",
503
//            res->mIdPath.c_str());
504
        delete res;
505
        return;
506
    }
507
508
    timeval tv;
509
11100
    gettimeofday(&tv, nullptr);
510
11100
    const time_t timestamp = static_cast<time_t>(tv.tv_sec);
511
512
11100
    res->mTimeStamp = timestamp;
513
11100
    if (mOrphanedResources.empty())
514
290
        mOldestOrphan = timestamp;
515
516
22200
    mOrphanedResources.insert(*resIter);
517
11100
    mResources.erase(resIter);
518
#else  // DISABLE_RESOURCE_CACHING
519
520
    delete res;
521
#endif  // DISABLE_RESOURCE_CACHING
522
}
523
524
5
void moveToDeleted(Resource *const res)
525
{
526
5
    if (res == nullptr)
527
        return;
528
529
5
    bool found(false);
530
5
    const int count = res->mRefCount;
531
5
    if (count == 1)
532
1
        logResource(res);
533
5
    res->decRef();
534
10
    ResourceIterator resIter = mResources.find(res->mIdPath);
535

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

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

3
        if (resIter != mResources.end() && resIter->second == res)
570
        {
571
            mResources.erase(resIter);
572
        }
573
        else
574
        {
575
            resIter = mOrphanedResources.find(res->mIdPath);
576
            if (resIter != mOrphanedResources.end() && resIter->second == res)
577
                mOrphanedResources.erase(resIter);
578
        }
579
580
1
        delete res;
581
    }
582
    else
583
    {
584
        res->decRef();
585
    }
586
}
587
588
451
void deleteInstance()
589
{
590
#ifdef DUMP_LEAKED_RESOURCES
591
451
    logger->log1("clean orphans start");
592
451
    ResourceManager::cleanProtected();
593
1096
    while (ResourceManager::cleanOrphans(true))
594
        continue;
595
451
    logger->log1("clean orphans end");
596
451
    ResourceIterator iter = ResourceManager::mResources.begin();
597
598
#ifdef UNITTESTS
599
451
    bool status(false);
600
#endif  // UNITTESTS
601
602
451
    while (iter != ResourceManager::mResources.end())
603
    {
604
        const Resource *const res = iter->second;
605
        if (res != nullptr)
606
        {
607
            if (res->mRefCount != 0u)
608
            {
609
                logger->log(std::string("ResourceLeak: ").append(
610
                    res->mIdPath).append(" (").append(toString(
611
                    res->mRefCount)).append(")"));
612
#ifdef UNITTESTS
613
                status = true;
614
#endif  // UNITTESTS
615
            }
616
        }
617
        ++iter;
618
    }
619
#ifdef UNITTESTS
620
451
    if (status)
621
        reportAlways("Found leaked resources.");
622
#endif  // UNITTESTS
623
#endif  // DUMP_LEAKED_RESOURCES
624
625
451
    deleteResourceManager();
626
451
}
627
628
void scheduleDelete(SDL_Surface *const surface)
629
{
630
    deletedSurfaces.insert(surface);
631
}
632
633
451
void clearScheduled()
634
{
635
    BLOCK_START("ResourceManager::clearScheduled")
636
902
    FOR_EACH (std::set<SDL_Surface*>::iterator, i, deletedSurfaces)
637
        MSDL_FreeSurface(*i);
638
451
    deletedSurfaces.clear();
639
    BLOCK_END("ResourceManager::clearScheduled")
640
451
}
641
642
void clearCache()
643
{
644
    cleanProtected();
645
    while (cleanOrphans(true))
646
        continue;
647
}
648
649
int calcMemoryLocal()
650
{
651
    int sz = 24;
652
    FOR_EACH (std::set<SDL_Surface*>::iterator, it, deletedSurfaces)
653
    {
654
        sz += memoryManager.getSurfaceSize(*it);
655
    }
656
    return sz;
657
}
658
659
int calcMemoryChilds(const int level)
660
{
661
    int sz = 0;
662
    FOR_EACH (ResourceCIterator, it, mResources)
663
    {
664
        sz += static_cast<int>((*it).first.capacity());
665
        sz += (*it).second->calcMemory(level + 1);
666
    }
667
    FOR_EACH (ResourceCIterator, it, mOrphanedResources)
668
    {
669
        sz += static_cast<int>((*it).first.capacity());
670
        sz += (*it).second->calcMemory(level + 1);
671
    }
672
    FOR_EACH (std::set<Resource*>::const_iterator, it, mDeletedResources)
673
    {
674
        sz += (*it)->calcMemory(level + 1);
675
    }
676
    return sz;
677
}
678
679
int calcMemory(const int level)
680
{
681
    const int sumLocal = calcMemoryLocal();
682
    const int sumChilds = calcMemoryChilds(0);
683
    memoryManager.printMemory("resource manager",
684
        level,
685
        sumLocal,
686
        sumChilds);
687
    return sumLocal + sumChilds;
688
}
689
690
int size() noexcept2
691
{
692
    return CAST_S32(mResources.size());
693
}
694
695
#if defined(DEBUG_DUMP_LEAKS) || defined(UNITTESTS)
696
98
Resources &getResources()
697
{
698
98
    return mResources;
699
}
700
701
49
Resources &getOrphanedResources()
702
{
703
49
    return mOrphanedResources;
704
}
705
706
45
const std::set<Resource*> &getDeletedResources()
707
{
708
45
    return mDeletedResources;
709
}
710
#endif  // defined(DEBUG_DUMP_LEAKS) || defined(UNITTESTS)
711
712

3
}  // namespace ResourceManager