Files
UnrealEngine/Engine/Source/Runtime/AudioMixer/Private/Quartz/QuartzSubsystem.cpp
2025-05-18 13:04:45 +08:00

699 lines
21 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Quartz/QuartzSubsystem.h"
#include "Engine/World.h"
#include "Quartz/AudioMixerClockHandle.h"
#include "Quartz/QuartzMetronome.h"
#include "Quartz/AudioMixerClockManager.h"
#include "Sound/QuartzQuantizationUtilities.h"
#include "ProfilingDebugging/CountersTrace.h"
#include "ProfilingDebugging/CsvProfiler.h"
#include "Stats/Stats.h"
#include "AudioDevice.h"
#include "AudioMixerDevice.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(QuartzSubsystem)
static int32 MaxQuartzSubscribersToUpdatePerTickCvar = -1;
FAutoConsoleVariableRef CVarMaxQuartzSubscribersToUpdatePerTick(
TEXT("au.Quartz.MaxSubscribersToUpdatePerTick"),
MaxQuartzSubscribersToUpdatePerTickCvar,
TEXT("Limits the number of Quartz subscribers to update per Tick.\n")
TEXT("<= 0: No Limit, >= 1: Limit"),
ECVF_Default);
static int32 SimulateNoAudioDeviceCvar = 0;
FAutoConsoleVariableRef CVarSimulateNoAudioDevice(
TEXT("au.Quartz.SimulateNoAudioDevice"),
SimulateNoAudioDeviceCvar,
TEXT("If enabled, the QuartzSubsystem will assume no audio device, and will run new clocks in headless mode.\n")
TEXT("0: Not Enabled, 1: Enabled"),
ECVF_Default);
static Audio::FMixerDevice* GetAudioMixerDevice(const UWorld* InWorld)
{
if(!InWorld || SimulateNoAudioDeviceCvar)
{
return nullptr;
}
FAudioDevice* AudioDevicePtr = InWorld->GetAudioDeviceRaw();
if(AudioDevicePtr)
{
return static_cast<Audio::FMixerDevice*>(AudioDevicePtr);
}
return nullptr;
}
static TUniquePtr<FScopeLock> GetPersistentStateScopeLock(const UWorld* InWorld)
{
if(Audio::FMixerDevice* MixerDevicePtr = GetAudioMixerDevice(InWorld))
{
return MakeUnique<FScopeLock>(&MixerDevicePtr->QuartzPersistentStateCritSec);
}
return {};
}
void UQuartzSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
TUniquePtr<FScopeLock> Lock(GetPersistentStateScopeLock(GetWorld()));
if(const UWorld* WorldPtr = GetWorld())
{
if(Audio::FMixerDevice const* MixerDevice = static_cast<Audio::FMixerDevice*>(WorldPtr->GetAudioDeviceRaw()))
{
ClockManagerDataPtr = MixerDevice->QuartzSubsystemData;
}
}
if(!ClockManagerDataPtr)
{
ClockManagerDataPtr = MakeShared<Audio::FPersistentQuartzSubsystemData>();
}
}
void UQuartzSubsystem::Deinitialize()
{
Super::Deinitialize();
TUniquePtr<FScopeLock> Lock(GetPersistentStateScopeLock(GetWorld()));
if(Audio::FMixerDevice* MixerDevice = GetAudioMixerDevice(GetWorld()))
{
MixerDevice->QuartzSubsystemData = ClockManagerDataPtr;
}
}
void UQuartzSubsystem::BeginDestroy()
{
Super::BeginDestroy();
// force un-subscribe all Quartz tickable objects
if (ClockManagerDataPtr)
{
ClockManagerDataPtr->SubsystemClockManager.Flush();
}
}
void FQuartzTickableObjectsManager::Tick(float DeltaTime)
{
const int32 NumSubscribers = QuartzTickSubscribers.Num();
if (MaxQuartzSubscribersToUpdatePerTickCvar <= 0 || NumSubscribers <= MaxQuartzSubscribersToUpdatePerTickCvar)
{
TArray<FQuartzTickableObject*> SubscribersCopy = QuartzTickSubscribers;
// we can afford to update ALL subscribers
for (FQuartzTickableObject* Entry : SubscribersCopy)
{
if (Entry && Entry->QuartzIsTickable())
{
Entry->QuartzTick(DeltaTime);
}
}
UpdateIndex = 0;
}
else
{
// only update up to our limit
for (int i = 0; i < MaxQuartzSubscribersToUpdatePerTickCvar; ++i)
{
FQuartzTickableObject* CurrentSubscriber = QuartzTickSubscribers[UpdateIndex];
if (!ensure(CurrentSubscriber))
{
continue;
}
if (CurrentSubscriber->QuartzIsTickable())
{
CurrentSubscriber->QuartzTick(DeltaTime);
}
if (++UpdateIndex == NumSubscribers)
{
UpdateIndex = 0;
}
}
}
}
bool FQuartzTickableObjectsManager::IsTickable() const
{
const int32 NumSubscribers = QuartzTickSubscribers.Num();
const bool bHasTickSubscribers = NumSubscribers > 0;
TRACE_INT_VALUE(TEXT("QuartzSubsystem::NumSubscribers"), NumSubscribers);
// if our manager has no clocks, and we have no ClockHandle subscribers, we don't need to tick
if (!bHasTickSubscribers)
{
return false;
}
// if our manager has no clocks, and none of our subscribers are tickable, we don't need to tick
for (const FQuartzTickableObject * Entry : QuartzTickSubscribers)
{
if (Entry && Entry->QuartzIsTickable())
{
return true;
}
}
return false;
}
void FQuartzTickableObjectsManager::SubscribeToQuartzTick(FQuartzTickableObject* InObjectToTick)
{
if (!InObjectToTick)
{
return;
}
QuartzTickSubscribers.AddUnique(InObjectToTick);
}
void FQuartzTickableObjectsManager::UnsubscribeFromQuartzTick(FQuartzTickableObject* InObjectToTick)
{
if (!InObjectToTick)
{
return;
}
QuartzTickSubscribers.RemoveSingleSwap(InObjectToTick);
}
bool UQuartzSubsystem::DoesSupportWorldType(EWorldType::Type WorldType) const
{
return Super::DoesSupportWorldType(WorldType) || WorldType == EWorldType::EditorPreview;
}
void UQuartzSubsystem::Tick(float DeltaTime)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Audio);
Super::Tick(DeltaTime);
TRACE_CPUPROFILER_EVENT_SCOPE(QuartzSubsystem::Tick);
check(TickableObjectManagerPtr);
check(ClockManagerDataPtr);
TUniquePtr<FScopeLock> Lock(GetPersistentStateScopeLock(GetWorld()));
PruneStaleProxies();
ClockManagerDataPtr->SubsystemClockManager.LowResoultionUpdate(DeltaTime);
TickableObjectManagerPtr->Tick(DeltaTime);
}
bool UQuartzSubsystem::IsTickableWhenPaused() const
{
return bTickEvenWhenPaused;
}
bool UQuartzSubsystem::IsTickable() const
{
check(TickableObjectManagerPtr);
check(ClockManagerDataPtr);
TUniquePtr<FScopeLock> Lock(GetPersistentStateScopeLock(GetWorld()));
const int32 NumClocks = ClockManagerDataPtr->SubsystemClockManager.GetNumClocks();
TRACE_INT_VALUE(TEXT("QuartzSubsystem::NumClocks"), NumClocks);
// IsTickable() updates unreal insights values
const bool bSubscribersNeedUpdate = TickableObjectManagerPtr->IsTickable();
const bool bIsManagingClocks = NumClocks > 0;
return bIsManagingClocks || bSubscribersNeedUpdate;
}
TStatId UQuartzSubsystem::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(UQuartzSubsystem, STATGROUP_Tickables);
}
void UQuartzSubsystem::SubscribeToQuartzTick(FQuartzTickableObject * InObjectToTick)
{
check(TickableObjectManagerPtr.IsValid());
TickableObjectManagerPtr->SubscribeToQuartzTick(InObjectToTick);
}
void UQuartzSubsystem::UnsubscribeFromQuartzTick(FQuartzTickableObject * InObjectToTick)
{
check(TickableObjectManagerPtr.IsValid());
TickableObjectManagerPtr->UnsubscribeFromQuartzTick(InObjectToTick);
}
UQuartzSubsystem* UQuartzSubsystem::Get(const UWorld* const World)
{
if (World)
{
return World->GetSubsystem<UQuartzSubsystem>();
}
return nullptr;
}
Audio::FQuartzQuantizedRequestData UQuartzSubsystem::CreateRequestDataForSchedulePlaySound(UQuartzClockHandle* InClockHandle, const FOnQuartzCommandEventBP& InDelegate, const FQuartzQuantizationBoundary& InQuantizationBoundary)
{
Audio::FQuartzQuantizedRequestData CommandInitInfo;
if (!InClockHandle)
{
return {};
}
CommandInitInfo.ClockName = InClockHandle->GetClockName();
CommandInitInfo.QuantizationBoundary = InQuantizationBoundary;
CommandInitInfo.QuantizedCommandPtr = MakeShared<Audio::FQuantizedPlayCommand>();
CommandInitInfo.GameThreadSubscribers.Append(InQuantizationBoundary.GameThreadSubscribers);
CommandInitInfo.GameThreadSubscribers.Add(InClockHandle->GetQuartzSubscriber());
if (InDelegate.IsBound())
{
CommandInitInfo.GameThreadDelegateID = InClockHandle->AddCommandDelegate(InDelegate);
}
return CommandInitInfo;
}
bool UQuartzSubsystem::IsQuartzEnabled()
{
return true;
}
Audio::FQuartzQuantizedRequestData UQuartzSubsystem::CreateRequestDataForTickRateChange(UQuartzClockHandle* InClockHandle, const FOnQuartzCommandEventBP& InDelegate, const Audio::FQuartzClockTickRate& InNewTickRate, const FQuartzQuantizationBoundary& InQuantizationBoundary)
{
if (!ensure(InClockHandle))
{
return { };
}
const TSharedPtr<Audio::FQuantizedTickRateChange> TickRateChangeCommandPtr = MakeShared<Audio::FQuantizedTickRateChange>();
TickRateChangeCommandPtr->SetTickRate(InNewTickRate);
Audio::FQuartzQuantizedRequestData CommandInitInfo;
CommandInitInfo.ClockName = InClockHandle->GetClockName();
CommandInitInfo.QuantizationBoundary = InQuantizationBoundary;
CommandInitInfo.QuantizedCommandPtr = TickRateChangeCommandPtr;
CommandInitInfo.GameThreadSubscribers.Append(InQuantizationBoundary.GameThreadSubscribers);
CommandInitInfo.GameThreadSubscribers.Add(InClockHandle->GetQuartzSubscriber());
if (InDelegate.IsBound())
{
CommandInitInfo.GameThreadDelegateID = InClockHandle->AddCommandDelegate(InDelegate);
}
return CommandInitInfo;
}
Audio::FQuartzQuantizedRequestData UQuartzSubsystem::CreateRequestDataForTransportReset(UQuartzClockHandle* InClockHandle, const FQuartzQuantizationBoundary& InQuantizationBoundary, const FOnQuartzCommandEventBP& InDelegate)
{
if (!ensure(InClockHandle))
{
return { };
}
const TSharedPtr<Audio::FQuantizedTransportReset> TransportResetCommandPtr = MakeShared<Audio::FQuantizedTransportReset>();
Audio::FQuartzQuantizedRequestData CommandInitInfo;
CommandInitInfo.ClockName = InClockHandle->GetClockName();
CommandInitInfo.QuantizationBoundary = InQuantizationBoundary;
CommandInitInfo.QuantizedCommandPtr = TransportResetCommandPtr;
CommandInitInfo.GameThreadSubscribers.Append(InQuantizationBoundary.GameThreadSubscribers);
CommandInitInfo.GameThreadSubscribers.Add(InClockHandle->GetQuartzSubscriber());
if (InDelegate.IsBound())
{
CommandInitInfo.GameThreadDelegateID = InClockHandle->AddCommandDelegate(InDelegate);
}
return CommandInitInfo;
}
Audio::FQuartzQuantizedRequestData UQuartzSubsystem::CreateRequestDataForStartOtherClock(UQuartzClockHandle* InClockHandle, FName InClockToStart, const FQuartzQuantizationBoundary& InQuantizationBoundary, const FOnQuartzCommandEventBP& InDelegate)
{
if (!ensure(InClockHandle))
{
return { };
}
const TSharedPtr<Audio::FQuantizedOtherClockStart> TransportResetCommandPtr = MakeShared<Audio::FQuantizedOtherClockStart>();
Audio::FQuartzQuantizedRequestData CommandInitInfo;
CommandInitInfo.ClockName = InClockHandle->GetClockName();
CommandInitInfo.OtherClockName = InClockToStart;
CommandInitInfo.QuantizationBoundary = InQuantizationBoundary;
CommandInitInfo.QuantizedCommandPtr = TransportResetCommandPtr;
CommandInitInfo.GameThreadSubscribers.Append(InQuantizationBoundary.GameThreadSubscribers);
CommandInitInfo.GameThreadSubscribers.Add(InClockHandle->GetQuartzSubscriber());
if (InDelegate.IsBound())
{
CommandInitInfo.GameThreadDelegateID = InClockHandle->AddCommandDelegate(InDelegate);
}
return CommandInitInfo;
}
Audio::FQuartzQuantizedRequestData UQuartzSubsystem::CreateRequestDataForQuantizedNotify(UQuartzClockHandle* InClockHandle, const FQuartzQuantizationBoundary& InQuantizationBoundary, const FOnQuartzCommandEventBP& InDelegate, float InMsOffset)
{
if (!ensure(InClockHandle))
{
return { };
}
const TSharedPtr<Audio::FQuantizedNotify> NotifyCommandPtr = MakeShared<Audio::FQuantizedNotify>(InMsOffset);
Audio::FQuartzQuantizedRequestData CommandInitInfo;
CommandInitInfo.ClockName = InClockHandle->GetClockName();
CommandInitInfo.QuantizationBoundary = InQuantizationBoundary;
CommandInitInfo.QuantizedCommandPtr = NotifyCommandPtr;
CommandInitInfo.GameThreadSubscribers.Append(InQuantizationBoundary.GameThreadSubscribers);
CommandInitInfo.GameThreadSubscribers.Add(InClockHandle->GetQuartzSubscriber());
if (InDelegate.IsBound())
{
CommandInitInfo.GameThreadDelegateID = InClockHandle->AddCommandDelegate(InDelegate);
}
return CommandInitInfo;
}
Audio::FQuartzClockManager* UQuartzSubsystem::GetClockManager(const UObject* WorldContextObject, bool bUseAudioEngineClockManager)
{
// decide if the clock should be managed by the AudioDevice (audio engine) or the Subsystem (this object)
Audio::FQuartzClockManager* ClockManager;
Audio::FMixerDevice* MixerDevice = GetAudioMixerDevice(WorldContextObject->GetWorld());
if(!bUseAudioEngineClockManager || !MixerDevice)
{
check(ClockManagerDataPtr);
TUniquePtr<FScopeLock> Lock(GetPersistentStateScopeLock(GetWorld()));
ClockManager = &ClockManagerDataPtr->SubsystemClockManager;
}
else
{
ClockManager = &MixerDevice->QuantizedEventClockManager;
}
// we should have fallen back to this object
return ClockManager;
}
UQuartzClockHandle* UQuartzSubsystem::CreateNewClock(const UObject* WorldContextObject, FName ClockName, FQuartzClockSettings InSettings, bool bOverrideSettingsIfClockExists, bool bUseAudioEngineClockManager)
{
if (ClockName.IsNone() || !WorldContextObject)
{
return nullptr;
}
Audio::FQuartzClockManager* ClockManager = GetClockManager(WorldContextObject, bUseAudioEngineClockManager);
check(ClockManager); // should have at least fallen back to "this" object as a manager
// numerator of time signature must be >= 1
if (InSettings.TimeSignature.NumBeats < 1)
{
UE_LOG(LogAudioQuartz, Warning, TEXT("Clock: (%s) is attempting to set a time signature with a Numerator < 1. Clamping to 1 beat per bar"), *ClockName.ToString());
InSettings.TimeSignature.NumBeats = 1;
}
Audio::FQuartzClockProxy ClockProxy = ClockManager->GetOrCreateClock(ClockName, InSettings, bOverrideSettingsIfClockExists);
UQuartzClockHandle* ClockHandle = static_cast<UQuartzClockHandle*>(NewObject<UQuartzClockHandle>()->Init(WorldContextObject->GetWorld()));
ClockHandle->SubscribeToClock(WorldContextObject, ClockName, &ClockProxy);
// if we are not the manager for the clock, it means the FAudioDevice is,
// so we hold onto our own copy of the proxy
check(ClockManagerDataPtr);
TUniquePtr<FScopeLock> Lock(GetPersistentStateScopeLock(GetWorld()));
ClockManagerDataPtr->ActiveAudioMixerClockProxies.Add(ClockProxy);
return ClockHandle;
}
void UQuartzSubsystem::DeleteClockByName(const UObject* WorldContextObject, FName ClockName)
{
// first look for the clock on the audio device's clock manager
bool bShouldDeleteSynchronous = false;
Audio::FQuartzClockManager* ClockManager = GetClockManager(WorldContextObject, /*bUseAudioEngineClockManager*/ true);
if (ClockManager && !ClockManager->DoesClockExist(ClockName))
{
// if we didn't find it, assume the clock is on the subsystem's clock manager
ClockManager = GetClockManager(WorldContextObject, /*bUseAudioEngineClockManager*/ false);
bShouldDeleteSynchronous = true;
}
// if the clock is managed by the audio mixer device,
// bShouldDeleteSynchronous is false, and it will be deleted on the correct thread.
// if its managed by the subsystem, bShouldDeleteSynchronous will be true
if(ClockManager)
{
ClockManager->RemoveClock(ClockName, bShouldDeleteSynchronous);
PruneStaleProxies();
}
}
void UQuartzSubsystem::DeleteClockByHandle(const UObject* WorldContextObject, UQuartzClockHandle*& InClockHandle)
{
if (InClockHandle)
{
DeleteClockByName(WorldContextObject, InClockHandle->GetClockName());
}
}
UQuartzClockHandle* UQuartzSubsystem::GetHandleForClock(const UObject* WorldContextObject, FName ClockName)
{
Audio::FQuartzClockManager* ClockManager = GetClockManager(WorldContextObject);
if (!ClockManager || !ClockManager->DoesClockExist(ClockName))
{
return nullptr;
}
Audio::FQuartzClockProxy ClockHandle = ClockManager->GetClock(ClockName);
UQuartzClockHandle* ClockHandlePtr = static_cast<UQuartzClockHandle*>(NewObject<UQuartzClockHandle>()->Init(WorldContextObject->GetWorld()));
return ClockHandlePtr->SubscribeToClock(WorldContextObject, ClockName, &ClockHandle);
}
Audio::FQuartzClockProxy UQuartzSubsystem::GetProxyForClock(FName ClockName) const
{
if(Audio::FQuartzClockProxy const* ProxyPtr = FindProxyByName(ClockName))
{
return *ProxyPtr; // caller gets their own copy
}
return {};
}
void UQuartzSubsystem::AddProxyForExternalClock(const Audio::FQuartzClockProxy& InProxy)
{
// make sure we aren't adding a duplicate name
if(FindProxyByName(InProxy.GetClockName()))
{
UE_LOG(LogAudioQuartz, Warning, TEXT("Received request to add external Clock: (%s) when a clock of that name already exists (Ignoring Request)"), *InProxy.GetClockName().ToString());
return;
}
}
bool UQuartzSubsystem::DoesClockExist(const UObject* WorldContextObject, FName ClockName)
{
Audio::FQuartzClockProxy const* Proxy = FindProxyByName(ClockName);
if(Proxy && Proxy->IsValid())
{
return Proxy->DoesClockExist();
}
return {};
}
bool UQuartzSubsystem::IsClockRunning(const UObject* WorldContextObject, FName ClockName)
{
Audio::FQuartzClockProxy const* Proxy = FindProxyByName(ClockName);
if(Proxy && Proxy->IsValid())
{
return Proxy->IsClockRunning();
}
return {};
}
float UQuartzSubsystem::GetDurationOfQuantizationTypeInSeconds(const UObject* WorldContextObject, FName ClockName, const EQuartzCommandQuantization& QuantizationType, float Multiplier)
{
Audio::FQuartzClockProxy const* Proxy = FindProxyByName(ClockName);
if(Proxy && Proxy->IsValid())
{
return Proxy->GetDurationOfQuantizationTypeInSeconds(QuantizationType, Multiplier);
}
return {};
}
FQuartzTransportTimeStamp UQuartzSubsystem::GetCurrentClockTimestamp(const UObject* WorldContextObject, const FName& InClockName)
{
Audio::FQuartzClockProxy const* Proxy = FindProxyByName(InClockName);
if(Proxy && Proxy->IsValid())
{
return Proxy->GetCurrentClockTimestamp();
}
return {};
}
float UQuartzSubsystem::GetEstimatedClockRunTime(const UObject* WorldContextObject, const FName& InClockName)
{
Audio::FQuartzClockProxy const* Proxy = FindProxyByName(InClockName);
if(Proxy && Proxy->IsValid())
{
return Proxy->GetEstimatedClockRunTimeSeconds();
}
return {};
}
// todo: move FQuartLatencyTracker off the AudioMixerClockManager? (GameThread->AudioRenderThread tracking)
float UQuartzSubsystem::GetGameThreadToAudioRenderThreadAverageLatency(const UObject* WorldContextObject)
{
Audio::FQuartzClockManager* ClockManager = GetClockManager(WorldContextObject);
if (!ClockManager)
{
return { };
}
return ClockManager->GetLifetimeAverageLatency();
}
float UQuartzSubsystem::GetGameThreadToAudioRenderThreadMinLatency(const UObject* WorldContextObject)
{
Audio::FQuartzClockManager* ClockManager = GetClockManager(WorldContextObject);
if (!ClockManager)
{
return { };
}
return ClockManager->GetMinLatency();
}
float UQuartzSubsystem::GetGameThreadToAudioRenderThreadMaxLatency(const UObject* WorldContextObject)
{
Audio::FQuartzClockManager* ClockManager = GetClockManager(WorldContextObject);
if (!ClockManager)
{
return { };
}
return ClockManager->GetMinLatency();
}
float UQuartzSubsystem::GetAudioRenderThreadToGameThreadAverageLatency()
{
check(TickableObjectManagerPtr.IsValid());
return TickableObjectManagerPtr->GetLifetimeAverageLatency();
}
float UQuartzSubsystem::GetAudioRenderThreadToGameThreadMinLatency()
{
check(TickableObjectManagerPtr.IsValid());
return TickableObjectManagerPtr->GetMinLatency();
}
float UQuartzSubsystem::GetAudioRenderThreadToGameThreadMaxLatency()
{
check(TickableObjectManagerPtr.IsValid());
return TickableObjectManagerPtr->GetMaxLatency();
}
float UQuartzSubsystem::GetRoundTripAverageLatency(const UObject* WorldContextObject)
{
// very much an estimate
return GetAudioRenderThreadToGameThreadAverageLatency() + GetGameThreadToAudioRenderThreadAverageLatency(WorldContextObject);
}
float UQuartzSubsystem::GetRoundTripMinLatency(const UObject* WorldContextObject)
{
return GetAudioRenderThreadToGameThreadMaxLatency() + GetGameThreadToAudioRenderThreadMaxLatency(WorldContextObject);
}
float UQuartzSubsystem::GetRoundTripMaxLatency(const UObject* WorldContextObject)
{
return GetAudioRenderThreadToGameThreadMinLatency() + GetGameThreadToAudioRenderThreadMinLatency(WorldContextObject);
}
void UQuartzSubsystem::SetQuartzSubsystemTickableWhenPaused(const bool bInTickableWhenPaused)
{
bTickEvenWhenPaused = bInTickableWhenPaused;
}
TWeakPtr<FQuartzTickableObjectsManager> UQuartzSubsystem::GetTickableObjectManager() const
{
return TickableObjectManagerPtr;
}
void UQuartzSubsystem::PruneStaleProxies()
{
check(ClockManagerDataPtr);
TUniquePtr<FScopeLock> Lock(GetPersistentStateScopeLock(GetWorld()));
PruneStaleProxiesInternal(ClockManagerDataPtr->ActiveExternalClockProxies);
PruneStaleProxiesInternal(ClockManagerDataPtr->ActiveAudioMixerClockProxies);
}
void UQuartzSubsystem::PruneStaleProxiesInternal(TArray<Audio::FQuartzClockProxy>& ContainerToPrune)
{
for(int32 i = 0; i < ContainerToPrune.Num(); ++i)
{
if(ContainerToPrune[i].IsValid() == false)
{
ContainerToPrune.RemoveAtSwap(i--, EAllowShrinking::No);
}
}
}
Audio::FQuartzClockProxy* UQuartzSubsystem::FindProxyByName(const FName& ClockName)
{
check(ClockManagerDataPtr);
TUniquePtr<FScopeLock> Lock(GetPersistentStateScopeLock(GetWorld()));
Audio::FQuartzClockProxy* Result = ClockManagerDataPtr->ActiveAudioMixerClockProxies.FindByKey(ClockName);
// if the subsystem doesn't have a match, check the externally-registered clock proxies
if(!Result)
{
Result = ClockManagerDataPtr->ActiveExternalClockProxies.FindByKey(ClockName);
}
return Result;
}
Audio::FQuartzClockProxy const* UQuartzSubsystem::FindProxyByName(const FName& ClockName) const
{
check(ClockManagerDataPtr);
TUniquePtr<FScopeLock> Lock(GetPersistentStateScopeLock(GetWorld()));
Audio::FQuartzClockProxy const* Result = ClockManagerDataPtr->ActiveAudioMixerClockProxies.FindByKey(ClockName);
// if the subsystem doesn't have a match, check the externally-registered clock proxies
if(!Result)
{
Result = ClockManagerDataPtr->ActiveExternalClockProxies.FindByKey(ClockName);
}
return Result;
}