]> git.tdb.fi Git - libs/gl.git/blob - source/resourcemanager.cpp
Avoid segfaults when LoadingThread's resource is cleared
[libs/gl.git] / source / resourcemanager.cpp
1 #include <algorithm>
2 #include <msp/time/utils.h>
3 #include "resourcemanager.h"
4 #include "resourcewatcher.h"
5
6 using namespace std;
7
8 namespace Msp {
9 namespace GL {
10
11 ResourceManager::ResourceManager():
12         policy(LOAD_ON_DEMAND),
13         async_loads(true),
14         size_limit(0),
15         frame(0),
16         min_retain_frames(30),
17         max_retain_frames(0),
18         next_unload(0),
19         thread(*this)
20 { }
21
22 ResourceManager::~ResourceManager()
23 {
24         thread.terminate();
25
26         while(!resources.empty())
27                 resources.begin()->second.resource->set_manager(0);
28 }
29
30 void ResourceManager::set_loading_policy(LoadingPolicy p)
31 {
32         policy = p;
33 }
34
35 void ResourceManager::set_async_loads(bool a)
36 {
37         async_loads = a;
38 }
39
40 void ResourceManager::set_size_limit(UInt64 s)
41 {
42         size_limit = s;
43 }
44
45 void ResourceManager::set_min_retain_frames(unsigned f)
46 {
47         min_retain_frames = f;
48 }
49
50 void ResourceManager::set_max_retain_frames(unsigned f)
51 {
52         max_retain_frames = f;
53 }
54
55 void ResourceManager::add_resource(Resource &r)
56 {
57         insert_unique(resources, &r, ManagedResource(r));
58 }
59
60 void *ResourceManager::get_data_for_resource(const Resource &r)
61 {
62         return &get_item(resources, &r);
63 }
64
65 void ResourceManager::set_resource_location(Resource &r, DataFile::Collection &c, const string &n)
66 {
67         ManagedResource &managed = get_item(resources, &r);
68         managed.collection = &c;
69         managed.name = n;
70
71         if(policy==LOAD_IMMEDIATELY)
72                 load_resource(r);
73 }
74
75 void ResourceManager::load_resource(Resource &r)
76 {
77         ManagedResource &managed = get_item(resources, &r);
78         if(!managed.collection)
79                 throw runtime_error("no location");
80
81         if(managed.state!=ManagedResource::NOT_LOADED)
82                 return;
83
84         if(async_loads)
85         {
86                 managed.state = ManagedResource::LOAD_QUEUED;
87                 queue.push_back(&managed);
88         }
89         else
90         {
91                 managed.start_loading();
92                 while(!managed.loader->process()) ;
93                 managed.finish_loading();
94         }
95 }
96
97 void ResourceManager::resource_used(const Resource &r)
98 {
99         ManagedResource *managed = reinterpret_cast<ManagedResource *>(r.get_manager_data());
100         if(managed->state==ManagedResource::NOT_LOADED && policy!=LOAD_MANUALLY)
101                 load_resource(*managed->resource);
102
103         managed->last_used = frame;
104         if(max_retain_frames && !next_unload)
105                 next_unload = frame+max_retain_frames;
106 }
107
108 void ResourceManager::remove_resource(Resource &r)
109 {
110         ManagedResource *loading = thread.get_resource();
111         if(loading && loading->resource==&r)
112                 thread.set_resource(0);
113
114         remove_existing(resources, &r);
115 }
116
117 void ResourceManager::watch_resource(const Resource &r, ResourceWatcher &w)
118 {
119         get_item(resources, &r).add_watcher(w);
120 }
121
122 void ResourceManager::unwatch_resource(const Resource &r, ResourceWatcher &w)
123 {
124         get_item(resources, &r).remove_watcher(w);
125 }
126
127 void ResourceManager::tick()
128 {
129         LoadingThread::State thread_state = thread.get_state();
130         bool check_total_size = false;
131         if(thread_state==LoadingThread::SYNC_PENDING || thread_state==LoadingThread::LOAD_FINISHED)
132         {
133                 thread.sync();
134                 check_total_size = true;
135         }
136         else if(thread_state==LoadingThread::IDLE && !queue.empty())
137         {
138                 ManagedResource *managed = queue.front();
139                 queue.pop_front();
140                 thread.set_resource(managed);
141         }
142
143         ++frame;
144         if(frame==next_unload)
145         {
146                 unsigned unload_limit = frame-max_retain_frames;
147                 next_unload = 0;
148
149                 for(ResourceMap::iterator i=resources.begin(); i!=resources.end(); ++i)
150                         if(i->second.state==ManagedResource::LOADED)
151                         {
152                                 if(i->second.last_used<=unload_limit)
153                                         i->second.unload();
154                                 else if(!next_unload || i->second.last_used<next_unload)
155                                         next_unload = i->second.last_used;
156                         }
157
158                 if(next_unload)
159                         next_unload += max_retain_frames;
160         }
161
162         if(check_total_size)
163         {
164                 while(get_total_data_size()>size_limit)
165                 {
166                         unsigned unload_limit = frame-min_retain_frames;
167                         ManagedResource *best = 0;
168                         UInt64 best_impact = 0;
169                         for(ResourceMap::iterator i=resources.begin(); i!=resources.end(); ++i)
170                                 if(i->second.state==ManagedResource::LOADED && i->second.last_used<unload_limit)
171                                 {
172                                         UInt64 impact = i->second.data_size*(frame-i->second.last_used);
173                                         if(!best || impact>best_impact)
174                                         {
175                                                 best = &i->second;
176                                                 best_impact = impact;
177                                         }
178                                 }
179
180                         if(!best)
181                                 break;
182
183                         best->unload();
184                 }
185         }
186 }
187
188 UInt64 ResourceManager::get_total_data_size() const
189 {
190         UInt64 total = 0;
191         for(ResourceMap::const_iterator i=resources.begin(); i!=resources.end(); ++i)
192                 if(i->second.state==ManagedResource::LOADED)
193                         total += i->second.data_size;
194         return total;
195 }
196
197
198 ResourceManager::ManagedResource::ManagedResource(Resource &r):
199         resource(&r),
200         collection(0),
201         io(0),
202         loader(0),
203         state(NOT_LOADED),
204         last_used(0),
205         data_size(0)
206 { }
207
208 void ResourceManager::ManagedResource::start_loading()
209 {
210         io = collection->open_raw(name);
211         loader = resource->load(*io);
212         if(!loader)
213         {
214                 delete io;
215                 io = 0;
216                 throw logic_error("no loader created");
217         }
218         state = LOADING;
219 }
220
221 void ResourceManager::ManagedResource::finish_loading()
222 {
223         delete loader;
224         loader = 0;
225         state = LOADED;
226         delete io;
227         io = 0;
228         data_size = resource->get_data_size();
229
230         for(vector<ResourceWatcher *>::const_iterator i=watchers.begin(); i!=watchers.end(); ++i)
231                 (*i)->resource_loaded(*resource);
232 }
233
234 void ResourceManager::ManagedResource::unload()
235 {
236         resource->unload();
237         state = NOT_LOADED;
238
239         for(vector<ResourceWatcher *>::const_iterator i=watchers.begin(); i!=watchers.end(); ++i)
240                 (*i)->resource_unloaded(*resource);
241 }
242
243 void ResourceManager::ManagedResource::add_watcher(ResourceWatcher &w)
244 {
245         if(find(watchers.begin(), watchers.end(), &w)==watchers.end())
246                 watchers.push_back(&w);
247 }
248
249 void ResourceManager::ManagedResource::remove_watcher(ResourceWatcher &w)
250 {
251         vector<ResourceWatcher *>::iterator end = remove(watchers.begin(), watchers.end(), &w);
252         if(end!=watchers.end())
253                 watchers.erase(end, watchers.end());
254 }
255
256
257 ResourceManager::LoadingThread::LoadingThread(ResourceManager &m):
258         manager(m),
259         sem(1),
260         resource(0),
261         state(IDLE)
262 {
263         launch();
264 }
265
266 void ResourceManager::LoadingThread::main()
267 {
268         while(state!=TERMINATING)
269         {
270                 sem.wait();
271
272                 if(state==BUSY)
273                 {
274                         Resource::AsyncLoader *ldr = resource->loader;
275                         bool finished = false;
276                         while(!finished && !ldr->needs_sync())
277                                 finished = ldr->process();
278
279                         if(finished)
280                                 state = LOAD_FINISHED;
281                         else
282                                 state = SYNC_PENDING;
283                 }
284         }
285 }
286
287 void ResourceManager::LoadingThread::set_resource(ManagedResource *r)
288 {
289         if(state!=IDLE)
290         {
291                 while(state==BUSY) ;
292                 // Force finish to clean up the loader
293                 state = LOAD_FINISHED;
294                 sync();
295         }
296
297         resource = r;
298         if(resource)
299         {
300                 resource->start_loading();
301                 state = BUSY;
302                 sem.signal();
303         }
304 }
305
306 void ResourceManager::LoadingThread::sync()
307 {
308         State s = state;
309         bool finished = (s==LOAD_FINISHED);
310         if(s==SYNC_PENDING)
311         {
312                 Resource::AsyncLoader *ldr = resource->loader;
313                 while(!finished && ldr->needs_sync())
314                         finished = ldr->process();
315
316                 if(!finished)
317                 {
318                         state = BUSY;
319                         sem.signal();
320                         return;
321                 }
322         }
323
324         if(finished)
325         {
326                 resource->finish_loading();
327                 resource = 0;
328                 state = IDLE;
329         }
330 }
331
332 void ResourceManager::LoadingThread::terminate()
333 {
334         while(state==BUSY) ;
335         state = TERMINATING;
336         sem.signal();
337         join();
338 }
339
340 } // namespace GL
341 } // namespace Msp