aboutsummaryrefslogtreecommitdiffstats
path: root/OpenAL32
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2018-01-31 20:21:54 -0800
committerChris Robinson <[email protected]>2018-01-31 20:21:54 -0800
commit7256bc92fa954b6ff313f30694a27b7f47c1589d (patch)
tree7faf7ad76b9e8766c89936d850e1a3df915b6880 /OpenAL32
parent0394d5a44fd5b1c90c7e4ae9d660bec8a19175f1 (diff)
Add a thread to marshal events from the mixer
To avoid having unknown user code running in the mixer thread that could significantly delay the mixed output, a lockless ringbuffer is used for the mixer to provide events that a secondary thread will pop off and process.
Diffstat (limited to 'OpenAL32')
-rw-r--r--OpenAL32/Include/alMain.h15
-rw-r--r--OpenAL32/event.c74
2 files changed, 86 insertions, 3 deletions
diff --git a/OpenAL32/Include/alMain.h b/OpenAL32/Include/alMain.h
index 6ad67b7b..e7a95eef 100644
--- a/OpenAL32/Include/alMain.h
+++ b/OpenAL32/Include/alMain.h
@@ -158,6 +158,7 @@ static const union {
extern "C" {
#endif
+struct ll_ringbuffer;
struct Hrtf;
struct HrtfEntry;
struct DirectHrtfState;
@@ -613,6 +614,14 @@ enum {
EventType_Deprecated = 1<<4,
};
+typedef struct AsyncEvent {
+ unsigned int EnumType;
+ ALenum Type;
+ ALuint ObjectId;
+ ALuint Param;
+ ALchar Message[1008];
+} AsyncEvent;
+
struct ALCcontext_struct {
RefCount ref;
@@ -664,8 +673,12 @@ struct ALCcontext_struct {
ATOMIC(struct ALeffectslotArray*) ActiveAuxSlots;
- almtx_t EventCbLock;
+ almtx_t EventThrdLock;
+ althrd_t EventThread;
+ alcnd_t EventCnd;
+ struct ll_ringbuffer *AsyncEvents;
ATOMIC(ALbitfieldSOFT) EnabledEvts;
+ almtx_t EventCbLock;
ALEVENTPROCSOFT EventCb;
void *EventParam;
diff --git a/OpenAL32/event.c b/OpenAL32/event.c
index 9a3a92b4..82a6efb1 100644
--- a/OpenAL32/event.c
+++ b/OpenAL32/event.c
@@ -6,8 +6,48 @@
#include "AL/alext.h"
#include "alMain.h"
#include "alError.h"
+#include "ringbuffer.h"
+static int EventThread(void *arg)
+{
+ ALCcontext *context = arg;
+
+ almtx_lock(&context->EventCbLock);
+ while(1)
+ {
+ AsyncEvent evt;
+ ALbitfieldSOFT enabledevts;
+
+ if(ll_ringbuffer_read_space(context->AsyncEvents) == 0)
+ {
+ /* Wait 50ms before checking again. Because events are delivered
+ * asynchronously by the mixer, it's possible for one to be written
+ * in between checking for a readable element and sleeping. So to
+ * ensure events don't get left to go stale in the ringbuffer, we
+ * need to keep checking regardless of being signaled.
+ */
+ struct timespec ts;
+ altimespec_get(&ts, AL_TIME_UTC);
+ ts.tv_nsec += 50000000;
+ ts.tv_sec += ts.tv_nsec/1000000000;
+ ts.tv_nsec %= 1000000000;
+ alcnd_timedwait(&context->EventCnd, &context->EventCbLock, &ts);
+ continue;
+ }
+ ll_ringbuffer_read(context->AsyncEvents, (char*)&evt, 1);
+ if(!evt.EnumType) break;
+
+ /* Should check the actual type is enabled here too. */
+ enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_acquire);
+ if(context->EventCb && (enabledevts&evt.EnumType) != evt.EnumType)
+ context->EventCb(evt.Type, evt.ObjectId, evt.Param, (ALsizei)strlen(evt.Message),
+ evt.Message, context->EventParam);
+ }
+ almtx_unlock(&context->EventCbLock);
+ return 0;
+}
+
AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable)
{
ALCcontext *context;
@@ -39,7 +79,13 @@ AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, A
if(enable)
{
- ALbitfieldSOFT enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed);
+ ALbitfieldSOFT enabledevts;
+ bool isrunning;
+ almtx_lock(&context->EventThrdLock);
+ if(!context->AsyncEvents)
+ context->AsyncEvents = ll_ringbuffer_create(64, sizeof(AsyncEvent));
+ enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed);
+ isrunning = !!enabledevts;
while(ATOMIC_COMPARE_EXCHANGE_WEAK(&context->EnabledEvts, &enabledevts, enabledevts|flags,
almemory_order_acq_rel, almemory_order_acquire) == 0)
{
@@ -47,14 +93,38 @@ AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, A
* just try again.
*/
}
+ if(!isrunning && flags)
+ althrd_create(&context->EventThread, EventThread, context);
+ almtx_unlock(&context->EventThrdLock);
}
else
{
- ALbitfieldSOFT enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed);
+ ALbitfieldSOFT enabledevts;
+ bool isrunning;
+ almtx_lock(&context->EventThrdLock);
+ enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed);
+ isrunning = !!enabledevts;
while(ATOMIC_COMPARE_EXCHANGE_WEAK(&context->EnabledEvts, &enabledevts, enabledevts&~flags,
almemory_order_acq_rel, almemory_order_acquire) == 0)
{
}
+ if(isrunning && !(enabledevts&~flags))
+ {
+ static const AsyncEvent kill_evt = { 0 };
+ while(ll_ringbuffer_write_space(context->AsyncEvents) == 0)
+ althrd_yield();
+ ll_ringbuffer_write(context->AsyncEvents, (const char*)&kill_evt, 1);
+ althrd_join(context->EventThread, NULL);
+ }
+ else
+ {
+ /* Wait to ensure the event handler sees the changed flags before
+ * returning.
+ */
+ almtx_lock(&context->EventCbLock);
+ almtx_unlock(&context->EventCbLock);
+ }
+ almtx_unlock(&context->EventThrdLock);
}
done: