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. --- OpenAL32/event.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) (limited to 'OpenAL32/event.c') 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