diff options
author | Chris Robinson <[email protected]> | 2018-01-31 20:21:54 -0800 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2018-01-31 20:21:54 -0800 |
commit | 7256bc92fa954b6ff313f30694a27b7f47c1589d (patch) | |
tree | 7faf7ad76b9e8766c89936d850e1a3df915b6880 /OpenAL32 | |
parent | 0394d5a44fd5b1c90c7e4ae9d660bec8a19175f1 (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.h | 15 | ||||
-rw-r--r-- | OpenAL32/event.c | 74 |
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: |