Asynchronously stop voices if its source is being deleted

This commit is contained in:
Chris Robinson
2020-02-20 22:50:37 -08:00
parent acf7f6f74e
commit bdb8ef22b0
4 changed files with 154 additions and 12 deletions
+52 -12
View File
@@ -486,6 +486,50 @@ inline bool SourceShouldUpdate(ALsource *source, ALCcontext *context)
}
VoiceChange *GetVoiceChangers(ALCcontext *ctx, size_t count)
{
VoiceChange *tail{ctx->mVoiceChangeTail};
if UNLIKELY(!count) return tail;
if(tail == ctx->mCurrentVoiceChange.load(std::memory_order_acquire))
{
ctx->allocVoiceChanges(count);
tail = ctx->mVoiceChangeTail;
}
else if(count > 1)
{
VoiceChange *head{tail->mNext.load(std::memory_order_acquire)};
size_t avail{1};
for(;avail < count;++avail)
{
if(head == ctx->mCurrentVoiceChange.load(std::memory_order_acquire))
break;
head = head->mNext.load(std::memory_order_acquire);
}
if(avail < count)
{
ctx->allocVoiceChanges(count - avail);
tail = ctx->mVoiceChangeTail;
}
}
VoiceChange *head{tail};
for(size_t avail{1};avail < count;++avail)
head = head->mNext.load(std::memory_order_relaxed);
ctx->mVoiceChangeTail = head->mNext.exchange(nullptr, std::memory_order_relaxed);
return tail;
}
void SendVoiceChangers(ALCcontext *ctx, VoiceChange *tail)
{
VoiceChange *oldhead{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
while(VoiceChange *next{oldhead->mNext.load(std::memory_order_relaxed)})
oldhead = next;
oldhead->mNext.store(tail, std::memory_order_release);
}
bool EnsureSources(ALCcontext *context, size_t needed)
{
size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(),
@@ -541,20 +585,16 @@ void FreeSource(ALCcontext *context, ALsource *source)
if(IsPlayingOrPaused(source))
{
ALCdevice *device{context->mDevice.get()};
BackendLockGuard _{*device->Backend};
if(ALvoice *voice{GetSourceVoice(source, context)})
{
voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
voice->mSourceID.store(0u, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
/* Don't set the voice to stopping if it was already stopped or
* stopping.
*/
ALvoice::State oldvstate{ALvoice::Playing};
voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping,
std::memory_order_acq_rel, std::memory_order_acquire);
VoiceChange *vchg{GetVoiceChangers(context, 1)};
voice->mPendingStop.store(true, std::memory_order_relaxed);
vchg->mVoice = voice;
vchg->mSourceID = id;
vchg->mState = AL_STOPPED;
SendVoiceChangers(context, vchg);
}
}
+26
View File
@@ -1642,6 +1642,24 @@ void ALCcontext::processUpdates()
}
void ALCcontext::allocVoiceChanges(size_t addcount)
{
constexpr size_t clustersize{128};
/* Convert element count to cluster count. */
addcount = (addcount+(clustersize-1)) / clustersize;
while(addcount)
{
VoiceChangeCluster cluster{new VoiceChange[clustersize]};
for(size_t i{1};i < clustersize;++i)
cluster[i-1].mNext.store(std::addressof(cluster[i]), std::memory_order_relaxed);
cluster[clustersize-1].mNext.store(mVoiceChangeTail, std::memory_order_relaxed);
mVoiceChangeCluster.emplace_back(std::move(cluster));
mVoiceChangeTail = mVoiceChangeCluster.back().get();
--addcount;
}
}
/* alcSetError
*
* Stores the latest ALC device error
@@ -2568,6 +2586,14 @@ void ALCcontext::init()
}
mActiveAuxSlots.store(auxslots, std::memory_order_relaxed);
allocVoiceChanges(1);
{
VoiceChange *cur{mVoiceChangeTail};
while(VoiceChange *next{cur->mNext.load(std::memory_order_relaxed)})
cur = next;
mCurrentVoiceChange.store(cur, std::memory_order_relaxed);
}
mExtensionList = alExtList;
+29
View File
@@ -56,6 +56,17 @@ struct ALcontextProps {
};
struct VoiceChange {
ALvoice *mVoice{nullptr};
ALuint mSourceID{0};
ALenum mState{0};
std::atomic<VoiceChange*> mNext{nullptr};
DEF_NEWDEL(VoiceChange)
};
struct SourceSubList {
uint64_t FreeMask{~0_u64};
ALsource *Sources{nullptr}; /* 64 */
@@ -128,6 +139,24 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext> {
std::atomic<ALvoiceProps*> mFreeVoiceProps{nullptr};
std::atomic<ALeffectslotProps*> mFreeEffectslotProps{nullptr};
/* Asynchronous voice change actions are processed as a linked list of
* VoiceChange objects by the mixer, which is atomically appended to.
* However, to avoid allocating each object individually, they're allocated
* in clusters that are stored in a vector for easy automatic cleanup.
*/
using VoiceChangeCluster = std::unique_ptr<VoiceChange[]>;
al::vector<VoiceChangeCluster> mVoiceChangeCluster;
/* The voice change tail is the beginning of the "free" elements, up to and
* *excluding* the current. If tail==current, there's no free elements and
* new ones need to be allocated. The current voice change is the element
* last processed, and any after are pending.
*/
VoiceChange *mVoiceChangeTail{};
std::atomic<VoiceChange*> mCurrentVoiceChange{};
void allocVoiceChanges(size_t addcount);
al::vector<ALvoice> mVoices;
using ALeffectslotArray = al::FlexArray<ALeffectslot*>;
+47
View File
@@ -1605,9 +1605,56 @@ void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
}
void SendSourceStateEvent(ALCcontext *context, ALuint id, ALenum state)
{
RingBuffer *ring{context->mAsyncEvents.get()};
auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len < 1) return;
AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}};
evt->u.srcstate.id = id;
evt->u.srcstate.state = state;
ring->writeAdvance(1);
}
void ProcessVoiceChanges(ALCcontext *ctx)
{
VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
VoiceChange *next{cur->mNext.load(std::memory_order_acquire)};
if(!next) return;
const ALbitfieldSOFT enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)};
do {
cur = next;
bool success{false};
ALvoice *voice{cur->mVoice};
if(cur->mState == AL_INITIAL || cur->mState == AL_STOPPED)
{
if(voice)
{
voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
voice->mSourceID.exchange(0u, std::memory_order_relaxed);
ALvoice::State oldvstate{ALvoice::Playing};
success = voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping,
std::memory_order_relaxed, std::memory_order_acquire);
voice->mPendingStop.store(false, std::memory_order_release);
}
success |= (cur->mState == AL_INITIAL);
}
if(success && (enabledevt&EventType_SourceStateChange))
SendSourceStateEvent(ctx, cur->mSourceID, cur->mState);
} while((next=cur->mNext.load(std::memory_order_acquire)));
ctx->mCurrentVoiceChange.store(cur, std::memory_order_release);
}
void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray &slots,
const al::span<ALvoice> voices)
{
ProcessVoiceChanges(ctx);
IncrementRef(ctx->mUpdateCount);
if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire))
{