X-Git-Url: http://git.tdb.fi/?a=blobdiff_plain;ds=sidebyside;f=source%2Fresources%2Fresourcemanager.cpp;fp=source%2Fresources%2Fresourcemanager.cpp;h=30f75013252fe2397819cb5cb5a49330e54347de;hb=7aaec9a70b8d7733429bec043f8e33e02956f266;hp=0000000000000000000000000000000000000000;hpb=bec07999d95b76f4b47cffcc564d0cd0afc0435e;p=libs%2Fgl.git diff --git a/source/resources/resourcemanager.cpp b/source/resources/resourcemanager.cpp new file mode 100644 index 00000000..30f75013 --- /dev/null +++ b/source/resources/resourcemanager.cpp @@ -0,0 +1,550 @@ +#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), + 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) +{ + 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_managed_resource(r); + if(!managed.location.collection) + throw runtime_error("no location"); + + if(managed.state!=ManagedResource::NOT_LOADED) + return; + + 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(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) + 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 &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() +{ + ++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+min_retain_framesstate = ManagedResource::NOT_LOADED; + queue.clear(); + return; + } + + while(thread.needs_work() && !queue.empty()) + { + ManagedResource *managed = queue.front(); + queue.pop_front(); + 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), + load_priority(r.get_load_priority()), + io(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(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::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(): + Thread("ResourceManager"), + sem(1), + capacity(2), + size(0), + loaded_data_size(0), + done(false) +{ + launch(); +} + +void ResourceManager::LoadingThread::main() +{ + bool wait_for_work = false; + while(!done) + { + if(wait_for_work) + sem.wait(); + + if(ManagedResource *managed = front(async_queue)) + { + 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; + } +} + +ResourceManager::ManagedResource *ResourceManager::LoadingThread::front(LoadQueue &que) +{ + 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 + { + bool was_empty = async_queue.empty(); + async_queue.push_back(&r); + if(was_empty) + sem.signal(); + } + + ++size; +} + +void ResourceManager::LoadingThread::remove_resource(ManagedResource &r) +{ + while(!try_remove_resource(r)) + Time::sleep(Time::msec); + + r.finish_loading(); + if(r.state==ManagedResource::LOADED) + { + MutexLock lock(data_size_mutex); + loaded_data_size += r.data_size; + } +} + +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); + --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; + } + + bool any_finished = false; + while(ManagedResource *managed = front(sync_queue)) + { + 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() +{ + done = true; + sem.signal(); + join(); + async_queue.clear(); + sync_queue.clear(); +} + +} // namespace GL +} // namespace Msp