Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

273 lines
5.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/Array.h"
#include "Concepts/ConvertibleTo.h"
#include "Misc/ScopeRWLock.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/ObjectPtr.h"
#include "Dataflow/DataflowContextCache.h"
class UObject;
class UClass;
class UScriptStruct;
/**
* Concept that detects a ptr to UObject type, requiring:
* - T is a ptr type
* - T is convertible UObject*
* - T Has a method GetClass the returns a UClass
*/
template<typename T>
concept CIsUObjectType = requires (T Object)
{
requires UE::CConvertibleTo<T, UObject*>;
{ Object->GetClass() } -> UE::CConvertibleTo<UClass*>;
};
/**
* Concept that detects a USTRUCT() type, requiring:
* - T has a static method StaticStruct that returns a UScriptStruct*
*/
template<typename T>
concept CIsUStructType = requires (T Object)
{
{ T::StaticStruct() } -> UE::CConvertibleTo<UScriptStruct*>;
};
/**
* Wrapper for an array that implements copy-on-write for the array.
* If there are N readers but no writers there will be a single copy of the array
* If a codepath writes to the array and they aren't the only referencer, the array will be duplicated
* This means each modification will not implicitly duplicate - only if there are multiple referencers
* otherwise, we modify in place.
*/
template<typename T>
class TCopyOnWriteArray
{
public:
TCopyOnWriteArray()
{
}
TCopyOnWriteArray(const TCopyOnWriteArray& Other)
{
State = Other.State;
}
TCopyOnWriteArray(TCopyOnWriteArray&& Other)
{
State = MoveTemp(Other.State);
}
TCopyOnWriteArray& operator=(const TCopyOnWriteArray& Other)
{
State = Other.State;
return *this;
}
TCopyOnWriteArray& operator=(TCopyOnWriteArray&& Other)
{
State = MoveTemp(Other.State);
return *this;
}
const T& operator [](int32 Index)
{
checkf(State, TEXT("Array index out of bounds: %d into an array of size 0"), Index);
return State->Arr[Index];
}
TArray<T>::RangedForConstIteratorType begin() const
{
if(!State)
{
static const TArray<T> EmptyArray;
return EmptyArray.begin();
}
const TArray<T>& Arr = State->Arr;
return Arr.begin();
}
TArray<T>::RangedForConstIteratorType end() const
{
if(!State)
{
static const TArray<T> EmptyArray;
return EmptyArray.end();
}
const TArray<T>& Arr = State->Arr;
return Arr.end();
}
template<typename... Args>
int32 Emplace(Args&&... InArgs)
{
FWriteScopeLock ScopeLock(Lock);
CheckCopyOnWrite();
State->Arr.Emplace(Forward<Args>(InArgs)...);
return State->Arr.Num();
}
int32 Add(const T& Value)
{
FWriteScopeLock ScopeLock(Lock);
CheckCopyOnWrite();
State->Arr.Add(Value);
return State->Arr.Num();
}
int32 Add(T&& Value)
{
FWriteScopeLock ScopeLock(Lock);
CheckCopyOnWrite();
State->Arr.Add(Forward<T>(Value));
return State->Arr.Num();
}
void Modify(int32 AtIndex, const T& Value)
{
FWriteScopeLock ScopeLock(Lock);
CheckCopyOnWrite();
State->Arr[AtIndex] = Value;
}
void Modify(int32 AtIndex, T&& Value)
{
FWriteScopeLock ScopeLock(Lock);
CheckCopyOnWrite();
State->Arr[AtIndex] = MoveTemp(Value);
}
void Append(const TArray<T>& Other)
{
FWriteScopeLock ScopeLock(Lock);
CheckCopyOnWrite();
State->Arr.Append(Other);
}
// If the array contains UObjects, we need to emit references to them directly
void AddReferencedObjects(FReferenceCollector& Collector) const
requires CIsUObjectType<T>
{
if(!State)
{
return;
}
for(const T& Item : State->Arr)
{
TObjectPtr<UObject> AsObject = Item;
Collector.AddReferencedObject(AsObject);
AsObject->AddReferencedObjects(AsObject, Collector);
}
}
// If the array contains USTRUCT types, they might reference UObject types and
// need to emit references. Defer to the struct here to add required references
void AddReferencedObjects(FReferenceCollector& Collector) const
requires CIsUStructType<T>
{
if(!State)
{
return;
}
UScriptStruct* Struct = T::StaticStruct();
for(const T& Item : State->Arr)
{
Struct->GetCppStructOps()->AddStructReferencedObjects()((void*)&Item, Collector);
}
}
// Base case for storage types that don't emit references
void AddReferencedObjects(FReferenceCollector& Collector) const
{
}
// Copy the internal state into a new TArray
TArray<T> ToArray() const
{
if(!State)
{
return {};
}
TArray<T> NewArray;
NewArray.Reserve(State->Arr.Num());
NewArray.Append(State->Arr);
return MoveTemp(NewArray);
}
private:
void CheckCopyOnWrite()
{
// If we have no state, or there are more than one reader, duplicate before writing
if(!State || State->GetRefCount() > 1)
{
Duplicate();
}
}
void Duplicate()
{
TRefCountPtr<FState> Curr = State;
TRefCountPtr<FState> New = new FState;
// Duplicate if there's a previous state
if(Curr)
{
// It's safe to iterate and append the old array here. If we're duplicating then all writers will duplicate.
// The current state is logically const at this point in time.
if constexpr(UE::Dataflow::template TIsUObjectPtrElement<T>::Value)
{
// UObjects need to run through StaticDuplicateObject rather than just be copied.
New->Arr.Reserve(Curr->Arr.Num());
for(const T& From : Curr->Arr)
{
New->Arr.Add(Cast<typename TRemoveObjectPointer<T>::Type>(StaticDuplicateObject(From, GetTransientPackage())));
}
}
else
{
New->Arr.Append(Curr->Arr);
}
}
State = New;
}
struct FState : public FThreadSafeRefCountedObject
{
TArray<T> Arr;
};
TRefCountPtr<FState> State = nullptr;
FRWLock Lock;
};
// Variadic helper to call AddReferencedObjects on multiple arrays
template<typename... T>
void AddArrayReferences(FReferenceCollector& Collector, const T&... Args)
{
(Args.AddReferencedObjects(Collector), ...);
}