]> git.tdb.fi Git - libs/gl.git/blobdiff - source/resources/resourcemanager.cpp
Rearrange soucre files into subdirectories
[libs/gl.git] / source / resources / resourcemanager.cpp
diff --git a/source/resources/resourcemanager.cpp b/source/resources/resourcemanager.cpp
new file mode 100644 (file)
index 0000000..30f7501
--- /dev/null
@@ -0,0 +1,550 @@
+#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 "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<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);
+
+       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<ManagedResource::LOADED)
+               thread.remove_resource(managed);
+
+       for(vector<ResourceObserver *>::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_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+min_retain_frames<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.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),
+       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<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<ResourceObserver *>::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<ResourceObserver *>::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<ResourceObserver *>::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