Commit 88fc453f authored by David Reid's avatar David Reid

Add the MA_ATOMIC annotation.

Variables are marked with this annotation to make it clear that access
to the variable should be done through atomics.

I've also review the use of volatile in this commit.

Public issue https://github.com/mackron/miniaudio/issues/259
parent 687e9e55
......@@ -1649,6 +1649,12 @@ MA_LOG_LEVEL_ERROR
#define MA_LOG_LEVEL MA_LOG_LEVEL_ERROR
#endif
/*
An annotation for variables which must be used atomically. This doesn't actually do anything - it's
just used as a way for humans to identify variables that should be used atomically.
*/
#define MA_ATOMIC
typedef struct ma_context ma_context;
typedef struct ma_device ma_device;
......@@ -2728,10 +2734,10 @@ typedef struct
ma_uint32 subbufferSizeInBytes;
ma_uint32 subbufferCount;
ma_uint32 subbufferStrideInBytes;
volatile ma_uint32 encodedReadOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. */
volatile ma_uint32 encodedWriteOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. */
ma_bool8 ownsBuffer; /* Used to know whether or not miniaudio is responsible for free()-ing the buffer. */
ma_bool8 clearOnWriteAcquire; /* When set, clears the acquired write buffer before returning from ma_rb_acquire_write(). */
MA_ATOMIC ma_uint32 encodedReadOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */
MA_ATOMIC ma_uint32 encodedWriteOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */
ma_bool8 ownsBuffer; /* Used to know whether or not miniaudio is responsible for free()-ing the buffer. */
ma_bool8 clearOnWriteAcquire; /* When set, clears the acquired write buffer before returning from ma_rb_acquire_write(). */
ma_allocation_callbacks allocationCallbacks;
} ma_rb;
......@@ -3917,7 +3923,7 @@ struct ma_device
ma_context* pContext;
ma_device_type type;
ma_uint32 sampleRate;
volatile ma_uint32 state; /* The state of the device is variable and can change at any time on any thread, so tell the compiler as such with `volatile`. */
MA_ATOMIC ma_uint32 state; /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */
ma_device_callback_proc onData; /* Set once at initialization time and should not be changed after. */
ma_stop_proc onStop; /* Set once at initialization time and should not be changed after. */
void* pUserData; /* Application defined data. */
......@@ -3933,7 +3939,7 @@ struct ma_device
ma_bool8 isOwnerOfContext; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */
ma_bool8 noPreZeroedOutputBuffer;
ma_bool8 noClip;
volatile float masterVolumeFactor; /* Volatile so we can use some thread safety when applying volume to periods. */
MA_ATOMIC float masterVolumeFactor; /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */
ma_duplex_rb duplexRB; /* Intermediary buffer for duplex device on asynchronous backends. */
struct
{
......@@ -3997,24 +4003,24 @@ struct ma_device
/*IAudioClient**/ ma_ptr pAudioClientCapture;
/*IAudioRenderClient**/ ma_ptr pRenderClient;
/*IAudioCaptureClient**/ ma_ptr pCaptureClient;
/*IMMDeviceEnumerator**/ ma_ptr pDeviceEnumerator; /* Used for IMMNotificationClient notifications. Required for detecting default device changes. */
/*IMMDeviceEnumerator**/ ma_ptr pDeviceEnumerator; /* Used for IMMNotificationClient notifications. Required for detecting default device changes. */
ma_IMMNotificationClient notificationClient;
/*HANDLE*/ ma_handle hEventPlayback; /* Auto reset. Initialized to signaled. */
/*HANDLE*/ ma_handle hEventCapture; /* Auto reset. Initialized to unsignaled. */
ma_uint32 actualPeriodSizeInFramesPlayback; /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */
/*HANDLE*/ ma_handle hEventPlayback; /* Auto reset. Initialized to signaled. */
/*HANDLE*/ ma_handle hEventCapture; /* Auto reset. Initialized to unsignaled. */
ma_uint32 actualPeriodSizeInFramesPlayback; /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */
ma_uint32 actualPeriodSizeInFramesCapture;
ma_uint32 originalPeriodSizeInFrames;
ma_uint32 originalPeriodSizeInMilliseconds;
ma_uint32 originalPeriods;
ma_performance_profile originalPerformanceProfile;
volatile ma_bool32 hasDefaultPlaybackDeviceChanged; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */
volatile ma_bool32 hasDefaultCaptureDeviceChanged; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */
ma_uint32 periodSizeInFramesPlayback;
ma_uint32 periodSizeInFramesCapture;
volatile ma_bool32 isStartedCapture; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */
volatile ma_bool32 isStartedPlayback; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */
ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */
ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */
MA_ATOMIC ma_bool8 hasDefaultPlaybackDeviceChanged; /* Can be read and written simultaneously across different threads. Must be used atomically. */
MA_ATOMIC ma_bool8 hasDefaultCaptureDeviceChanged; /* Can be read and written simultaneously across different threads. Must be used atomically. */
MA_ATOMIC ma_bool8 isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically. */
MA_ATOMIC ma_bool8 isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically. */
ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */
ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */
ma_bool8 noHardwareOffloading;
ma_bool8 allowCaptureAutoStreamRouting;
ma_bool8 allowPlaybackAutoStreamRouting;
......@@ -4163,14 +4169,14 @@ struct ma_device
ma_event operationCompletionEvent;
ma_semaphore operationSemaphore;
ma_uint32 operation;
volatile ma_result operationResult;
ma_result operationResult;
ma_timer timer;
double priorRunTime;
ma_uint32 currentPeriodFramesRemainingPlayback;
ma_uint32 currentPeriodFramesRemainingCapture;
ma_uint64 lastProcessedFramePlayback;
ma_uint64 lastProcessedFrameCapture;
volatile ma_bool32 isStarted;
MA_ATOMIC ma_bool8 isStarted; /* Read and written by multiple threads. Must be used atomically. */
} null_device;
#endif
};
......@@ -11903,7 +11909,7 @@ static ma_thread_result MA_THREADCALL ma_device_thread__null(void* pData)
ma_event_wait(&pDevice->null_device.operationEvent);
/* At this point an event should have been triggered. */
operation = c89atomic_load_32(&pDevice->null_device.operation);
operation = pDevice->null_device.operation;
/* Starting the device needs to put the thread into a loop. */
if (operation == MA_DEVICE_OP_START__NULL) {
......@@ -11911,7 +11917,7 @@ static ma_thread_result MA_THREADCALL ma_device_thread__null(void* pData)
ma_timer_init(&pDevice->null_device.timer);
/* Getting here means a suspend or kill operation has been requested. */
c89atomic_exchange_32((c89atomic_uint32*)&pDevice->null_device.operationResult, MA_SUCCESS);
pDevice->null_device.operationResult = MA_SUCCESS;
ma_event_signal(&pDevice->null_device.operationCompletionEvent);
ma_semaphore_release(&pDevice->null_device.operationSemaphore);
continue;
......@@ -11924,7 +11930,7 @@ static ma_thread_result MA_THREADCALL ma_device_thread__null(void* pData)
ma_timer_init(&pDevice->null_device.timer);
/* We're done. */
c89atomic_exchange_32((c89atomic_uint32*)&pDevice->null_device.operationResult, MA_SUCCESS);
pDevice->null_device.operationResult = MA_SUCCESS;
ma_event_signal(&pDevice->null_device.operationCompletionEvent);
ma_semaphore_release(&pDevice->null_device.operationSemaphore);
continue;
......@@ -11932,7 +11938,7 @@ static ma_thread_result MA_THREADCALL ma_device_thread__null(void* pData)
/* Killing the device means we need to get out of this loop so that this thread can terminate. */
if (operation == MA_DEVICE_OP_KILL__NULL) {
c89atomic_exchange_32((c89atomic_uint32*)&pDevice->null_device.operationResult, MA_SUCCESS);
pDevice->null_device.operationResult = MA_SUCCESS;
ma_event_signal(&pDevice->null_device.operationCompletionEvent);
ma_semaphore_release(&pDevice->null_device.operationSemaphore);
break;
......@@ -11941,7 +11947,7 @@ static ma_thread_result MA_THREADCALL ma_device_thread__null(void* pData)
/* Getting a signal on a "none" operation probably means an error. Return invalid operation. */
if (operation == MA_DEVICE_OP_NONE__NULL) {
MA_ASSERT(MA_FALSE); /* <-- Trigger this in debug mode to ensure developers are aware they're doing something wrong (or there's a bug in a miniaudio). */
c89atomic_exchange_32((c89atomic_uint32*)&pDevice->null_device.operationResult, (c89atomic_uint32)MA_INVALID_OPERATION);
pDevice->null_device.operationResult = MA_INVALID_OPERATION;
ma_event_signal(&pDevice->null_device.operationCompletionEvent);
ma_semaphore_release(&pDevice->null_device.operationSemaphore);
continue; /* Continue the loop. Don't terminate. */
......@@ -11955,6 +11961,12 @@ static ma_result ma_device_do_operation__null(ma_device* pDevice, ma_uint32 oper
{
ma_result result;
/*
TODO: Need to review this and consider just using mutual exclusion. I think the original motivation
for this was to just post the event to a queue and return immediately, but that has since changed
and now this function is synchronous. I think this can be simplified to just use a mutex.
*/
/*
The first thing to do is wait for an operation slot to become available. We only have a single slot for this, but we could extend this later
to support queing of operations.
......@@ -11968,7 +11980,7 @@ static ma_result ma_device_do_operation__null(ma_device* pDevice, ma_uint32 oper
When we get here it means the background thread is not referencing the operation code and it can be changed. After changing this we need to
signal an event to the worker thread to let it know that it can start work.
*/
c89atomic_exchange_32(&pDevice->null_device.operation, operation);
pDevice->null_device.operation = operation;
/* Once the operation code has been set, the worker thread can start work. */
if (ma_event_signal(&pDevice->null_device.operationEvent) != MA_SUCCESS) {
......@@ -11980,7 +11992,7 @@ static ma_result ma_device_do_operation__null(ma_device* pDevice, ma_uint32 oper
return MA_ERROR;
}
return c89atomic_load_i32(&pDevice->null_device.operationResult);
return pDevice->null_device.operationResult;
}
static ma_uint64 ma_device_get_total_run_time_in_frames__null(ma_device* pDevice)
......@@ -12155,7 +12167,7 @@ static ma_result ma_device_start__null(ma_device* pDevice)
ma_device_do_operation__null(pDevice, MA_DEVICE_OP_START__NULL);
c89atomic_exchange_32(&pDevice->null_device.isStarted, MA_TRUE);
c89atomic_exchange_8(&pDevice->null_device.isStarted, MA_TRUE);
return MA_SUCCESS;
}
......@@ -12165,7 +12177,7 @@ static ma_result ma_device_stop__null(ma_device* pDevice)
ma_device_do_operation__null(pDevice, MA_DEVICE_OP_SUSPEND__NULL);
c89atomic_exchange_32(&pDevice->null_device.isStarted, MA_FALSE);
c89atomic_exchange_8(&pDevice->null_device.isStarted, MA_FALSE);
return MA_SUCCESS;
}
......@@ -12179,7 +12191,7 @@ static ma_result ma_device_write__null(ma_device* pDevice, const void* pPCMFrame
*pFramesWritten = 0;
}
wasStartedOnEntry = c89atomic_load_32(&pDevice->null_device.isStarted);
wasStartedOnEntry = c89atomic_load_8(&pDevice->null_device.isStarted);
/* Keep going until everything has been read. */
totalPCMFramesProcessed = 0;
......@@ -12205,7 +12217,7 @@ static ma_result ma_device_write__null(ma_device* pDevice, const void* pPCMFrame
if (pDevice->null_device.currentPeriodFramesRemainingPlayback == 0) {
pDevice->null_device.currentPeriodFramesRemainingPlayback = 0;
if (!c89atomic_load_32(&pDevice->null_device.isStarted) && !wasStartedOnEntry) {
if (!c89atomic_load_8(&pDevice->null_device.isStarted) && !wasStartedOnEntry) {
result = ma_device_start__null(pDevice);
if (result != MA_SUCCESS) {
break;
......@@ -12225,7 +12237,7 @@ static ma_result ma_device_write__null(ma_device* pDevice, const void* pPCMFrame
ma_uint64 currentFrame;
/* Stop waiting if the device has been stopped. */
if (!c89atomic_load_32(&pDevice->null_device.isStarted)) {
if (!c89atomic_load_8(&pDevice->null_device.isStarted)) {
break;
}
......@@ -12296,7 +12308,7 @@ static ma_result ma_device_read__null(ma_device* pDevice, void* pPCMFrames, ma_u
ma_uint64 currentFrame;
/* Stop waiting if the device has been stopped. */
if (!c89atomic_load_32(&pDevice->null_device.isStarted)) {
if (!c89atomic_load_8(&pDevice->null_device.isStarted)) {
break;
}
......@@ -13157,7 +13169,7 @@ typedef struct
struct ma_completion_handler_uwp
{
ma_completion_handler_uwp_vtbl* lpVtbl;
volatile ma_uint32 counter;
MA_ATOMIC ma_uint32 counter;
HANDLE hEvent;
};
......@@ -13368,10 +13380,10 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDefaultDeviceChanged
that properly.
*/
if (dataFlow == ma_eRender && pThis->pDevice->type != ma_device_type_loopback) {
c89atomic_exchange_32(&pThis->pDevice->wasapi.hasDefaultPlaybackDeviceChanged, MA_TRUE);
c89atomic_exchange_8(&pThis->pDevice->wasapi.hasDefaultPlaybackDeviceChanged, MA_TRUE);
}
if (dataFlow == ma_eCapture || pThis->pDevice->type == ma_device_type_loopback) {
c89atomic_exchange_32(&pThis->pDevice->wasapi.hasDefaultCaptureDeviceChanged, MA_TRUE);
c89atomic_exchange_8(&pThis->pDevice->wasapi.hasDefaultCaptureDeviceChanged, MA_TRUE);
}
(void)pDefaultDeviceID;
......@@ -14556,7 +14568,7 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev
ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualPeriodSizeInFramesCapture);
/* The device may be in a started state. If so we need to immediately restart it. */
if (c89atomic_load_32(&pDevice->wasapi.isStartedCapture)) {
if (c89atomic_load_8(&pDevice->wasapi.isStartedCapture)) {
HRESULT hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture);
if (FAILED(hr)) {
return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device after reinitialization.", ma_result_from_HRESULT(hr));
......@@ -14592,7 +14604,7 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev
ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualPeriodSizeInFramesPlayback);
/* The device may be in a started state. If so we need to immediately restart it. */
if (c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) {
if (c89atomic_load_8(&pDevice->wasapi.isStartedPlayback)) {
HRESULT hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback);
if (FAILED(hr)) {
return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device after reinitialization.", ma_result_from_HRESULT(hr));
......@@ -14809,8 +14821,8 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf
}
#endif
c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_FALSE);
c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_FALSE);
c89atomic_exchange_8(&pDevice->wasapi.isStartedCapture, MA_FALSE);
c89atomic_exchange_8(&pDevice->wasapi.isStartedPlayback, MA_FALSE);
return MA_SUCCESS;
}
......@@ -14855,11 +14867,11 @@ static ma_bool32 ma_device_is_reroute_required__wasapi(ma_device* pDevice, ma_de
MA_ASSERT(pDevice != NULL);
if (deviceType == ma_device_type_playback) {
return c89atomic_load_32(&pDevice->wasapi.hasDefaultPlaybackDeviceChanged);
return c89atomic_load_8(&pDevice->wasapi.hasDefaultPlaybackDeviceChanged);
}
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) {
return c89atomic_load_32(&pDevice->wasapi.hasDefaultCaptureDeviceChanged);
return c89atomic_load_8(&pDevice->wasapi.hasDefaultCaptureDeviceChanged);
}
return MA_FALSE;
......@@ -14874,10 +14886,10 @@ static ma_result ma_device_reroute__wasapi(ma_device* pDevice, ma_device_type de
}
if (deviceType == ma_device_type_playback) {
c89atomic_exchange_32(&pDevice->wasapi.hasDefaultPlaybackDeviceChanged, MA_FALSE);
c89atomic_exchange_8(&pDevice->wasapi.hasDefaultPlaybackDeviceChanged, MA_FALSE);
}
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) {
c89atomic_exchange_32(&pDevice->wasapi.hasDefaultCaptureDeviceChanged, MA_FALSE);
c89atomic_exchange_8(&pDevice->wasapi.hasDefaultCaptureDeviceChanged, MA_FALSE);
}
......@@ -14952,7 +14964,7 @@ static ma_result ma_device_audio_thread__wasapi(ma_device* pDevice)
if (FAILED(hr)) {
return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device.", ma_result_from_HRESULT(hr));
}
c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_TRUE);
c89atomic_exchange_8(&pDevice->wasapi.isStartedCapture, MA_TRUE);
}
while (ma_device_get_state(pDevice) == MA_STATE_STARTED && !exitLoop) {
......@@ -15264,7 +15276,7 @@ static ma_result ma_device_audio_thread__wasapi(ma_device* pDevice)
mappedDeviceBufferSizeInFramesPlayback = 0;
}
if (!c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) {
if (!c89atomic_load_8(&pDevice->wasapi.isStartedPlayback)) {
ma_uint32 startThreshold = pDevice->playback.internalPeriodSizeInFrames * 1;
/* Prevent a deadlock. If we don't clamp against the actual buffer size we'll never end up starting the playback device which will result in a deadlock. */
......@@ -15279,7 +15291,7 @@ static ma_result ma_device_audio_thread__wasapi(ma_device* pDevice)
ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture);
return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr));
}
c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE);
c89atomic_exchange_8(&pDevice->wasapi.isStartedPlayback, MA_TRUE);
}
}
} break;
......@@ -15430,7 +15442,7 @@ static ma_result ma_device_audio_thread__wasapi(ma_device* pDevice)
}
framesWrittenToPlaybackDevice += framesAvailablePlayback;
if (!c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) {
if (!c89atomic_load_8(&pDevice->wasapi.isStartedPlayback)) {
if (pDevice->playback.shareMode == ma_share_mode_exclusive || framesWrittenToPlaybackDevice >= pDevice->playback.internalPeriodSizeInFrames*1) {
hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback);
if (FAILED(hr)) {
......@@ -15438,7 +15450,7 @@ static ma_result ma_device_audio_thread__wasapi(ma_device* pDevice)
exitLoop = MA_TRUE;
break;
}
c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE);
c89atomic_exchange_8(&pDevice->wasapi.isStartedPlayback, MA_TRUE);
}
}
} break;
......@@ -15465,7 +15477,7 @@ static ma_result ma_device_audio_thread__wasapi(ma_device* pDevice)
return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device.", ma_result_from_HRESULT(hr));
}
c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_FALSE);
c89atomic_exchange_8(&pDevice->wasapi.isStartedCapture, MA_FALSE);
}
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
......@@ -15478,7 +15490,7 @@ static ma_result ma_device_audio_thread__wasapi(ma_device* pDevice)
The buffer needs to be drained before stopping the device. Not doing this will result in the last few frames not getting output to
the speakers. This is a problem for very short sounds because it'll result in a significant portion of it not getting played.
*/
if (c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) {
if (c89atomic_load_8(&pDevice->wasapi.isStartedPlayback)) {
/* We need to make sure we put a timeout here or else we'll risk getting stuck in a deadlock in some cases. */
DWORD waitTime = pDevice->wasapi.actualPeriodSizeInFramesPlayback / pDevice->playback.internalSampleRate;
......@@ -15523,7 +15535,7 @@ static ma_result ma_device_audio_thread__wasapi(ma_device* pDevice)
return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device.", ma_result_from_HRESULT(hr));
}
c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_FALSE);
c89atomic_exchange_8(&pDevice->wasapi.isStartedPlayback, MA_FALSE);
}
return MA_SUCCESS;
......@@ -803,16 +803,16 @@ struct ma_node_output_bus
ma_uint8 outputBusIndex; /* The index of the output bus on pNode that this output bus represents. */
ma_uint8 channels; /* The number of channels in the audio stream for this bus. */
/* Mutable via multiple threads. The weird ordering here is for packing reasons. */
volatile ma_uint8 inputNodeInputBusIndex; /* The index of the input bus on the input. Required for detaching. */
volatile ma_uint8 flags; /* Some state flags for tracking the read state of the output buffer. */
volatile ma_uint16 refCount; /* Reference count for some thread-safety when detaching. */
volatile ma_bool8 isAttached; /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */
volatile ma_spinlock lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */
volatile float volume; /* Linear 0..1 */
volatile ma_node_output_bus* pNext; /* If null, it's the tail node or detached. */
volatile ma_node_output_bus* pPrev; /* If null, it's the head node or detached. */
volatile ma_node* pInputNode; /* The node that this output bus is attached to. Required for detaching. */
/* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */
MA_ATOMIC ma_uint8 inputNodeInputBusIndex; /* The index of the input bus on the input. Required for detaching. */
MA_ATOMIC ma_uint8 flags; /* Some state flags for tracking the read state of the output buffer. */
MA_ATOMIC ma_uint16 refCount; /* Reference count for some thread-safety when detaching. */
MA_ATOMIC ma_bool8 isAttached; /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */
MA_ATOMIC ma_spinlock lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */
MA_ATOMIC float volume; /* Linear 0..1 */
MA_ATOMIC ma_node_output_bus* pNext; /* If null, it's the tail node or detached. */
MA_ATOMIC ma_node_output_bus* pPrev; /* If null, it's the head node or detached. */
MA_ATOMIC ma_node* pInputNode; /* The node that this output bus is attached to. Required for detaching. */
};
/*
......@@ -823,12 +823,12 @@ typedef struct ma_node_input_bus ma_node_input_bus;
struct ma_node_input_bus
{
/* Mutable via multiple threads. */
ma_node_output_bus head; /* Dummy head node for simplifying some lock-free thread-safety stuff. */
volatile ma_spinlock lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */
volatile ma_uint16 nextCounter; /* This is used to determine whether or not the input bus is finding the next node in the list. Used for thread safety when detaching output buses. */
ma_node_output_bus head; /* Dummy head node for simplifying some lock-free thread-safety stuff. */
MA_ATOMIC ma_spinlock lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */
MA_ATOMIC ma_uint16 nextCounter; /* This is used to determine whether or not the input bus is finding the next node in the list. Used for thread safety when detaching output buses. */
/* Set once at startup. */
ma_uint8 channels; /* The number of channels in the audio stream for this bus. */
ma_uint8 channels; /* The number of channels in the audio stream for this bus. */
};
......@@ -848,9 +848,9 @@ struct ma_node_base
ma_uint32 readCounter; /* For loop prevention. Compared with the current read count of the node graph. If larger, means a loop was encountered and reading is aborted and no samples read. */
/* These variables are read and written between different threads. */
volatile ma_node_state state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */
volatile ma_uint64 stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */
volatile ma_uint64 localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */
MA_ATOMIC ma_node_state state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */
MA_ATOMIC ma_uint64 stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */
MA_ATOMIC ma_uint64 localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */
ma_node_input_bus inputBuses[MA_MAX_NODE_BUS_COUNT];
ma_node_output_bus outputBuses[MA_MAX_NODE_BUS_COUNT];
};
......@@ -888,11 +888,11 @@ MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels);
struct ma_node_graph
{
/* Immutable. */
ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */
ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */
/* Read and written by multiple threads. */
volatile ma_uint32 readCounter; /* Nodes spin on this while they wait for reading for finish before returning from ma_node_uninit(). */
volatile ma_bool8 isReading;
MA_ATOMIC ma_uint32 readCounter; /* Nodes spin on this while they wait for reading for finish before returning from ma_node_uninit(). */
MA_ATOMIC ma_bool8 isReading;
};
MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph);
......@@ -920,7 +920,7 @@ typedef struct
{
ma_node_base base;
ma_data_source* pDataSource;
volatile ma_bool32 looping; /* This can be modified and read across different threads so marking as volatile to make it clear that we need to use atomics to read and modify this. */
MA_ATOMIC ma_bool32 looping; /* This can be modified and read across different threads. Must be used atomically. */
} ma_data_source_node;
MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_data_source_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source_node* pDataSourceNode);
......@@ -1016,7 +1016,7 @@ typedef struct
{
struct
{
volatile ma_uint32 bitfield; /* Marking as volatile because the allocation and freeing routines need to make copies of this which must never be optimized away by the compiler. */
MA_ATOMIC ma_uint32 bitfield; /* Must be used atomically because the allocation and freeing routines need to make copies of this which must never be optimized away by the compiler. */
} groups[MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY/32];
ma_uint32 slots[MA_RESOURCE_MANAGER_JOB_QUEUE_CAPACITY]; /* 32 bits for reference counting for ABA mitigation. */
ma_uint32 count; /* Allocation count. */
......@@ -1192,7 +1192,7 @@ struct ma_resource_manager_data_buffer_node
{
ma_uint32 hashedName32; /* The hashed name. This is the key. */
ma_uint32 refCount;
volatile ma_result result; /* Result from asynchronous loading. When loading set to MA_BUSY. When fully loaded set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. */
MA_ATOMIC ma_result result; /* Result from asynchronous loading. When loading set to MA_BUSY. When fully loaded set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. */
ma_uint32 executionCounter; /* For allocating execution orders for jobs. */
ma_uint32 executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */
ma_bool32 isDataOwnedByResourceManager; /* Set to true when the underlying data buffer was allocated the resource manager. Set to false if it is owned by the application (via ma_resource_manager_register_*()). */
......@@ -1211,7 +1211,7 @@ struct ma_resource_manager_data_buffer
ma_uint64 cursorInPCMFrames; /* Only updated by the public API. Never written nor read from the job thread. */
ma_uint64 lengthInPCMFrames; /* The total length of the sound in PCM frames. This is set at load time. */
ma_bool32 seekToCursorOnNextRead; /* On the next read we need to seek to the frame cursor. */
volatile ma_bool32 isLooping;
MA_ATOMIC ma_bool32 isLooping; /* Can be read and written by different threads at the same time. Must be used atomically. */
ma_resource_manager_data_buffer_connector connectorType;
union
{
......@@ -1235,17 +1235,17 @@ struct ma_resource_manager_data_stream
ma_uint32 executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */
/* Written by the public API, read by the job thread. */
volatile ma_bool32 isLooping; /* Whether or not the stream is looping. It's important to set the looping flag at the data stream level for smooth loop transitions. */
MA_ATOMIC ma_bool32 isLooping; /* Whether or not the stream is looping. It's important to set the looping flag at the data stream level for smooth loop transitions. */
/* Written by the job thread, read by the public API. */
void* pPageData; /* Buffer containing the decoded data of each page. Allocated once at initialization time. */
volatile ma_uint32 pageFrameCount[2]; /* The number of valid PCM frames in each page. Used to determine the last valid frame. */
MA_ATOMIC ma_uint32 pageFrameCount[2]; /* The number of valid PCM frames in each page. Used to determine the last valid frame. */
/* Written and read by both the public API and the job thread. */
volatile ma_result result; /* Result from asynchronous loading. When loading set to MA_BUSY. When initialized set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. If an error occurs when loading, set to an error code. */
volatile ma_bool32 isDecoderAtEnd; /* Whether or not the decoder has reached the end. */
volatile ma_bool32 isPageValid[2]; /* Booleans to indicate whether or not a page is valid. Set to false by the public API, set to true by the job thread. Set to false as the pages are consumed, true when they are filled. */
volatile ma_bool32 seekCounter; /* When 0, no seeking is being performed. When > 0, a seek is being performed and reading should be delayed with MA_BUSY. */
/* Written and read by both the public API and the job thread. These must be atomic. */
MA_ATOMIC ma_result result; /* Result from asynchronous loading. When loading set to MA_BUSY. When initialized set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. If an error occurs when loading, set to an error code. */
MA_ATOMIC ma_bool32 isDecoderAtEnd; /* Whether or not the decoder has reached the end. */
MA_ATOMIC ma_bool32 isPageValid[2]; /* Booleans to indicate whether or not a page is valid. Set to false by the public API, set to true by the job thread. Set to false as the pages are consumed, true when they are filled. */
MA_ATOMIC ma_bool32 seekCounter; /* When 0, no seeking is being performed. When > 0, a seek is being performed and reading should be delayed with MA_BUSY. */
};
struct ma_resource_manager_data_source
......@@ -1534,8 +1534,8 @@ struct ma_sound
ma_engine_node engineNode; /* Must be the first member for compatibility with the ma_node API. */
ma_data_source* pDataSource;
ma_uint64 seekTarget; /* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */
volatile ma_bool8 isLooping; /* False by default. */
volatile ma_bool8 atEnd;
MA_ATOMIC ma_bool8 isLooping; /* False by default. */
MA_ATOMIC ma_bool8 atEnd;
ma_bool8 ownsDataSource;
/*
......@@ -1598,7 +1598,7 @@ struct ma_engine
ma_bool8 ownsDevice;
ma_mutex inlinedSoundLock; /* For synchronizing access so the inlined sound list. */
ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */
volatile ma_uint32 inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */
MA_ATOMIC ma_uint32 inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */
};
MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine);
......@@ -4415,7 +4415,7 @@ MA_API ma_result ma_slot_allocator_alloc(ma_slot_allocator* pAllocator, ma_uint6
ma_uint32 newBitfield;
ma_uint32 bitOffset;
oldBitfield = pAllocator->groups[iGroup].bitfield; /* <-- This copy must happen. The compiler must not optimize this away. pAllocator->groups[iGroup].bitfield is marked as volatile. */
oldBitfield = c89atomic_load_32(&pAllocator->groups[iGroup].bitfield); /* <-- This copy must happen. The compiler must not optimize this away. */
/* Fast check to see if anything is available. */
if (oldBitfield == 0xFFFFFFFF) {
......@@ -4482,7 +4482,7 @@ MA_API ma_result ma_slot_allocator_free(ma_slot_allocator* pAllocator, ma_uint64
ma_uint32 oldBitfield;
ma_uint32 newBitfield;
oldBitfield = pAllocator->groups[iGroup].bitfield; /* <-- This copy must happen. The compiler must not optimize this away. pAllocator->groups[iGroup].bitfield is marked as volatile. */
oldBitfield = c89atomic_load_32(&pAllocator->groups[iGroup].bitfield); /* <-- This copy must happen. The compiler must not optimize this away. */
newBitfield = oldBitfield & ~(1 << iBit);
if (c89atomic_compare_and_swap_32(&pAllocator->groups[iGroup].bitfield, oldBitfield, newBitfield) == oldBitfield) {
......@@ -6965,9 +6965,9 @@ MA_API ma_result ma_resource_manager_data_stream_get_looping(const ma_resource_m
MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pAvailableFrames)
{
volatile ma_uint32 pageIndex0;
volatile ma_uint32 pageIndex1;
volatile ma_uint32 relativeCursor;
ma_uint32 pageIndex0;
ma_uint32 pageIndex1;
ma_uint32 relativeCursor;
ma_uint64 availableFrames;
if (pAvailableFrames == NULL) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment