Files
UnrealEngine/Engine/Source/Developer/DerivedDataCache/Private/DerivedDataRequestOwner.cpp
2025-05-18 13:04:45 +08:00

349 lines
8.8 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DerivedDataRequestOwner.h"
#include "Async/EventCount.h"
#include "Containers/Array.h"
#include "DerivedDataRequest.h"
#include "DerivedDataRequestTypes.h"
#include "HAL/CriticalSection.h"
#include "Misc/ScopeExit.h"
#include "Misc/ScopeRWLock.h"
#include "Tasks/Task.h"
#include "Templates/Function.h"
#include "Templates/RefCounting.h"
#include <atomic>
namespace UE::DerivedData::Private
{
class FRequestOwnerShared final : public IRequestOwner, public FRequestBase
{
public:
explicit FRequestOwnerShared(EPriority Priority);
~FRequestOwnerShared() final = default;
void Begin(IRequest* Request) final;
TRefCountPtr<IRequest> End(IRequest* Request) final;
void BeginBarrier(const FRequestBarrier& Barrier) final;
void EndBarrier(const FRequestBarrier& Barrier) final;
inline EPriority GetPriority() const final { return Priority.load(std::memory_order_relaxed); }
inline bool IsCanceled() const final { return bIsCanceled.load(std::memory_order_relaxed); }
inline void SetPriority(EPriority Priority) final;
inline void Cancel() final;
inline void Wait() final;
inline bool Poll() const;
inline void KeepAlive();
inline void Destroy();
private:
mutable FRWLock Lock;
TArray<TRefCountPtr<IRequest>, TInlineAllocator<1>> Requests;
FEventCount BarrierEvent;
uint32 BarrierCount{0};
uint32 PriorityBarrierCount{0};
std::atomic<EPriority> Priority{EPriority::Normal};
std::atomic<bool> bIsCanceled{false};
std::atomic<bool> bKeepAlive{false};
uint8 bPriorityChangedInBarrier : 1;
uint8 bBeginExecuted : 1;
};
FRequestOwnerShared::FRequestOwnerShared(EPriority NewPriority)
: Priority(NewPriority)
, bPriorityChangedInBarrier(false)
, bBeginExecuted(false)
{
AddRef(); // Release is called by Destroy.
}
void FRequestOwnerShared::Begin(IRequest* Request)
{
AddRef();
EPriority NewPriority;
{
FWriteScopeLock WriteLock(Lock);
checkf(BarrierCount > 0 || !bBeginExecuted,
TEXT("At least one FRequestBarrier must be in scope when beginning a request after the first request. "
"The overload of End that invokes a callback handles this automatically for most use cases."));
check(Request);
Requests.Add(Request);
bBeginExecuted = true;
if (!bPriorityChangedInBarrier)
{
return;
}
NewPriority = GetPriority();
}
// Loop until priority is stable. Another thread may be changing the priority concurrently.
for (EPriority CheckPriority; ; NewPriority = CheckPriority)
{
Request->SetPriority(NewPriority);
{
FReadScopeLock ScopeLock(Lock);
CheckPriority = GetPriority();
}
if (CheckPriority == NewPriority)
{
break;
}
}
}
TRefCountPtr<IRequest> FRequestOwnerShared::End(IRequest* Request)
{
ON_SCOPE_EXIT { Release(); };
FWriteScopeLock WriteLock(Lock);
check(Request);
TRefCountPtr<IRequest>* RequestPtr = Requests.FindByKey(Request);
check(RequestPtr);
TRefCountPtr<IRequest> RequestRef = MoveTemp(*RequestPtr);
Requests.RemoveAtSwap(UE_PTRDIFF_TO_INT32(RequestPtr - Requests.GetData()), EAllowShrinking::No);
return RequestRef;
}
void FRequestOwnerShared::BeginBarrier(const FRequestBarrier& Barrier)
{
AddRef();
FWriteScopeLock WriteLock(Lock);
++BarrierCount;
if (EnumHasAnyFlags(Barrier.GetFlags(), ERequestBarrierFlags::Priority))
{
++PriorityBarrierCount;
}
}
void FRequestOwnerShared::EndBarrier(const FRequestBarrier& Barrier)
{
ON_SCOPE_EXIT { Release(); };
bool bNotifyBarrier = false;
{
FWriteScopeLock WriteLock(Lock);
if (EnumHasAnyFlags(Barrier.GetFlags(), ERequestBarrierFlags::Priority) && --PriorityBarrierCount == 0)
{
bPriorityChangedInBarrier = false;
}
if (--BarrierCount == 0)
{
bNotifyBarrier = true;
}
}
if (bNotifyBarrier)
{
BarrierEvent.Notify();
}
}
void FRequestOwnerShared::SetPriority(EPriority NewPriority)
{
TArray<TRefCountPtr<IRequest>, TInlineAllocator<16>> LocalRequests;
if (FWriteScopeLock WriteLock(Lock); GetPriority() == NewPriority)
{
return;
}
else
{
Priority.store(NewPriority, std::memory_order_relaxed);
LocalRequests = Requests;
bPriorityChangedInBarrier = (PriorityBarrierCount > 0);
}
for (IRequest* Request : LocalRequests)
{
Request->SetPriority(NewPriority);
}
}
void FRequestOwnerShared::Cancel()
{
for (;;)
{
TRefCountPtr<IRequest> LocalRequest;
FEventCountToken BarrierToken;
{
FWriteScopeLock WriteLock(Lock);
bIsCanceled.store(true, std::memory_order_relaxed);
if (!Requests.IsEmpty())
{
LocalRequest = Requests.Last();
}
else if (BarrierCount > 0)
{
BarrierToken = BarrierEvent.PrepareWait();
}
else
{
return;
}
}
if (LocalRequest)
{
LocalRequest->Cancel();
}
else
{
checkf(!FRequestBarrier::HasBarrierForOwnerOnCallingThread(*this),
TEXT("Called Cancel() on a request owner while the calling thread holds a FRequestBarrier for it. "
"This will cause a deadlock. To destroy a request owner from within a completion callback, "
"first call KeepAlive() on the request owner to disable the auto-cancel mechanism."));
BarrierEvent.Wait(BarrierToken);
}
}
}
void FRequestOwnerShared::Wait()
{
for (;;)
{
TRefCountPtr<IRequest> LocalRequest;
FEventCountToken BarrierToken;
{
FWriteScopeLock WriteLock(Lock);
if (!Requests.IsEmpty())
{
LocalRequest = Requests.Last();
}
else if (BarrierCount > 0)
{
BarrierToken = BarrierEvent.PrepareWait();
}
else
{
return;
}
}
if (LocalRequest)
{
LocalRequest->Wait();
}
else
{
checkf(!FRequestBarrier::HasBarrierForOwnerOnCallingThread(*this),
TEXT("Called Wait() on a request owner while the calling thread holds a FRequestBarrier for it. "
"This will cause a deadlock. Narrow the scope of the barrier or move the wait outside of it."));
BarrierEvent.Wait(BarrierToken);
}
}
}
bool FRequestOwnerShared::Poll() const
{
FReadScopeLock ReadLock(Lock);
return Requests.IsEmpty() && BarrierCount == 0;
}
void FRequestOwnerShared::KeepAlive()
{
bKeepAlive = true;
}
void FRequestOwnerShared::Destroy()
{
if (!bKeepAlive)
{
Cancel();
}
Release();
}
} // UE::DerivedData::Private
namespace UE::DerivedData
{
FRequestOwner::FRequestOwner(EPriority Priority) : Owner(new Private::FRequestOwnerShared(Priority)) {}
static Private::FRequestOwnerShared* ToSharedOwner(IRequestOwner* Owner) { return static_cast<Private::FRequestOwnerShared*>(Owner); }
void FRequestOwner::KeepAlive() { return ToSharedOwner(Owner.Get())->KeepAlive(); }
EPriority FRequestOwner::GetPriority() const { return ToSharedOwner(Owner.Get())->GetPriority(); }
void FRequestOwner::SetPriority(EPriority Priority) { return ToSharedOwner(Owner.Get())->SetPriority(Priority); }
void FRequestOwner::Cancel() { return ToSharedOwner(Owner.Get())->Cancel(); }
void FRequestOwner::Wait() { return ToSharedOwner(Owner.Get())->Wait(); }
bool FRequestOwner::Poll() const { return ToSharedOwner(Owner.Get())->Poll(); }
FRequestOwner::operator IRequest*() { return ToSharedOwner(Owner.Get()); }
void FRequestOwner::Destroy(IRequestOwner& SharedOwner) { return ToSharedOwner(&SharedOwner)->Destroy(); }
void IRequestOwner::LaunchTask(const TCHAR* DebugName, TUniqueFunction<void ()>&& TaskBody)
{
using namespace Tasks;
struct FTaskRequest final : public FRequestBase
{
void SetPriority(EPriority Priority) final {}
void Cancel() final { Task.Wait(); }
void Wait() final { Task.Wait(); }
FTask Task;
};
Tasks::FTaskEvent TaskEvent(TEXT("LaunchTaskRequest"));
FTaskRequest* Request = new FTaskRequest;
ETaskPriority TaskPriority;
switch (GetPriority())
{
case EPriority::Highest:
case EPriority::Blocking:
TaskPriority = ETaskPriority::High;
break;
case EPriority::High:
TaskPriority = ETaskPriority::BackgroundHigh;
break;
default:
TaskPriority = ETaskPriority::BackgroundNormal;
break;
}
Request->Task = Launch(
DebugName,
[this, Request, TaskBody = MoveTemp(TaskBody)]
{
End(Request, TaskBody);
},
TaskEvent,
TaskPriority);
Begin(Request);
TaskEvent.Trigger();
}
static thread_local FRequestBarrier* GRequestBarriersByThread;
FRequestBarrier::FRequestBarrier(IRequestOwner& InOwner, ERequestBarrierFlags InFlags)
: Owner(InOwner)
, NextOnThread(GRequestBarriersByThread)
, Flags(InFlags)
{
GRequestBarriersByThread = this;
Owner.BeginBarrier(*this);
}
FRequestBarrier::~FRequestBarrier()
{
Owner.EndBarrier(*this);
checkf(GRequestBarriersByThread == this,
TEXT("Barriers on a thread must be destroyed in the reverse of their creation order."));
GRequestBarriersByThread = NextOnThread;
}
bool FRequestBarrier::HasBarrierForOwnerOnCallingThread(IRequestOwner& QueryOwner)
{
for (FRequestBarrier* It = GRequestBarriersByThread; It; It = It->NextOnThread)
{
if (&It->Owner == &QueryOwner)
{
return true;
}
}
return false;
}
} // UE::DerivedData