These changes expand on the notification system which allows a program
to be notified on when a sound has reached various stages in the async
loading process. There are two stages:
1) Decoder initialization
2) Completion of decoding
The previous system has the following problems:
* There's no way to wait on only the decoder initialization
* The callback doesn't work well if you want to pass it through
multiple layers. Example:
- Client wants to wait for the sound to complete
- Pass the callback into ma_sound_init_from_file(), which passes
it into ma_resource_manager_data_source_init(). The latter will
fire the callback, but will do so before ma_sound_init_from_file()
has returned. The client will think the sound has finished
loading and will reference the `ma_sound` object before it's
completed initialization.
* It's not easy to wait on object in bulk. Clients may want to pump a
bunch of ASYNC sounds in one go, and then just have a single
notification for the group.
The new system introduces the notion of a "fence". A fence contains a
counter which is incremented and decremented from anywhere. It also has
a wait routine which will block until the counter hits zero:
ma_fence_acquire() - Increments the counter
ma_fence_release() - Decrements the counter
ma_fence_wait() - Blocks until the counter is 0.
The fence system can be used to wait for a number of sounds to finish
decoding rather than only one at a time, which is the case with the
previous system. Example:
```
/* Fences must be initialized before use. */
ma_fence_init(&fence);
/* Loop over each sound and start loading them, passing in the fence. */
for each sound in sounds
{
flags = MA_DATA_SOURCE_FLAG_DECODE | MA_DATA_SOURCE_FLAG_ASYNC;
ma_sound_init_from_file(&engine, pFilePath, flags, &group, &fence, &sound);
}
... do some other stuff while sounds are loading ...
/* Wait for sounds to finish loading. */
ma_fence_wait(&fence);
ma_fence_uninit(&fence);
```
The above example initializes a sound, but it can also be used by
resource managed data sources. When loading data sources directly from
the resource manager, you can specify a second fence which is used to
only wait until the internal decoder has been initialized.
This properly decouples data buffer nodes from data buffers and should
address some subtle bugs, especially with uninitializing data buffers
while they're still loading.
This also cleans up the decoding code, in particular by avoiding some
duplicate code between the synchronous and asynchronous decoding paths.