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:
parent
0394d5a44f
commit
7256bc92fa
20
Alc/ALc.c
20
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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user