X-Git-Url: http://git.tdb.fi/?p=libs%2Fgl.git;a=blobdiff_plain;f=source%2Fresourcemanager.cpp;h=4b72685d4d4ba6f4594a2349101cc253b9ecd36f;hp=cc69b895aa2ecd33196df0b087e0d3521ee2999c;hb=71240e5c5ef7165313664ee9fe81df95c0eff10b;hpb=47abe7c9e1633ca65f910a4db340724117a6f6e5 diff --git a/source/resourcemanager.cpp b/source/resourcemanager.cpp index cc69b895..4b72685d 100644 --- a/source/resourcemanager.cpp +++ b/source/resourcemanager.cpp @@ -1,23 +1,43 @@ +#include +#include +#include +#include #include #include "resourcemanager.h" +#include "resources.h" +#include "resourceobserver.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(); - for(ResourceMap::iterator i=resources.begin(); i!=resources.end(); ++i) - i->second.resource->set_manager(0); + while(!resources.empty()) + resources.begin()->second.resource->set_manager(0); } void ResourceManager::set_loading_policy(LoadingPolicy p) @@ -30,183 +50,499 @@ 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)); } -void *ResourceManager::get_data_for_resource(const Resource &r) +const ResourceManager::ManagedResource &ResourceManager::get_managed_resource(const Resource &r) const { - return &get_item(resources, &r); + 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); } +const ResourceManager::ResourceLocation *ResourceManager::get_resource_location(const Resource &r) const +{ + const ManagedResource &managed = get_managed_resource(r); + return managed.location.collection ? &managed.location : 0; +} + void ResourceManager::load_resource(Resource &r) { - ManagedResource &managed = get_item(resources, &r); - if(!managed.collection) + 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.start_loading(); - if(async_loads) - queue.push_back(&managed); + { + 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(); + managed.finish_loading(true); + total_data_size += managed.data_size; } } +bool ResourceManager::is_resource_loaded(const Resource &r) const +{ + ManagedResource *managed = reinterpret_cast(r.get_manager_data()); + return managed ? managed->state==ManagedResource::LOADED : false; +} + void ResourceManager::resource_used(const Resource &r) { ManagedResource *managed = reinterpret_cast(r.get_manager_data()); - if(!managed->loaded && !managed->loader && policy!=LOAD_MANUALLY) + if(!managed) + return; + 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 *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::const_iterator i=managed.observers.begin(); i!=managed.observers.end(); ++i) + (*i)->resource_removed(r); + + MutexLock lock(map_mutex); remove_existing(resources, &r); } +void ResourceManager::observe_resource(const Resource &r, ResourceObserver &w) +{ + get_managed_resource(r).add_observer(w); +} + +void ResourceManager::unobserve_resource(const Resource &r, ResourceObserver &w) +{ + get_managed_resource(r).remove_observer(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_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(); + 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_usedsecond.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), - loaded(false) + state(NOT_LOADED), + last_used(0), + data_size(0) { } void ResourceManager::ManagedResource::start_loading() { - io = collection->open_raw(name); - loader = resource->load(*io); + io = location.collection->open_raw(location.name); + if(!io) + throw resource_load_error(location.name, "open failed"); + + const Resources *res = dynamic_cast(location.collection); + loader = resource->load(*io, res); if(!loader) { delete io; io = 0; throw logic_error("no loader created"); } + state = LOADING; } -void ResourceManager::ManagedResource::finish_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; - loaded = true; delete io; io = 0; + + if(successful) + { + state = LOADED; + data_size = resource->get_data_size(); + + for(vector::const_iterator i=observers.begin(); i!=observers.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::const_iterator i=observers.begin(); i!=observers.end(); ++i) + (*i)->resource_unloaded(*resource); +} + +void ResourceManager::ManagedResource::add_observer(ResourceObserver &w) +{ + if(find(observers.begin(), observers.end(), &w)==observers.end()) + observers.push_back(&w); +} + +void ResourceManager::ManagedResource::remove_observer(ResourceObserver &w) +{ + vector::iterator end = remove(observers.begin(), observers.end(), &w); + if(end!=observers.end()) + observers.erase(end, observers.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)) { - resource->finish_loading(); - 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