Files
UnrealEngine/Engine/Source/Runtime/RHI/Public/PsoLruCache.h
2025-05-18 13:04:45 +08:00

644 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreTypes.h"
#include "Containers/Set.h"
#include "Misc/AssertionMacros.h"
/* Implements a Least Recently Used (LRU) cache.
*
* @param KeyType The type of cache entry keys.
* @param ValueType The type of cache entry values.
*/
template<typename KeyType, typename ValueType>
class TPsoLruCache
{
/** An entry in the LRU cache. */
struct FCacheEntry
{
/** The entry's lookup key. */
KeyType Key;
/** The less recent entry in the linked list. */
FCacheEntry* LessRecent;
/** The more recent entry in the linked list. */
FCacheEntry* MoreRecent;
/** The entry's value. */
ValueType Value;
/**
* Create and initialize a new instance.
*
* @param InKey The entry's key.
* @param InValue The entry's value.
*/
FCacheEntry(const KeyType& InKey, const ValueType& InValue)
: Key(InKey)
, LessRecent(nullptr)
, MoreRecent(nullptr)
, Value(InValue)
{ }
/** Add this entry before the given one. */
FORCEINLINE void LinkBefore(FCacheEntry* Other)
{
LessRecent = Other;
if (Other != nullptr)
{
Other->MoreRecent = this;
}
}
/** Remove this entry from the list. */
FORCEINLINE void Unlink()
{
if (LessRecent != nullptr)
{
LessRecent->MoreRecent = MoreRecent;
}
if (MoreRecent != nullptr)
{
MoreRecent->LessRecent = LessRecent;
}
LessRecent = nullptr;
MoreRecent = nullptr;
}
};
/** Lookup set key functions. */
struct FKeyFuncs : public BaseKeyFuncs<FCacheEntry*, KeyType>
{
FORCEINLINE static const KeyType& GetSetKey(const FCacheEntry* Entry)
{
return Entry->Key;
}
FORCEINLINE static bool Matches(KeyType A, KeyType B)
{
return A == B;
}
FORCEINLINE static uint32 GetKeyHash(KeyType Key)
{
return GetTypeHash(Key);
}
};
public:
/** Default constructor (empty cache that cannot hold any values). */
TPsoLruCache()
: LeastRecent(nullptr)
, MostRecent(nullptr)
, MaxNumElements(0)
{ }
/**
* Create and initialize a new instance.
*
* @param InMaxNumElements The maximum number of elements this cache can hold.
*/
TPsoLruCache(int32 InMaxNumElements)
: LeastRecent(nullptr)
, MostRecent(nullptr)
, MaxNumElements(InMaxNumElements)
{
Empty(InMaxNumElements);
}
/** Destructor. */
~TPsoLruCache()
{
Empty();
}
public:
/**
* Add an entry to the cache.
*
* The new entry must not exist in the cache,
* there must be space within the LRU for the new entry.
* The new entry will be marked as the most recently used one.
*
* @param Key The entry's lookup key.
* @param Value The entry's value.
* @return FSetElementId of the entry, update recent status without requiring a find operation.
* @see Empty, Find, GetKeys, Remove
*/
FSetElementId Add(const KeyType& Key, const ValueType& Value)
{
check(MaxNumElements > 0 && "Cannot add values to zero size FOpenGLLruCache");
check(!LookupSet.Contains(Key));
check(LookupSet.Num() < MaxNumElements);
// add new entry
FCacheEntry* NewEntry = new FCacheEntry(Key, Value);
NewEntry->LinkBefore(MostRecent);
MostRecent = NewEntry;
if (LeastRecent == nullptr)
{
LeastRecent = NewEntry;
}
return LookupSet.Add(NewEntry);
}
/**
* Check whether an entry with the specified key is in the cache.
*
* @param Key The key of the entry to check.
* @return true if the entry is in the cache, false otherwise.
* @see Add, ContainsByPredicate, Empty, FilterByPredicate, Find, GetKeys, Remove
*/
FORCEINLINE bool Contains(const KeyType& Key) const
{
return LookupSet.Contains(Key);
}
/**
* Check whether an entry for which a predicate returns true is in the cache.
*
* @param Pred The predicate functor to apply to each entry.
* @return true if at least one matching entry is in the cache, false otherwise.
* @see Contains, FilterByPredicate, FindByPredicate, RemoveByPredicate
*/
template<typename Predicate>
FORCEINLINE bool ContainsByPredicate(Predicate Pred) const
{
for (const FCacheEntry* Entry : LookupSet)
{
if (Pred(Entry->Key, Entry->Value))
{
return true;
}
}
return false;
}
/**
* Empty the cache.
*
* @param InMaxNumElements The maximum number of elements this cache can hold (default = 0).
* @see Add, Find, GetKeys, Max, Num, Remove
*/
void Empty(int32 InMaxNumElements = 0)
{
check(InMaxNumElements >= 0);
for (FCacheEntry* Entry : LookupSet)
{
delete Entry;
}
MaxNumElements = InMaxNumElements;
LookupSet.Empty(MaxNumElements);
MostRecent = nullptr;
LeastRecent = nullptr;
}
/**
* Filter the entries in the cache using a predicate.
*
* @param Pred The predicate functor to apply to each entry.
* @return Collection of values for which the predicate returned true.
* @see ContainsByPredicate, FindByPredicate, Find, RemoveByPredicate
*/
template<typename Predicate>
TArray<ValueType> FilterByPredicate(Predicate Pred) const
{
TArray<ValueType> Result;
for (const FCacheEntry* Entry : LookupSet)
{
if (Pred(Entry->Key, Entry->Value))
{
Result.Add(Entry->Value);
}
}
return Result;
}
/**
* Find the value of the entry with the specified key.
*
* @param Key The key of the entry to get.
* @return Pointer to the value, or nullptr if not found.
* @see Add, Contains, Empty, FindAndTouch, GetKeys, Remove
*/
FORCEINLINE const ValueType* Find(const KeyType& Key) const
{
FCacheEntry** EntryPtr = LookupSet.Find(Key);
if (EntryPtr != nullptr)
{
return &(*EntryPtr)->Value;
}
return nullptr;
}
/**
* Find the value of the entry with the specified key and mark it as the most recently used.
*
* @param Key The key of the entry to get.
* @return Pointer to the value, or nullptr if not found.
* @see Add, Contains, Empty, Find, GetKeys, Remove
*/
const ValueType* FindAndTouch(const KeyType& Key)
{
FCacheEntry** EntryPtr = LookupSet.Find(Key);
if (EntryPtr == nullptr)
{
return nullptr;
}
MarkAsRecent(**EntryPtr);
return &(*EntryPtr)->Value;
}
/**
* Find the value of an entry using a predicate.
*
* @param Pred The predicate functor to apply to each entry.
* @return Pointer to value for which the predicate returned true, or nullptr if not found.
* @see ContainsByPredicate, FilterByPredicate, RemoveByPredicate
*/
template<typename Predicate>
const ValueType* FindByPredicate(Predicate Pred) const
{
for (const FCacheEntry* Entry : LookupSet)
{
if (Pred(Entry->Key, Entry->Value))
{
return &Entry->Value;
}
}
return nullptr;
}
/**
* Find the keys of all cached entries.
*
* @param OutKeys Will contain the collection of keys.
* @see Add, Empty, Find
*/
void GetKeys(TArray<KeyType>& OutKeys) const
{
for (const FCacheEntry* Entry : LookupSet)
{
OutKeys.Add(Entry->Key);
}
}
/**
* Get the maximum number of entries in the cache.
*
* @return Maximum number of entries.
* @see Empty, Num
*/
FORCEINLINE int32 Max() const
{
return MaxNumElements;
}
/**
* Get the number of entries in the cache.
*
* @return Number of entries.
* @see Empty, Max
*/
FORCEINLINE int32 Num() const
{
return LookupSet.Num();
}
/**
* Remove all entries with the specified key from the cache.
*
* @param Key The key of the entries to remove.
* @see Add, Empty, Find, RemoveByPredicate
*/
void Remove(const KeyType& Key)
{
FCacheEntry** EntryPtr = LookupSet.Find(Key);
if (EntryPtr != nullptr)
{
Remove(*EntryPtr);
}
}
bool Remove(const KeyType& Key, ValueType& RemovedValue)
{
FCacheEntry** EntryPtr = LookupSet.Find(Key);
if (EntryPtr != nullptr)
{
RemovedValue = MoveTemp((*EntryPtr)->Value);
Remove(*EntryPtr);
return true;
}
return false;
}
/**
* Remove all entries using a predicate.
*
* @param Pred The predicate function to apply to each entry.
* @return Number of removed entries.
* @see ContainsByPredicate, FilterByPredicate, FindByPredicate, Remove
*/
template<typename Predicate>
int32 RemoveByPredicate(Predicate Pred)
{
int32 NumRemoved = 0;
for (const FCacheEntry* Entry : LookupSet)
{
if (Pred(Entry->Key, Entry->Value))
{
Remove(Entry);
++NumRemoved;
}
}
return NumRemoved;
}
/**
* Remove and return the least recent element from the cache.
*
* @return Copy of removed value.
*/
FORCEINLINE ValueType RemoveLeastRecent()
{
check(LeastRecent);
ValueType LeastRecentElement = MoveTemp(LeastRecent->Value);
Remove(LeastRecent);
return LeastRecentElement;
}
/**
* Return the least recent element from the cache.
*
* @return Copy of the least recent value.
*/
FORCEINLINE const ValueType GetLeastRecent() const
{
check(LeastRecent);
return LeastRecent->Value;
}
/**
* Remove and return the most recent element from the cache.
*
* @return Copy of removed value.
*/
FORCEINLINE ValueType RemoveMostRecent()
{
check(MostRecent);
ValueType MostRecentElement = MoveTemp(MostRecent->Value);
Remove(MostRecent);
return MostRecentElement;
}
FORCEINLINE void MarkAsRecent(const FSetElementId& LRUNode)
{
MarkAsRecent(*LookupSet[LRUNode]);
}
public:
/**
* Base class for cache iterators.
*
* Iteration begins at the most recent entry.
*/
template<bool Const>
class TBaseIterator
{
public:
FORCEINLINE TBaseIterator()
: CurrentEntry(nullptr)
{ }
FORCEINLINE TBaseIterator(const TPsoLruCache& Cache)
: CurrentEntry(Cache.MostRecent)
{ }
public:
FORCEINLINE TBaseIterator& operator++()
{
Increment();
return *this;
}
FORCEINLINE friend bool operator==(const TBaseIterator& Lhs, const TBaseIterator& Rhs)
{
return Lhs.CurrentEntry == Rhs.CurrentEntry;
}
FORCEINLINE friend bool operator!=(const TBaseIterator& Lhs, const TBaseIterator& Rhs)
{
return Lhs.CurrentEntry != Rhs.CurrentEntry;
}
ValueType& operator->() const
{
check(CurrentEntry != nullptr);
return CurrentEntry->Value;
}
ValueType& operator*() const
{
check(CurrentEntry != nullptr);
return CurrentEntry->Value;
}
FORCEINLINE explicit operator bool() const
{
return (CurrentEntry != nullptr);
}
FORCEINLINE bool operator!() const
{
return !(bool)*this;
}
public:
FORCEINLINE KeyType& Key() const
{
check(CurrentEntry != nullptr);
return CurrentEntry->Key;
}
FORCEINLINE ValueType& Value() const
{
check(CurrentEntry != nullptr);
return CurrentEntry->Value;
}
protected:
FCacheEntry* GetCurrentEntry()
{
return CurrentEntry;
}
void Increment()
{
check(CurrentEntry != nullptr);
CurrentEntry = CurrentEntry->LessRecent;
}
private:
FCacheEntry* CurrentEntry;
};
/**
* Cache iterator (const).
*/
class TConstIterator
: public TBaseIterator<true>
{
public:
FORCEINLINE TConstIterator()
: TBaseIterator<true>()
{ }
FORCEINLINE TConstIterator(const TPsoLruCache& Cache)
: TBaseIterator<true>(Cache)
{ }
};
/**
* Cache iterator.
*/
class TIterator
: public TBaseIterator<false>
{
public:
FORCEINLINE TIterator()
: TBaseIterator<false>()
, Cache(nullptr)
{ }
FORCEINLINE TIterator(TPsoLruCache& InCache)
: TBaseIterator<false>(InCache)
, Cache(&InCache)
{ }
/** Removes the current element from the cache and increments the iterator. */
FORCEINLINE void RemoveCurrentAndIncrement()
{
check(Cache != nullptr);
FCacheEntry* MoreRecentEntry = this->GetCurrentEntry();
this->Increment();
Cache->Remove(MoreRecentEntry);
}
private:
TPsoLruCache* Cache;
};
protected:
/**
* Mark the given entry as recently used.
*
* @param Entry The entry to mark.
*/
FORCEINLINE void MarkAsRecent(FCacheEntry& Entry)
{
check(LeastRecent != nullptr);
check(MostRecent != nullptr);
// if entry is least recent and not the only item in the list, make it not least recent
if ((&Entry == LeastRecent) && (LeastRecent->MoreRecent != nullptr))
{
LeastRecent = LeastRecent->MoreRecent;
}
// relink if not already the most recent item
if (&Entry != MostRecent)
{
Entry.Unlink();
Entry.LinkBefore(MostRecent);
MostRecent = &Entry;
}
}
/**
* Remove the specified entry from the cache.
*
* @param Entry The entry to remove.
*/
FORCEINLINE void Remove(FCacheEntry* Entry)
{
if (Entry == nullptr)
{
return;
}
LookupSet.Remove(Entry->Key);
if (Entry == LeastRecent)
{
LeastRecent = Entry->MoreRecent;
}
if (Entry == MostRecent)
{
MostRecent = Entry->LessRecent;
}
Entry->Unlink();
delete Entry;
}
private:
friend TIterator begin(TPsoLruCache& Cache) { return TIterator(Cache); }
friend TConstIterator begin(const TPsoLruCache& Cache) { return TConstIterator(Cache); }
friend TIterator end(TPsoLruCache& Cache) { return TIterator(); }
friend TConstIterator end(const TPsoLruCache& Cache) { return TConstIterator(); }
private:
/** Set of entries for fast lookup. */
TSet<FCacheEntry*, FKeyFuncs> LookupSet;
/** Least recent item in the cache. */
FCacheEntry* LeastRecent;
/** Most recent item in the cache. */
FCacheEntry* MostRecent;
/** Maximum number of elements in the cache. */
int32 MaxNumElements;
};