diff options
author | Andrei Karas <akaras@inbox.ru> | 2016-05-24 23:09:33 +0300 |
---|---|---|
committer | Andrei Karas <akaras@inbox.ru> | 2016-05-24 23:14:31 +0300 |
commit | d0a3a9f0f6dd3571e9f3a15bf4f8493a46d69eef (patch) | |
tree | d7204315244dc0a820df3c7834647893e83653e7 /src/resources/resourcemanager | |
parent | 21ea310d2fa35a38efdd63e01685262dd77f8bad (diff) | |
download | plus-d0a3a9f0f6dd3571e9f3a15bf4f8493a46d69eef.tar.gz plus-d0a3a9f0f6dd3571e9f3a15bf4f8493a46d69eef.tar.bz2 plus-d0a3a9f0f6dd3571e9f3a15bf4f8493a46d69eef.tar.xz plus-d0a3a9f0f6dd3571e9f3a15bf4f8493a46d69eef.zip |
Move resourcemanager into subdirectory.
Diffstat (limited to 'src/resources/resourcemanager')
-rw-r--r-- | src/resources/resourcemanager/resourcemanager.cpp | 1128 | ||||
-rw-r--r-- | src/resources/resourcemanager/resourcemanager.h | 277 |
2 files changed, 1405 insertions, 0 deletions
diff --git a/src/resources/resourcemanager/resourcemanager.cpp b/src/resources/resourcemanager/resourcemanager.cpp new file mode 100644 index 000000000..c59b09618 --- /dev/null +++ b/src/resources/resourcemanager/resourcemanager.cpp @@ -0,0 +1,1128 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2016 The ManaPlus Developers + * + * This file is part of The ManaPlus Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "resources/resourcemanager/resourcemanager.h" + +#include "configuration.h" +#include "logger.h" +#include "navigationmanager.h" + +#include "resources/map/walklayer.h" + +#ifdef USE_OPENGL +#include "resources/atlas/atlasmanager.h" +#include "resources/atlas/atlasresource.h" +#else // USE_OPENGL +#include "resources/image.h" +#endif // USE_OPENGL +#include "resources/imagehelper.h" +#include "resources/imageset.h" +#include "resources/memorymanager.h" +#include "resources/sdlmusic.h" +#include "resources/soundeffect.h" + +#include "resources/dye/dye.h" + +#include "resources/sprite/spritedef.h" + +#include "utils/checkutils.h" +#include "utils/delete2.h" +#include "utils/physfscheckutils.h" +#include "utils/physfsrwops.h" +#include "utils/sdlcheckutils.h" + +#ifdef USE_OPENGL +#include "render/shaders/shader.h" +#include "render/shaders/shaderprogram.h" +#include "render/shaders/shadersmanager.h" +#endif + +#include <SDL_image.h> +#include <sstream> + +#include <sys/time.h> + +#include "debug.h" + +ResourceManager *resourceManager = nullptr; +ResourceManager *ResourceManager::instance = nullptr; + +ResourceManager::ResourceManager() : + deletedSurfaces(), + mResources(), + mOrphanedResources(), + mDeletedResources(), + mOldestOrphan(0), + mDestruction(0), + mUseLongLiveSprites(config.getBoolValue("uselonglivesprites")) +{ + logger->log1("Initializing resource manager..."); +} + +ResourceManager::~ResourceManager() +{ + mDestruction = true; + mResources.insert(mOrphanedResources.begin(), mOrphanedResources.end()); + + // Release any remaining spritedefs first because they depend on image sets + ResourceIterator iter = mResources.begin(); + +#ifdef DEBUG_LEAKS + while (iter != mResources.end()) + { + if (iter->second) + { + if (iter->second->getRefCount()) + { + logger->log(std::string("ResourceLeak: ").append( + iter->second->getIdPath()).append(" (").append( + toString(iter->second->getRefCount())).append(")")); + } + } + ++iter; + } + + iter = mResources.begin(); +#endif + + while (iter != mResources.end()) + { +#ifdef DEBUG_LEAKS + if (iter->second && iter->second->getRefCount()) + { + ++iter; + continue; + } +#endif + if (dynamic_cast<SpriteDef*>(iter->second)) + { + cleanUp(iter->second); + const ResourceIterator toErase = iter; + ++iter; + mResources.erase(toErase); + } + else + { + ++iter; + } + } + + // Release any remaining image sets first because they depend on images + iter = mResources.begin(); + while (iter != mResources.end()) + { +#ifdef DEBUG_LEAKS + if (iter->second && iter->second->getRefCount()) + { + ++iter; + continue; + } +#endif + if (dynamic_cast<ImageSet*>(iter->second)) + { + cleanUp(iter->second); + const ResourceIterator toErase = iter; + ++iter; + mResources.erase(toErase); + } + else + { + ++iter; + } + } + + // Release remaining resources, logging the number of dangling references. + iter = mResources.begin(); + while (iter != mResources.end()) + { +#ifdef DEBUG_LEAKS + if (iter->second && iter->second->getRefCount()) + { + ++iter; + continue; + } +#endif + if (iter->second) + { + cleanUp(iter->second); + const ResourceIterator toErase = iter; + ++iter; + mResources.erase(toErase); + } + else + { + ++iter; + } + } + clearDeleted(); + clearScheduled(); +} + +void ResourceManager::init() +{ + if (!resourceManager) + resourceManager = new ResourceManager; +} + +void ResourceManager::cleanUp(Resource *const res) +{ + if (!res) + return; + + if (res->mRefCount > 0) + { + logger->log("ResourceManager::~ResourceManager() cleaning up %u " + "reference%s to %s", + res->mRefCount, + (res->mRefCount == 1) ? "" : "s", + res->mIdPath.c_str()); + } + + delete res; +#ifdef DEBUG_LEAKS + cleanOrphans(true); +#endif +} + +void ResourceManager::cleanProtected() +{ + ResourceIterator iter = mResources.begin(); + while (iter != mResources.end()) + { + Resource *const res = iter->second; + if (!res) + { + ++ iter; + continue; + } + if (res->isProtected()) + { + res->setProtected(false); + res->decRef(); + iter = mResources.begin(); + continue; + } + + ++ iter; + } +} + +bool ResourceManager::cleanOrphans(const bool always) +{ + timeval tv; + gettimeofday(&tv, nullptr); + // Delete orphaned resources after 30 seconds. + time_t oldest = static_cast<time_t>(tv.tv_sec); + const time_t threshold = oldest - 30; + + if (mOrphanedResources.empty() || (!always && mOldestOrphan >= threshold)) + return false; + + bool status(false); + ResourceIterator iter = mOrphanedResources.begin(); + while (iter != mOrphanedResources.end()) + { + Resource *const res = iter->second; + if (!res) + { + ++iter; + continue; + } + const time_t t = res->mTimeStamp; + if (!always && t >= threshold) + { + if (t < oldest) + oldest = t; + ++ iter; + } + else + { + logResource(res); + const ResourceIterator toErase = iter; + ++iter; + mOrphanedResources.erase(toErase); + delete res; // delete only after removal from list, + // to avoid issues in recursion + status = true; + } + } + + mOldestOrphan = oldest; + return status; +} + +void ResourceManager::logResource(const Resource *const res) +{ + if (!res) + return; +#ifdef USE_OPENGL + const Image *const image = dynamic_cast<const Image *const>(res); + if (image) + { + std::string src = image->getSource(); + const int count = image->getRefCount(); + if (count) + src.append(" ").append(toString(count)); + logger->log("resource(%s, %u) %s", res->mIdPath.c_str(), + image->getGLImage(), src.c_str()); + } + else + { + logger->log("resource(%s)", res->mIdPath.c_str()); + } +#else + logger->log("resource(%s)", res->mIdPath.c_str()); +#endif +} + +void ResourceManager::clearDeleted(const bool full) +{ + bool status(true); + logger->log1("clear deleted"); + while (status) + { + status = false; + std::set<Resource*>::iterator resDelIter = mDeletedResources.begin(); + while (resDelIter != mDeletedResources.end()) + { + if (!(*resDelIter)->getRefCount()) + { + status = true; + Resource *res = *resDelIter; + logResource(res); + mDeletedResources.erase(resDelIter); + delete res; + break; + } + ++ resDelIter; + } + } + if (full && !mDeletedResources.empty()) + { + logger->log1("leaks in deleted"); + std::set<Resource*>::iterator resDelIter = mDeletedResources.begin(); + while (resDelIter != mDeletedResources.end()) + { + logResource(*resDelIter); + + // for debug only +// delete *resDelIter; + // for debug only + + ++ resDelIter; + } + } +} +bool ResourceManager::setWriteDir(const std::string &path) const +{ + return static_cast<bool>(PhysFs::setWriteDir(path.c_str())); +} + +bool ResourceManager::addToSearchPath(const std::string &path, + const Append append) const +{ + logger->log("Adding to PhysicsFS: %s (%s)", path.c_str(), + append == Append_true ? "append" : "prepend"); + if (!PhysFs::addToSearchPath(path.c_str(), + append == Append_true ? 1 : 0)) + { + logger->log("Error: %s: addToSearchPath failed: %s", + path.c_str(), + PHYSFS_getLastError()); + return false; + } + return true; +} + +bool ResourceManager::removeFromSearchPath(const std::string &path) const +{ + logger->log("Removing from PhysicsFS: %s", path.c_str()); + if (!PhysFs::removeFromSearchPath(path.c_str())) + { + logger->log("Error: %s: removeFromSearchPath failed: %s", + path.c_str(), + PHYSFS_getLastError()); + return false; + } + return true; +} + +void ResourceManager::searchAndAddArchives(const std::string &restrict path, + const std::string &restrict ext, + const Append append) const +{ + const char *const dirSep = dirSeparator; + char **list = PhysFs::enumerateFiles(path.c_str()); + + for (char **i = list; *i; i++) + { + const size_t len = strlen(*i); + + if (len > ext.length() && !ext.compare((*i) + (len - ext.length()))) + { + const std::string file = path + (*i); + const std::string realPath = std::string( + PhysFs::getRealDir(file.c_str())); + addToSearchPath(std::string(realPath).append( + dirSep).append(file), append); + } + } + + PhysFs::freeList(list); +} + +void ResourceManager::searchAndRemoveArchives(const std::string &restrict path, + const std::string &restrict ext) + const +{ + const char *const dirSep = dirSeparator; + char **list = PhysFs::enumerateFiles(path.c_str()); + + for (char **i = list; *i; i++) + { + const size_t len = strlen(*i); + if (len > ext.length() && !ext.compare((*i) + (len - ext.length()))) + { + const std::string file = path + (*i); + const std::string realPath = std::string( + PhysFs::getRealDir(file.c_str())); + removeFromSearchPath(std::string(realPath).append( + dirSep).append(file)); + } + } + + PhysFs::freeList(list); +} + +bool ResourceManager::addResource(const std::string &idPath, + Resource *const resource) +{ + if (resource) + { + resource->incRef(); + resource->mIdPath = idPath; +#ifdef DEBUG_IMAGES + logger->log("set name %p, %s", static_cast<void*>(resource), + resource->mIdPath.c_str()); +#endif + mResources[idPath] = resource; + return true; + } + return false; +} + +Resource *ResourceManager::getFromCache(const std::string &filename, + const int variant) +{ + std::stringstream ss; + ss << filename << "[" << variant << "]"; + return getFromCache(ss.str()); +} + +bool ResourceManager::isInCache(const std::string &idPath) const +{ + const ResourceCIterator &resIter = mResources.find(idPath); + return (resIter != mResources.end() && resIter->second); +} + +Resource *ResourceManager::getTempResource(const std::string &idPath) +{ + const ResourceCIterator &resIter = mResources.find(idPath); + if (resIter != mResources.end()) + { + Resource *const res = resIter->second; + if (resIter->second) + return res; + } + return nullptr; +} + +Resource *ResourceManager::getFromCache(const std::string &idPath) +{ + // Check if the id exists, and return the value if it does. + ResourceIterator resIter = mResources.find(idPath); + if (resIter != mResources.end()) + { + if (resIter->second) + resIter->second->incRef(); + return resIter->second; + } + + resIter = mOrphanedResources.find(idPath); + if (resIter != mOrphanedResources.end()) + { + Resource *const res = resIter->second; + mResources.insert(*resIter); + mOrphanedResources.erase(resIter); + if (res) + res->incRef(); + return res; + } + return nullptr; +} + +Resource *ResourceManager::get(const std::string &idPath, + generator fun, + const void *const data) +{ +#ifndef DISABLE_RESOURCE_CACHING + Resource *resource = getFromCache(idPath); + if (resource) + return resource; + resource = fun(data); + + if (resource) + { + resource->incRef(); + resource->mIdPath = idPath; +#ifdef DEBUG_IMAGES + logger->log("set name %p, %s", static_cast<void*>(resource), + resource->mIdPath.c_str()); +#endif + mResources[idPath] = resource; + cleanOrphans(); + } + else + { + reportAlways("Error loading image: %s", idPath.c_str()); + } +#else + Resource *resource = fun(data, idPath); + + if (resource) + { + resource->incRef(); + resource->mIdPath = idPath; +#ifdef DEBUG_IMAGES + logger->log("set name %p, %s", static_cast<void*>(resource), + resource->mIdPath.c_str()); +#endif + } + else + { + reportAlways("Error loading image: " + idPath); + } +#endif + + // Returns nullptr if the object could not be created. + return resource; +} + +struct ResourceLoader final +{ + ResourceManager *manager; + std::string path; + ResourceManager::loader fun; + + static Resource *load(const void *const v) + { + if (!v) + return nullptr; + const ResourceLoader *const + rl = static_cast<const ResourceLoader *const>(v); + SDL_RWops *const rw = MPHYSFSRWOPS_openRead(rl->path.c_str()); + if (!rw) + { + reportAlways("Error loading resource: %s", + rl->path.c_str()); + return nullptr; + } + Resource *const res = rl->fun(rw, rl->path); + return res; + } +}; + +SDLMusic *ResourceManager::getMusic(const std::string &idPath) +{ + ResourceLoader rl = { this, idPath, &SDLMusic::load }; + return static_cast<SDLMusic*>(get(idPath, ResourceLoader::load, &rl)); +} + +SoundEffect *ResourceManager::getSoundEffect(const std::string &idPath) +{ + ResourceLoader rl = { this, idPath, &SoundEffect::load }; + return static_cast<SoundEffect*>(get(idPath, ResourceLoader::load, &rl)); +} + +struct DyedImageLoader final +{ + ResourceManager *manager; + std::string path; + static Resource *load(const void *const v) + { + BLOCK_START("DyedImageLoader::load") + if (!v) + { + BLOCK_END("DyedImageLoader::load") + return nullptr; + } + + const DyedImageLoader *const rl + = static_cast<const DyedImageLoader *const>(v); + if (!rl->manager) + { + BLOCK_END("DyedImageLoader::load") + return nullptr; + } + + std::string path1 = rl->path; + const size_t p = path1.find('|'); + Dye *d = nullptr; + if (p != std::string::npos) + { + d = new Dye(path1.substr(p + 1)); + path1 = path1.substr(0, p); + } + SDL_RWops *const rw = MPHYSFSRWOPS_openRead(path1.c_str()); + if (!rw) + { + delete d; + reportAlways("Image loading error: %s", path1.c_str()); + BLOCK_END("DyedImageLoader::load") + return nullptr; + } + Resource *const res = d ? imageHelper->load(rw, *d) + : imageHelper->load(rw); + delete d; + if (!res) + reportAlways("Image loading error: %s", path1.c_str()); + BLOCK_END("DyedImageLoader::load") + return res; + } +}; + +Image *ResourceManager::getImage(const std::string &idPath) +{ + DyedImageLoader rl = { this, idPath }; + return static_cast<Image*>(get(idPath, DyedImageLoader::load, &rl)); +} + +struct ImageSetLoader final +{ + ResourceManager *manager; + std::string path; + int w, h; + static Resource *load(const void *const v) + { + if (!v) + return nullptr; + + const ImageSetLoader *const + rl = static_cast<const ImageSetLoader *const>(v); + if (!rl->manager) + return nullptr; + + Image *const img = rl->manager->getImage(rl->path); + if (!img) + { + reportAlways("Image loading error: %s", rl->path.c_str()); + return nullptr; + } + ImageSet *const res = new ImageSet(img, rl->w, rl->h); + img->decRef(); + return res; + } +}; + +ImageSet *ResourceManager::getImageSet(const std::string &imagePath, + const int w, const int h) +{ + ImageSetLoader rl = { this, imagePath, w, h }; + std::stringstream ss; + ss << imagePath << "[" << w << "x" << h << "]"; + return static_cast<ImageSet*>(get(ss.str(), ImageSetLoader::load, &rl)); +} + + +struct SubImageSetLoader final +{ + ResourceManager *manager; + Image *parent; + int width, height; + static Resource *load(const void *const v) + { + if (!v) + return nullptr; + + const SubImageSetLoader *const + rl = static_cast<const SubImageSetLoader *const>(v); + if (!rl->manager) + return nullptr; + + if (!rl->parent) + return nullptr; + ImageSet *const res = new ImageSet(rl->parent, rl->width, rl->height); + return res; + } +}; + +ImageSet *ResourceManager::getSubImageSet(Image *const parent, + const int width, const int height) +{ + if (!parent) + return nullptr; + + const SubImageSetLoader rl = { this, parent, width, height }; + std::stringstream ss; + ss << parent->getIdPath() << ", set[" << width << "x" << height << "]"; + return static_cast<ImageSet*>(get(ss.str(), + SubImageSetLoader::load, &rl)); +} + +struct SubImageLoader final +{ + ResourceManager *manager; + Image *parent; + int x, y; + int width, height; + static Resource *load(const void *const v) + { + if (!v) + return nullptr; + + const SubImageLoader *const + rl = static_cast<const SubImageLoader *const>(v); + if (!rl->manager || !rl->parent) + return nullptr; + + Image *const res = rl->parent->getSubImage(rl->x, rl->y, + rl->width, rl->height); + if (!res) + { + reportAlways("SubImage loading error: %s", + rl->parent->getSource().c_str()); + } + return res; + } +}; + +Image *ResourceManager::getSubImage(Image *const parent, + const int x, const int y, + const int width, const int height) +{ + if (!parent) + return nullptr; + + const SubImageLoader rl = { this, parent, x, y, width, height}; + + std::stringstream ss; + ss << parent->getIdPath() << ",[" << x << "," << y << "," + << width << "x" << height << "]"; + return static_cast<Image*>(get(ss.str(), SubImageLoader::load, &rl)); +} + +#ifdef USE_OPENGL +struct AtlasLoader final +{ + const std::string name; + const StringVect *files; + + static Resource *load(const void *const v) + { + if (!v) + return nullptr; + + const AtlasLoader *const rl = static_cast<const AtlasLoader *const>(v); + AtlasResource *const resource = AtlasManager::loadTextureAtlas( + rl->name, *rl->files); + if (!resource) + reportAlways("Atlas creation error: %s", rl->name.c_str()); + return resource; + } +}; + +Resource *ResourceManager::getAtlas(const std::string &name, + const StringVect &files) +{ + AtlasLoader rl = { name, &files }; + return get("atlas_" + name, AtlasLoader::load, &rl); +} + +struct ShaderLoader final +{ + const std::string name; + const unsigned int type; + + static Resource *load(const void *const v) + { + if (!v) + return nullptr; + + const ShaderLoader *const rl + = static_cast<const ShaderLoader *const>(v); + Shader *const resource = shaders.createShader(rl->type, rl->name); + if (!resource) + reportAlways("Shader creation error: %s", rl->name.c_str()); + return resource; + } +}; + +Resource *ResourceManager::getShader(const unsigned int type, + const std::string &name) +{ + ShaderLoader rl = { name, type }; + return get("shader_" + name, ShaderLoader::load, &rl); +} + +struct ShaderProgramLoader final +{ + const std::string vertex; + const std::string fragment; + const bool isNewShader; + + static Resource *load(const void *const v) + { + if (!v) + return nullptr; + + const ShaderProgramLoader *const rl + = static_cast<const ShaderProgramLoader *const>(v); + ShaderProgram *const resource = shaders.createProgram( + rl->vertex, + rl->fragment, + rl->isNewShader); + if (!resource) + reportAlways("Shader program creation error"); + return resource; + } +}; + +Resource *ResourceManager::getShaderProgram(const std::string &vertex, + const std::string &fragment, + const bool isNewShader) +{ + ShaderProgramLoader rl = { vertex, fragment, isNewShader }; + return get("program_" + vertex + " + " + fragment, + ShaderProgramLoader::load, &rl); +} +#endif + +#ifndef DYECMD +struct WalkLayerLoader final +{ + const std::string name; + Map *map; + + static Resource *load(const void *const v) + { + if (!v) + return nullptr; + + const WalkLayerLoader *const rl = static_cast<const + WalkLayerLoader *const>(v); + Resource *const resource = NavigationManager::loadWalkLayer(rl->map); + if (!resource) + reportAlways("WalkLayer creation error"); + return resource; + } +}; +#endif + +#ifndef DYECMD +WalkLayer *ResourceManager::getWalkLayer(const std::string &name, + Map *const map) +{ + WalkLayerLoader rl = {name, map}; + return static_cast<WalkLayer*>(get("map_" + name, + WalkLayerLoader::load, &rl)); +} +#else +WalkLayer *ResourceManager::getWalkLayer(const std::string &name A_UNUSED, + Map *const map A_UNUSED) +{ + return nullptr; +} +#endif + +struct SpriteDefLoader final +{ + std::string path; + int variant; + bool useLongLiveSprites; + static Resource *load(const void *const v) + { + if (!v) + return nullptr; + + const SpriteDefLoader *const + rl = static_cast<const SpriteDefLoader *const>(v); + return SpriteDef::load(rl->path, rl->variant, rl->useLongLiveSprites); + } +}; + +SpriteDef *ResourceManager::getSprite(const std::string &path, + const int variant) +{ + SpriteDefLoader rl = { path, variant, mUseLongLiveSprites }; + std::stringstream ss; + ss << path << "[" << variant << "]"; + return static_cast<SpriteDef*>(get(ss.str(), SpriteDefLoader::load, &rl)); +} + +void ResourceManager::release(Resource *const res) +{ + if (!res || mDestruction) + return; + +#ifndef DISABLE_RESOURCE_CACHING + std::set<Resource*>::iterator resDelIter = mDeletedResources.find(res); + if (resDelIter != mDeletedResources.end()) + { + // we found zero counted image in deleted list. deleting it and exit. + mDeletedResources.erase(resDelIter); + delete res; + return; + } + + ResourceIterator resIter = mResources.find(res->mIdPath); + + if (resIter == mResources.end()) + { + reportAlways("no resource in cache: %s", + res->mIdPath.c_str()); + delete res; + return; + } + if (resIter->second != res) + { + reportAlways("in cache other image: %s", + res->mIdPath.c_str()); + delete res; + return; + } + + timeval tv; + gettimeofday(&tv, nullptr); + const time_t timestamp = static_cast<time_t>(tv.tv_sec); + + res->mTimeStamp = timestamp; + if (mOrphanedResources.empty()) + mOldestOrphan = timestamp; + + mOrphanedResources.insert(*resIter); + mResources.erase(resIter); +#else + delete res; +#endif +} + +void ResourceManager::moveToDeleted(Resource *const res) +{ + if (!res) + return; + + bool found(false); + const int count = res->getRefCount(); + if (count == 1) + logResource(res); + res->decRef(); + ResourceIterator resIter = mResources.find(res->mIdPath); + if (resIter != mResources.end() && resIter->second == res) + { + mResources.erase(resIter); + found = true; + } + else + { + resIter = mOrphanedResources.find(res->mIdPath); + if (resIter != mOrphanedResources.end() && resIter->second == res) + { + mOrphanedResources.erase(resIter); + found = true; + } + } + if (found) + { + if (count > 1) + mDeletedResources.insert(res); + else + delete res; + } +} + +void ResourceManager::decRefDelete(Resource *const res) +{ + if (!res) + return; + + const int count = res->getRefCount(); + if (count == 1) + { + logResource(res); + + ResourceIterator resIter = mResources.find(res->mIdPath); + if (resIter != mResources.end() && resIter->second == res) + { + mResources.erase(resIter); + } + else + { + resIter = mOrphanedResources.find(res->mIdPath); + if (resIter != mOrphanedResources.end() && resIter->second == res) + mOrphanedResources.erase(resIter); + } + + delete res; + } + else + { + res->decRef(); + } +} + +void ResourceManager::deleteInstance() +{ +#ifdef DUMP_LEAKED_RESOURCES + if (instance) + { + logger->log1("clean orphans start"); + instance->cleanProtected(); + while (instance->cleanOrphans(true)) + continue; + logger->log1("clean orphans end"); + ResourceIterator iter = instance->mResources.begin(); + + while (iter != instance->mResources.end()) + { + const Resource *const res = iter->second; + if (res) + { + if (res->getRefCount()) + { + logger->log(std::string("ResourceLeak: ").append( + res->getIdPath()).append(" (").append(toString( + res->getRefCount())).append(")")); + } + } + ++iter; + } + } +#endif + delete2(instance); +} + +SDL_Surface *ResourceManager::loadSDLSurface(const std::string &filename) const +{ + if (SDL_RWops *const rw = MPHYSFSRWOPS_openRead(filename.c_str())) + { + if (!IMG_isPNG(rw)) + { + reportAlways("Error, image is not png: %s", filename.c_str()); + return nullptr; + } + SDL_Surface *const surface = MIMG_LoadPNG_RW(rw); + SDL_RWclose(rw); + return surface; + } + return nullptr; +} + +void ResourceManager::scheduleDelete(SDL_Surface *const surface) +{ + deletedSurfaces.insert(surface); +} + +void ResourceManager::clearScheduled() +{ + BLOCK_START("ResourceManager::clearScheduled") + FOR_EACH (std::set<SDL_Surface*>::iterator, i, deletedSurfaces) + MSDL_FreeSurface(*i); + deletedSurfaces.clear(); + BLOCK_END("ResourceManager::clearScheduled") +} + +struct RescaledLoader final +{ + ResourceManager *manager; + const Image *image; + int width; + int height; + static Resource *load(const void *const v) + { + if (!v) + return nullptr; + const RescaledLoader *const rl + = static_cast<const RescaledLoader *const>(v); + if (!rl->manager || !rl->image) + return nullptr; + Image *const rescaled = rl->image->SDLgetScaledImage( + rl->width, rl->height); + if (!rescaled) + { + reportAlways("Rescale image failed: %s", + rl->image->getIdPath().c_str()); + return nullptr; + } + return rescaled; + } +}; + +Image *ResourceManager::getRescaled(const Image *const image, + const int width, const int height) +{ + if (!image) + return nullptr; + + std::string idPath = image->getIdPath() + strprintf( + "_rescaled%dx%d", width, height); + const RescaledLoader rl = { this, image, width, height }; + Image *const img = static_cast<Image *const>( + get(idPath, RescaledLoader::load, &rl)); + return img; +} + +void ResourceManager::clearCache() +{ + cleanProtected(); + while (cleanOrphans(true)) + continue; +} + +int ResourceManager::calcMemoryLocal() const +{ + int sz = sizeof(ResourceManager); + FOR_EACH (std::set<SDL_Surface*>::iterator, it, deletedSurfaces) + { + sz += memoryManager.getSurfaceSize(*it); + } + return sz; +} + +int ResourceManager::calcMemoryChilds(const int level) const +{ + int sz = 0; + FOR_EACH (ResourceCIterator, it, mResources) + { + sz += static_cast<int>((*it).first.capacity()); + sz += (*it).second->calcMemory(level + 1); + } + FOR_EACH (ResourceCIterator, it, mOrphanedResources) + { + sz += static_cast<int>((*it).first.capacity()); + sz += (*it).second->calcMemory(level + 1); + } + FOR_EACH (std::set<Resource*>::const_iterator, it, mDeletedResources) + { + sz += (*it)->calcMemory(level + 1); + } + return sz; +} diff --git a/src/resources/resourcemanager/resourcemanager.h b/src/resources/resourcemanager/resourcemanager.h new file mode 100644 index 000000000..2c2caabce --- /dev/null +++ b/src/resources/resourcemanager/resourcemanager.h @@ -0,0 +1,277 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2016 The ManaPlus Developers + * + * This file is part of The ManaPlus Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RESOURCES_RESOURCEMANAGER_RESOURCEMANAGER_H +#define RESOURCES_RESOURCEMANAGER_RESOURCEMANAGER_H + +#include "resources/memorycounter.h" + +#include "enums/simpletypes/append.h" + +#include "utils/stringvector.h" + +#include <map> +#include <set> + +#include "localconsts.h" + +class Image; +class ImageSet; +class Map; +class SDLMusic; +class Resource; +class SoundEffect; +class SpriteDef; +class WalkLayer; + +struct SDL_Surface; +struct SDL_RWops; + +/** + * A class for loading and managing resources. + */ +class ResourceManager final : public MemoryCounter +{ + friend class Resource; + + public: + typedef Resource *(*loader)(SDL_RWops *rw, + const std::string &name); + typedef Resource *(&generator)(const void *const data); + + ResourceManager(); + + A_DELETE_COPY(ResourceManager) + + /** + * Destructor. Cleans up remaining resources, warning about resources + * that were still referenced. + */ + ~ResourceManager(); + + /** + * Sets the write directory. + * + * @param path The path of the directory to be added. + * @return <code>true</code> on success, <code>false</code> otherwise. + */ + bool setWriteDir(const std::string &path) const; + + /** + * Adds a directory or archive to the search path. If append is true + * then the directory is added to the end of the search path, otherwise + * it is added at the front. + * + * @return <code>true</code> on success, <code>false</code> otherwise. + */ + bool addToSearchPath(const std::string &path, + const Append append) const; + + /** + * Remove a directory or archive from the search path. + * + * @return <code>true</code> on success, <code>false</code> otherwise. + */ + bool removeFromSearchPath(const std::string &path) const; + + /** + * Searches for zip files and adds them to the search path. + */ + void searchAndAddArchives(const std::string &restrict path, + const std::string &restrict ext, + const Append append) const; + + /** + * Searches for zip files and remove them from the search path. + */ + void searchAndRemoveArchives(const std::string &restrict path, + const std::string &restrict ext) const; + + /** + * Creates a resource and adds it to the resource map. + * + * @param idPath The resource identifier path. + * @param fun A function for generating the resource. + * @param data Extra parameters for the generator. + * @return A valid resource or <code>NULL</code> if the resource could + * not be generated. + */ + Resource *get(const std::string &idPath, + generator fun, + const void *const data) A_WARN_UNUSED; + + Resource *getFromCache(const std::string &idPath) A_WARN_UNUSED; + + Resource *getFromCache(const std::string &filename, + const int variant) A_WARN_UNUSED; + + /** + * Adds a preformatted resource to the resource map. + * + * @param path The file name. + * @param Resource The Resource to add. + * @return true if successfull, false otherwise. + */ + bool addResource(const std::string &idPath, Resource *const resource); + + /** + * Convenience wrapper around ResourceManager::get for loading + * images. + */ + Image *getImage(const std::string &idPath) A_WARN_UNUSED; + + /** + * Convenience wrapper around ResourceManager::get for loading + * songs. + */ + SDLMusic *getMusic(const std::string &idPath) A_WARN_UNUSED; + + /** + * Convenience wrapper around ResourceManager::get for loading + * samples. + */ + SoundEffect *getSoundEffect(const std::string &idPath) A_WARN_UNUSED; + + /** + * Creates a image set based on the image referenced by the given + * path and the supplied sprite sizes + */ + ImageSet *getImageSet(const std::string &imagePath, + const int w, const int h) A_WARN_UNUSED; + + ImageSet *getSubImageSet(Image *const parent, + const int width, + const int height) A_WARN_UNUSED; + + Image *getSubImage(Image *const parent, const int x, const int y, + const int width, const int height) A_WARN_UNUSED; + +#ifdef USE_OPENGL + Resource *getAtlas(const std::string &name, + const StringVect &files) A_WARN_UNUSED; + + Resource *getShader(const unsigned int type, + const std::string &name) A_WARN_UNUSED; + + Resource *getShaderProgram(const std::string &vertex, + const std::string &fragment, + const bool isNewShader) A_WARN_UNUSED; +#endif + + WalkLayer *getWalkLayer(const std::string &name, Map *const map); + + /** + * Creates a sprite definition based on a given path and the supplied + * variant. + */ + SpriteDef *getSprite(const std::string &path, + const int variant = 0) A_WARN_UNUSED; + + /** + * Releases a resource, placing it in the set of orphaned resources. + */ + void release(Resource *const res); + + void clearDeleted(const bool full = true); + + void decRefDelete(Resource *const res); + + /** + * Move resource to deleted resources list. + */ + void moveToDeleted(Resource *const res); + + Image *getRescaled(const Image *const image, + const int width, + const int height) A_WARN_UNUSED; + + /** + * Loads the given filename as an SDL surface. The returned surface is + * expected to be freed by the caller using SDL_FreeSurface. + */ + SDL_Surface *loadSDLSurface(const std::string &filename) + const A_WARN_UNUSED; + + void scheduleDelete(SDL_Surface *const surface); + + void clearScheduled(); + + /** + * Deletes the class instance if it exists. + */ + static void deleteInstance(); + + int size() const A_WARN_UNUSED + { return CAST_S32(mResources.size()); } + + typedef std::map<std::string, Resource*> Resources; + typedef Resources::iterator ResourceIterator; + typedef Resources::const_iterator ResourceCIterator; + +#ifdef DEBUG_DUMP_LEAKS + Resources* getResources() A_WARN_UNUSED + { return &mResources; } + + Resources* getOrphanedResources() A_WARN_UNUSED + { return &mOrphanedResources; } +#endif + + bool cleanOrphans(const bool always = false); + + void cleanProtected(); + + bool isInCache(const std::string &idPath) const A_WARN_UNUSED; + + Resource *getTempResource(const std::string &idPath) A_WARN_UNUSED; + + void clearCache(); + + int calcMemoryLocal() const override final; + + int calcMemoryChilds(const int level) const override final; + + std::string getCounterName() const override final + { return "ResourceManager"; } + + static void init(); + + private: + /** + * Deletes the resource after logging a cleanup message. + */ + static void cleanUp(Resource *const resource); + + static void logResource(const Resource *const res); + + static ResourceManager *instance; + std::set<SDL_Surface*> deletedSurfaces; + Resources mResources; + Resources mOrphanedResources; + std::set<Resource*> mDeletedResources; + time_t mOldestOrphan; + bool mDestruction; + bool mUseLongLiveSprites; +}; + +extern ResourceManager *resourceManager; + +#endif // RESOURCES_RESOURCEMANAGER_RESOURCEMANAGER_H |