+#include <algorithm>
+#include <typeinfo>
+#include <msp/debug/demangle.h>
+#include <msp/strings/format.h>
#include <msp/time/utils.h>
#include "resourcemanager.h"
+#include "resources.h"
+#include "resourcewatcher.h"
using namespace std;
namespace Msp {
namespace GL {
+resource_load_error::resource_load_error(const string &name, const string &err):
+ runtime_error(format("%s: %s", name, err))
+{ }
+
+resource_load_error::resource_load_error(const string &name, const exception &exc):
+ runtime_error(format("%s: %s: %s", name, Debug::demangle(typeid(exc).name()), exc.what()))
+{ }
+
+
ResourceManager::ResourceManager():
policy(LOAD_ON_DEMAND),
async_loads(true),
- thread(*this)
+ total_data_size(0),
+ size_limit(0),
+ frame(0),
+ min_retain_frames(30),
+ max_retain_frames(0),
+ next_unload(0)
{ }
ResourceManager::~ResourceManager()
{
thread.terminate();
+
+ while(!resources.empty())
+ resources.begin()->second.resource->set_manager(0);
+}
+
+void ResourceManager::set_loading_policy(LoadingPolicy p)
+{
+ policy = p;
+}
+
+void ResourceManager::set_async_loads(bool a)
+{
+ async_loads = a;
+}
+
+void ResourceManager::set_size_limit(UInt64 s)
+{
+ size_limit = s;
+}
+
+void ResourceManager::set_min_retain_frames(unsigned f)
+{
+ min_retain_frames = f;
+}
+
+void ResourceManager::set_max_retain_frames(unsigned f)
+{
+ max_retain_frames = f;
}
void ResourceManager::add_resource(Resource &r)
{
+ MutexLock lock(map_mutex);
insert_unique(resources, &r, ManagedResource(r));
}
+const ResourceManager::ManagedResource &ResourceManager::get_managed_resource(const Resource &r) const
+{
+ MutexLock lock(map_mutex);
+ return get_item(resources, &r);
+}
+
+ResourceManager::ManagedResource &ResourceManager::get_managed_resource(const Resource &r)
+{
+ MutexLock lock(map_mutex);
+ return get_item(resources, &r);
+}
+
void ResourceManager::set_resource_location(Resource &r, DataFile::Collection &c, const string &n)
{
- ManagedResource &managed = get_item(resources, &r);
- managed.collection = &c;
- managed.name = n;
+ set_resource_location(r, ResourceLocation(c, n));
+}
+
+void ResourceManager::set_resource_location(Resource &r, const ResourceLocation &l)
+{
+ {
+ MutexLock lock(map_mutex);
+ ManagedResource &managed = get_item(resources, &r);
+ managed.location = l;
+ }
if(policy==LOAD_IMMEDIATELY)
load_resource(r);
}
-void ResourceManager::load_resource(const Resource &r)
+const ResourceManager::ResourceLocation *ResourceManager::get_resource_location(const Resource &r) const
{
- ManagedResource &managed = get_item(resources, &r);
- if(!managed.collection)
+ const ManagedResource &managed = get_managed_resource(r);
+ return managed.location.collection ? &managed.location : 0;
+}
+
+void ResourceManager::load_resource(Resource &r)
+{
+ ManagedResource &managed = get_managed_resource(r);
+ if(!managed.location.collection)
throw runtime_error("no location");
- if(managed.loader)
+ if(managed.state!=ManagedResource::NOT_LOADED)
return;
- managed.io = managed.collection->open_raw(managed.name);
- managed.loader = managed.resource->load(*managed.io);
+ if(async_loads)
+ {
+ managed.state = ManagedResource::LOAD_QUEUED;
+ LoadQueue::iterator i;
+ for(i=queue.begin(); (i!=queue.end() && (*i)->load_priority>=managed.load_priority); ++i) ;
+ queue.insert(i, &managed);
+ }
+ else
+ {
+ managed.start_loading();
+ while(!managed.loader->process()) ;
+ managed.finish_loading(true);
+ total_data_size += managed.data_size;
+ }
+}
+
+bool ResourceManager::is_resource_loaded(const Resource &r) const
+{
+ ManagedResource *managed = reinterpret_cast<ManagedResource *>(r.get_manager_data());
+ return managed ? managed->state==ManagedResource::LOADED : false;
+}
+
+void ResourceManager::resource_used(const Resource &r)
+{
+ ManagedResource *managed = reinterpret_cast<ManagedResource *>(r.get_manager_data());
+ if(!managed)
+ return;
+ if(managed->state==ManagedResource::NOT_LOADED && policy!=LOAD_MANUALLY)
+ load_resource(*managed->resource);
- queue.push_back(&managed);
+ managed->last_used = frame;
+ if(max_retain_frames && !next_unload)
+ next_unload = frame+max_retain_frames+1;
}
void ResourceManager::remove_resource(Resource &r)
{
- ManagedResource *loading = thread.get_resource();
- if(loading && loading->resource==&r)
- thread.set_resource(0);
+ ManagedResource &managed = get_managed_resource(r);
+ ManagedResource::State state = managed.state;
+ if(state==ManagedResource::LOAD_QUEUED)
+ {
+ LoadQueue::iterator i = find(queue.begin(), queue.end(), &managed);
+ if(i!=queue.end())
+ queue.erase(i);
+ }
+ else if(state>ManagedResource::LOAD_QUEUED && state<ManagedResource::LOADED)
+ thread.remove_resource(managed);
+
+ for(vector<ResourceWatcher *>::const_iterator i=managed.watchers.begin(); i!=managed.watchers.end(); ++i)
+ (*i)->resource_removed(r);
+ MutexLock lock(map_mutex);
remove_existing(resources, &r);
}
+void ResourceManager::watch_resource(const Resource &r, ResourceWatcher &w)
+{
+ get_managed_resource(r).add_watcher(w);
+}
+
+void ResourceManager::unwatch_resource(const Resource &r, ResourceWatcher &w)
+{
+ get_managed_resource(r).remove_watcher(w);
+}
+
void ResourceManager::tick()
{
- LoadingThread::State thread_state = thread.get_state();
- if(thread_state==LoadingThread::SYNC_PENDING)
- thread.sync();
- else if(thread_state==LoadingThread::IDLE && !queue.empty())
+ ++frame;
+
+ bool do_unload = (frame>=next_unload);
+ if(thread.sync())
+ {
+ total_data_size += thread.get_and_reset_loaded_data_size();
+ do_unload = true;
+ }
+
+ if(thread.needs_work() && !queue.empty())
+ dispatch_work();
+
+ if(do_unload)
+ {
+ MutexLock lock(map_mutex);
+ if(max_retain_frames && frame>=next_unload)
+ {
+ unload_by_age();
+
+ next_unload = frame;
+ for(ResourceMap::iterator i=resources.begin(); i!=resources.end(); ++i)
+ if(i->second.state==ManagedResource::LOADED)
+ next_unload = min(next_unload, i->second.last_used);
+ next_unload = (next_unload<frame ? next_unload+max_retain_frames : 0);
+ }
+
+ if(size_limit)
+ unload_by_size();
+ }
+}
+
+void ResourceManager::dispatch_work()
+{
+ queue.sort(age_order);
+
+ if(queue.front()->last_used+10<frame)
+ {
+ for(LoadQueue::iterator i=queue.begin(); i!=queue.end(); ++i)
+ (*i)->state = ManagedResource::NOT_LOADED;
+ queue.clear();
+ return;
+ }
+
+ while(thread.needs_work() && !queue.empty())
{
ManagedResource *managed = queue.front();
queue.pop_front();
- thread.set_resource(managed);
+ thread.add_resource(*managed);
+ }
+}
+
+void ResourceManager::unload_by_age()
+{
+ unsigned unload_limit = frame-max_retain_frames;
+
+ for(ResourceMap::iterator i=resources.begin(); i!=resources.end(); ++i)
+ if(i->second.state==ManagedResource::LOADED && i->second.last_used<unload_limit)
+ {
+ i->second.unload();
+ total_data_size -= i->second.data_size;
+ }
+}
+
+void ResourceManager::unload_by_size()
+{
+ unsigned unload_limit = frame-min_retain_frames;
+
+ while(total_data_size>size_limit)
+ {
+ ManagedResource *best = 0;
+ UInt64 best_impact = 0;
+ for(ResourceMap::iterator i=resources.begin(); i!=resources.end(); ++i)
+ if(i->second.state==ManagedResource::LOADED && i->second.last_used<unload_limit)
+ {
+ UInt64 impact = i->second.data_size*(frame-i->second.last_used);
+ if(!best || impact>best_impact)
+ {
+ best = &i->second;
+ best_impact = impact;
+ }
+ }
+
+ if(!best)
+ break;
+
+ best->unload();
+ total_data_size -= best->data_size;
}
}
+bool ResourceManager::age_order(ManagedResource *mr1, ManagedResource *mr2)
+{
+ return mr1->last_used>mr2->last_used;
+}
+
+
+ResourceManager::ResourceLocation::ResourceLocation():
+ collection(0)
+{ }
+
+ResourceManager::ResourceLocation::ResourceLocation(DataFile::Collection &c, const string &n):
+ collection(&c),
+ name(n)
+{ }
+
ResourceManager::ManagedResource::ManagedResource(Resource &r):
resource(&r),
- collection(0),
+ load_priority(r.get_load_priority()),
io(0),
- loader(0)
+ loader(0),
+ state(NOT_LOADED),
+ last_used(0),
+ data_size(0)
{ }
+void ResourceManager::ManagedResource::start_loading()
+{
+ io = location.collection->open_raw(location.name);
+ if(!io)
+ throw resource_load_error(location.name, "open failed");
+
+ const Resources *res = dynamic_cast<Resources *>(location.collection);
+ loader = resource->load(*io, res);
+ if(!loader)
+ {
+ delete io;
+ io = 0;
+ throw logic_error("no loader created");
+ }
+ state = LOADING;
+}
+
+bool ResourceManager::ManagedResource::process(bool sync)
+{
+ while(state!=LOAD_FINISHED && loader->needs_sync()==sync)
+ if(loader->process())
+ state = LOAD_FINISHED;
+
+ return state==LOAD_FINISHED;
+}
+
+void ResourceManager::ManagedResource::finish_loading(bool successful)
+{
+ delete loader;
+ loader = 0;
+ delete io;
+ io = 0;
+
+ if(successful)
+ {
+ state = LOADED;
+ data_size = resource->get_data_size();
+
+ for(vector<ResourceWatcher *>::const_iterator i=watchers.begin(); i!=watchers.end(); ++i)
+ (*i)->resource_loaded(*resource);
+ }
+ else
+ {
+ resource->unload();
+ state = NOT_LOADED;
+ }
+}
+
+void ResourceManager::ManagedResource::finish_loading()
+{
+ finish_loading(state==LOAD_FINISHED);
+}
+
+void ResourceManager::ManagedResource::unload()
+{
+ resource->unload();
+ state = NOT_LOADED;
+
+ for(vector<ResourceWatcher *>::const_iterator i=watchers.begin(); i!=watchers.end(); ++i)
+ (*i)->resource_unloaded(*resource);
+}
+
+void ResourceManager::ManagedResource::add_watcher(ResourceWatcher &w)
+{
+ if(find(watchers.begin(), watchers.end(), &w)==watchers.end())
+ watchers.push_back(&w);
+}
+
+void ResourceManager::ManagedResource::remove_watcher(ResourceWatcher &w)
+{
+ vector<ResourceWatcher *>::iterator end = remove(watchers.begin(), watchers.end(), &w);
+ if(end!=watchers.end())
+ watchers.erase(end, watchers.end());
+}
+
-ResourceManager::LoadingThread::LoadingThread(ResourceManager &m):
- manager(m),
+ResourceManager::LoadingThread::LoadingThread():
sem(1),
- resource(0),
- state(IDLE)
+ capacity(2),
+ size(0),
+ loaded_data_size(0),
+ done(false)
{
launch();
}
void ResourceManager::LoadingThread::main()
{
- while(state!=TERMINATING)
+ bool wait_for_work = false;
+ while(!done)
{
- sem.wait();
+ if(wait_for_work)
+ sem.wait();
- if(state==BUSY)
+ if(ManagedResource *managed = front(async_queue))
{
- Resource::AsyncLoader *ldr = resource->loader;
- bool finished = false;
- while(!finished && !ldr->needs_sync())
- finished = ldr->process();
-
- if(finished)
- state = LOAD_FINISHED;
- else
- state = SYNC_PENDING;
+ try
+ {
+ managed->process(false);
+ }
+ catch(const exception &e)
+ {
+ MutexLock lock(queue_mutex);
+ error_queue.push_back(resource_load_error(managed->location.name, e));
+ managed->state = ManagedResource::LOAD_ERROR;
+ }
+
+ MutexLock lock(queue_mutex);
+ sync_queue.splice(sync_queue.end(), async_queue, async_queue.begin());
+ wait_for_work = async_queue.empty();
}
+ else
+ wait_for_work = true;
}
}
-void ResourceManager::LoadingThread::set_resource(ManagedResource *r)
+ResourceManager::ManagedResource *ResourceManager::LoadingThread::front(LoadQueue &que)
{
- if(state!=IDLE)
+ MutexLock lock(queue_mutex);
+ if(que.empty())
+ return 0;
+
+ return que.front();
+}
+
+void ResourceManager::LoadingThread::add_resource(ManagedResource &r)
+{
+ r.start_loading();
+
+ MutexLock lock(queue_mutex);
+ if(r.loader->needs_sync())
+ sync_queue.push_back(&r);
+ else
{
- while(state==BUSY) ;
- // Force finish to clean up the loader
- state = LOAD_FINISHED;
- sync();
+ bool was_empty = async_queue.empty();
+ async_queue.push_back(&r);
+ if(was_empty)
+ sem.signal();
}
- resource = r;
- state = BUSY;
- sem.signal();
+ ++size;
}
-void ResourceManager::LoadingThread::sync()
+void ResourceManager::LoadingThread::remove_resource(ManagedResource &r)
{
- State s = state;
- bool finished = (s==LOAD_FINISHED);
- if(s==SYNC_PENDING)
+ while(!try_remove_resource(r))
+ Time::sleep(Time::msec);
+
+ r.finish_loading();
+ if(r.state==ManagedResource::LOADED)
{
- Resource::AsyncLoader *ldr = resource->loader;
- while(!finished && ldr->needs_sync())
- finished = ldr->process();
+ MutexLock lock(data_size_mutex);
+ loaded_data_size += r.data_size;
+ }
+}
- if(!finished)
+bool ResourceManager::LoadingThread::try_remove_resource(ManagedResource &r)
+{
+ MutexLock lock(queue_mutex);
+
+ LoadQueue::iterator i = find(async_queue.begin(), async_queue.end(), &r);
+ if(i==async_queue.end())
+ {
+ i = find(sync_queue.begin(), sync_queue.end(), &r);
+ if(i!=sync_queue.end())
{
- state = BUSY;
- sem.signal();
- return;
+ sync_queue.erase(i);
+ --size;
}
}
+ else if(i==async_queue.begin())
+ return false;
+ else
+ {
+ async_queue.erase(i);
+ --size;
+ }
+
+ return true;
+}
+
+bool ResourceManager::LoadingThread::sync()
+{
+ {
+ MutexLock lock(queue_mutex);
+
+ if(!error_queue.empty())
+ {
+ resource_load_error err = error_queue.front();
+ error_queue.pop_front();
+ throw err;
+ }
+
+ unsigned async_size = async_queue.size();
+ if(async_size==0 && size==capacity)
+ ++capacity;
+ else if(async_size>2 && capacity>2)
+ --capacity;
+ }
- if(finished)
+ bool any_finished = false;
+ while(ManagedResource *managed = front(sync_queue))
{
- delete resource->loader;
- resource->loader = 0;
- delete resource->io;
- resource->io = 0;
- resource = 0;
- state = IDLE;
+ if(managed->state==ManagedResource::LOAD_ERROR || managed->process(true))
+ {
+ managed->finish_loading();
+ if(managed->state==ManagedResource::LOADED)
+ {
+ MutexLock lock(data_size_mutex);
+ loaded_data_size += managed->data_size;
+ }
+ any_finished = true;
+ --size;
+
+ MutexLock lock(queue_mutex);
+ sync_queue.pop_front();
+ }
+ else
+ {
+ MutexLock lock(queue_mutex);
+ bool was_empty = async_queue.empty();
+ async_queue.splice(async_queue.end(), sync_queue, sync_queue.begin());
+ if(was_empty)
+ sem.signal();
+ }
}
+
+ return any_finished;
+}
+
+UInt64 ResourceManager::LoadingThread::get_and_reset_loaded_data_size()
+{
+ MutexLock lock(data_size_mutex);
+ UInt64 result = loaded_data_size;
+ loaded_data_size = 0;
+ return result;
}
void ResourceManager::LoadingThread::terminate()
{
- while(state==BUSY) ;
- state = TERMINATING;
+ done = true;
sem.signal();
join();
+ async_queue.clear();
+ sync_queue.clear();
}
} // namespace GL