// Copyright Epic Games, Inc. All Rights Reserved. #include "MassEntityQuery.h" #include "MassDebugger.h" #include "MassEntityManager.h" #include "MassArchetypeData.h" #include "MassCommandBuffer.h" #include "MassExecutionContext.h" #include "VisualLogger/VisualLogger.h" #include "Async/ParallelFor.h" #include "Containers/UnrealString.h" #include "MassProcessor.h" #include "MassEntityTrace.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MassEntityQuery) #if WITH_MASSENTITY_DEBUG #include "MassRequirementAccessDetector.h" #include "MassDebugLogging.h" #endif // WITH_MASSENTITY_DEBUG namespace UE::Mass::Tweakables { /** * Controls whether ParallelForEachEntityChunk actually performs ParallelFor operations. * If `false` the call is passed to the regular ForEachEntityChunk call. */ bool bAllowParallelExecution = true; namespace { static FAutoConsoleVariableRef AnonymousCVars[] = { { TEXT("mass.AllowQueryParallelFor"), bAllowParallelExecution, TEXT("Controls whether EntityQueries are allowed to utilize ParallelFor construct"), ECVF_Cheat } }; } } //----------------------------------------------------------------------------- // FScopedEntityQueryContext //----------------------------------------------------------------------------- struct FScopedEntityQueryContext { FScopedEntityQueryContext(FMassEntityQuery& InQuery, FMassExecutionContext& ExecutionContext) : Query(InQuery), CachedExecutionContext(ExecutionContext) , ScopedAccessDetector(InQuery) { CachedExecutionContext.PushQuery(InQuery); bSubsystemRequirementsCached = CachedExecutionContext.CacheSubsystemRequirements(InQuery); } ~FScopedEntityQueryContext() { if (IsSuccessfullySetUp()) { CachedExecutionContext.ClearExecutionData(); CachedExecutionContext.FlushDeferred(); } CachedExecutionContext.PopQuery(Query); } bool IsSuccessfullySetUp() const { return bSubsystemRequirementsCached; } FMassEntityQuery& Query; FMassExecutionContext& CachedExecutionContext; UE::Mass::Debug::FScopedRequirementAccessDetector ScopedAccessDetector; bool bSubsystemRequirementsCached; }; //----------------------------------------------------------------------------- // FMassEntityQuery //----------------------------------------------------------------------------- FMassEntityQuery::FMassEntityQuery(UMassProcessor& Owner) { UE_TRACE_MASS_QUERY_CREATED() RegisterWithProcessor(Owner); } FMassEntityQuery::FMassEntityQuery(const TSharedPtr& EntityManager) : FMassFragmentRequirements(EntityManager) { UE_TRACE_MASS_QUERY_CREATED() } FMassEntityQuery::FMassEntityQuery(const TSharedRef& EntityManager, std::initializer_list InitList) : FMassFragmentRequirements(EntityManager) { UE_TRACE_MASS_QUERY_CREATED() for (const UScriptStruct* FragmentType : InitList) { AddRequirement(FragmentType, EMassFragmentAccess::ReadWrite, EMassFragmentPresence::All); } } FMassEntityQuery::FMassEntityQuery(const TSharedRef& EntityManager, TConstArrayView InitList) : FMassFragmentRequirements(EntityManager) { UE_TRACE_MASS_QUERY_CREATED() for (const UScriptStruct* FragmentType : InitList) { AddRequirement(FragmentType, EMassFragmentAccess::ReadWrite, EMassFragmentPresence::All); } } void FMassEntityQuery::RegisterWithProcessor(UMassProcessor& Owner) { UE_TRACE_MASS_QUERY_REGISTERED_TO_PROCESSOR(&Owner) ExpectedContextType = EMassExecutionContextType::Processor; Owner.RegisterQuery(*this); #if WITH_MASSENTITY_DEBUG bRegistered = true; #endif // WITH_MASSENTITY_DEBUG } void FMassEntityQuery::CacheArchetypes() { check(CachedEntityManager); const uint32 InEntityManagerHash = PointerHash(CachedEntityManager.Get()); // Do an incremental update if the last updated archetype data version is different bool bUpdateArchetypes = CachedEntityManager->GetArchetypeDataVersion() != LastUpdatedArchetypeDataVersion; // Force a full update if the entity system changed or if the requirements changed if (EntitySubsystemHash != InEntityManagerHash || HasIncrementalChanges()) { bUpdateArchetypes = true; EntitySubsystemHash = InEntityManagerHash; ValidArchetypes.Reset(); OrderedArchetypeIndices.Reset(); CachedGroupIDs.Reset(); LastUpdatedArchetypeDataVersion = 0; ArchetypeFragmentMapping.Reset(); if (HasIncrementalChanges()) { ConsumeIncrementalChangesCount(); if (CheckValidity()) { SortRequirements(); } else { bUpdateArchetypes = false; UE_VLOG_UELOG(CachedEntityManager->GetOwner(), LogMass, Error, TEXT("FMassEntityQuery::CacheArchetypes: requirements not valid: %s"), *FMassDebugger::GetRequirementsDescription(*this)); } } } // Process any new archetype that is newer than the LastUpdatedArchetypeDataVersion if (bUpdateArchetypes) { TArray NewValidArchetypes; #if WITH_EDITOR if (bHasArchetypeMatchOverride) { CachedEntityManager->ForEachArchetype(static_cast(LastUpdatedArchetypeDataVersion), TNumericLimits::Max() /*last*/, [this, &NewValidArchetypes](const FMassEntityManager& EntityManager, const FMassArchetypeHandle& Handle) { const FMassArchetypeCompositionDescriptor& Composition = EntityManager.GetArchetypeComposition(Handle); if (ArchetypeMatchOverride.Match(&ArchetypeMatchOverride.Data, Composition)) { NewValidArchetypes.Add(Handle); } }); } else #endif { CachedEntityManager->GetMatchingArchetypes(*this, NewValidArchetypes, LastUpdatedArchetypeDataVersion); } LastUpdatedArchetypeDataVersion = CachedEntityManager->GetArchetypeDataVersion(); if (NewValidArchetypes.Num()) { const int32 FirstNewArchetype = ValidArchetypes.Num(); ValidArchetypes.Append(NewValidArchetypes); TRACE_CPUPROFILER_EVENT_SCOPE_STR("Mass RequirementsBinding") const TConstArrayView LocalRequirements = GetFragmentRequirements(); ArchetypeFragmentMapping.AddDefaulted(NewValidArchetypes.Num()); for (int i = FirstNewArchetype; i < ValidArchetypes.Num(); ++i) { FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ValidArchetypes[i]); ArchetypeData.GetRequirementsFragmentMapping(LocalRequirements, ArchetypeFragmentMapping[i].EntityFragments); if (ChunkFragmentRequirements.Num()) { ArchetypeData.GetRequirementsChunkFragmentMapping(ChunkFragmentRequirements, ArchetypeFragmentMapping[i].ChunkFragments); } if (ConstSharedFragmentRequirements.Num()) { ArchetypeData.GetRequirementsConstSharedFragmentMapping(ConstSharedFragmentRequirements, ArchetypeFragmentMapping[i].ConstSharedFragments); } if (SharedFragmentRequirements.Num()) { ArchetypeData.GetRequirementsSharedFragmentMapping(SharedFragmentRequirements, ArchetypeFragmentMapping[i].SharedFragments); } } if (IsGrouping()) { SortArchetypes(FirstNewArchetype); } else { BuildOrderedArchetypeIndices(FirstNewArchetype); } } } } void FMassEntityQuery::ForEachEntityChunkInCollections(TConstArrayView EntityCollections , FMassExecutionContext& ExecutionContext, const FMassExecuteFunction& ExecuteFunction) { for (const FMassArchetypeEntityCollection& EntityCollection : EntityCollections) { ForEachEntityChunk(EntityCollection, ExecutionContext, ExecuteFunction); } } void FMassEntityQuery::ForEachEntityChunk(const FMassArchetypeEntityCollection& EntityCollection, FMassExecutionContext& ExecutionContext , const FMassExecuteFunction& ExecuteFunction) { // mz@todo I don't like that we're copying data here. ExecutionContext.SetEntityCollection(EntityCollection); ForEachEntityChunk(ExecutionContext, ExecuteFunction); ExecutionContext.ClearEntityCollection(); } void FMassEntityQuery::ForEachEntityChunk(FMassExecutionContext& ExecutionContext, const FMassExecuteFunction& ExecuteFunction) { UE_TRACE_SCOPED_MASS_QUERY_FOR_EACH() checkf(CachedEntityManager == ExecutionContext.GetSharedEntityManager() && CachedEntityManager.IsValid() , TEXT("FMassEntityQuery needs to be initialized with a valid EntityManager and the execution context has to come from the same manager")); #if WITH_MASSENTITY_DEBUG checkf(ExecutionContext.GetExecutionType() == ExpectedContextType && (ExpectedContextType == EMassExecutionContextType::Local || bRegistered) , TEXT("ExecutionContextType mismatch, make sure all the queries run as part of processor execution are registered with some processor with a FMassEntityQuery::RegisterWithProcessor call")); #endif FScopedEntityQueryContext ScopedQueryContext(*this, ExecutionContext); if (ScopedQueryContext.IsSuccessfullySetUp() == false) { // required subsystems are not available, bail out. return; } if (FMassFragmentRequirements::IsEmpty()) { if (ensureMsgf(ExecutionContext.GetEntityCollection().IsEmpty() == false, TEXT("Using empty queries is only supported in combination with Entity Collections that explicitly indicate entities to process"))) { static const FMassQueryRequirementIndicesMapping EmptyMapping; const FMassArchetypeHandle& ArchetypeHandle = ExecutionContext.GetEntityCollection().GetArchetype(); FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ArchetypeHandle); ArchetypeData.ExecuteFunction(ExecutionContext, ExecuteFunction , EmptyMapping , ExecutionContext.GetEntityCollection().GetRanges() , ChunkCondition); } } else { // note that the following function will usually only resort to verifying that the data is up to date by // checking the version number. In rare cases when it would result in non trivial cost we actually // do need those calculations. CacheArchetypes(); // if there's a chunk collection set by the external code - usse that if (ExecutionContext.GetEntityCollection().IsEmpty() == false) { const FMassArchetypeHandle& ArchetypeHandle = ExecutionContext.GetEntityCollection().GetArchetype(); const int32 ArchetypeIndex = ValidArchetypes.Find(ArchetypeHandle); // if given ArchetypeHandle cannot be found in ValidArchetypes then it doesn't match the query's requirements if (ArchetypeIndex == INDEX_NONE) { UE_VLOG_UELOG(CachedEntityManager->GetOwner(), LogMass, Verbose, TEXT("Attempted to execute FMassEntityQuery with an incompatible Archetype: %s. Note that this is fine for observers.") , *FMassDebugger::GetArchetypeRequirementCompatibilityDescription(*this, ArchetypeHandle)); return; } ExecutionContext.ApplyFragmentRequirements(*this); FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ArchetypeHandle); UE_TRACE_SCOPED_MASS_QUERY_FOR_EACH_REPORT_ARCHETYPE(ArchetypeData) ArchetypeData.ExecuteFunction(ExecutionContext, ExecuteFunction , GetRequirementsMappingForArchetype(ArchetypeHandle) , ExecutionContext.GetEntityCollection().GetRanges() , ChunkCondition); } else { // it's important to set requirements after caching archetypes due to that call potentially sorting the requirements and the order is relevant here. ExecutionContext.ApplyFragmentRequirements(*this); // note that this checkSlow is here on purpose, for debugging purposes, not data verification purposes. checkSlow(OrderedArchetypeIndices.Num() == ValidArchetypes.Num()); for (const int32 ArchetypeIndex : OrderedArchetypeIndices) { const FMassArchetypeHandle& ArchetypeHandle = ValidArchetypes[ArchetypeIndex]; FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ArchetypeHandle); UE_TRACE_SCOPED_MASS_QUERY_FOR_EACH_REPORT_ARCHETYPE(ArchetypeData) ArchetypeData.ExecuteFunction(ExecutionContext, ExecuteFunction, ArchetypeFragmentMapping[ArchetypeIndex], ChunkCondition); ExecutionContext.ClearFragmentViews(*this); } } } } void FMassEntityQuery::ParallelForEachEntityChunkInCollection(TConstArrayView EntityCollections , FMassExecutionContext& ExecutionContext, const FMassExecuteFunction& ExecuteFunction , const EParallelExecutionFlags Flags) { if (UE::Mass::Tweakables::bAllowParallelExecution == false && !EnumHasAnyFlags(Flags, EParallelExecutionFlags::Force)) { ForEachEntityChunkInCollections(EntityCollections, ExecutionContext, ExecuteFunction); return; } ParallelFor(EntityCollections.Num(), [this, &ExecutionContext, &ExecuteFunction, &EntityCollections, Flags](const int32 JobIndex) { FMassExecutionContext LocalExecutionContext = ExecutionContext; LocalExecutionContext.SetEntityCollection(EntityCollections[JobIndex]); ParallelForEachEntityChunk(LocalExecutionContext, ExecuteFunction, Flags); }); } void FMassEntityQuery::ParallelForEachEntityChunk(FMassExecutionContext& ExecutionContext , const FMassExecuteFunction& ExecuteFunction, const EParallelExecutionFlags Flags) { if (UE::Mass::Tweakables::bAllowParallelExecution == false && !EnumHasAnyFlags(Flags, EParallelExecutionFlags::Force)) { ForEachEntityChunk(ExecutionContext, ExecuteFunction); return; } else if (IsGrouping()) { UE_VLOG_UELOG(CachedEntityManager->GetOwner(), LogMass, Warning, TEXT("Calling %hs is not supported for grouping queries yet. Calling regular ForEachEntityChunk instead."), __FUNCTION__); ForEachEntityChunk(ExecutionContext, ExecuteFunction); return; } checkf(CachedEntityManager == ExecutionContext.GetSharedEntityManager() && CachedEntityManager.IsValid() , TEXT("FMassEntityQuery needs to be initialized with a valid EntityManager and the execution context has to come from the same manager")); #if WITH_MASSENTITY_DEBUG checkf(ExecutionContext.GetExecutionType() == ExpectedContextType && (ExpectedContextType == EMassExecutionContextType::Local || bRegistered) , TEXT("ExecutionContextType mismatch, make sure all the queries run as part of processor execution are registered with some processor with a FMassEntityQuery::RegisterWithProcessor call")); #endif FScopedEntityQueryContext ScopedQueryContext(*this, ExecutionContext); if (ScopedQueryContext.IsSuccessfullySetUp() == false) { // required subsystems are not available, bail out. return; } struct FChunkJob { FMassArchetypeData& Archetype; const int32 ArchetypeIndex; const FMassArchetypeEntityCollection::FArchetypeEntityRange EntityRange; }; TArray Jobs; if (FMassFragmentRequirements::IsEmpty()) { if (ensureMsgf(ExecutionContext.GetEntityCollection().IsEmpty() == false, TEXT("Using empty queries is only supported in combination with Entity Collections that explicitly indicate entities to process"))) { const FMassArchetypeHandle& ArchetypeHandle = ExecutionContext.GetEntityCollection().GetArchetype(); FMassArchetypeData& ArchetypeRef = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ArchetypeHandle); for (const FMassArchetypeEntityCollection::FArchetypeEntityRange& EntityRange : ExecutionContext.GetEntityCollection().GetRanges()) { Jobs.Add({ ArchetypeRef, INDEX_NONE, EntityRange }); } } } else { // note that the following function will usualy only resort to verifying that the data is up to date by // checking the version number. In rare cases when it would result in non trivial cost we actually // do need those calculations. CacheArchetypes(); // if there's a chunk collection set by the external code - use that if (ExecutionContext.GetEntityCollection().IsEmpty() == false) { const FMassArchetypeHandle& ArchetypeHandle = ExecutionContext.GetEntityCollection().GetArchetype(); const int32 ArchetypeIndex = ValidArchetypes.Find(ArchetypeHandle); // if given ArchetypeHandle cannot be found in ValidArchetypes then it doesn't match the query's requirements if (ArchetypeIndex == INDEX_NONE) { UE_VLOG_UELOG(CachedEntityManager->GetOwner(), LogMass, Verbose, TEXT("Attempted to execute FMassEntityQuery with an incompatible Archetype: %s. Note that this is fine for observers.") , *FMassDebugger::GetArchetypeRequirementCompatibilityDescription(*this, ExecutionContext.GetEntityCollection().GetArchetype())); return; } ExecutionContext.ApplyFragmentRequirements(*this); FMassArchetypeData& ArchetypeRef = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ArchetypeHandle); for (const FMassArchetypeEntityCollection::FArchetypeEntityRange& EntityRange : ExecutionContext.GetEntityCollection().GetRanges()) { Jobs.Add({ ArchetypeRef, ArchetypeIndex, EntityRange }); } } else { ExecutionContext.ApplyFragmentRequirements(*this); for (int ArchetypeIndex = 0; ArchetypeIndex < ValidArchetypes.Num(); ++ArchetypeIndex) { FMassArchetypeHandle& ArchetypeHandle = ValidArchetypes[ArchetypeIndex]; FMassArchetypeData& ArchetypeRef = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ArchetypeHandle); const FMassArchetypeEntityCollection AsEntityCollection(ArchetypeHandle); for (const FMassArchetypeEntityCollection::FArchetypeEntityRange& EntityRange : AsEntityCollection.GetRanges()) { Jobs.Add({ ArchetypeRef, ArchetypeIndex, EntityRange }); } } } } if (Jobs.Num()) { const EParallelForFlags ParallelForFlags = EnumHasAnyFlags(Flags, EParallelExecutionFlags::AutoBalance) ? EParallelForFlags::Unbalanced : EParallelForFlags::None; if (bAllowParallelCommands) { struct FTaskContext { FTaskContext() = default; TSharedPtr GetCommandBuffer() { if (!CommandBuffer) { // lazily creating the command buffer to ensure we create it in the same thread it's going to be used in CommandBuffer = MakeShared(); } return CommandBuffer; } private: TSharedPtr CommandBuffer; }; TArray TaskContext; ParallelForWithTaskContext(TaskContext, Jobs.Num(), [this, &ExecutionContext, &ExecuteFunction, &Jobs](FTaskContext& TaskContext, const int32 JobIndex) { FMassExecutionContext LocalExecutionContext(ExecutionContext, *this, TaskContext.GetCommandBuffer()); Jobs[JobIndex].Archetype.ExecutionFunctionForChunk(LocalExecutionContext, ExecuteFunction , Jobs[JobIndex].ArchetypeIndex != INDEX_NONE ? ArchetypeFragmentMapping[Jobs[JobIndex].ArchetypeIndex] : FMassQueryRequirementIndicesMapping() , Jobs[JobIndex].EntityRange , ChunkCondition); LocalExecutionContext.PopQuery(*this); }, ParallelForFlags); // merge all command buffers for (FTaskContext& CommandContext : TaskContext) { ExecutionContext.Defer().MoveAppend(*CommandContext.GetCommandBuffer()); } } else { ParallelFor(Jobs.Num(), [this, &ExecutionContext, &ExecuteFunction, &Jobs](const int32 JobIndex) { FMassExecutionContext LocalExecutionContext(ExecutionContext, *this); Jobs[JobIndex].Archetype.ExecutionFunctionForChunk(LocalExecutionContext, ExecuteFunction , Jobs[JobIndex].ArchetypeIndex != INDEX_NONE ? ArchetypeFragmentMapping[Jobs[JobIndex].ArchetypeIndex] : FMassQueryRequirementIndicesMapping() , Jobs[JobIndex].EntityRange , ChunkCondition); LocalExecutionContext.PopQuery(*this); }, ParallelForFlags); } } } int32 FMassEntityQuery::GetNumMatchingEntities() { CacheArchetypes(); int32 TotalEntities = 0; for (FMassArchetypeHandle& ArchetypeHandle : ValidArchetypes) { if (const FMassArchetypeData* Archetype = FMassArchetypeHelper::ArchetypeDataFromHandle(ArchetypeHandle)) { TotalEntities += Archetype->GetNumEntities(); } } return TotalEntities; } int32 FMassEntityQuery::GetNumMatchingEntities(TConstArrayView EntityCollections) { int32 TotalEntities = 0; for (const FMassArchetypeEntityCollection& EntityCollection : EntityCollections) { if (DoesArchetypeMatchRequirements(EntityCollection.GetArchetype())) { for (const FMassArchetypeEntityCollection::FArchetypeEntityRange& EntityRange : EntityCollection.GetRanges()) { TotalEntities += EntityRange.Length; } } } return TotalEntities; } bool FMassEntityQuery::HasMatchingEntities() { CacheArchetypes(); for (FMassArchetypeHandle& ArchetypeHandle : ValidArchetypes) { const FMassArchetypeData* Archetype = FMassArchetypeHelper::ArchetypeDataFromHandle(ArchetypeHandle); if (Archetype && Archetype->GetNumEntities() > 0) { return true; } } return false; } TArray FMassEntityQuery::CreateMatchingEntitiesCollection() { TArray Collections; CacheArchetypes(); Collections.Reserve(ValidArchetypes.Num()); for (FMassArchetypeHandle& ArchetypeHandle : ValidArchetypes) { Collections.Emplace(ArchetypeHandle); } return Collections; } TArray FMassEntityQuery::GetMatchingEntityHandles() { TArray Handles; CacheArchetypes(); for (FMassArchetypeHandle& ArchetypeHandle : ValidArchetypes) { FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ArchetypeHandle); ArchetypeData.ExportEntityHandles(Handles); } return Handles; } void FMassEntityQuery::GroupBy(UE::Mass::FArchetypeGroupType GroupType) { GroupBy(GroupType, [](const UE::Mass::FArchetypeGroupID A, const UE::Mass::FArchetypeGroupID B) { return A < B; }); } void FMassEntityQuery::GroupBy(UE::Mass::FArchetypeGroupType GroupType, const TFunction& Predicate) { GroupSortingSteps.Emplace(GroupType, Predicate); IncrementChangeCounter(); } void FMassEntityQuery::ResetGrouping() { GroupSortingSteps.Reset(); IncrementChangeCounter(); } void FMassEntityQuery::BuildOrderedArchetypeIndices(const int32 FirstNewArchetypeIndex) { OrderedArchetypeIndices.SetNumUninitialized(ValidArchetypes.Num()); for (int32 ArchetypeIndex = FirstNewArchetypeIndex; ArchetypeIndex < ValidArchetypes.Num(); ++ArchetypeIndex) { OrderedArchetypeIndices[ArchetypeIndex] = ArchetypeIndex; } } void FMassEntityQuery::SortArchetypes(const int32 FirstNewArchetypeIndex) { if (GroupSortingSteps.Num() == 0) { BuildOrderedArchetypeIndices(FirstNewArchetypeIndex); return; } check(CachedEntityManager); // first we need to cache the required group IDs from the new archetypes CachedGroupIDs.SetNum(ValidArchetypes.Num()); OrderedArchetypeIndices.SetNumUninitialized(ValidArchetypes.Num()); for (int32 NewArchetypeIndex = FirstNewArchetypeIndex; NewArchetypeIndex < ValidArchetypes.Num(); ++NewArchetypeIndex) { OrderedArchetypeIndices[NewArchetypeIndex] = NewArchetypeIndex; TArray& ArchetypeGroupIDs = CachedGroupIDs[NewArchetypeIndex]; ArchetypeGroupIDs.Reserve(GroupSortingSteps.Num()); const UE::Mass::FArchetypeGroups& ArchetypeGroups = CachedEntityManager->GetGroupsForArchetype(ValidArchetypes[NewArchetypeIndex]); for (const FArchetypeGroupingStep& Step : GroupSortingSteps) { // ArchetypeGroups.GetID will return InvalidArchetypeGroupID if the given group type is not in ArchetypeGroups. // This is what we want. ArchetypeGroupIDs.Add(ArchetypeGroups.GetID(Step.GroupType)); } } TConstArrayView> GroupIDs = MakeConstArrayView(CachedGroupIDs); TArray> Ranges; Ranges.Add({0, OrderedArchetypeIndices.Num()}); int32 MaxRangeSize = OrderedArchetypeIndices.Num(); int32 Step = 0; int32 RangesProcessed = 0; while (Step < GroupSortingSteps.Num() && MaxRangeSize > 1) { const bool bLastIteration = (Step == GroupSortingSteps.Num() - 1); const int32 RangesThisIteration = Ranges.Num(); MaxRangeSize = 0; for (; RangesProcessed < RangesThisIteration; ++RangesProcessed) { const TTuple Range = Ranges[RangesProcessed]; const int32 RangeLength = Range.Get<1>() - Range.Get<0>(); MakeArrayView(&OrderedArchetypeIndices[Range.Get<0>()], RangeLength) .Sort([Step, &GroupIDs, Sorter = GroupSortingSteps[Step].Predicate](const int32 A, const int32 B) { return Sorter(GroupIDs[A][Step], GroupIDs[B][Step]); }); // figure out new ranges if (bLastIteration == false) { int32 SubRangeStart = Range.Get<0>(); UE::Mass::FArchetypeGroupID PrevValue = GroupIDs[OrderedArchetypeIndices[SubRangeStart]][Step]; for (int32 Index = SubRangeStart + 1; Index < Range.Get<1>(); ++Index) { const UE::Mass::FArchetypeGroupID NewValue = GroupIDs[OrderedArchetypeIndices[Index]][Step]; if (NewValue != PrevValue) { PrevValue = NewValue; Ranges.Push({SubRangeStart, Index}); MaxRangeSize = FMath::Max(MaxRangeSize, Index - SubRangeStart); SubRangeStart = Index; } } // the loop above doesn't create any ranges when there's no change in GroupID among processed archetypes. // Similarly, it doesn't store the "last" range. We're addressing this now. Ranges.Push({SubRangeStart, Range.Get<1>()}); } } check(MaxRangeSize >= 1 || bLastIteration); ++Step; }; } const FMassQueryRequirementIndicesMapping& FMassEntityQuery::GetRequirementsMappingForArchetype(const FMassArchetypeHandle ArchetypeHandle) const { static const FMassQueryRequirementIndicesMapping FallbackEmptyMapping; checkf(HasIncrementalChanges() == false, TEXT("Fetching cached fragments mapping while the query's cached data is out of sync!")); const int32 ArchetypeIndex = ValidArchetypes.Find(ArchetypeHandle); return ArchetypeIndex != INDEX_NONE ? ArchetypeFragmentMapping[ArchetypeIndex] : FallbackEmptyMapping; } void FMassEntityQuery::ExportRequirements(FMassExecutionRequirements& OutRequirements) const { FMassSubsystemRequirements::ExportRequirements(OutRequirements); FMassFragmentRequirements::ExportRequirements(OutRequirements); } void FMassEntityQuery::DebugEnableEntityOwnerLogging() { #if WITH_MASSENTITY_DEBUG if (RequiredOptionalFragments.Contains() == false) { AddRequirement(EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional); } #endif // WITH_MASSENTITY_DEBUG } //----------------------------------------------------------------------------- // DEPRECATED //----------------------------------------------------------------------------- // deprecated FMassEntityQuery::FMassEntityQuery(std::initializer_list) : FMassEntityQuery() { } // deprecated FMassEntityQuery::FMassEntityQuery(TConstArrayView) : FMassEntityQuery() { } // deprecated void FMassEntityQuery::ForEachEntityChunk(FMassEntityManager&, FMassExecutionContext& ExecutionContext, const FMassExecuteFunction& ExecuteFunction) { ForEachEntityChunk(ExecutionContext, ExecuteFunction); } // deprecated void FMassEntityQuery::ForEachEntityChunk(const FMassArchetypeEntityCollection& EntityCollection, FMassEntityManager& , FMassExecutionContext& ExecutionContext, const FMassExecuteFunction& ExecuteFunction) { ForEachEntityChunk(EntityCollection, ExecutionContext, ExecuteFunction); } // deprecated void FMassEntityQuery::ParallelForEachEntityChunk(FMassEntityManager&, FMassExecutionContext& ExecutionContext , const FMassExecuteFunction& ExecuteFunction, const EParallelForMode ParallelMode) { const EParallelExecutionFlags Flags = EnumHasAnyFlags(ParallelMode, ForceParallelExecution) ? EParallelExecutionFlags::Force : EParallelExecutionFlags::Default; ParallelForEachEntityChunk(ExecutionContext, ExecuteFunction, Flags); } // deprecated void FMassEntityQuery::ForEachEntityChunkInCollections(TConstArrayView EntityCollections, FMassEntityManager& , FMassExecutionContext& ExecutionContext, const FMassExecuteFunction& ExecuteFunction) { ForEachEntityChunkInCollections(EntityCollections, ExecutionContext, ExecuteFunction); } // deprecated void FMassEntityQuery::ParallelForEachEntityChunkInCollection(TConstArrayView EntityCollections , FMassEntityManager&, FMassExecutionContext& ExecutionContext, const FMassExecuteFunction& ExecuteFunction , const EParallelForMode ParallelMode) { const EParallelExecutionFlags Flags = EnumHasAnyFlags(ParallelMode, ForceParallelExecution) ? EParallelExecutionFlags::Force : EParallelExecutionFlags::Default; ParallelForEachEntityChunkInCollection(EntityCollections, ExecutionContext, ExecuteFunction, Flags); } // deprecated void FMassEntityQuery::CacheArchetypes(const FMassEntityManager&) { CacheArchetypes(); } // deprecated int32 FMassEntityQuery::GetNumMatchingEntities(FMassEntityManager&) { return GetNumMatchingEntities(); } // deprecated bool FMassEntityQuery::HasMatchingEntities(FMassEntityManager&) { return HasMatchingEntities(); }