summaryrefslogtreecommitdiff
path: root/src/resources/resourcemanager
diff options
context:
space:
mode:
authorAndrei Karas <akaras@inbox.ru>2016-05-24 23:09:33 +0300
committerAndrei Karas <akaras@inbox.ru>2016-05-24 23:14:31 +0300
commitd0a3a9f0f6dd3571e9f3a15bf4f8493a46d69eef (patch)
treed7204315244dc0a820df3c7834647893e83653e7 /src/resources/resourcemanager
parent21ea310d2fa35a38efdd63e01685262dd77f8bad (diff)
downloadmv-d0a3a9f0f6dd3571e9f3a15bf4f8493a46d69eef.tar.gz
mv-d0a3a9f0f6dd3571e9f3a15bf4f8493a46d69eef.tar.bz2
mv-d0a3a9f0f6dd3571e9f3a15bf4f8493a46d69eef.tar.xz
mv-d0a3a9f0f6dd3571e9f3a15bf4f8493a46d69eef.zip
Move resourcemanager into subdirectory.
Diffstat (limited to 'src/resources/resourcemanager')
-rw-r--r--src/resources/resourcemanager/resourcemanager.cpp1128
-rw-r--r--src/resources/resourcemanager/resourcemanager.h277
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