// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "MuT/Compiler.h" #include "MuT/NodeImage.h" #include "MuR/Image.h" #include "MuR/Mesh.h" #include "MuR/ModelPrivate.h" #include "MuR/MutableMath.h" #include "MuR/Operations.h" #include "MuR/ParametersPrivate.h" #include "MuR/Ptr.h" #include "MuR/RefCounted.h" #include "Containers/Array.h" #include "Containers/ContainerAllocationPolicies.h" #include "Containers/Map.h" #include "HAL/PlatformCrt.h" #include "HAL/PlatformMath.h" #include "Misc/AssertionMacros.h" #include "Templates/TypeHash.h" #include "Templates/Function.h" #include "Hash/CityHash.h" #include #include #include namespace std { template struct hash> { uint64 operator()(const UE::Mutable::Private::Ptr& k) const { return hash()(k.get()); } }; } namespace { // TODO: Replace with UE hashing template inline void hash_combine(uint64& seed, const T& v) { std::hash hasher; seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } } namespace UE::Mutable::Private { class ASTOp; class ASTOpFixed; class ASTOpImageCrop; class ASTOpImagePixelFormat; class ASTOpMeshOptimizeSkinning; class ASTOpMeshFormat; class ASTOpImageSwizzle; class ASTOpImageMipmap; class ASTOpMeshExtractLayoutBlocks; struct FProxyFileContext; template inline uint32 GetTypeHash(const Ptr& p) { return ::GetTypeHash(p.get()); } template inline uint32 GetTypeHash(const Ptr& p) { return ::GetTypeHash(p.get()); } //--------------------------------------------------------------------------------------------- //! This class stores the expression that defines the size of an image. //-------------------------------------------------------------------------------------------- class ImageSizeExpression : public RefCounted { public: enum { ISET_UNKNOWN, ISET_CONSTANT, ISET_LAYOUTFACTOR, ISET_CONDITIONAL } type; // For constant sizes FImageSize size = FImageSize(0, 0); // For layout factor sizes Ptr layout; uint16 factor[2]; // For conditionals Ptr condition; Ptr yes; Ptr no; //! ImageSizeExpression() { type = ISET_UNKNOWN; size[0] = 0; size[1] = 0; layout = 0; factor[0] = 0; factor[1] = 0; condition = 0; } //! void CopyFrom(const ImageSizeExpression& o) { type = o.type; size = o.size; layout = o.layout; factor[0] = o.factor[0]; factor[1] = o.factor[1]; condition = o.condition; yes = o.yes; no = o.no; } //! bool operator==( const ImageSizeExpression& other ) const { if ( type == other.type ) { switch (type) { case ISET_CONSTANT: return size[0]==other.size[0] && size[1] == other.size[1]; case ISET_LAYOUTFACTOR: return layout==other.layout && factor[0]==other.factor[0] && factor[1]==other.factor[1]; case ISET_CONDITIONAL: return condition==other.condition && *yes==*(other.yes) && *no==*(other.no); default: return false; } } return false; } void Optimise() { switch (type) { case ISET_CONSTANT: break; case ISET_LAYOUTFACTOR: // TODO: See if the layout is constant and so is this expression. break; case ISET_CONDITIONAL: yes->Optimise(); no->Optimise(); // TODO: See if the condition is constant if ( *yes==*no ) { CopyFrom( *yes ); } default: break; } } }; typedef TArray> ASTOpList; typedef TSet> ASTOpSet; //! Detailed optimization flags struct FModelOptimizationOptions { bool bEnabled = true; bool bOptimiseOverlappedMasks = false; bool bConstReduction = true; //! Preprocess all mesh fragments so that they use the same skeleton, even if not all bones //! are relevant for all fragments. bool bUniformizeSkeleton = true; //! Maximum number of iterations when optimising models. If 0 as many as necessary will be performed. int32 MaxOptimisationLoopCount = 8; /** If valied, store resource data in disk instead of memory. */ FProxyFileContext* DiskCacheContext = nullptr; /** Compile optimizing for the generation of smaller mipmaps of every image. */ bool bEnableProgressiveImages = false; // Additional advanced fine-tuning parameters //--------------------------------------------------------------------- //! Ratio used to decide if it is worth to generate a crop operation float AcceptableCropRatio = 0.5f; //! Ratio used to decide if it is worth to generate a crop operation float MinRLECompressionGain = 1.2f; // External resource provision functions //--------------------------------------------------------------------- /** Function used to request an engine resource from the compiler. */ FReferencedImageResourceFunc ReferencedImageResourceProvider; FReferencedMeshResourceFunc ReferencedMeshResourceProvider; bool bDisableImageGeneration = false; bool bDisableMeshGeneration = false; }; struct FLinkerOptions { FLinkerOptions(FImageOperator& InImOp) : ImageOperator(InImOp) { } // TODO: Unused? int32 MinTextureResidentMipCount = 0; /** This flag controls the splitting of image data into mips to store separately. It is usually necessary to * be able to generate progressive textures (for texture streaming). */ bool bSeparateImageMips = true; /** Structure used to speedup mesh constant comparison. */ struct FDeduplicationMeshFuncs : TDefaultMapHashableKeyFuncs, int32, false> { static FORCEINLINE bool Matches(KeyInitType A, KeyInitType B) { return *A == *B; } static FORCEINLINE uint32 GetKeyHash(KeyInitType Key) { const UE::Mutable::Private::FMesh* Data = Key.Get(); return HashCombineFast( ::GetTypeHash(Data->VertexBuffers.GetElementCount()), ::GetTypeHash(Data->IndexBuffers.GetElementCount()) ); } }; TMap, int32, FDefaultSetAllocator, FDeduplicationMeshFuncs> MeshConstantMap; /** Structure used to speedup image mip comparison. */ struct FDeduplicationImageFuncs : TDefaultMapHashableKeyFuncs, int32, false> { static FORCEINLINE bool Matches(KeyInitType A, KeyInitType B) { return *A == *B; } static FORCEINLINE uint32 GetKeyHash(KeyInitType Key) { const UE::Mutable::Private::FImage* Data = Key.Get(); uint32 Hash = HashCombineFast(::GetTypeHash(Data->GetFormat()), GetTypeHash(Data->GetSize())); TArrayView DataView = Data->DataStorage.GetLOD(0); uint64 DataHash = CityHash64(reinterpret_cast(DataView.GetData()), DataView.Num()); Hash = HashCombineFast(Hash, ::GetTypeHash(DataHash)); return Hash; } }; TMap, int32, FDefaultSetAllocator, FDeduplicationImageFuncs> ImageConstantMipMap; /** Image operation functions, so that they can be overriden. */ FImageOperator& ImageOperator; /** Store for additional data generated during compilation, but not necessary for the runtime. */ struct FAdditionalData { /** Source data descriptor for every image constant that has been generated. * It must have the same size than the Program::ConstantImages array. */ TArray SourceImagePerConstant; /** Source data descriptor for every mesh constant that has been generated. * It must have the same size than the Program::ConstantMeshes array. */ TArray SourceMeshPerConstant; }; FAdditionalData AdditionalData; }; //! For each operation we sink, the map from old instructions to new instructions. struct FSinkerOldToNewKey { Ptr Op; Ptr SinkingOp; friend inline uint32 GetTypeHash(const FSinkerOldToNewKey& Key) { return HashCombineFast(::GetTypeHash(Key.Op.get()), ::GetTypeHash(Key.SinkingOp.get())); } friend inline bool operator==(const FSinkerOldToNewKey& A, const FSinkerOldToNewKey& B) { return A.Op==B.Op && A.SinkingOp==B.SinkingOp; } }; /** */ class Sink_ImageCropAST { public: Ptr Apply(const ASTOpImageCrop* root); protected: const ASTOpImageCrop* Root = nullptr; Ptr InitialSource; //! For each operation we sink, the map from old instructions to new instructions. TMap> OldToNew; Ptr Visit(Ptr at, const ASTOpImageCrop* currentCropOp); }; /** */ class Sink_ImagePixelFormatAST { public: Ptr Apply(const ASTOpImagePixelFormat* root); protected: const ASTOpImagePixelFormat* Root = nullptr; Ptr InitialSource; TMap> OldToNew; Ptr Visit(Ptr at, const ASTOpImagePixelFormat* currentFormatOp); }; /** */ class Sink_ImageSwizzleAST { public: Ptr Apply(const ASTOpImageSwizzle* Root); protected: const ASTOpImageSwizzle* Root = nullptr; Ptr InitialSource; TMap> OldToNew; Ptr Visit(Ptr at, const ASTOpImageSwizzle* CurrentSwizzleOp); }; /** */ class Sink_MeshFormatAST { public: Ptr Apply(const ASTOpMeshFormat* Root); protected: const ASTOpMeshFormat* Root = nullptr; Ptr InitialSource; TMap> OldToNew; Ptr Visit(const Ptr& Op, const ASTOpMeshFormat* CurrentSinkOp); }; /** */ class Sink_MeshOptimizeSkinningAST { public: Ptr Apply(const ASTOpMeshOptimizeSkinning* InRoot); protected: const ASTOpMeshOptimizeSkinning* Root = nullptr; Ptr InitialSource; TMap> OldToNew; Ptr Visit(const Ptr& Op, const ASTOpMeshOptimizeSkinning* CurrentSinkOp); }; /** */ class Sink_MeshExtractLayoutBlocksAST { public: Ptr Apply(const ASTOpMeshExtractLayoutBlocks* Root); protected: const ASTOpMeshExtractLayoutBlocks* Root = nullptr; Ptr InitialSource; TMap> OldToNew; Ptr Visit(const Ptr& at, const ASTOpMeshExtractLayoutBlocks* currentFormatOp); }; /** */ class Sink_ImageMipmapAST { public: Ptr Apply(const ASTOpImageMipmap* Root); protected: const ASTOpImageMipmap* Root = nullptr; Ptr InitialSource; //! For each operation we sink, the map from old instructions to new instructions. TMap> OldToNew; Ptr Visit(Ptr at, const ASTOpImageMipmap* currentMipmapOp); }; struct FOptimizeSinkContext { Sink_ImageCropAST ImageCropSinker; Sink_ImagePixelFormatAST ImagePixelFormatSinker; Sink_ImageSwizzleAST ImageSwizzleSinker; Sink_ImageMipmapAST ImageMipmapSinker; Sink_MeshFormatAST MeshFormatSinker; Sink_MeshExtractLayoutBlocksAST MeshExtractLayoutBlocksSinker; Sink_MeshOptimizeSkinningAST MeshOptimizeSkinningSinker; }; //! class ASTChild { public: explicit ASTChild(ASTOp* parent, const Ptr& child=Ptr()); ASTChild(const Ptr& parent, const Ptr& child); ~ASTChild(); ASTChild(const ASTChild&) = delete; ASTChild& operator=(const ASTChild&) = delete; // move constructor ASTChild(ASTChild&& rhs) : Parent(rhs.Parent) , Child(rhs.Child) , ParentIndexInChild(rhs.ParentIndexInChild) { rhs.Parent=nullptr; rhs.Child.reset(); } // Move assignment ASTChild& operator=( ASTChild&& ); ASTChild& operator=( const Ptr& ); inline explicit operator bool() const { return Child.get()!=nullptr; } inline Ptr& child() { return Child; } inline const Ptr& child() const { return Child; } inline const Ptr& operator->() const { return Child; } inline bool operator==(const ASTChild& o) const { return Child==o.Child; } class ASTOp* Parent; Ptr Child; int32 ParentIndexInChild = 0; private: inline void AddParent(); inline void ClearParent(); }; //--------------------------------------------------------------------------------------------- //! Abstract Syntax Tree of operations in the mutable virtual machine. //! Avoid any kind of recursivity here, since the hierarchy can be very deep, and it will //! easily cause stack overflows with production models. //--------------------------------------------------------------------------------------------- class ASTOp : public RefCounted { private: //! Operations referring to this one. They may be null: elements are never removed //! from this TArray. TArray > Parents; UE::FMutex ParentsMutex; public: inline ASTOp() { bIsConstantSubgraph = false; bHasSpecialOpInSubgraph = false; } virtual ~ASTOp() {} //! Get the operation type virtual EOpType GetOpType() const = 0; //! Validate that everything is fine with this tree virtual void Assert(); //! Run something for each child operation, with a chance to modify it. virtual void ForEachChild( const TFunctionRef ) = 0; //! Run something for each parent operation+. void ForEachParent(const TFunctionRef) const; //! Run something for each child operation, with a chance to modify it. virtual bool operator==( const ASTOp& other ) const; //! Hint hash method for op sorting and containers. It's a hash of the actual operation. virtual uint64 Hash() const = 0; //! Shallow clone. New node will have no parents but reference to the same children. using MapChildFunc = TFunction(const Ptr&)>; using MapChildFuncRef = TFunctionRef(const Ptr&)>; virtual Ptr Clone(MapChildFuncRef mapChild) const = 0; // virtual bool IsConditional() const { return false; } virtual bool IsSwitch() const { return false; } protected: void RemoveChildren(); virtual bool IsEqual(const ASTOp& other) const = 0; public: //--------------------------------------------------------------------------------------------- static void FullAssert( const TArray>& roots ); static int32 CountNodes( const TArray>& roots ); inline bool IsConstantOp() const { EOpType Type = GetOpType(); return Type == EOpType::BO_CONSTANT || Type == EOpType::NU_CONSTANT || Type == EOpType::SC_CONSTANT || Type == EOpType::CO_CONSTANT || Type == EOpType::IM_CONSTANT || Type == EOpType::ME_CONSTANT || Type == EOpType::LA_CONSTANT || Type == EOpType::PR_CONSTANT || Type == EOpType::ST_CONSTANT || Type == EOpType::ED_CONSTANT ; } //! Deep clone. New node will have no parents and reference new children static Ptr DeepClone( const Ptr& ); //! static void LogHistogram(ASTOpList& roots); // Code optimisation methods //--------------------------------------------------------------------------------------------- //! virtual Ptr OptimiseSize() const { return nullptr; } virtual Ptr OptimiseSemantic(const FModelOptimizationOptions&, int32 Pass) const { return nullptr; } virtual Ptr OptimiseSink(const FModelOptimizationOptions&, FOptimizeSinkContext& ) const { return nullptr; } virtual Ptr GetImageSizeExpression() const { check( false ); return nullptr; } // Code linking //--------------------------------------------------------------------------------------------- /** Convert the operation graph at Root into code in the given program. * Potentially destroys the data in this operation, so it shouldn't be used after calling Link. */ static OP::ADDRESS FullLink( Ptr& Root, FProgram&, FLinkerOptions* ); private: /** Convert this operation into code in the given program. * It assumes children have been linked already * Potentially destroys the data in this operation, so it shouldn't be used after calling Link. */ virtual void Link( FProgram&, FLinkerOptions* ) = 0; protected: //--------------------------------------------------------------------------------------------- //! //--------------------------------------------------------------------------------------------- struct FRangeData { //! ASTChild rangeSize; //! FString rangeName; //! FString rangeUID; //! FRangeData( ASTOp* parentOp, Ptr childOp, const FString& name, const FString& uid ) : rangeSize( parentOp, childOp ) , rangeName(name) , rangeUID(uid) { } FRangeData(const FRangeData&) = delete; FRangeData& operator=(const FRangeData&) = delete; FRangeData& operator=(FRangeData&&) = delete; // move constructor FRangeData(FRangeData&& rhs) : rangeSize(MoveTemp(rhs.rangeSize)) , rangeName(MoveTemp(rhs.rangeName)) , rangeUID(MoveTemp(rhs.rangeUID)) { } //! //! bool operator==(const FRangeData& o) const { return rangeSize==o.rangeSize && rangeName==o.rangeName && rangeUID==o.rangeUID; } }; //! static void LinkRange(FProgram& program, const FRangeData& range, OP::ADDRESS& rangeSize, uint16& rangeId); public: // Generic traversals //--------------------------------------------------------------------------------------------- //! static void Traverse_TopDown_Unique( const TArray>& roots, TFunctionRef&)> f ); //! \todo: it is not strictly top down. static void Traverse_TopDown_Unique_Imprecise( const TArray>& roots, TFunctionRef&)> f ); //! Kind of top-down, but really not. //! This version is slighlty faster, but doesn't support recursive traversals so //! use it only in controlled cases. static void Traverse_TopRandom_Unique_NonReentrant ( const TArray>& roots, TFunctionRef&)> f ); template struct FKeyFuncs : BaseKeyFuncs, STATE>, Ptr, false> { static Ptr GetSetKey(TPair, STATE> Element) { return Element.Key; } static bool Matches(const Ptr& lhs, const Ptr& rhs) { return lhs == rhs || *lhs == *rhs; } static uint32 GetKeyHash(const Ptr& Key) { return Key->Hash(); } }; //! Kind of top-down, but really not. //! This version is slighlty faster, but doesn't support recursive traversals so //! use it only in controlled cases. template static inline void Traverse_TopDown_Unique_Imprecise_WithState ( Ptr& root, const STATE& initialState, TFunctionRef&, STATE&, TArray,STATE>>&)> f ) { if (!root) { return; } TArray,STATE>> Pending; Pending.Emplace( root, initialState ); struct custom_partial_hash { uint64 operator()(const std::pair,STATE>& k) const { return std::hash()(k.first.get()); } }; TSet,STATE>, FKeyFuncs> Traversed; while (!Pending.IsEmpty()) { TPair,STATE> current = Pending.Pop(); // It could have been completed in another branch bool bAlreadyAdded = false; Traversed.Add({ current.Key,current.Value }, &bAlreadyAdded); if (!bAlreadyAdded) { // Process. State in current.Value may change bool bRecurse = f(current.Key,current.Value,Pending); // Recurse children if (bRecurse) { current.Key->ForEachChild([&]( ASTChild& c ) { if (c.Child && !Traversed.Contains(c.Child)) { Pending.Emplace( c.Child, current.Value ); } }); } } } } //! This version is slighlty faster, but doesn't support recursive traversals so //! use it only in controlled cases. static void Traverse_BottomUp_Unique_NonReentrant( ASTOpList& roots, TFunctionRef&)> f ); //! This version is slighlty faster, but doesn't support recursive traversals so //! use it only in controlled cases. static void Traverse_BottomUp_Unique_NonReentrant ( ASTOpList& roots, TFunctionRef&)> f, TFunctionRef accept ); //! static void Traverse_BottomUp_Unique ( ASTOpList& roots, TFunctionRef&)> f, TFunctionRef accept = [](const ASTOp*){return true;} ); //! static void Traverse_BottomUp_Unique ( Ptr& root, TFunctionRef&)> f, TFunctionRef accept = [](const ASTOp*){return true;} ); //! OP::ADDRESS linkedAddress = 0; //! Generic traverse control counter. It should always be left to 0 after any process for all //! nodes in the hierarchy. uint32 m_traverseIndex = 0; static std::atomic s_lastTraverseIndex; //! int8 linkedRange = -1; /** Embedded node data for the constant subtree detection.This flag is only valid if the * constant detection process has been executed and no relevant AST transformations have * happened. */ uint8 bIsConstantSubgraph : 1; uint8 bHasSpecialOpInSubgraph : 1; private: friend class ASTChild; //! Remove a node from the parent list if it is there. //void RemoveParent(const ASTOp* parent); public: /** Get the number of ASTOps that have this one as child, in any existing AST graph. */ int32 GetParentCount() const; //! Make all parents of this node point at the other node instead. static void Replace( const Ptr& node, const Ptr& other ); // Other code generation utilities //--------------------------------------------------------------------------------------------- //! This class contains the support data to accelerate the GetImageDesc recursive function. //! If none is provided in the call, one will be created at that level and used from there on. class FGetImageDescContext { public: TMap m_results; }; //! virtual FImageDesc GetImageDesc( bool returnBestOption=false, FGetImageDescContext* context=nullptr ) const; /** */ class FGetSourceDataDescriptorContext { public: TMap Cache; }; /** */ virtual FSourceDataDescriptor GetSourceDataDescriptor(FGetSourceDataDescriptorContext* = nullptr) const; //! Optional cache struct to use int he method below. using FBlockLayoutSizeCache=TMap< const TPair, TPair>; //! Return the size in layout blocks of a particular block given by absolute index virtual void GetBlockLayoutSize( uint64 BlockId, int32* pBlockX, int32* pBlockY, FBlockLayoutSizeCache* cache ); void GetBlockLayoutSizeCached(uint64 BlockId, int32* pBlockX, int32* pBlockY, FBlockLayoutSizeCache* cache ); //! Return the size in pixels of the layout grid block for the image operation virtual void GetLayoutBlockSize( int32* pBlockX, int32* pBlockY ); virtual bool IsImagePlainConstant(FVector4f& colour ) const; virtual bool IsColourConstant(FVector4f& colour ) const; //! virtual bool GetNonBlackRect( FImageRect& maskUsage ) const; enum class EClosedMeshTest : uint8 { No, Yes, Unknown }; /** This can be overriden to help detect sugraph mesh properties. */ virtual EClosedMeshTest IsClosedMesh(TMap* Cache=nullptr ) const; // Logic expression evaluation //--------------------------------------------------------------------------------------------- typedef enum { BET_UNKNOWN, BET_TRUE, BET_FALSE, } FBoolEvalResult; using FEvaluateBoolCache = std::unordered_map; virtual FBoolEvalResult EvaluateBool( ASTOpList& /*facts*/, FEvaluateBoolCache* = nullptr ) const { check(false); return BET_UNKNOWN; } virtual int EvaluateInt( ASTOpList& /*facts*/, bool &unknown ) const { check(false); unknown = true; return 0; } }; template inline Ptr Clone( const ASTOp* s ) { ASTOp::MapChildFunc Identity = [](const Ptr& o) {return o; }; Ptr c = s->Clone(Identity); Ptr t = static_cast(c.get()); return t; } template inline Ptr Clone( const Ptr& s ) { ASTOp::MapChildFunc Identity = [](const Ptr& o) {return o; }; Ptr c = s->Clone(Identity); Ptr t = static_cast(c.get()); return t; } //--------------------------------------------------------------------------------------------- //! //--------------------------------------------------------------------------------------------- template class Visitor_TopDown_Unique_Const { private: //! virtual bool Visit( const Ptr& node ) = 0; private: //! States found so far TArray States; //! Index of the current state, from the States TArray. int32 CurrentState; struct FPending { FPending() { StateIndex = 0; } FPending(Ptr InAt, int32 InStateIndex) { At = InAt; StateIndex = InStateIndex; } Ptr At; int32 StateIndex; }; TArray Pending; //! List of Traversed nodes with the state in which they were Traversed. TMultiMap,int32> Traversed; protected: //! const STATE& GetCurrentState() const { return States[CurrentState]; } //! STATE GetDefaultState() const { return States[0]; } //! For manual recursion that changes the state for a specific path. void RecurseWithState(const Ptr& InAt, const STATE& NewState) { if(InAt) { int32 Index = States.Find(NewState); if (Index==INDEX_NONE) { States.Add(NewState); } int32 StateIndex = States.Find(NewState); Pending.Add( FPending(InAt,StateIndex) ); } } //! For manual recursion that doesn't change the state for a specific path. void RecurseWithCurrentState(const Ptr& InAt) { if(InAt) { Pending.Emplace(InAt, CurrentState ); } } //! Can be called from visit to set the state to visit all children ops void SetCurrentState(const STATE& NewState) { int32 Index = States.Find(NewState); if (Index == INDEX_NONE) { States.Add(NewState); } CurrentState = States.Find(NewState); } public: //! Ensure virtual destruction virtual ~Visitor_TopDown_Unique_Const() {} //! void Traverse(const ASTOpList& Roots, const STATE& InitialState) { Pending.Empty(); States.Empty(); States.Add(InitialState); CurrentState = 0; for( const Ptr& Root: Roots) { if (Root) { Pending.Add(FPending(Root,CurrentState) ); } } while (Pending.Num()) { FPending Item = Pending.Pop(); TArray Found; Traversed.MultiFind(Item.At, Found); bool bVisitedInThisState = false; for (int32 Value: Found) { if (Value==Item.StateIndex) { bVisitedInThisState = true; break; } } // It could have been completed in another branch if (!bVisitedInThisState) { Traversed.Add( Item.At, Item.StateIndex); // Process CurrentState = Item.StateIndex; bool bRecurse = Visit(Item.At); // Recurse children if (bRecurse) { Item.At->ForEachChild([&]( ASTChild& c ) { if (c) { Pending.Add( FPending(c.child(), CurrentState) ); } }); } } } } }; //--------------------------------------------------------------------------------------------- //! Stateless top-down code visitor that can change the instructions. Iterative version. //! Once an instruction has changed, all the chain of instructions up to the root will be //! cloned, referencing the new instruction. //--------------------------------------------------------------------------------------------- class Visitor_TopDown_Unique_Cloning { public: //! Ensure virtual destruction virtual ~Visitor_TopDown_Unique_Cloning() {} protected: //! Do the actual work by overriding this in the derived classes. virtual Ptr Visit( Ptr at, bool& processChildren ) = 0; void Traverse( Ptr& root ); private: //! Operations to be processed TArray< TPair> > Pending; //! Map for visited operations TMap,Ptr> OldToNew; //! inline Ptr GetOldToNew( const Ptr& Old ) const { Ptr n; const Ptr* it = OldToNew.Find(Old); if (it) { n = *it; } it = OldToNew.Find(n); while (it && it->get()!=nullptr && *it!=n) { n = *it; it = OldToNew.Find(n); } return n; } //! Process all the Pending operations and visit all children if necessary void Process(); }; inline void ASTChild::AddParent() { UE::TScopeLock Lock(Child->ParentsMutex); ParentIndexInChild = Child->Parents.Num(); Child->Parents.Add(Parent); } inline void ASTChild::ClearParent() { UE::TScopeLock Lock(Child->ParentsMutex); check( ParentIndexInChildParents.Num() ); // Can't do this, because the indices are stored in children. //Child->m_parents.RemoveAtSwap(ParentIndexInChild); Child->Parents[ParentIndexInChild] = nullptr; } /** */ class FUniqueOpPool { private: struct FKeyFuncs : BaseKeyFuncs, Ptr, false> { static KeyInitType GetSetKey(ElementInitType Element) { return Element; } static bool Matches(const Ptr& lhs, const Ptr& rhs) { return lhs==rhs || *lhs == *rhs; } static uint32 GetKeyHash(const Ptr& Key) { return Key->Hash(); } }; // Existing ops, per type TSet, FKeyFuncs> Visited[(int32)EOpType::COUNT]; public: bool bDisabled = false; Ptr Add( const Ptr& Op ) { if (bDisabled) { return Op; } if (!Op) { return nullptr; } TSet, FKeyFuncs>& Container = Visited[(int32)Op->GetOpType()]; return Container.FindOrAdd(Op); } }; }