Asynchronously stop voices if its source is being deleted
This commit is contained in:
+52
-12
@@ -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
@@ -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;
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user