// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "Containers/ArrayView.h" #include "Math/Range.h" #include "Math/RangeBound.h" #include "Math/UnrealMathSSE.h" #include "Misc/AssertionMacros.h" #include "Misc/FrameNumber.h" #include "Serialization/Archive.h" #include "Templates/Function.h" #include "Templates/MemoryOps.h" #include "Templates/UnrealTemplate.h" #include "UObject/SequencerObjectVersion.h" struct FMovieSceneEvaluationTree; struct FMovieSceneEvaluationTreeNode; struct FMovieSceneEvaluationTreeRangeIterator; template struct TMovieSceneEvaluationTreeDataIterator; template struct TMovieSceneEvaluationTreeFormatter; /** A structure that uniquely identifies an entry within a TEvaluationTreeEntryContainer */ struct FEvaluationTreeEntryHandle { /** * Default construction to an invalid handle */ FEvaluationTreeEntryHandle() : EntryIndex(INDEX_NONE) {} /** * Check whether this identifier has been initialized. Caution: does not check that the identifier is valid within a given TEvaluationTreeEntryContainer */ bool IsValid() const { return EntryIndex != INDEX_NONE; } /** * Equality operator */ friend bool operator==(const FEvaluationTreeEntryHandle& A, const FEvaluationTreeEntryHandle& B) { return A.EntryIndex == B.EntryIndex; } /** * Inequality operator */ friend bool operator!=(const FEvaluationTreeEntryHandle& A, const FEvaluationTreeEntryHandle& B) { return A.EntryIndex != B.EntryIndex; } /** * Serialization operator */ friend FArchive& operator<<(FArchive& Ar, FEvaluationTreeEntryHandle& In) { return Ar << In.EntryIndex; } private: template friend struct TEvaluationTreeEntryContainer; template friend struct TMovieSceneEvaluationTreeFormatter; FEvaluationTreeEntryHandle(int32 InEntryIndex) : EntryIndex(InEntryIndex) {} /** Specifies an index into TEvaluationTreeEntryContainer::Entries */ int32 EntryIndex; }; /** * Sub-divided container type that allocates smaller 'buckets' of capacity within a single allocation. * New entries, or entries needing additional capacity are reallocated at the end of the array to avoid reallocation or shuffling. * Memory footprint can be compacted once the container is fully built using TEvaluationTreeEntryContainer::Compact * Designed specifically to solve allocation cost incurred when compiling very large trees of sequence data. * Restrictions: Container can only ever add entries, never remove */ template struct TEvaluationTreeEntryContainer { /** * Allocate a new entry of the specified capacity. Will default-construct Capacity elements in the item array. * * @param InitialCapacity The initial capacity of the entry * @return The identifier to be used to access the entry */ FEvaluationTreeEntryHandle AllocateEntry(int32 InitialCapacity = 2); /** * Access the entry contents corresponding to the specified ID * @return An array view for the currently allocated elements, not including spare capacity */ TArrayView Get(FEvaluationTreeEntryHandle ID); /** * Const access to the entry contents corresponding to the specified ID * @return A const array view for the currently allocated elements, not including spare capacity */ TArrayView Get(FEvaluationTreeEntryHandle ID) const; /** * Add a new element to the entry contents with the specified identifier * * @param ID The entry identifier, obtained through AllocateEntry * @param Element The element to add */ void Add(FEvaluationTreeEntryHandle ID, ElementType&& Element); /** * Insert a new element to the entry with the specified identifier at a specific index within the entry * * @param ID The entry identifier, obtained through AllocateEntry * @param Index The index within the entry at which to insert the new element * @param Element The element to add */ void Insert(FEvaluationTreeEntryHandle ID, int32 Index, ElementType&& Element); /** * Compress the item array to remove any unused capacity. */ void Compact(); /** * Reset this container to its default state */ void Reset() { Entries.Reset(); Items.Reset(); } /** * Serialize this container */ friend FArchive& operator<<(FArchive& Ar, TEvaluationTreeEntryContainer& In) { if (Ar.IsSaving()) { In.Compact(); } Ar.UsingCustomVersion(FSequencerObjectVersion::GUID); return Ar << In.Entries << In.Items; } /** * Equality operator */ friend bool operator==(const TEvaluationTreeEntryContainer& A, const TEvaluationTreeEntryContainer& B) { return A.Entries == B.Entries && A.Items == B.Items; } private: /** * Reserves the entry with the specified identifier to a new capacity * * @param ID The entry identifier, obtained through AllocateEntry * @param NewCapacity The new capacity to hold in the entry */ void ReserveEntry(FEvaluationTreeEntryHandle ID, int32 NewCapacity); struct FEntry { /** The index into Items of the first item */ int32 StartIndex; /** The number of currently valid items */ int32 Size; /** The total capacity of allowed items before reallocating */ int32 Capacity; friend FArchive& operator<<(FArchive& Ar, FEntry& In) { Ar << In.StartIndex << In.Size << In.Capacity; return Ar; } /** * Equality operator */ friend bool operator==(const FEntry& A, const FEntry& B) { return A.StartIndex == B.StartIndex && A.Size == B.Size && A.Capacity == B.Capacity; } }; /** List of allocated entries for each allocated entry. Should only ever grow, never shrink. Shrinking would cause previously established handles to become invalid. */ TArray Entries; /** Linear array of allocated entry contents. Once allocated, indices are never invalidated until Compact is called. Entries needing more capacity are re-allocated on the end of the array. */ TArray Items; }; /** * Interface used for performing an abstract operation on an FMovieSceneEvaluationTreeNode. */ struct IMovieSceneEvaluationTreeNodeOperator { /** * Virtual destruction */ virtual ~IMovieSceneEvaluationTreeNodeOperator() {} /** * Called to invoke the operator for the specified node * * @param Node The node on which the operator should be performed */ virtual void operator()(FMovieSceneEvaluationTreeNode& Node) const = 0; }; /** * A handle to a node in an FMovieSceneEvaluationTree, defined in terms of an entry handle (corrsponding to FMovieSceneEvaluationTree::ChildNodes), and its child index */ struct FMovieSceneEvaluationTreeNodeHandle { /** Construction from the node's parent's children entry handle, and this node's index within its parent's children */ FMovieSceneEvaluationTreeNodeHandle(FEvaluationTreeEntryHandle InChildrenHandle, int32 InIndex) : ChildrenHandle(InChildrenHandle), Index(InIndex) {} /** Special handle that represents the root node */ static FMovieSceneEvaluationTreeNodeHandle Root() { return FMovieSceneEvaluationTreeNodeHandle(FEvaluationTreeEntryHandle(), 0); } /** Special handle that represents an invalid node */ static FMovieSceneEvaluationTreeNodeHandle Invalid() { return FMovieSceneEvaluationTreeNodeHandle(FEvaluationTreeEntryHandle(), INDEX_NONE); } /** Check whether this node is the root node or not */ bool IsRoot() const { return *this == Root(); } /** Check whether this node should correspond to a node. Does not check whether this handle is actually valid for any given tree */ bool IsValid() const { return ChildrenHandle.IsValid() || Index == 0; } /** Access the children handle that this node is contained within */ FEvaluationTreeEntryHandle GetHandle() const { return ChildrenHandle; } /** Access the index of this node amongst its siblings */ int32 GetChildIndex() const { return Index; } private: /** Test two node handles for equality */ friend bool operator==(const FMovieSceneEvaluationTreeNodeHandle& A, const FMovieSceneEvaluationTreeNodeHandle& B) { return A.ChildrenHandle == B.ChildrenHandle && A.Index == B.Index; } /** Test two node handles for inequality */ friend bool operator!=(const FMovieSceneEvaluationTreeNodeHandle& A, const FMovieSceneEvaluationTreeNodeHandle& B) { return A.ChildrenHandle != B.ChildrenHandle || A.Index != B.Index; } /** Serialization operator */ friend FArchive& operator<<(FArchive& Ar, FMovieSceneEvaluationTreeNodeHandle& In) { return Ar << In.ChildrenHandle << In.Index; } /** Entry handle for the parent's children in FMovieSceneEvaluationTree::ChildNodes */ FEvaluationTreeEntryHandle ChildrenHandle; /** The index of this child within its parent's children */ int32 Index; }; /** * A tree node that represents a unique time range in the evaluation tree (or a grouping of smaller sub-divisions) */ struct FMovieSceneEvaluationTreeNode { /** Constructors */ FMovieSceneEvaluationTreeNode() : Range(TRange::Empty()) , Parent(FMovieSceneEvaluationTreeNodeHandle::Invalid()) {} FMovieSceneEvaluationTreeNode(TRange InRange, FMovieSceneEvaluationTreeNodeHandle InParent) : Range(InRange) , Parent(InParent) {} /** Serialization operator */ friend FArchive& operator<<(FArchive& Ar, FMovieSceneEvaluationTreeNode& In) { return Ar << In.Range << In.Parent << In.ChildrenID << In.DataID; } /** * Equality operator */ friend bool operator==(const FMovieSceneEvaluationTreeNode& A, const FMovieSceneEvaluationTreeNode& B) { return A.Range == B.Range && A.Parent == B.Parent && A.ChildrenID == B.ChildrenID && A.DataID == B.DataID; } /** The time-range that this node represents */ TRange Range; /** Handle to the parent node */ FMovieSceneEvaluationTreeNodeHandle Parent; /** Identifier for the child node entries associated with this node (FMovieSceneEvaluationTree::ChildNodes) */ FEvaluationTreeEntryHandle ChildrenID; /** Identifier for externally stored data entries associated with this node */ FEvaluationTreeEntryHandle DataID; }; /** * A tree structure used to efficiently reference overlapping time ranges hierarchically. * Each node represents a unique combination of 'entities' overlapping for a given range. * Example structure (dependent on order of addition): * Time -inf 10 20 25 30 inf * [============= 0 ===========] * [============= 1 ==================] * [========================== 2 ==================================] * [================== 3 ==========================] * [================== 4 ==========================] * [===== 5 ======] * ---------------------------------------------------------------------------------------------------------------------------------------- * Where each time range is added in order, this is represented as: * [======== 3 =======][========== 0,2,3 ==========][============= 1,2 ================][============ 4 ===========] * | \ * [===== 5 ====] [======== 4 ========] * ---------------------------------------------------------------------------------------------------------------------------------------- * Unique Ranges [ 3 | 0,2,3 | 0,2,3,5 | 0,2,3 | 1,2 | 1,2,4 | 4 ] */ struct FMovieSceneEvaluationTree { FMovieSceneEvaluationTree() : RootNode(FMovieSceneEvaluationTreeNode(TRange::All(), FMovieSceneEvaluationTreeNodeHandle::Invalid())) {} FMovieSceneEvaluationTree(const FMovieSceneEvaluationTree& RHS) = default; FMovieSceneEvaluationTree& operator=(const FMovieSceneEvaluationTree& RHS) = default; /** Move construction must ensure that the root node is correctly restored to its default to ensure that invariant */ FMovieSceneEvaluationTree(FMovieSceneEvaluationTree&& RHS) : RootNode(RHS.RootNode) , ChildNodes(MoveTemp(RHS.ChildNodes)) { RHS.RootNode = FMovieSceneEvaluationTreeNode(TRange::All(), FMovieSceneEvaluationTreeNodeHandle::Invalid()); } /** Move assignment must ensure that the root node is correctly restored to its default to ensure that invariant */ FMovieSceneEvaluationTree& operator=(FMovieSceneEvaluationTree&& RHS) { RootNode = RHS.RootNode; ChildNodes = MoveTemp(RHS.ChildNodes); RHS.RootNode = FMovieSceneEvaluationTreeNode(TRange::All(), FMovieSceneEvaluationTreeNodeHandle::Invalid()); return *this; } /** * Start iterating this tree from the specified time * * @param Time The time at which we should start iterating * @return A bi-directional iterator that is set to the unique range that overlaps the current time */ MOVIESCENE_API FMovieSceneEvaluationTreeRangeIterator IterateFromTime(FFrameNumber Time) const; /** * Start iterating this tree from the specified lower boundary * * @param InStartingLowerBound The lowerbound at which we should start iterating * @return A bi-directional iterator that is set to the unique range that overlaps the specified lowerbound */ MOVIESCENE_API FMovieSceneEvaluationTreeRangeIterator IterateFromLowerBound(TRangeBound InStartingLowerBound) const; /** * Access this tree's root node (infinite range) * * @return The root node */ const FMovieSceneEvaluationTreeNode& GetRootNode() const { return RootNode; } /** * Non-const access to a node from its handle. Handle must be valid. * @return A reference to the node that is valid as long as the tree remains unmodified */ FMovieSceneEvaluationTreeNode& GetNode(FMovieSceneEvaluationTreeNodeHandle Handle) { check(IsValid(Handle)); return GetNode(Handle.GetHandle(), Handle.GetChildIndex()); } /** * Const access to a node from its handle. Handle must be valid. * @return A reference to the node that is valid as long as the tree remains unmodified */ const FMovieSceneEvaluationTreeNode& GetNode(FMovieSceneEvaluationTreeNodeHandle Handle) const { check(IsValid(Handle)); return GetNode(Handle.GetHandle(), Handle.GetChildIndex()); } /** * Get the children associated with the specified node * * @param InNode The node to get children for * @return Array view of the node's children */ MOVIESCENE_API TArrayView GetChildren(const FMovieSceneEvaluationTreeNode& InNode) const; /** * Get the children associated with the specified node * * @param InNode The node to get children for * @return Array view of the node's children */ MOVIESCENE_API TArrayView< FMovieSceneEvaluationTreeNode> GetChildren(const FMovieSceneEvaluationTreeNode& InNode); /** * Check whether the specified handle corresponds to a node within this tree * * @param InHandle Node handle to test * @return true if the handle is valid, false otherwise */ bool IsValid(FMovieSceneEvaluationTreeNodeHandle Handle) const { return Handle.GetHandle().IsValid() ? ChildNodes.Get(Handle.GetHandle()).IsValidIndex(Handle.GetChildIndex()) : Handle.GetChildIndex() == 0; } void Reset() { RootNode.DataID = RootNode.ChildrenID = FEvaluationTreeEntryHandle(); ChildNodes.Reset(); } /** * Insert the specified time range into this tree * * @param InTimeRange The time range to add to the tree */ MOVIESCENE_API void AddTimeRange(TRange InTimeRange); /** * Serialize this evaluation tree */ friend FArchive& operator<<(FArchive& Ar, FMovieSceneEvaluationTree& In) { return Ar << In.RootNode << In.ChildNodes; } protected: /** * Non-const access to a node from its parent's ChildrenID and this node's index. * @return A reference to the node that is valid as long as the tree remains unmodified */ FMovieSceneEvaluationTreeNode& GetNode(FEvaluationTreeEntryHandle ChildrenID, int32 Index) { return ChildrenID.IsValid() ? ChildNodes.Get(ChildrenID)[Index] : RootNode; } /** * Const access to a node from its parent's ChildrenID and this node's index. * @return A reference to the node that is valid as long as the tree remains unmodified */ const FMovieSceneEvaluationTreeNode& GetNode(FEvaluationTreeEntryHandle ChildrenID, int32 Index) const { return ChildrenID.IsValid() ? ChildNodes.Get(ChildrenID)[Index] : RootNode; } /** * Insert the specified time range into this tree * * @param InTimeRange The time range to add to the tree * @param InOperator Operator implementation to call for each node that is relevant to the time range * @param InParent The current parent node * @param InPredicate (Optional) Predicate that determines whether the time should be added for a particular point */ MOVIESCENE_API void AddTimeRange(TRange InTimeRange, const IMovieSceneEvaluationTreeNodeOperator& InOperator, FMovieSceneEvaluationTreeNodeHandle InParent, const TFunctionRef* Predicate); /** * Helper function that creates a new child for the specified parent node * * @param InEffectiveRange The time range that this child should represent. Must not overlap any other child's time range. * @param InOperator Operator implementation to call for the new child node * @param InsertIndex The index at which to insert the new child (children must be sorted) * @param InParent The node to add the child to */ MOVIESCENE_API void InsertNewChild(TRange InEffectiveRange, const IMovieSceneEvaluationTreeNodeOperator& InOperator, int32 InsertIndex, FMovieSceneEvaluationTreeNodeHandle InParent); /** This tree's root node */ FMovieSceneEvaluationTreeNode RootNode; /** Segmented array of all child nodes within this tree (in no particular order) */ TEvaluationTreeEntryContainer ChildNodes; }; /** * Type that iterates contiguous range/data combinations sequentially (including empty space between time ranges) */ struct FMovieSceneEvaluationTreeRangeIterator { /** Iterate the tree from -infinity */ MOVIESCENE_API FMovieSceneEvaluationTreeRangeIterator(const FMovieSceneEvaluationTree& InTree); /** Iterate the tree ranges starting at the range that overlaps the specified lower bound */ MOVIESCENE_API FMovieSceneEvaluationTreeRangeIterator(const FMovieSceneEvaluationTree& InTree, TRangeBound StartingBound); /** Move onto the next time range */ FMovieSceneEvaluationTreeRangeIterator& operator++() { Iter(true); return *this; } /** Move onto the previous time range */ FMovieSceneEvaluationTreeRangeIterator& operator--() { Iter(false); return *this; } /** Get an iterator pointing to the next range */ FMovieSceneEvaluationTreeRangeIterator Next() const { FMovieSceneEvaluationTreeRangeIterator N = *this; return ++N; } /** Get an iterator pointing to the previous range */ FMovieSceneEvaluationTreeRangeIterator Previous() const { FMovieSceneEvaluationTreeRangeIterator P = *this; return --P; } /** Get the current range */ TRange Range() const { return CurrentRange; } /** Get the current node */ FMovieSceneEvaluationTreeNodeHandle Node() const { return CurrentNodeHandle; } /** conversion to "bool" returning true if the iterator has not reached the last element. */ explicit operator bool() const { return CurrentNodeHandle.IsValid(); } private: /** (In)Equality operators */ friend bool operator==(const FMovieSceneEvaluationTreeRangeIterator& Lhs, const FMovieSceneEvaluationTreeRangeIterator& Rhs) { return Lhs.Tree == Rhs.Tree && Lhs.CurrentNodeHandle == Rhs.CurrentNodeHandle && Lhs.CurrentRange == Rhs.CurrentRange; } friend bool operator!=(const FMovieSceneEvaluationTreeRangeIterator& Lhs, const FMovieSceneEvaluationTreeRangeIterator& Rhs) { return Lhs.Tree != Rhs.Tree || Lhs.CurrentNodeHandle != Rhs.CurrentNodeHandle || Lhs.CurrentRange != Rhs.CurrentRange; } /** Compare a bound with a range based on whether we're iterating forwards or backwards */ static bool CompareBound(bool bForwards, TRange Range, TRangeBound Bound) { return bForwards ? Range.GetLowerBound() == Bound : Range.GetUpperBound() == Bound; } /** Access the 'leading' bound of a range (lowerbound if forwards, upperbound if backwards) */ static TRangeBound GetLeadingBound(bool bForwards, TRange Range) { return bForwards ? Range.GetLowerBound() : Range.GetUpperBound(); } /** Access the 'trailing' bound of a range (upperbound if forwards, lowerbound if backwards) */ static TRangeBound GetTrailingBound(bool bForwards, TRange Range) { return bForwards ? Range.GetUpperBound() : Range.GetLowerBound(); } /** Iterate onto the next range based on whether we're going forwards or backwards */ MOVIESCENE_API void Iter(bool bForwards); /** Find the next child within the given parent that is >= the specified bound based on whether we're going forwards or backwards */ MOVIESCENE_API FMovieSceneEvaluationTreeNodeHandle FindNextChild(FMovieSceneEvaluationTreeNodeHandle ParentNodeHandle, TRangeBound PredicateBound, bool bForwards); private: /** The unique time range that we're currently at */ TRange CurrentRange; /** Handle of the current node */ FMovieSceneEvaluationTreeNodeHandle CurrentNodeHandle; /** The tree we're iterating */ const FMovieSceneEvaluationTree* Tree; }; /** * Templated version of FMovieSceneEvaluationTree that is also able to associate arbitrary data with nodes */ template struct TMovieSceneEvaluationTree : FMovieSceneEvaluationTree { /** * Check whether this tree is empty */ bool IsEmpty() const { return GetChildren(GetRootNode()).Num() == 0 && GetDataForSingleNode(GetRootNode()).Num() == 0; } /** * Add a time range with no data associated * * @param InTimeRange The range with which this data should be associated */ void Add(TRange InTimeRange) { struct FNullOperator : IMovieSceneEvaluationTreeNodeOperator { virtual void operator()(FMovieSceneEvaluationTreeNode& InNode) const override {} }; AddTimeRange(InTimeRange, FNullOperator(), FMovieSceneEvaluationTreeNodeHandle::Root(), nullptr); } /** * Add a new time range with the associated data to the tree. Ensures no duplicates. * * @param InTimeRange The range with which this data should be associated * @param InData The data to assoicate with this time range */ void AddUnique(TRange InTimeRange, DataType InData) { AddTimeRange(InTimeRange, FAddUniqueOperator(*this, MoveTemp(InData)), FMovieSceneEvaluationTreeNodeHandle::Root(), nullptr); } /** * Adds a new time range with the associated data to the tree, only for segments where there's no data yet. * * @param InTimeRange The range with which this data should be associated * @param InData The data to assoicate with this time range */ void AddIfEmpty(TRange InTimeRange, DataType InData) { AddSelective(InTimeRange, MoveTemp(InData), [this](FMovieSceneEvaluationTreeNodeHandle InNodeHandle){ return this->GetAllData(InNodeHandle).IsValid() == false; }); } /** * Adds a new time range with the associated data to the tree, only for segments where there's no data yet. * * @param InTimeRange The range with which this data should be associated * @param InData The data to assoicate with this time range */ void AddIfEmptySelective(TRange InTimeRange, DataType InData, TFunctionRef Predicate) { AddSelective(InTimeRange, MoveTemp(InData), [this, &Predicate](FMovieSceneEvaluationTreeNodeHandle InNodeHandle){ return this->GetAllData(InNodeHandle).IsValid() && Predicate(InNodeHandle); }); } /** * Add a new time range with the associated data to the tree. * * @param InTimeRange The range with which this data should be associated * @param InData The data to assoicate with this time range */ void AddSelective(TRange InTimeRange, DataType InData, TFunctionRef Predicate) { AddTimeRange(InTimeRange, FAddOperator(*this, MoveTemp(InData)), FMovieSceneEvaluationTreeNodeHandle::Root(), &Predicate); } /** * Add a new time range with the associated data to the tree. * * @param InTimeRange The range with which this data should be associated * @param InData The data to assoicate with this time range */ void Add(TRange InTimeRange, DataType InData) { AddTimeRange(InTimeRange, FAddOperator(*this, MoveTemp(InData)), FMovieSceneEvaluationTreeNodeHandle::Root(), nullptr); } /** * Compact the memory used by this tree so it's using as little as possible */ void Compact() { ChildNodes.Compact(); Data.Compact(); } /** * Reset this tree */ void Reset() { FMovieSceneEvaluationTree::Reset(); Data.Reset(); } /** * Access the data associated with a single node in the tree. */ TArrayView GetDataForSingleNode(const FMovieSceneEvaluationTreeNode& InNode) const { return InNode.DataID.IsValid() ? Data.Get(InNode.DataID) : TArrayView(); } /** * Access the data associated with a single node in the tree. */ TArrayView GetMutableDataForSingleNode(const FMovieSceneEvaluationTreeNode& InNode) { return InNode.DataID.IsValid() ? Data.Get(InNode.DataID) : TArrayView(); } /** * Create an iterator that will iterate all data associated with the specified node * * @param NodeHandle The node to get all data for */ TMovieSceneEvaluationTreeDataIterator GetAllData(FMovieSceneEvaluationTreeNodeHandle NodeHandle) const { return TMovieSceneEvaluationTreeDataIterator(*this, NodeHandle); } /** * Serialization operator */ friend FArchive& operator<<(FArchive& Ar, TMovieSceneEvaluationTree& In) { auto& BaseTree = static_cast(In); return Ar << BaseTree << In.Data; } /** * Equality operator */ friend bool operator==(const TMovieSceneEvaluationTree& A, const TMovieSceneEvaluationTree& B) { return A.Data == B.Data && A.RootNode == B.RootNode && A.ChildNodes == B.ChildNodes; } private: /** Tree data container that corresponds to FMovieSceneEvaluationTreeNode::DataID */ TEvaluationTreeEntryContainer Data; friend struct TMovieSceneEvaluationTreeFormatter; private: /** Operator that adds data to nodes provided it doesn't already exist on the node */ struct FAddUniqueOperator : IMovieSceneEvaluationTreeNodeOperator { FAddUniqueOperator(TMovieSceneEvaluationTree& InTree, DataType&& InDataToInsert) : Tree(InTree), DataToInsert(InDataToInsert) {} virtual void operator()(FMovieSceneEvaluationTreeNode& InNode) const override { // If we haven't allocated data for this node yet, do so now if (!InNode.DataID.IsValid()) { InNode.DataID = Tree.Data.AllocateEntry(1); } TArrayView NodeData = Tree.Data.Get(InNode.DataID); if (!NodeData.Contains(DataToInsert)) { Tree.Data.Add(InNode.DataID, CopyTemp(DataToInsert)); } } TMovieSceneEvaluationTree& Tree; DataType DataToInsert; }; /** Operator that adds data to nodes */ struct FAddOperator : IMovieSceneEvaluationTreeNodeOperator { FAddOperator(TMovieSceneEvaluationTree& InTree, DataType&& InDataToInsert) : Tree(InTree), DataToInsert(InDataToInsert) {} virtual void operator()(FMovieSceneEvaluationTreeNode& InNode) const override { // If we haven't allocated data for this node yet, do so now if (!InNode.DataID.IsValid()) { InNode.DataID = Tree.Data.AllocateEntry(1); } Tree.Data.Add(InNode.DataID, CopyTemp(DataToInsert)); } TMovieSceneEvaluationTree& Tree; DataType DataToInsert; }; }; /** * An iterator type that is iterates all the data associated with a given node and its parents */ template struct TMovieSceneEvaluationTreeDataIterator { /** Construction from a tree and a node */ TMovieSceneEvaluationTreeDataIterator(const TMovieSceneEvaluationTree& InTree, FMovieSceneEvaluationTreeNodeHandle StartNode) : Tree(&InTree) , CurrentNode(StartNode.IsValid() ? &InTree.GetNode(StartNode) : nullptr) , DataIndex(0) { // Walk up the tree until we find some data. We reset the iterator so begin() == end() where there is no data while (CurrentNode && !InTree.GetDataForSingleNode(*CurrentNode).IsValidIndex(DataIndex)) { CurrentNode = CurrentNode->Parent.IsValid() ? &InTree.GetNode(CurrentNode->Parent) : nullptr; DataIndex = 0; } } /** Dereference the data for the current iteration */ const DataType& operator*() const { return Tree->GetDataForSingleNode(*CurrentNode)[DataIndex]; } /** Access the data for the current iteration */ const DataType* operator->() const { return &Tree->GetDataForSingleNode(*CurrentNode)[DataIndex]; } /** Check the iterator for validity */ bool IsValid() const { return CurrentNode && Tree->GetDataForSingleNode(*CurrentNode).IsValidIndex(DataIndex); } /** Check the iterator for validity */ explicit operator bool() const { return IsValid(); } /** Move on to the next piece of data */ TMovieSceneEvaluationTreeDataIterator& operator++() { ++DataIndex; // Skip up parents while the data index is invalid while (CurrentNode && !Tree->GetDataForSingleNode(*CurrentNode).IsValidIndex(DataIndex)) { CurrentNode = CurrentNode->Parent.IsValid() ? &Tree->GetNode(CurrentNode->Parent) : nullptr; DataIndex = 0; } return *this; } /** Range-for support */ friend TMovieSceneEvaluationTreeDataIterator begin(const TMovieSceneEvaluationTreeDataIterator& In) { return In; } friend TMovieSceneEvaluationTreeDataIterator end(const TMovieSceneEvaluationTreeDataIterator& In) { return TMovieSceneEvaluationTreeDataIterator(*In.Tree, FMovieSceneEvaluationTreeNodeHandle::Invalid()); } /** (In)Equality operators */ friend bool operator==(const TMovieSceneEvaluationTreeDataIterator& A, const TMovieSceneEvaluationTreeDataIterator& B) { return A.Tree == B.Tree && A.CurrentNode == B.CurrentNode && A.DataIndex == B.DataIndex; } friend bool operator!=(const TMovieSceneEvaluationTreeDataIterator& A, const TMovieSceneEvaluationTreeDataIterator& B) { return A.Tree != B.Tree || A.CurrentNode != B.CurrentNode || A.DataIndex != B.DataIndex; } private: /** The tree we're reading */ const TMovieSceneEvaluationTree* Tree; /** The current node of the iteration (nullptr for invalid iterators) */ const FMovieSceneEvaluationTreeNode* CurrentNode; /** The current data index within CurrentNode's data */ int32 DataIndex; }; template FEvaluationTreeEntryHandle TEvaluationTreeEntryContainer::AllocateEntry(int32 InitialCapacity) { FEvaluationTreeEntryHandle ID{ Entries.Num() }; Entries.Add(FEntry{Items.Num(), 0, InitialCapacity}); Items.SetNum(Items.Num() + InitialCapacity); return ID; } template TArrayView TEvaluationTreeEntryContainer::Get(FEvaluationTreeEntryHandle ID) { const FEntry& Entry = Entries[ID.EntryIndex]; return TArrayView(&Items[Entry.StartIndex], Entry.Size); } template TArrayView TEvaluationTreeEntryContainer::Get(FEvaluationTreeEntryHandle ID) const { const FEntry& Entry = Entries[ID.EntryIndex]; return TArrayView(&Items[Entry.StartIndex], Entry.Size); } template void TEvaluationTreeEntryContainer::Add(FEvaluationTreeEntryHandle ID, ElementType&& Element) { FEntry& Entry = Entries[ID.EntryIndex]; if (Entry.Size+1 > Entry.Capacity) { ReserveEntry(ID, FMath::Max(Entry.Capacity*2, 2)); } Items[Entry.StartIndex + Entry.Size] = MoveTemp(Element); ++Entry.Size; } template void TEvaluationTreeEntryContainer::Insert(FEvaluationTreeEntryHandle ID, int32 Index, ElementType&& Element) { FEntry& Entry = Entries[ID.EntryIndex]; if (Entry.Size+1 > Entry.Capacity) { ReserveEntry(ID, FMath::Max(Entry.Capacity*2, 2)); } Index = FMath::Clamp(Index, 0, Entry.Capacity-1); // Shift anything >= the insert index up one if (Index <= Entry.Size-1) { ElementType* OldLocation = &Items[Entry.StartIndex + Index]; void* NewLocation = &Items[Entry.StartIndex + Index + 1]; RelocateConstructItems(NewLocation, OldLocation, Entry.Size - Index); DefaultConstructItems((void*)OldLocation, 1); } // Assign the new element ++Entry.Size; Items[Entry.StartIndex + Index] = MoveTemp(Element); } template void TEvaluationTreeEntryContainer::Compact() { TArray NewItems; for (FEntry& Entry : Entries) { if (Entry.Size > 0) { const int32 OldIndex = Entry.StartIndex; Entry.StartIndex = NewItems.Num(); NewItems.Append(&Items[OldIndex], Entry.Size); } else { Entry.StartIndex = INDEX_NONE; } Entry.Capacity = Entry.Size; } NewItems.Shrink(); Items = MoveTemp(NewItems); } template void TEvaluationTreeEntryContainer::ReserveEntry(FEvaluationTreeEntryHandle ID, int32 NewCapacity) { FEntry& Entry = Entries[ID.EntryIndex]; // Function should only be called to grow an entry check(NewCapacity > Entry.Capacity); Entry.Capacity = NewCapacity; int32 NewStartIndex = Items.Num(); // Reallocate the items to the new entry location Items.SetNum(Items.Num() + Entry.Capacity); ElementType* OldLocation = &Items[Entry.StartIndex]; void* NewLocation = &Items[NewStartIndex]; RelocateConstructItems(NewLocation, OldLocation, Entry.Size); DefaultConstructItems((void*)OldLocation, Entry.Size); Entry.StartIndex = NewStartIndex; }