X-Git-Url: http://git.tdb.fi/?a=blobdiff_plain;f=source%2Fresourcemanager.cpp;h=6461c33620eb3aa386418afadad48cab8f6a51d6;hb=f17d748c753f0ffa03d53369cc7aaf3288a5c1e7;hp=81aea8c16f9af3f006a3030f14708ed5da6894de;hpb=7db48f2836ec42c6e2dbeff336634a03bc6d7a62;p=libs%2Fgl.git diff --git a/source/resourcemanager.cpp b/source/resourcemanager.cpp index 81aea8c1..6461c336 100644 --- a/source/resourcemanager.cpp +++ b/source/resourcemanager.cpp @@ -1,20 +1,62 @@ +#include +#include +#include +#include #include #include "resourcemanager.h" +#include "resourcewatcher.h" using namespace std; namespace Msp { namespace GL { +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) + 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) @@ -22,6 +64,11 @@ void ResourceManager::add_resource(Resource &r) insert_unique(resources, &r, ManagedResource(r)); } +void *ResourceManager::get_data_for_resource(const Resource &r) +{ + return &get_item(resources, &r); +} + void ResourceManager::set_resource_location(Resource &r, DataFile::Collection &c, const string &n) { ManagedResource &managed = get_item(resources, &r); @@ -32,132 +79,395 @@ void ResourceManager::set_resource_location(Resource &r, DataFile::Collection &c load_resource(r); } -void ResourceManager::load_resource(const Resource &r) +void ResourceManager::load_resource(Resource &r) { ManagedResource &managed = get_item(resources, &r); if(!managed.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; + queue.push_back(&managed); + } + else + { + managed.start_loading(); + while(!managed.loader->process()) ; + managed.finish_loading(true); + } +} - queue.push_back(&managed); +bool ResourceManager::is_resource_loaded(const Resource &r) +{ + ManagedResource *managed = reinterpret_cast(r.get_manager_data()); + return managed ? managed->state==ManagedResource::LOADED : false; } -void ResourceManager::remove_resource(Resource &r) +void ResourceManager::resource_used(const Resource &r) { - ManagedResource *loading = thread.get_resource(); - if(loading && loading->resource==&r) - thread.set_resource(0); + ManagedResource *managed = reinterpret_cast(r.get_manager_data()); + if(managed->state==ManagedResource::NOT_LOADED && policy!=LOAD_MANUALLY) + load_resource(*managed->resource); + + managed->last_used = frame; + if(max_retain_frames && !next_unload) + next_unload = frame+max_retain_frames+1; +} +void ResourceManager::remove_resource(Resource &r) +{ + ManagedResource &managed = get_item(resources, &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=next_unload); + if(thread.sync()) + do_unload = true; + + if(thread.needs_work() && !queue.empty()) + dispatch_work(); + + if(do_unload) + { + 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_unloadlast_used+10state = 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_usedsecond.unload(); +} + +void ResourceManager::unload_by_size() +{ + unsigned unload_limit = frame-min_retain_frames; + + while(get_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_usedsecond.data_size*(frame-i->second.last_used); + if(!best || impact>best_impact) + { + best = &i->second; + best_impact = impact; + } + } + + if(!best) + break; + + best->unload(); + } +} + +UInt64 ResourceManager::get_total_data_size() const +{ + UInt64 total = 0; + for(ResourceMap::const_iterator i=resources.begin(); i!=resources.end(); ++i) + if(i->second.state==ManagedResource::LOADED) + total += i->second.data_size; + return total; +} + +bool ResourceManager::age_order(ManagedResource *mr1, ManagedResource *mr2) +{ + return mr1->last_used>mr2->last_used; +} + ResourceManager::ManagedResource::ManagedResource(Resource &r): resource(&r), collection(0), io(0), - loader(0) + loader(0), + state(NOT_LOADED), + last_used(0), + data_size(0) { } +void ResourceManager::ManagedResource::start_loading() +{ + io = collection->open_raw(name); + loader = resource->load(*io); + 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::const_iterator i=watchers.begin(); i!=watchers.end(); ++i) + (*i)->resource_loaded(*resource); + } + else + { + resource->unload(); + state = NOT_LOADED; + } +} -ResourceManager::LoadingThread::LoadingThread(ResourceManager &m): - manager(m), +void ResourceManager::ManagedResource::finish_loading() +{ + finish_loading(state==LOAD_FINISHED); +} + +void ResourceManager::ManagedResource::unload() +{ + resource->unload(); + state = NOT_LOADED; + + for(vector::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::iterator end = remove(watchers.begin(), watchers.end(), &w); + if(end!=watchers.end()) + watchers.erase(end, watchers.end()); +} + + +ResourceManager::LoadingThread::LoadingThread(): sem(1), - resource(0), - state(IDLE) + capacity(2), + 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->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 &queue) { - if(state!=IDLE) + MutexLock lock(queue_mutex); + if(queue.empty()) + return 0; + + return queue.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) +{ + while(!try_remove_resource(r)) + Time::sleep(Time::msec); + + r.finish_loading(); +} + +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()) + sync_queue.erase(i); + } + else if(i==async_queue.begin()) + return false; + else + async_queue.erase(i); + + return true; +} + +bool ResourceManager::LoadingThread::sync() { - State s = state; - bool finished = (s==LOAD_FINISHED); - if(s==SYNC_PENDING) { - Resource::AsyncLoader *ldr = resource->loader; - while(!finished && ldr->needs_sync()) - finished = ldr->process(); + MutexLock lock(queue_mutex); - if(!finished) + if(!error_queue.empty()) { - state = BUSY; - sem.signal(); - return; + 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(); + 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; } void ResourceManager::LoadingThread::terminate() { - while(state==BUSY) ; - state = TERMINATING; + done = true; sem.signal(); join(); + async_queue.clear(); + sync_queue.clear(); } } // namespace GL