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

982 lines
34 KiB
C++

// 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<typename DataType> struct TMovieSceneEvaluationTreeDataIterator;
template<typename DataType> 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<typename T> friend struct TEvaluationTreeEntryContainer;
template<typename T> friend struct TMovieSceneEvaluationTreeFormatter;
FEvaluationTreeEntryHandle(int32 InEntryIndex) : EntryIndex(InEntryIndex) {}
/** Specifies an index into TEvaluationTreeEntryContainer<T>::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<typename ElementType>
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<ElementType> 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<const ElementType> 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<ElementType>& A, const TEvaluationTreeEntryContainer<ElementType>& 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<FEntry> 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<ElementType> 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<FFrameNumber>::Empty())
, Parent(FMovieSceneEvaluationTreeNodeHandle::Invalid())
{}
FMovieSceneEvaluationTreeNode(TRange<FFrameNumber> 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<FFrameNumber> 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<FFrameNumber>::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<FFrameNumber>::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<FFrameNumber>::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<FFrameNumber> 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<const FMovieSceneEvaluationTreeNode> 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<FFrameNumber> 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<FFrameNumber> InTimeRange, const IMovieSceneEvaluationTreeNodeOperator& InOperator, FMovieSceneEvaluationTreeNodeHandle InParent, const TFunctionRef<bool(FMovieSceneEvaluationTreeNodeHandle)>* 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<FFrameNumber> 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<FMovieSceneEvaluationTreeNode> 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<FFrameNumber> 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<FFrameNumber> 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<FFrameNumber> Range, TRangeBound<FFrameNumber> Bound)
{
return bForwards ? Range.GetLowerBound() == Bound : Range.GetUpperBound() == Bound;
}
/** Access the 'leading' bound of a range (lowerbound if forwards, upperbound if backwards) */
static TRangeBound<FFrameNumber> GetLeadingBound(bool bForwards, TRange<FFrameNumber> Range)
{
return bForwards ? Range.GetLowerBound() : Range.GetUpperBound();
}
/** Access the 'trailing' bound of a range (upperbound if forwards, lowerbound if backwards) */
static TRangeBound<FFrameNumber> GetTrailingBound(bool bForwards, TRange<FFrameNumber> 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<FFrameNumber> PredicateBound, bool bForwards);
private:
/** The unique time range that we're currently at */
TRange<FFrameNumber> 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<typename DataType>
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<FFrameNumber> 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<FFrameNumber> 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<FFrameNumber> 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<FFrameNumber> InTimeRange, DataType InData, TFunctionRef<bool(FMovieSceneEvaluationTreeNodeHandle)> 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<FFrameNumber> InTimeRange, DataType InData, TFunctionRef<bool(FMovieSceneEvaluationTreeNodeHandle)> 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<FFrameNumber> 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<const DataType> GetDataForSingleNode(const FMovieSceneEvaluationTreeNode& InNode) const
{
return InNode.DataID.IsValid() ? Data.Get(InNode.DataID) : TArrayView<const DataType>();
}
/**
* Access the data associated with a single node in the tree.
*/
TArrayView<DataType> GetMutableDataForSingleNode(const FMovieSceneEvaluationTreeNode& InNode)
{
return InNode.DataID.IsValid() ? Data.Get(InNode.DataID) : TArrayView<DataType>();
}
/**
* Create an iterator that will iterate all data associated with the specified node
*
* @param NodeHandle The node to get all data for
*/
TMovieSceneEvaluationTreeDataIterator<DataType> GetAllData(FMovieSceneEvaluationTreeNodeHandle NodeHandle) const
{
return TMovieSceneEvaluationTreeDataIterator<DataType>(*this, NodeHandle);
}
/**
* Serialization operator
*/
friend FArchive& operator<<(FArchive& Ar, TMovieSceneEvaluationTree<DataType>& In)
{
auto& BaseTree = static_cast<FMovieSceneEvaluationTree&>(In);
return Ar << BaseTree << In.Data;
}
/**
* Equality operator
*/
friend bool operator==(const TMovieSceneEvaluationTree<DataType>& A, const TMovieSceneEvaluationTree<DataType>& B)
{
return A.Data == B.Data && A.RootNode == B.RootNode && A.ChildNodes == B.ChildNodes;
}
private:
/** Tree data container that corresponds to FMovieSceneEvaluationTreeNode::DataID */
TEvaluationTreeEntryContainer<DataType> Data;
friend struct TMovieSceneEvaluationTreeFormatter<DataType>;
private:
/** Operator that adds data to nodes provided it doesn't already exist on the node */
struct FAddUniqueOperator : IMovieSceneEvaluationTreeNodeOperator
{
FAddUniqueOperator(TMovieSceneEvaluationTree<DataType>& 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<const DataType> NodeData = Tree.Data.Get(InNode.DataID);
if (!NodeData.Contains(DataToInsert))
{
Tree.Data.Add(InNode.DataID, CopyTemp(DataToInsert));
}
}
TMovieSceneEvaluationTree<DataType>& Tree;
DataType DataToInsert;
};
/** Operator that adds data to nodes */
struct FAddOperator : IMovieSceneEvaluationTreeNodeOperator
{
FAddOperator(TMovieSceneEvaluationTree<DataType>& 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<DataType>& Tree;
DataType DataToInsert;
};
};
/**
* An iterator type that is iterates all the data associated with a given node and its parents
*/
template<typename DataType>
struct TMovieSceneEvaluationTreeDataIterator
{
/** Construction from a tree and a node */
TMovieSceneEvaluationTreeDataIterator(const TMovieSceneEvaluationTree<DataType>& 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<DataType> begin(const TMovieSceneEvaluationTreeDataIterator<DataType>& In) { return In; }
friend TMovieSceneEvaluationTreeDataIterator<DataType> end(const TMovieSceneEvaluationTreeDataIterator<DataType>& In) { return TMovieSceneEvaluationTreeDataIterator<DataType>(*In.Tree, FMovieSceneEvaluationTreeNodeHandle::Invalid()); }
/** (In)Equality operators */
friend bool operator==(const TMovieSceneEvaluationTreeDataIterator<DataType>& A, const TMovieSceneEvaluationTreeDataIterator<DataType>& B) { return A.Tree == B.Tree && A.CurrentNode == B.CurrentNode && A.DataIndex == B.DataIndex; }
friend bool operator!=(const TMovieSceneEvaluationTreeDataIterator<DataType>& A, const TMovieSceneEvaluationTreeDataIterator<DataType>& B) { return A.Tree != B.Tree || A.CurrentNode != B.CurrentNode || A.DataIndex != B.DataIndex; }
private:
/** The tree we're reading */
const TMovieSceneEvaluationTree<DataType>* 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<typename ElementType>
FEvaluationTreeEntryHandle TEvaluationTreeEntryContainer<ElementType>::AllocateEntry(int32 InitialCapacity)
{
FEvaluationTreeEntryHandle ID{ Entries.Num() };
Entries.Add(FEntry{Items.Num(), 0, InitialCapacity});
Items.SetNum(Items.Num() + InitialCapacity);
return ID;
}
template<typename ElementType>
TArrayView<ElementType> TEvaluationTreeEntryContainer<ElementType>::Get(FEvaluationTreeEntryHandle ID)
{
const FEntry& Entry = Entries[ID.EntryIndex];
return TArrayView<ElementType>(&Items[Entry.StartIndex], Entry.Size);
}
template<typename ElementType>
TArrayView<const ElementType> TEvaluationTreeEntryContainer<ElementType>::Get(FEvaluationTreeEntryHandle ID) const
{
const FEntry& Entry = Entries[ID.EntryIndex];
return TArrayView<const ElementType>(&Items[Entry.StartIndex], Entry.Size);
}
template<typename ElementType>
void TEvaluationTreeEntryContainer<ElementType>::Add(FEvaluationTreeEntryHandle ID, ElementType&& Element)
{
FEntry& Entry = Entries[ID.EntryIndex];
if (Entry.Size+1 > Entry.Capacity)
{
ReserveEntry(ID, FMath::Max<int32>(Entry.Capacity*2, 2));
}
Items[Entry.StartIndex + Entry.Size] = MoveTemp(Element);
++Entry.Size;
}
template<typename ElementType>
void TEvaluationTreeEntryContainer<ElementType>::Insert(FEvaluationTreeEntryHandle ID, int32 Index, ElementType&& Element)
{
FEntry& Entry = Entries[ID.EntryIndex];
if (Entry.Size+1 > Entry.Capacity)
{
ReserveEntry(ID, FMath::Max<int32>(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<ElementType>(NewLocation, OldLocation, Entry.Size - Index);
DefaultConstructItems<ElementType>((void*)OldLocation, 1);
}
// Assign the new element
++Entry.Size;
Items[Entry.StartIndex + Index] = MoveTemp(Element);
}
template<typename ElementType>
void TEvaluationTreeEntryContainer<ElementType>::Compact()
{
TArray<ElementType> 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<typename ElementType>
void TEvaluationTreeEntryContainer<ElementType>::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<ElementType>(NewLocation, OldLocation, Entry.Size);
DefaultConstructItems<ElementType>((void*)OldLocation, Entry.Size);
Entry.StartIndex = NewStartIndex;
}