/[svn]/linuxsampler/trunk/src/common/ResourceManager.h
ViewVC logotype

Contents of /linuxsampler/trunk/src/common/ResourceManager.h

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1040 - (show annotations) (download) (as text)
Wed Feb 7 15:41:31 2007 UTC (17 years, 2 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 26709 byte(s)
* bugfix: sampler crashed when trying to persistently map a not existent or
  corrupt .gig file ("MAP MIDI_INSTRUMENT ... PERSISTENT")
* behavior fix: reset FX send levels i.e. when receiving a MIDI "reset all
  controllers" message
* bumped version to 0.4.0.3cvs

1 /***************************************************************************
2 * *
3 * LinuxSampler - modular, streaming capable sampler *
4 * *
5 * Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck *
6 * Copyright (C) 2005 - 2007 Christian Schoenebeck *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the Free Software *
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
21 * MA 02111-1307 USA *
22 ***************************************************************************/
23
24 #ifndef __RESOURCE_MANAGER__
25 #define __RESOURCE_MANAGER__
26
27 #include <set>
28 #include <map>
29 #include <vector>
30
31 #include "Exception.h"
32 #include "Mutex.h"
33
34 namespace LinuxSampler {
35
36 /** @brief Interface class for Resource Consumers.
37 *
38 * Interface class for consumer classes which use a resource managed
39 * by the ResourceManager class. All classes which use the ResourceManager
40 * to aquire resources have to derive from this interface class and
41 * implement the abstract methods.
42 */
43 template<class T_res>
44 class ResourceConsumer {
45 public:
46 /**
47 * Will be called by the ResourceManager to inform the
48 * consumer that a resource currently used by him is going
49 * to be updated. The consumer can then react by stopping
50 * usage until resource is updated. The ResourceManager will
51 * not update the resource until this method returns. This
52 * method needs to be implemented by the consumer.
53 *
54 * @param pResource - resource going to be updated
55 * @param pUpdateArg - pointer the consumer might use to store
56 * informations he might need when update
57 * process was completed
58 */
59 virtual void ResourceToBeUpdated(T_res* pResource, void*& pUpdateArg) = 0;
60
61 /**
62 * Will be called by the ResourceManager to inform the
63 * consumer that resource update was completed. This method
64 * needs to be implemented by the consumer.
65 *
66 * @param pOldResource - (now invalid) pointer to the old
67 * resource
68 * @param pNewResource - (valid) pointer to the updated
69 * resource
70 * @param pUpdateArg - pointer the consumer might have used when
71 * ResourceToBeUpdated() was called
72 */
73 virtual void ResourceUpdated(T_res* pOldResource, T_res* pNewResource, void* pUpdateArg) = 0;
74
75 /**
76 * Might be called by the ResourceManager periodically during an
77 * update / creation of a resource to inform the consumer about the
78 * current progress of that process. This method needs to be
79 * implemented by the consumer.
80 *
81 * @param fProgress - current progress as value between 0.0 and 1.0
82 */
83 virtual void OnResourceProgress(float fProgress) = 0;
84 };
85
86 /** @brief Manager for sharing resources.
87 *
88 * Abstract base class for sharing resources between multiple consumers.
89 * A consumer can borrow a resource from the ResourceManager, if the
90 * resource doesn't exist yet it will be created. Other consumers will
91 * just be given the same pointer to the resource then. When all consumers
92 * gave back their pointer to the resource, the resource will (by default)
93 * be destroyed.
94 *
95 * Descendants of this base class have to implement the (protected)
96 * Create() and Destroy() methods to create and destroy a resource.
97 *
98 * This class is thread safe (by default). Its methods should however not
99 * be called in a realtime context due to this! Alternatively one can
100 * call the respective methods with bLock = false, in that case thread
101 * safety mechanisms will be omitted - use with care !
102 */
103 template<class T_key, class T_res>
104 class ResourceManager {
105 public:
106 /**
107 * Defines life-time strategy for resources.
108 */
109 enum mode_t {
110 ON_DEMAND = 0, ///< Create resource when needed, free it once not needed anymore (default behavior).
111 ON_DEMAND_HOLD = 1, ///< Create resource when needed and keep it even if not needed anymore.
112 PERSISTENT = 2 ///< Immediately create resource and keep it.
113 };
114
115 private:
116 typedef std::set<ResourceConsumer<T_res>*> ConsumerSet;
117 struct resource_entry_t {
118 T_key key;
119 T_res* resource; ///< pointer to the resource
120 mode_t mode; ///< When should the resource be created? When should it be destroyed?
121 ConsumerSet consumers; ///< list of all consumers who currently use the resource
122 void* lifearg; ///< optional pointer the descendant might use to store informations about a created resource
123 void* entryarg; ///< optional pointer the descendant might use to store informations about an entry
124 };
125 typedef std::map<T_key, resource_entry_t> ResourceMap;
126 ResourceMap ResourceEntries;
127 Mutex ResourceEntriesMutex; // Mutex for protecting the ResourceEntries map
128
129 public:
130 /**
131 * Returns (the keys of) all current entries of this
132 * ResourceManager instance.
133 *
134 * @param bLock - use thread safety mechanisms
135 */
136 std::vector<T_key> Entries(bool bLock = true) {
137 std::vector<T_key> result;
138 if (bLock) ResourceEntriesMutex.Lock();
139 for (typename ResourceMap::iterator iter = ResourceEntries.begin();
140 iter != ResourceEntries.end(); iter++)
141 {
142 result.push_back(iter->first);
143 }
144 if (bLock) ResourceEntriesMutex.Unlock();
145 return result;
146 }
147
148 /**
149 * Borrow a resource identified by \a Key. The ResourceManager will
150 * mark the resource as in usage by the consumer given with
151 * \a pConsumer. If the Resource doesn't exist yet it will be
152 * created.
153 *
154 * @param Key - resource identifier
155 * @param pConsumer - identifier of the consumer who borrows it
156 * @param bLock - use thread safety mechanisms
157 * @returns pointer to resource
158 */
159 T_res* Borrow(T_key Key, ResourceConsumer<T_res>* pConsumer, bool bLock = true) {
160 if (bLock) ResourceEntriesMutex.Lock();
161 // search for an entry for this resource
162 typename ResourceMap::iterator iterEntry = ResourceEntries.find(Key);
163 if (iterEntry == ResourceEntries.end()) { // entry doesn't exist yet
164 // already create an entry for the resource
165 resource_entry_t entry;
166 entry.key = Key;
167 entry.resource = NULL;
168 entry.mode = ON_DEMAND; // default mode
169 entry.lifearg = NULL;
170 entry.entryarg = NULL;
171 entry.consumers.insert(pConsumer);
172 ResourceEntries[Key] = entry;
173 try {
174 // actually create the resource
175 entry.resource = Create(Key, pConsumer, entry.lifearg);
176 } catch (...) {
177 // creating the resource failed, so remove the entry
178 ResourceEntries.erase(Key);
179 if (bLock) ResourceEntriesMutex.Unlock();
180 // rethrow the same exception
181 throw;
182 }
183 // now update the entry with the created resource
184 ResourceEntries[Key] = entry;
185 OnBorrow(entry.resource, pConsumer, entry.lifearg);
186 if (bLock) ResourceEntriesMutex.Unlock();
187 return entry.resource;
188 } else { // entry already exists
189 resource_entry_t& entry = iterEntry->second;
190 if (!entry.resource) { // create resource if not created already
191 try {
192 entry.resource = Create(Key, pConsumer, entry.lifearg);
193 } catch (...) {
194 entry.resource = NULL;
195 if (bLock) ResourceEntriesMutex.Unlock();
196 throw; // rethrow the same exception
197 }
198 }
199 entry.consumers.insert(pConsumer);
200 OnBorrow(entry.resource, pConsumer, entry.lifearg);
201 if (bLock) ResourceEntriesMutex.Unlock();
202 return entry.resource;
203 }
204 }
205
206 /**
207 * Give back a resource. This tells the ResourceManager that the
208 * consumer given by \a pConsumer doesn't need the resource anymore.
209 * If the resource is not needed by any consumer anymore and the
210 * resource has a life-time strategy of ON_DEMAND (which is the
211 * default setting) then the resource will be destroyed.
212 *
213 * @param pResource - pointer to resource
214 * @param pConsumer - identifier of the consumer who borrowed the
215 * resource
216 * @param bLock - use thread safety mechanisms
217 */
218 void HandBack(T_res* pResource, ResourceConsumer<T_res>* pConsumer, bool bLock = true) {
219 if (bLock) ResourceEntriesMutex.Lock();
220 // search for the entry associated with the given resource
221 typename ResourceMap::iterator iter = ResourceEntries.begin();
222 typename ResourceMap::iterator end = ResourceEntries.end();
223 for (; iter != end; iter++) {
224 if (iter->second.resource == pResource) { // found entry for resource
225 resource_entry_t& entry = iter->second;
226 entry.consumers.erase(pConsumer);
227 // remove entry if necessary
228 if (entry.mode == ON_DEMAND && !entry.entryarg && entry.consumers.empty()) {
229 T_res* resource = entry.resource;
230 void* arg = entry.lifearg;
231 ResourceEntries.erase(iter);
232 // destroy resource if necessary
233 if (resource) Destroy(resource, arg);
234 }
235 if (bLock) ResourceEntriesMutex.Unlock();
236 return;
237 }
238 }
239 if (bLock) ResourceEntriesMutex.Unlock();
240 }
241
242 /**
243 * Request update of a resource. All consumers will be informed
244 * about the pending update of the resource so they can safely react
245 * by stopping its usage first, then the resource will be recreated
246 * and finally the consumers will be informed once the update was
247 * completed, so they can continue to use the resource.
248 *
249 * @param pResource - resource to be updated
250 * @param pConsumer - consumer who requested the update
251 * @param bLock - use thread safety mechanisms
252 */
253 void Update(T_res* pResource, ResourceConsumer<T_res>* pConsumer, bool bLock = true) {
254 if (bLock) ResourceEntriesMutex.Lock();
255 typename ResourceMap::iterator iter = ResourceEntries.begin();
256 typename ResourceMap::iterator end = ResourceEntries.end();
257 for (; iter != end; iter++) {
258 if (iter->second.resource == pResource) {
259 resource_entry_t& entry = iter->second;
260 // inform all consumers about pending update
261 std::map<ResourceConsumer<T_res>*,void*> updateargs;
262 typename ConsumerSet::iterator iterCons = entry.consumers.begin();
263 typename ConsumerSet::iterator endCons = entry.consumers.end();
264 for (; iterCons != endCons; iterCons++) {
265 if (*iterCons == pConsumer) continue;
266 void* updatearg = NULL;
267 (*iterCons)->ResourceToBeUpdated(entry.resource, updatearg);
268 if (updatearg) updateargs[*iterCons] = updatearg;
269 }
270 // update resource
271 T_res* pOldResource = entry.resource;
272 Destroy(entry.resource, entry.lifearg);
273 entry.resource = Create(entry.key, pConsumer, entry.lifearg);
274 // inform all consumers about update completed
275 iterCons = entry.consumers.begin();
276 endCons = entry.consumers.end();
277 for (; iterCons != endCons; iterCons++) {
278 if (*iterCons == pConsumer) continue;
279 typename std::map<ResourceConsumer<T_res>*,void*>::iterator iterArg = updateargs.find(*iterCons);
280 void* updatearg = (iterArg != updateargs.end()) ? iterArg->second : NULL;
281 (*iterCons)->ResourceUpdated(pOldResource, entry.resource, updatearg);
282 }
283 if (bLock) ResourceEntriesMutex.Unlock();
284 return;
285 }
286 }
287 if (bLock) ResourceEntriesMutex.Unlock();
288 }
289
290 /**
291 * Returns the life-time strategy of the given resource.
292 *
293 * @param Key - ID of the resource
294 * @param bLock - use thread safety mechanisms
295 */
296 mode_t AvailabilityMode(T_key Key, bool bLock = true) {
297 if (bLock) ResourceEntriesMutex.Lock();
298 typename ResourceMap::iterator iterEntry = ResourceEntries.find(Key);
299 if (iterEntry == ResourceEntries.end()) {
300 if (bLock) ResourceEntriesMutex.Unlock();
301 return ON_DEMAND; // resource entry doesn't exist, so we return the default mode
302 }
303 resource_entry_t& entry = iterEntry->second;
304 if (bLock) ResourceEntriesMutex.Unlock();
305 return entry.mode;
306 }
307
308 /**
309 * Change life-time strategy of the given resource. If a life-time
310 * strategy of PERSISTENT was given and the resource was not created
311 * yet, it will immediately be created and this method will block
312 * until the resource creation was completed.
313 *
314 * @param Key - ID of the resource
315 * @param Mode - life-time strategy of resource to be set
316 * @param bLock - use thread safety mechanisms
317 * @throws Exception in case an invalid Mode was given
318 */
319 void SetAvailabilityMode(T_key Key, mode_t Mode, bool bLock = true) {
320 if (Mode != ON_DEMAND && Mode != ON_DEMAND_HOLD && Mode != PERSISTENT)
321 throw Exception("ResourceManager::SetAvailabilityMode(): invalid mode");
322
323 if (bLock) ResourceEntriesMutex.Lock();
324 // search for an entry for this resource
325 typename ResourceMap::iterator iterEntry = ResourceEntries.find(Key);
326 resource_entry_t* pEntry = NULL;
327 if (iterEntry == ResourceEntries.end()) { // resource entry doesn't exist
328 if (Mode == ON_DEMAND) {
329 if (bLock) ResourceEntriesMutex.Unlock();
330 return; // we don't create an entry for the default value
331 }
332 // create an entry for the resource
333 pEntry = &ResourceEntries[Key];
334 pEntry->key = Key;
335 pEntry->resource = NULL;
336 pEntry->mode = Mode;
337 pEntry->lifearg = NULL;
338 pEntry->entryarg = NULL;
339 } else { // resource entry exists
340 pEntry = &iterEntry->second;
341 // remove entry if necessary
342 if (Mode == ON_DEMAND && !pEntry->entryarg && pEntry->consumers.empty()) {
343 T_res* resource = pEntry->resource;
344 void* arg = pEntry->lifearg;
345 ResourceEntries.erase(iterEntry);
346 // destroy resource if necessary
347 if (resource) Destroy(resource, arg);
348 // done
349 if (bLock) ResourceEntriesMutex.Unlock();
350 return;
351 }
352 pEntry->mode = Mode; // apply new mode
353 }
354
355 // already create the resource if necessary
356 if (pEntry->mode == PERSISTENT && !pEntry->resource) {
357 try {
358 // actually create the resource
359 pEntry->resource = Create(Key, NULL /*no consumer yet*/, pEntry->lifearg);
360 } catch (...) {
361 // creating the resource failed, so skip it for now
362 pEntry->resource = NULL;
363 if (bLock) ResourceEntriesMutex.Unlock();
364 // rethrow the same exception
365 throw;
366 }
367 }
368 if (bLock) ResourceEntriesMutex.Unlock();
369 }
370
371 /**
372 * Returns true in case the resource associated with \a Key is
373 * currently created / "alive".
374 *
375 * @param Key - ID of resource
376 * @param bLock - use thread safety mechanisms
377 */
378 bool IsCreated(T_key Key, bool bLock = true) {
379 return Resource(Key, bLock) != NULL;
380 }
381
382 /**
383 * Returns custom data sticked to the given resource previously by
384 * a SetCustomData() call.
385 *
386 * @param Key - ID of resource
387 * @param bLock - use thread safety mechanisms
388 */
389 void* CustomData(T_key Key, bool bLock = true) {
390 if (bLock) ResourceEntriesMutex.Lock();
391 typename ResourceMap::iterator iterEntry = ResourceEntries.find(Key);
392 if (iterEntry == ResourceEntries.end()) {
393 if (bLock) ResourceEntriesMutex.Unlock();
394 return NULL; // resource entry doesn't exist, so we return the default mode
395 }
396 resource_entry_t entry = iterEntry->second;
397 void* res = entry.entryarg;
398 if (bLock) ResourceEntriesMutex.Unlock();
399 return res;
400 }
401
402 /**
403 * This method can be used to stick custom data to an resource
404 * entry. In case the custom data is not needed anymore, you should
405 * call this method again and set \a pData to NULL, so the
406 * ResourceManager might safe space by removing the respective
407 * entry if not needed anymore.
408 *
409 * @param Key - ID of resource
410 * @param pData - pointer to custom data, or NULL if not needed anymore
411 * @param bLock - use thread safety mechanisms
412 */
413 void SetCustomData(T_key Key, void* pData, bool bLock = true) {
414 if (bLock) ResourceEntriesMutex.Lock();
415 typename ResourceMap::iterator iterEntry = ResourceEntries.find(Key);
416 if (pData) {
417 if (iterEntry == ResourceEntries.end()) { // entry doesnt exist, so create one
418 resource_entry_t* pEntry = &ResourceEntries[Key];
419 pEntry->key = Key;
420 pEntry->resource = NULL;
421 pEntry->mode = ON_DEMAND;
422 pEntry->lifearg = NULL;
423 pEntry->entryarg = pData; // set custom data
424 } else { // entry exists, so just update its custom data
425 iterEntry->second.entryarg = pData;
426 }
427 } else { // !pData
428 if (iterEntry == ResourceEntries.end()) {
429 if (bLock) ResourceEntriesMutex.Unlock();
430 return; // entry doesnt exist, so nothing to do
431 }
432 // entry exists, remove it if necessary
433 resource_entry_t* pEntry = &iterEntry->second;
434 if (pEntry->mode == ON_DEMAND && pEntry->consumers.empty()) {
435 ResourceEntries.erase(iterEntry);
436 } else iterEntry->second.entryarg = NULL;
437 }
438 if (bLock) ResourceEntriesMutex.Unlock();
439 }
440
441 /**
442 * Prevent this ResourceManager instance to be used by another
443 * thread at the same time. All methods of this class are thread
444 * safe by default. However sometimes you might need atomicity among
445 * a sequence of method calls. In this case you would first call
446 * this Lock() method, call the respective operational methods of
447 * this class (<b>Important:</b> each one by setting bLock = false,
448 * this avoids double locking). And once the required sequence of
449 * atomic method calls is done, you have to call Unlock() to
450 * reenable accessibility of this ResourceManager instance for other
451 * threads.
452 *
453 * @see Mutex::Lock()
454 */
455 void Lock() {
456 ResourceEntriesMutex.Lock();
457 }
458
459 /**
460 * Has to be called after a Lock() call to reenable this
461 * ResourceManager instance to be accessible by another thread
462 * again.
463 *
464 * @see Mutex::Unlock()
465 */
466 void Unlock() {
467 ResourceEntriesMutex.Unlock();
468 }
469
470 virtual ~ResourceManager() {} // due to C++'s nature we cannot destroy created resources here
471
472 protected:
473 /**
474 * Has to be implemented by the descendant to create (allocate) a
475 * resource identified by \a Key.
476 *
477 * @param Key - identifier of the resource
478 * @param pConsumer - identifier of the consumer who borrows the
479 * resource
480 * @param pArg - pointer the descendant can use to store
481 * informations he might need for destruction of
482 * the resource
483 * @returns pointer to new resource
484 */
485 virtual T_res* Create(T_key Key, ResourceConsumer<T_res>* pConsumer, void*& pArg) = 0;
486
487 /**
488 * Has to be implemented by the descendant to destroy (free) a
489 * resource pointed by \a pResource.
490 *
491 * @param pResource - pointer to the resource
492 * @param pArg - pointer the descendant might have used when
493 * Create() was called to store informations
494 * about the resource
495 */
496 virtual void Destroy(T_res* pResource, void* pArg) = 0;
497
498 /**
499 * Has to be implemented by the descendant to react when a consumer
500 * borrows a resource (no matter if freshly created or an already
501 * created one). Of course reacting is optional, but the descendant
502 * at least has to provide a method with empty body.
503 *
504 * @param pResource - pointer to the resource
505 * @param pConsumer - identifier of the consumer who borrows the
506 * resource
507 * @param pArg - pointer the descendant might have used when
508 * Create() was called to store informations
509 * about the resource, this information can be
510 * updated by the descendant here
511 */
512 virtual void OnBorrow(T_res* pResource, ResourceConsumer<T_res>* pConsumer, void*& pArg) = 0;
513
514 /**
515 * Dispatcher method which should be periodically called by the
516 * descendant during update or creation of the resource associated
517 * with \a Key. This method will inform all associated consumers
518 * of the given resource about the current progress.
519 *
520 * @param Key - unique identifier of the resource which is
521 * currently creating or updating
522 * @param fProgress - current progress of that creation / update
523 * process as value between 0.0 and 1.0
524 */
525 void DispatchResourceProgressEvent(T_key Key, float fProgress) {
526 // no Mutex here, since Create() is already protected
527 typename ResourceMap::iterator iterEntry = ResourceEntries.find(Key);
528 if (iterEntry != ResourceEntries.end()) {
529 resource_entry_t& entry = iterEntry->second;
530 // inform all consumers of that resource about current progress
531 typename ConsumerSet::iterator iterCons = entry.consumers.begin();
532 typename ConsumerSet::iterator endCons = entry.consumers.end();
533 for (; iterCons != endCons; iterCons++) {
534 (*iterCons)->OnResourceProgress(fProgress);
535 }
536 }
537 }
538
539 /**
540 * Returns pointer to the resource associated with \a Key if
541 * currently created / "alive", NULL otherwise. This method
542 * should be taken with great care in multi-threaded scenarios,
543 * since the returned resource might be destroyed by a concurrent
544 * HandBack() call.
545 *
546 * @param Key - ID of resource
547 * @param bLock - use thread safety mechanisms
548 */
549 T_res* Resource(T_key Key, bool bLock = true) {
550 if (bLock) ResourceEntriesMutex.Lock();
551 typename ResourceMap::iterator iterEntry = ResourceEntries.find(Key);
552 T_res* result = (iterEntry == ResourceEntries.end()) ? NULL : iterEntry->second.resource;
553 if (bLock) ResourceEntriesMutex.Unlock();
554 return result;
555 }
556 };
557
558 } // namespace LinuxSampler
559
560 #endif // __RESOURCE_MANAGER__

  ViewVC Help
Powered by ViewVC