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.
This change affects cases where a sound is loaded while the same sound
is still in the process of being decoded. It makes it so the ASYNC flag
will only wait for the underlying data buffer to be initialized,
whereas the lack of ASYNC flag will result in it waiting for the entire
sound to be fully decoded.