// Copyright Epic Games, Inc. All Rights Reserved. #include "EntitySystem/MovieSceneMutualComponentInclusivity.h" #include "EntitySystem/MovieSceneMutualComponentInitializer.h" #include "EntitySystem/MovieSceneEntitySystemLinker.h" #include "EntitySystem/MovieSceneComponentRegistry.h" #include "EntitySystem/MovieSceneComponentTypeInfo.h" #include "Misc/StringBuilder.h" #include "MovieSceneFwd.h" namespace UE::MovieScene { void FMutualComponentInitializers::Add(IMutualComponentInitializer* InitializerToAdd) { // No need to check for uniqueness here because initializers can only // exist in the command buffer once Initializers.Add(InitializerToAdd); } void FMutualComponentInitializers::Reset() { Initializers.Reset(); } void FMutualComponentInitializers::Execute(const FEntityRange& Range, const FEntityAllocationWriteContext& WriteContext) const { for (IMutualComponentInitializer* Initializer : Initializers) { Initializer->Run(Range, WriteContext); } } int32 FMutualInclusivityGraphCommandBuffer::ComputeMutuallyInclusiveComponents(EMutuallyInclusiveComponentType TypesToCompute, const FComponentMask& InMask, FComponentMask& OutMask, FMutualComponentInitializers& OutInitializers) const { int32 NumNewComponents = 0; FComponentMask WorkingMask = InMask; // NOTE: It is possible to call this function with the same reference for InMask and OutMask // such that populating the mutual components adds them to the same input. This function is written to allow that while still being performant. // This flexibility allows us to compute mutually inclusive components to a separate mask, as well as the same mask. // Command buffer will take the form of 1 or more match commands followed by 1 or more include commands. // All match commands must be satisfied for subsequent include commands to pass // // CommandBuffers adhere to the following schema: // [ // { type = mandatory | optional } // Specifies the type of the commands that follow // { simple }, // // { match_all[1..N] -> include[1..N] -> (initializer) } // |------------------short circuits to end-----| // // { match_any[1..N] -> short_circuit -> include[1..N] -> (initializer) } // | |-----------|----short circuits to end-----| // |---short circuits to includes-| for (int32 Index = 0; Index < Commands.Num(); ++Index) { FCommand Command = Commands[Index]; switch (Command.CommandType) { case ECommandType::ShortCircuit: // - 1 because Index will be incremented as part of the loop iteration Index = static_cast(Command.ShortCircuit.ShortCircuitIndex) - 1; break; case ECommandType::Type: // If the type doesn't match, short-circuit the entire stack of commands if (!EnumHasAnyFlags(Command.Type.Type, TypesToCompute)) { // - 1 because Index will be incremented as part of the loop iteration Index = static_cast(Command.Type.ShortCircuitIndex) - 1; } break; case ECommandType::MatchAll: if (!WorkingMask.Contains(Command.Match.ComponentTypeID)) { // ShortCircuitIndex points to the end of the Include and initializer commands for this stack // - 1 because Index will be incremented as part of the loop iteration Index = static_cast(Command.Match.ShortCircuitIndex) - 1; } break; case ECommandType::MatchAny: if (WorkingMask.Contains(Command.Match.ComponentTypeID)) { // ShortCircuitIndex points to the first Include command in this stack // - 1 because Index will be incremented as part of the loop iteration Index = static_cast(Command.Match.ShortCircuitIndex) - 1; } break; case ECommandType::Simple: if (WorkingMask.Contains(Command.Simple.MatchID) && !WorkingMask.Contains(Command.Simple.IncludeID)) { ++NumNewComponents; OutMask.Set(Command.Simple.IncludeID); WorkingMask.Set(Command.Simple.IncludeID); } break; case ECommandType::Include: if (!WorkingMask.Contains(Command.Include.ComponentTypeID)) { ++NumNewComponents; OutMask.Set(Command.Include.ComponentTypeID); WorkingMask.Set(Command.Include.ComponentTypeID); } break; case ECommandType::Initialize: OutInitializers.Add(Command.Initialize.Initializer); break; } } return NumNewComponents; } void FMutualInclusivityGraphCommandBuffer::CheckInvariants() const { // Check invariants #if DO_CHECK auto TestShortCircuitIndex = [this](int32 Index) { checkf(Index != 0 && Index <= this->Commands.Num(), TEXT("Invalid short circuit index %d for command buffer length"), Index, this->Commands.Num()); }; for (int32 Index = 0; Index < Commands.Num(); ++Index) { FCommand Command = Commands[Index]; switch (Command.CommandType) { case ECommandType::ShortCircuit: TestShortCircuitIndex(Command.ShortCircuit.ShortCircuitIndex); break; case ECommandType::Type: TestShortCircuitIndex(Command.Type.ShortCircuitIndex); break; case ECommandType::MatchAll: TestShortCircuitIndex(Command.Match.ShortCircuitIndex); break; case ECommandType::MatchAny: TestShortCircuitIndex(Command.Match.ShortCircuitIndex); checkf(Commands[Command.Match.ShortCircuitIndex-1].CommandType == ECommandType::ShortCircuit, TEXT("Expected short circuit command to proceed match any commands")); break; default: break; } } #endif } void FMutualInclusivityGraph::DefineMutualInclusionRule(FComponentTypeID Predicate, std::initializer_list Dependents) { DefineMutualInclusionRule(Predicate, Dependents, FMutuallyInclusiveComponentParams()); } void FMutualInclusivityGraph::DefineMutualInclusionRule(FComponentTypeID Predicate, std::initializer_list Dependents, FMutuallyInclusiveComponentParams&& Parameters) { bCommandBufferInvalidated = true; FIncludePair& IncludePair = SimpleIncludes.FindOrAdd(Predicate); FIncludes& Includes = Parameters.Type == EMutuallyInclusiveComponentType::Mandatory ? IncludePair.MandatoryIncludes : IncludePair.OptionalIncludes; for (FComponentTypeID Dependent : Dependents) { Includes.Add(Dependent); } if (Parameters.CustomInitializer) { Includes.Initializers.Emplace(MoveTemp(Parameters.CustomInitializer)); } } void FMutualInclusivityGraph::DefineComplexInclusionRule(const FComplexInclusivityFilter& InFilter, std::initializer_list Dependents) { DefineComplexInclusionRule(InFilter, Dependents, FMutuallyInclusiveComponentParams()); } void FMutualInclusivityGraph::DefineComplexInclusionRule(const FComplexInclusivityFilter& InFilter, std::initializer_list Dependents, FMutuallyInclusiveComponentParams&& Parameters) { bCommandBufferInvalidated = true; FIncludePair& IncludePair = ComplexIncludes.FindOrAdd(InFilter); FIncludes& Includes = Parameters.Type == EMutuallyInclusiveComponentType::Mandatory ? IncludePair.MandatoryIncludes : IncludePair.OptionalIncludes; for (FComponentTypeID Dependent : Dependents) { Includes.Add(Dependent); } if (Parameters.CustomInitializer) { Includes.Initializers.Emplace(MoveTemp(Parameters.CustomInitializer)); } } int32 FMutualInclusivityGraph::ComputeMutuallyInclusiveComponents(EMutuallyInclusiveComponentType TypesToCompute, const FComponentMask& InMask, FComponentMask& OutMask, FMutualComponentInitializers& OutInitializers) const { if (bCommandBufferInvalidated) { ReconstructCommandBuffer(); } return CommandBuffer.ComputeMutuallyInclusiveComponents(TypesToCompute, InMask, OutMask, OutInitializers); } int32 FMutualInclusivityGraph::FindPrerequisiteChainLength(const FDirectedGraph& Graph, FComponentTypeID Component, TMap& InOutCache) { if (const int32* Depth = InOutCache.Find(Component)) { return *Depth; } TArrayView Edges = Graph.GetEdgesFrom(Component.BitIndex()); int32 MaxDepth = 0; if (Edges.Num() != 0) { for (FDirectedGraph::FDirectionalEdge Edge : Edges) { MaxDepth = FMath::Max(MaxDepth, FindPrerequisiteChainLength(Graph, FComponentTypeID::FromBitIndex(Edge.ToNode), InOutCache)); } } InOutCache.Add(Component, MaxDepth + 1); return MaxDepth + 1; } void FMutualInclusivityGraph::ReconstructCommandBuffer() const { using FCommand = FMutualInclusivityGraphCommandBuffer::FCommand; using ECommandType = FMutualInclusivityGraphCommandBuffer::ECommandType; FDirectedGraph Graph; // Step 1: Allocate nodes that can be produced from mutual includes for (const TTuple& Pair : SimpleIncludes) { for (FComponentTypeID Dependent : Pair.Value.MandatoryIncludes.Includes) { Graph.AllocateNode(Dependent.BitIndex()); } for (FComponentTypeID Dependent : Pair.Value.OptionalIncludes.Includes) { Graph.AllocateNode(Dependent.BitIndex()); } } for (const TTuple& Pair : ComplexIncludes) { for (FComponentTypeID Dependent : Pair.Value.MandatoryIncludes.Includes) { Graph.AllocateNode(Dependent.BitIndex()); } for (FComponentTypeID Dependent : Pair.Value.OptionalIncludes.Includes) { Graph.AllocateNode(Dependent.BitIndex()); } } // Step 2: Define edges between dependent nodes. Dependencies point from dependent to predicate so we can walk _up_ the graph for (const TTuple& Pair : SimpleIncludes) { if (Graph.IsNodeAllocated(Pair.Key.BitIndex())) { for (FComponentTypeID Dependent : Pair.Value.MandatoryIncludes.Includes) { Graph.MakeEdge(Dependent.BitIndex(), Pair.Key.BitIndex()); } for (FComponentTypeID Dependent : Pair.Value.OptionalIncludes.Includes) { Graph.MakeEdge(Dependent.BitIndex(), Pair.Key.BitIndex()); } } } for (const TTuple& Pair : ComplexIncludes) { for (FComponentMaskIterator It(Pair.Key.Mask.Iterate()); It; ++It) { FComponentTypeID Predicate = FComponentTypeID::FromBitIndex(It.GetIndex()); if (Graph.IsNodeAllocated(Predicate.BitIndex())) { for (FComponentTypeID Dependent : Pair.Value.MandatoryIncludes.Includes) { Graph.MakeEdge(Dependent.BitIndex(), Predicate.BitIndex()); } for (FComponentTypeID Dependent : Pair.Value.OptionalIncludes.Includes) { Graph.MakeEdge(Dependent.BitIndex(), Predicate.BitIndex()); } } } } if (!ensureMsgf(!Graph.IsCyclic(), TEXT("Mutual component inclusion graph is cyclic! See log for graph output."))) { FComponentRegistry* ComponentRegistry = UMovieSceneEntitySystemLinker::GetComponents(); auto EmitLabel = [=](uint16 NodeID, FStringBuilderBase& OutStringBuilder) { FComponentTypeID ComponentTypeID = FComponentTypeID::FromBitIndex(NodeID); const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(ComponentTypeID); #if UE_MOVIESCENE_ENTITY_DEBUG OutStringBuilder.Appendf(TEXT("\t\tnode%d[label=\"%s\"];\n"), ComponentTypeID.BitIndex(), *ComponentTypeInfo.DebugInfo->DebugName); #else OutStringBuilder.Appendf(TEXT("\t\tnode%d[label=\"%d\"];\n"), ComponentTypeID.BitIndex(), ComponentTypeID.BitIndex()); #endif }; FDirectedGraphStringParameters Parameters{ TEXT("Mutual Includes") }; UE_LOG(LogMovieScene, Warning, TEXT("Printing Graph: \n %s"), *Graph.ToString(Parameters, EmitLabel)); } // Step 3: Compute ordering constraints based on depth within the graph TMap DependencyDepths; for (TConstSetBitIterator<> SetBitIter(Graph.GetNodeMask()); SetBitIter; ++SetBitIter) { FindPrerequisiteChainLength(Graph, FComponentTypeID::FromBitIndex(SetBitIter.GetIndex()), DependencyDepths); } // Step 4: Organize simple inclusion by depth using the predicate component, so that it is ordered after // any other include that might introduce this component TArray> OrderedSimpleInclusion; for (const TTuple& Pair : SimpleIncludes) { const int32* GraphDepth = DependencyDepths.Find(Pair.Key); OrderedSimpleInclusion.Add(MakeTuple(Pair.Key, GraphDepth ? *GraphDepth : 0)); } Algo::SortBy(OrderedSimpleInclusion, [](const TTuple& In){return In.Get<1>();}); // Step 5: Organize complex inclusion by depth using the predicate components, so that it is ordered after // any other include that might introduce this component TArray> OrderedComplexInclusion; for (const TTuple& Pair : ComplexIncludes) { int32 Depth = 0; for (FComponentMaskIterator It(Pair.Key.Mask.Iterate()); It; ++It) { FComponentTypeID Predicate = FComponentTypeID::FromBitIndex(It.GetIndex()); if (const int32* GraphDepth = DependencyDepths.Find(Predicate)) { Depth = FMath::Max(Depth, *GraphDepth); } } OrderedComplexInclusion.Add(MakeTuple(Pair.Key, Depth)); } Algo::SortBy(OrderedComplexInclusion, [](const TTuple& In){return In.Get<1>();}); // Step 6: Generate the command buffer from the two ordered arrays, ensuring simple includes come before complex includes CommandBuffer.Commands.Empty(); int32 SimpleIndex = 0; int32 ComplexIndex = 0; int32 LastTypeIndex = INDEX_NONE; EMutuallyInclusiveComponentType CurrentType = EMutuallyInclusiveComponentType::All; auto EmitTypeCommand = [this, &LastTypeIndex, &CurrentType](EMutuallyInclusiveComponentType Type) { // Emit a type command if this is a different type from the last one added if (Type != CurrentType) { if (LastTypeIndex != INDEX_NONE) { this->CommandBuffer.Commands[LastTypeIndex].Type.ShortCircuitIndex = this->CommandBuffer.Commands.Num(); } LastTypeIndex = this->CommandBuffer.Commands.Num(); // Add the type command - we will populate the ShortCircuitIndex later this->CommandBuffer.Commands.Emplace(FMutualInclusivityGraphCommandBuffer::FTypeCommand{ 0, Type }); CurrentType = Type; } }; auto AddSimpleCommand = [this, &EmitTypeCommand](FComponentTypeID PredicateComponent, const FIncludes& Dependencies, EMutuallyInclusiveComponentType Type) { // Emit a type command if this is a different type from the last one added EmitTypeCommand(Type); // If the match fails, skip the match command and the includes const int32 NumPredicateComponents = 1; const int32 NumIncludeComponents = Dependencies.Includes.Num(); const int32 NumInitializers = Dependencies.Initializers.Num(); const uint16 ShortCircuitIndex = static_cast(this->CommandBuffer.Commands.Num() + NumPredicateComponents + NumIncludeComponents + NumInitializers); // Use a simple command if possible to reduce the size of the array if (NumIncludeComponents == 1 && NumInitializers == 0) { this->CommandBuffer.Commands.Emplace(FMutualInclusivityGraphCommandBuffer::FSimpleCommand{ PredicateComponent, Dependencies.Includes[0] }); } else { this->CommandBuffer.Commands.Emplace(FMutualInclusivityGraphCommandBuffer::FMatchCommand{ PredicateComponent, ShortCircuitIndex }, EComplexInclusivityFilterMode::AllOf); for (FComponentTypeID Dependency : Dependencies.Includes) { this->CommandBuffer.Commands.Add(FMutualInclusivityGraphCommandBuffer::FIncludeCommand{ Dependency }); } for (const TUniquePtr& Initializer : Dependencies.Initializers) { this->CommandBuffer.Commands.Add(FMutualInclusivityGraphCommandBuffer::FInitializeCommand{ Initializer.Get() }); } } }; auto AddSimpleCommands = [this, &AddSimpleCommand](FComponentTypeID Predicate) { const FIncludePair* Includes = this->SimpleIncludes.Find(Predicate); if (Includes) { // Handle mandatory includes first if (Includes->MandatoryIncludes.Includes.Num() != 0) { AddSimpleCommand(Predicate, Includes->MandatoryIncludes, EMutuallyInclusiveComponentType::Mandatory); } if (Includes->OptionalIncludes.Includes.Num() != 0) { AddSimpleCommand(Predicate, Includes->OptionalIncludes, EMutuallyInclusiveComponentType::Optional); } } }; auto AddComplexCommand = [this, &EmitTypeCommand](const FComplexInclusivityFilter& Predicate, const FIncludes& Dependencies, EMutuallyInclusiveComponentType Type) { // Emit a type command if this is a different type from the last one added EmitTypeCommand(Type); // If the match fails, skip the match command and the includes const int32 NumPredicateComponents = Predicate.Mask.NumComponents(); const int32 NumInitializers = Dependencies.Initializers.Num(); if (Predicate.Mode == EComplexInclusivityFilterMode::AnyOf) { // The AllOf command is a stream of matches that jump to the start of the includes on success, // followed by a shortcircuit that skips the includes if it is reached const uint16 IncludeStartIndex = static_cast(this->CommandBuffer.Commands.Num() + NumPredicateComponents + 1); const uint16 ShortCircuitIndex = static_cast(IncludeStartIndex + Dependencies.Includes.Num() + NumInitializers); for (FComponentMaskIterator It(Predicate.Mask.Iterate()); It; ++It) { FComponentTypeID PredicateComponent = FComponentTypeID::FromBitIndex(It.GetIndex()); this->CommandBuffer.Commands.Emplace(FMutualInclusivityGraphCommandBuffer::FMatchCommand{ PredicateComponent, IncludeStartIndex }, Predicate.Mode); } this->CommandBuffer.Commands.Emplace(FMutualInclusivityGraphCommandBuffer::FShortCircuitCommand{ ShortCircuitIndex }); } else { // The Any command is a stream of matches that skip to the end of the command on failure const uint16 ShortCircuitIndex = static_cast(this->CommandBuffer.Commands.Num() + NumPredicateComponents + Dependencies.Includes.Num() + NumInitializers); for (FComponentMaskIterator It(Predicate.Mask.Iterate()); It; ++It) { FComponentTypeID PredicateComponent = FComponentTypeID::FromBitIndex(It.GetIndex()); this->CommandBuffer.Commands.Emplace(FMutualInclusivityGraphCommandBuffer::FMatchCommand{ PredicateComponent, ShortCircuitIndex }, Predicate.Mode); } } // Add includes for (FComponentTypeID Dependency : Dependencies.Includes) { this->CommandBuffer.Commands.Add(FMutualInclusivityGraphCommandBuffer::FIncludeCommand{ Dependency }); } // Add custom initializers for (const TUniquePtr& Initializer : Dependencies.Initializers) { this->CommandBuffer.Commands.Add(FMutualInclusivityGraphCommandBuffer::FInitializeCommand{ Initializer.Get() }); } }; auto AddComplexCommands = [this, &AddComplexCommand](const FComplexInclusivityFilter& Predicate) { const FIncludePair* Includes = this->ComplexIncludes.Find(Predicate); if (Includes) { // Handle mandatory includes first if (Includes->MandatoryIncludes.Includes.Num() != 0) { AddComplexCommand(Predicate, Includes->MandatoryIncludes, EMutuallyInclusiveComponentType::Mandatory); } if (Includes->OptionalIncludes.Includes.Num() != 0) { AddComplexCommand(Predicate, Includes->OptionalIncludes, EMutuallyInclusiveComponentType::Optional); } } }; // Keep going until we have nothing left while (SimpleIndex < OrderedSimpleInclusion.Num() || ComplexIndex < OrderedComplexInclusion.Num()) { const bool bHasSimple = SimpleIndex < OrderedSimpleInclusion.Num(); const bool bHasComplex = ComplexIndex < OrderedComplexInclusion.Num(); if (bHasSimple && bHasComplex) { // Decide which to add first based on the order const int32 SimpleOrder = OrderedSimpleInclusion[SimpleIndex].Get<1>(); const int32 ComplexOrder = OrderedComplexInclusion[ComplexIndex].Get<1>(); if (SimpleOrder <= ComplexOrder) { while (SimpleIndex < OrderedSimpleInclusion.Num() && OrderedSimpleInclusion[SimpleIndex].Get<1>() == SimpleOrder) { FComponentTypeID Predicate = OrderedSimpleInclusion[SimpleIndex].Get<0>(); AddSimpleCommands(Predicate); ++SimpleIndex; } } else { while (ComplexIndex < OrderedComplexInclusion.Num() && OrderedComplexInclusion[ComplexIndex].Get<1>() == ComplexOrder) { const FComplexInclusivityFilter& Predicate = OrderedComplexInclusion[ComplexIndex].Get<0>(); AddComplexCommands(Predicate); ++ComplexIndex; } } } else if (bHasSimple) { while (SimpleIndex < OrderedSimpleInclusion.Num()) { FComponentTypeID Predicate = OrderedSimpleInclusion[SimpleIndex].Get<0>(); AddSimpleCommands(Predicate); ++SimpleIndex; } } else { while (ComplexIndex < OrderedComplexInclusion.Num()) { const FComplexInclusivityFilter& Predicate = OrderedComplexInclusion[ComplexIndex].Get<0>(); AddComplexCommands(Predicate); ++ComplexIndex; } } } // Populate the most recent type command if one was added if (LastTypeIndex != INDEX_NONE) { CommandBuffer.Commands[LastTypeIndex].Type.ShortCircuitIndex = CommandBuffer.Commands.Num(); } bCommandBufferInvalidated = false; CommandBuffer.CheckInvariants(); } } // namespace UE::MovieScene