From 7256bc92fa954b6ff313f30694a27b7f47c1589d Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 31 Jan 2018 20:21:54 -0800 Subject: 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. --- Alc/ALc.c | 20 ++++++++++++- OpenAL32/Include/alMain.h | 15 +++++++++- OpenAL32/event.c | 74 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/Alc/ALc.c b/Alc/ALc.c index 04b8f9bd..632e1f18 100644 --- a/Alc/ALc.c +++ b/Alc/ALc.c @@ -41,6 +41,7 @@ #include "bformatdec.h" #include "alu.h" #include "alconfig.h" +#include "ringbuffer.h" #include "fpu_modes.h" #include "cpu_caps.h" @@ -2611,8 +2612,11 @@ static ALvoid InitContext(ALCcontext *Context) Context->MetersPerUnit = AL_DEFAULT_METERS_PER_UNIT; ATOMIC_FLAG_TEST_AND_SET(&Context->PropsClean, almemory_order_relaxed); ATOMIC_INIT(&Context->DeferUpdates, AL_FALSE); - almtx_init(&Context->EventCbLock, almtx_plain); + almtx_init(&Context->EventThrdLock, almtx_plain); + alcnd_init(&Context->EventCnd); + Context->AsyncEvents = NULL; ATOMIC_INIT(&Context->EnabledEvts, 0); + almtx_init(&Context->EventCbLock, almtx_plain); Context->EventCb = NULL; Context->EventParam = NULL; @@ -2743,7 +2747,21 @@ static void FreeContext(ALCcontext *context) } TRACE("Freed "SZFMT" listener property object%s\n", count, (count==1)?"":"s"); + if(ATOMIC_EXCHANGE(&context->EnabledEvts, 0, almemory_order_acq_rel)) + { + 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); + } + almtx_destroy(&context->EventCbLock); + almtx_destroy(&context->EventThrdLock); + alcnd_destroy(&context->EventCnd); + + ll_ringbuffer_free(context->AsyncEvents); + context->AsyncEvents = NULL; ALCdevice_DecRef(context->Device); context->Device = NULL; 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: -- cgit v1.2.3