// 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 concept CIsUObjectType = requires (T Object) { requires UE::CConvertibleTo; { Object->GetClass() } -> UE::CConvertibleTo; }; /** * Concept that detects a USTRUCT() type, requiring: * - T has a static method StaticStruct that returns a UScriptStruct* */ template concept CIsUStructType = requires (T Object) { { T::StaticStruct() } -> UE::CConvertibleTo; }; /** * 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 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::RangedForConstIteratorType begin() const { if(!State) { static const TArray EmptyArray; return EmptyArray.begin(); } const TArray& Arr = State->Arr; return Arr.begin(); } TArray::RangedForConstIteratorType end() const { if(!State) { static const TArray EmptyArray; return EmptyArray.end(); } const TArray& Arr = State->Arr; return Arr.end(); } template int32 Emplace(Args&&... InArgs) { FWriteScopeLock ScopeLock(Lock); CheckCopyOnWrite(); State->Arr.Emplace(Forward(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(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& 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 { if(!State) { return; } for(const T& Item : State->Arr) { TObjectPtr 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 { 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 ToArray() const { if(!State) { return {}; } TArray 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 Curr = State; TRefCountPtr 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::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::Type>(StaticDuplicateObject(From, GetTransientPackage()))); } } else { New->Arr.Append(Curr->Arr); } } State = New; } struct FState : public FThreadSafeRefCountedObject { TArray Arr; }; TRefCountPtr State = nullptr; FRWLock Lock; }; // Variadic helper to call AddReferencedObjects on multiple arrays template void AddArrayReferences(FReferenceCollector& Collector, const T&... Args) { (Args.AddReferencedObjects(Collector), ...); }