]> git.tdb.fi Git - libs/gl.git/blob - source/resourcemanager.cpp
Pass errors from the resource loading thread to the main thread
[libs/gl.git] / source / resourcemanager.cpp
1 #include <algorithm>
2 #include <typeinfo>
3 #include <msp/debug/demangle.h>
4 #include <msp/strings/format.h>
5 #include <msp/time/utils.h>
6 #include "resourcemanager.h"
7 #include "resourcewatcher.h"
8
9 using namespace std;
10
11 namespace Msp {
12 namespace GL {
13
14 resource_load_error::resource_load_error(const string &name, const exception &exc):
15         runtime_error(format("%s: %s: %s", name, Debug::demangle(typeid(exc).name()), exc.what()))
16 { }
17
18
19 ResourceManager::ResourceManager():
20         policy(LOAD_ON_DEMAND),
21         async_loads(true),
22         size_limit(0),
23         frame(0),
24         min_retain_frames(30),
25         max_retain_frames(0),
26         next_unload(0)
27 { }
28
29 ResourceManager::~ResourceManager()
30 {
31         thread.terminate();
32
33         while(!resources.empty())
34                 resources.begin()->second.resource->set_manager(0);
35 }
36
37 void ResourceManager::set_loading_policy(LoadingPolicy p)
38 {
39         policy = p;
40 }
41
42 void ResourceManager::set_async_loads(bool a)
43 {
44         async_loads = a;
45 }
46
47 void ResourceManager::set_size_limit(UInt64 s)
48 {
49         size_limit = s;
50 }
51
52 void ResourceManager::set_min_retain_frames(unsigned f)
53 {
54         min_retain_frames = f;
55 }
56
57 void ResourceManager::set_max_retain_frames(unsigned f)
58 {
59         max_retain_frames = f;
60 }
61
62 void ResourceManager::add_resource(Resource &r)
63 {
64         insert_unique(resources, &r, ManagedResource(r));
65 }
66
67 void *ResourceManager::get_data_for_resource(const Resource &r)
68 {
69         return &get_item(resources, &r);
70 }
71
72 void ResourceManager::set_resource_location(Resource &r, DataFile::Collection &c, const string &n)
73 {
74         ManagedResource &managed = get_item(resources, &r);
75         managed.collection = &c;
76         managed.name = n;
77
78         if(policy==LOAD_IMMEDIATELY)
79                 load_resource(r);
80 }
81
82 void ResourceManager::load_resource(Resource &r)
83 {
84         ManagedResource &managed = get_item(resources, &r);
85         if(!managed.collection)
86                 throw runtime_error("no location");
87
88         if(managed.state!=ManagedResource::NOT_LOADED)
89                 return;
90
91         if(async_loads)
92         {
93                 managed.state = ManagedResource::LOAD_QUEUED;
94                 queue.push_back(&managed);
95         }
96         else
97         {
98                 managed.start_loading();
99                 while(!managed.loader->process()) ;
100                 managed.finish_loading(true);
101         }
102 }
103
104 void ResourceManager::resource_used(const Resource &r)
105 {
106         ManagedResource *managed = reinterpret_cast<ManagedResource *>(r.get_manager_data());
107         if(managed->state==ManagedResource::NOT_LOADED && policy!=LOAD_MANUALLY)
108                 load_resource(*managed->resource);
109
110         managed->last_used = frame;
111         if(max_retain_frames && !next_unload)
112                 next_unload = frame+max_retain_frames+1;
113 }
114
115 void ResourceManager::remove_resource(Resource &r)
116 {
117         ManagedResource &managed = get_item(resources, &r);
118         ManagedResource::State state = managed.state;
119         if(state==ManagedResource::LOAD_QUEUED)
120         {
121                 LoadQueue::iterator i = find(queue.begin(), queue.end(), &managed);
122                 if(i!=queue.end())
123                         queue.erase(i);
124         }
125         else if(state>ManagedResource::LOAD_QUEUED && state<ManagedResource::LOADED)
126                 thread.remove_resource(managed);
127         remove_existing(resources, &r);
128 }
129
130 void ResourceManager::watch_resource(const Resource &r, ResourceWatcher &w)
131 {
132         get_item(resources, &r).add_watcher(w);
133 }
134
135 void ResourceManager::unwatch_resource(const Resource &r, ResourceWatcher &w)
136 {
137         get_item(resources, &r).remove_watcher(w);
138 }
139
140 void ResourceManager::tick()
141 {
142         ++frame;
143
144         bool do_unload = (frame>=next_unload);
145         if(thread.sync())
146                 do_unload = true;
147
148         if(thread.needs_work() && !queue.empty())
149                 dispatch_work();
150
151         if(do_unload)
152         {
153                 if(max_retain_frames && frame>=next_unload)
154                 {
155                         unload_by_age();
156
157                         next_unload = frame;
158                         for(ResourceMap::iterator i=resources.begin(); i!=resources.end(); ++i)
159                                 if(i->second.state==ManagedResource::LOADED)
160                                         next_unload = min(next_unload, i->second.last_used);
161                         next_unload = (next_unload<frame ? next_unload+max_retain_frames : 0);
162                 }
163
164                 if(size_limit)
165                         unload_by_size();
166         }
167 }
168
169 void ResourceManager::dispatch_work()
170 {
171         queue.sort(age_order);
172
173         if(queue.front()->last_used+10<frame)
174         {
175                 for(LoadQueue::iterator i=queue.begin(); i!=queue.end(); ++i)
176                         (*i)->state = ManagedResource::NOT_LOADED;
177                 queue.clear();
178                 return;
179         }
180
181         while(thread.needs_work() && !queue.empty())
182         {
183                 ManagedResource *managed = queue.front();
184                 queue.pop_front();
185                 thread.add_resource(*managed);
186         }
187 }
188
189 void ResourceManager::unload_by_age()
190 {
191         unsigned unload_limit = frame-max_retain_frames;
192
193         for(ResourceMap::iterator i=resources.begin(); i!=resources.end(); ++i)
194                 if(i->second.state==ManagedResource::LOADED && i->second.last_used<unload_limit)
195                         i->second.unload();
196 }
197
198 void ResourceManager::unload_by_size()
199 {
200         unsigned unload_limit = frame-min_retain_frames;
201
202         while(get_total_data_size()>size_limit)
203         {
204                 ManagedResource *best = 0;
205                 UInt64 best_impact = 0;
206                 for(ResourceMap::iterator i=resources.begin(); i!=resources.end(); ++i)
207                         if(i->second.state==ManagedResource::LOADED && i->second.last_used<unload_limit)
208                         {
209                                 UInt64 impact = i->second.data_size*(frame-i->second.last_used);
210                                 if(!best || impact>best_impact)
211                                 {
212                                         best = &i->second;
213                                         best_impact = impact;
214                                 }
215                         }
216
217                 if(!best)
218                         break;
219
220                 best->unload();
221         }
222 }
223
224 UInt64 ResourceManager::get_total_data_size() const
225 {
226         UInt64 total = 0;
227         for(ResourceMap::const_iterator i=resources.begin(); i!=resources.end(); ++i)
228                 if(i->second.state==ManagedResource::LOADED)
229                         total += i->second.data_size;
230         return total;
231 }
232
233 bool ResourceManager::age_order(ManagedResource *mr1, ManagedResource *mr2)
234 {
235         return mr1->last_used>mr2->last_used;
236 }
237
238
239 ResourceManager::ManagedResource::ManagedResource(Resource &r):
240         resource(&r),
241         collection(0),
242         io(0),
243         loader(0),
244         state(NOT_LOADED),
245         last_used(0),
246         data_size(0)
247 { }
248
249 void ResourceManager::ManagedResource::start_loading()
250 {
251         io = collection->open_raw(name);
252         loader = resource->load(*io);
253         if(!loader)
254         {
255                 delete io;
256                 io = 0;
257                 throw logic_error("no loader created");
258         }
259         state = LOADING;
260 }
261
262 bool ResourceManager::ManagedResource::process(bool sync)
263 {
264         while(state!=LOAD_FINISHED && loader->needs_sync()==sync)
265                 if(loader->process())
266                         state = LOAD_FINISHED;
267
268         return state==LOAD_FINISHED;
269 }
270
271 void ResourceManager::ManagedResource::finish_loading(bool successful)
272 {
273         delete loader;
274         loader = 0;
275         delete io;
276         io = 0;
277
278         if(successful)
279         {
280                 state = LOADED;
281                 data_size = resource->get_data_size();
282
283                 for(vector<ResourceWatcher *>::const_iterator i=watchers.begin(); i!=watchers.end(); ++i)
284                         (*i)->resource_loaded(*resource);
285         }
286         else
287         {
288                 resource->unload();
289                 state = NOT_LOADED;
290         }
291 }
292
293 void ResourceManager::ManagedResource::finish_loading()
294 {
295         finish_loading(state==LOAD_FINISHED);
296 }
297
298 void ResourceManager::ManagedResource::unload()
299 {
300         resource->unload();
301         state = NOT_LOADED;
302
303         for(vector<ResourceWatcher *>::const_iterator i=watchers.begin(); i!=watchers.end(); ++i)
304                 (*i)->resource_unloaded(*resource);
305 }
306
307 void ResourceManager::ManagedResource::add_watcher(ResourceWatcher &w)
308 {
309         if(find(watchers.begin(), watchers.end(), &w)==watchers.end())
310                 watchers.push_back(&w);
311 }
312
313 void ResourceManager::ManagedResource::remove_watcher(ResourceWatcher &w)
314 {
315         vector<ResourceWatcher *>::iterator end = remove(watchers.begin(), watchers.end(), &w);
316         if(end!=watchers.end())
317                 watchers.erase(end, watchers.end());
318 }
319
320
321 ResourceManager::LoadingThread::LoadingThread():
322         sem(1),
323         capacity(2),
324         size(0),
325         done(false)
326 {
327         launch();
328 }
329
330 void ResourceManager::LoadingThread::main()
331 {
332         bool wait_for_work = false;
333         while(!done)
334         {
335                 if(wait_for_work)
336                         sem.wait();
337
338                 if(ManagedResource *managed = front(async_queue))
339                 {
340                         try
341                         {
342                                 managed->process(false);
343                         }
344                         catch(const exception &e)
345                         {
346                                 MutexLock lock(queue_mutex);
347                                 error_queue.push_back(resource_load_error(managed->name, e));
348                                 managed->state = ManagedResource::LOAD_ERROR;
349                         }
350
351                         MutexLock lock(queue_mutex);
352                         sync_queue.splice(sync_queue.end(), async_queue, async_queue.begin());
353                         wait_for_work = async_queue.empty();
354                 }
355                 else
356                         wait_for_work = true;
357         }
358 }
359
360 ResourceManager::ManagedResource *ResourceManager::LoadingThread::front(LoadQueue &queue)
361 {
362         MutexLock lock(queue_mutex);
363         if(queue.empty())
364                 return 0;
365
366         return queue.front();
367 }
368
369 void ResourceManager::LoadingThread::add_resource(ManagedResource &r)
370 {
371         r.start_loading();
372
373         MutexLock lock(queue_mutex);
374         if(r.loader->needs_sync())
375                 sync_queue.push_back(&r);
376         else
377         {
378                 bool was_empty = async_queue.empty();
379                 async_queue.push_back(&r);
380                 if(was_empty)
381                         sem.signal();
382         }
383
384         ++size;
385 }
386
387 void ResourceManager::LoadingThread::remove_resource(ManagedResource &r)
388 {
389         while(!try_remove_resource(r))
390                 Time::sleep(Time::msec);
391
392         r.finish_loading();
393 }
394
395 bool ResourceManager::LoadingThread::try_remove_resource(ManagedResource &r)
396 {
397         MutexLock lock(queue_mutex);
398
399         LoadQueue::iterator i = find(async_queue.begin(), async_queue.end(), &r);
400         if(i==async_queue.end())
401         {
402                 i = find(sync_queue.begin(), sync_queue.end(), &r);
403                 if(i!=sync_queue.end())
404                         sync_queue.erase(i);
405         }
406         else if(i==async_queue.begin())
407                 return false;
408         else
409                 async_queue.erase(i);
410
411         return true;
412 }
413
414 bool ResourceManager::LoadingThread::sync()
415 {
416         {
417                 MutexLock lock(queue_mutex);
418
419                 if(!error_queue.empty())
420                 {
421                         resource_load_error err = error_queue.front();
422                         error_queue.pop_front();
423                         throw err;
424                 }
425
426                 unsigned async_size = async_queue.size();
427                 if(async_size==0 && size==capacity)
428                         ++capacity;
429                 else if(async_size>2 && capacity>2)
430                         --capacity;
431         }
432
433         bool any_finished = false;
434         while(ManagedResource *managed = front(sync_queue))
435         {
436                 if(managed->state==ManagedResource::LOAD_ERROR || managed->process(true))
437                 {
438                         managed->finish_loading();
439                         any_finished = true;
440                         --size;
441
442                         MutexLock lock(queue_mutex);
443                         sync_queue.pop_front();
444                 }
445                 else
446                 {
447                         MutexLock lock(queue_mutex);
448                         bool was_empty = async_queue.empty();
449                         async_queue.splice(async_queue.end(), sync_queue, sync_queue.begin());
450                         if(was_empty)
451                                 sem.signal();
452                 }
453         }
454
455         return any_finished;
456 }
457
458 void ResourceManager::LoadingThread::terminate()
459 {
460         done = true;
461         sem.signal();
462         join();
463         async_queue.clear();
464         sync_queue.clear();
465 }
466
467 } // namespace GL
468 } // namespace Msp