/*
* The Mana Client
* Copyright (C) 2004-2009 The Mana World Development Team
* Copyright (C) 2009-2012 The Mana Developers
*
* This file is part of The Mana 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 .
*/
#include "resources/resourcemanager.h"
#include "client.h"
#include "log.h"
#include "resources/dye.h"
#include "resources/image.h"
#include "resources/imageset.h"
#include "resources/music.h"
#include "resources/soundeffect.h"
#include "resources/spritedef.h"
#include "utils/filesystem.h"
#include
#include
#include
#include
#include
ResourceManager *ResourceManager::instance = nullptr;
ResourceManager::ResourceManager()
{
logger->log("Initializing resource manager...");
}
ResourceManager::~ResourceManager()
{
auto cleanupResources = [&](auto match)
{
// Include any orphaned resources into the main list for cleanup
mResources.insert(mOrphanedResources.begin(), mOrphanedResources.end());
mOrphanedResources.clear();
for (auto iter = mResources.begin(); iter != mResources.end(); )
{
if (match(iter->second))
{
cleanUp(iter->second);
iter = mResources.erase(iter);
}
else
{
++iter;
}
}
};
// SpriteDef references ImageSet
cleanupResources([](Resource *res) { return dynamic_cast(res); });
// ImageSet references Image
cleanupResources([](Resource *res) { return dynamic_cast(res); });
// Release remaining resources
cleanupResources([](Resource *res) { return true; });
assert(mOrphanedResources.empty());
}
void ResourceManager::cleanUp(Resource *res)
{
if (res->mRefCount > 0)
{
logger->log("ResourceManager::~ResourceManager() cleaning up %d "
"reference%s to %s",
res->mRefCount,
(res->mRefCount == 1) ? "" : "s",
res->mIdPath.c_str());
}
delete res;
}
void ResourceManager::cleanOrphans()
{
// Delete orphaned resources after 30 seconds.
time_t oldest = time(nullptr);
const time_t threshold = oldest - 30;
if (mOrphanedResources.empty() || mOldestOrphan >= threshold)
return;
auto iter = mOrphanedResources.begin();
while (iter != mOrphanedResources.end())
{
Resource *res = iter->second;
const time_t t = res->mTimeStamp;
if (t >= threshold)
{
if (t < oldest)
oldest = t;
++iter;
}
else
{
logger->log("ResourceManager::release(%s)", res->mIdPath.c_str());
iter = mOrphanedResources.erase(iter);
delete res; // delete only after removal from list, to avoid issues in recursion
}
}
mOldestOrphan = oldest;
}
bool ResourceManager::addToSearchPath(const std::string &path, bool append)
{
logger->log("Adding to PhysicsFS: %s", path.c_str());
if (!FS::addToSearchPath(path, append))
{
logger->log("Error: %s", FS::getLastError());
return false;
}
return true;
}
void ResourceManager::searchAndAddArchives(const std::string &path,
const std::string &ext,
bool append)
{
const char *dirSep = FS::getDirSeparator();
for (auto fileName : FS::enumerateFiles(path))
{
const size_t len = strlen(fileName);
if (len > ext.length() && ext != (fileName + (len - ext.length())))
{
std::string file = path + fileName;
if (auto realDir = FS::getRealDir(file))
{
std::string archive = std::string(*realDir) + dirSep + file;
addToSearchPath(archive, append);
}
}
}
}
std::string ResourceManager::getPath(const std::string &file)
{
// Get the real directory of the file
auto realDir = FS::getRealDir(file);
std::string path;
if (realDir)
{
path = std::string(*realDir) + "/" + file;
}
else
{
// if not found in search path return the default path
path = Client::getPackageDirectory() + "/" + file;
}
return path;
}
Resource *ResourceManager::get(const std::string &idPath,
const std::function &generator)
{
// Check if the id exists, and return the value if it does.
auto resIter = mResources.find(idPath);
if (resIter != mResources.end())
{
return resIter->second;
}
resIter = mOrphanedResources.find(idPath);
if (resIter != mOrphanedResources.end())
{
Resource *res = resIter->second;
mResources.insert(*resIter);
mOrphanedResources.erase(resIter);
return res;
}
Resource *resource = generator();
if (resource)
{
resource->mIdPath = idPath;
mResources[idPath] = resource;
cleanOrphans();
}
return resource;
}
ResourceRef ResourceManager::getMusic(const std::string &path)
{
return static_cast(get(path, [&] () -> Resource * {
if (SDL_RWops *rw = FS::openBufferedRWops(path))
return Music::load(rw);
return nullptr;
}));
}
ResourceRef ResourceManager::getSoundEffect(const std::string &path)
{
return static_cast(get(path, [&] () -> Resource * {
if (SDL_RWops *rw = FS::openBufferedRWops(path))
return SoundEffect::load(rw);
return nullptr;
}));
}
ResourceRef ResourceManager::getImage(const std::string &idPath)
{
return static_cast(get(idPath, [&] () -> Resource * {
std::string path = idPath;
std::string::size_type p = path.find('|');
std::unique_ptr d;
if (p != std::string::npos)
{
d = std::make_unique(path.substr(p + 1));
path = path.substr(0, p);
}
SDL_RWops *rw = FS::openRWops(path);
if (!rw)
return nullptr;
Resource *res = d ? Image::load(rw, *d)
: Image::load(rw);
return res;
}));
}
ResourceRef ResourceManager::getImageSet(const std::string &imagePath,
int w, int h)
{
std::stringstream ss;
ss << imagePath << "[" << w << "x" << h << "]";
return static_cast(get(ss.str(), [&] () -> Resource * {
auto img = getImage(imagePath);
if (!img)
return nullptr;
return new ImageSet(img, w, h);
}));
}
ResourceRef ResourceManager::getSprite(const std::string &path, int variant)
{
std::stringstream ss;
ss << path << "[" << variant << "]";
return static_cast(get(ss.str(), [&] () -> Resource * {
return SpriteDef::load(path, variant);
}));
}
void ResourceManager::release(Resource *res)
{
auto resIter = mResources.find(res->mIdPath);
// The resource has to exist
assert(resIter != mResources.end() && resIter->second == res);
const time_t timestamp = time(nullptr);
res->mTimeStamp = timestamp;
if (mOrphanedResources.empty())
mOldestOrphan = timestamp;
mOrphanedResources.insert(*resIter);
mResources.erase(resIter);
}
void ResourceManager::remove(Resource *res)
{
mResources.erase(res->mIdPath);
}
ResourceManager *ResourceManager::getInstance()
{
// Create a new instance if necessary.
if (!instance)
instance = new ResourceManager;
return instance;
}
void ResourceManager::deleteInstance()
{
delete instance;
instance = nullptr;
}