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.
This commit is contained in:
Chris Robinson 2018-01-31 20:21:54 -08:00
parent 0394d5a44f
commit 7256bc92fa
3 changed files with 105 additions and 4 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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: