// Copyright Epic Games, Inc. All Rights Reserved. #include "MassRequirements.h" #include "MassArchetypeData.h" #include "MassProcessorDependencySolver.h" #include "MassTypeManager.h" #include "MassEntityManager.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MassRequirements) #if WITH_MASSENTITY_DEBUG #include "MassRequirementAccessDetector.h" #endif // WITH_MASSENTITY_DEBUG namespace UE::Mass::Private { template void ExportRequirements(TConstArrayView Requirements, TMassExecutionAccess& Out) { for (const FMassFragmentRequirementDescription& Requirement : Requirements) { if (Requirement.Presence != EMassFragmentPresence::None) { check(Requirement.StructType); if (Requirement.AccessMode == EMassFragmentAccess::ReadOnly) { Out.Read.Add(*Requirement.StructType); } else if (Requirement.AccessMode == EMassFragmentAccess::ReadWrite) { Out.Write.Add(*Requirement.StructType); } } } } template<> void ExportRequirements(TConstArrayView Requirements , TMassExecutionAccess& Out) { for (const FMassFragmentRequirementDescription& Requirement : Requirements) { if (Requirement.Presence != EMassFragmentPresence::None) { check(Requirement.StructType); if (ensureMsgf(Requirement.AccessMode == EMassFragmentAccess::ReadOnly, TEXT("ReadOnly is the only supported AccessMode for ConstSharedFragments"))) { Out.Read.Add(*Requirement.StructType); } } } } } //----------------------------------------------------------------------------- // FMassSubsystemRequirements //----------------------------------------------------------------------------- void FMassSubsystemRequirements::ExportRequirements(FMassExecutionRequirements& OutRequirements) const { OutRequirements.RequiredSubsystems.Read += RequiredConstSubsystems; OutRequirements.RequiredSubsystems.Write += RequiredMutableSubsystems; } void FMassSubsystemRequirements::Reset() { RequiredConstSubsystems.Reset(); RequiredMutableSubsystems.Reset(); bRequiresGameThreadExecution = false; } bool FMassSubsystemRequirements::IsGameThreadOnlySubsystem(const TSubclassOf SubsystemClass, const TSharedRef& EntityManager) { const UE::Mass::FTypeInfo* TypeInfo = EntityManager->GetTypeManager().GetTypeInfo(SubsystemClass); if (ensureMsgf(TypeInfo, TEXT("Failed to find type information for %s"), *GetNameSafe(SubsystemClass))) { const UE::Mass::FSubsystemTypeTraits* SystemTraits = TypeInfo->GetAsSystemTraits(); if (ensureMsgf(SystemTraits, TEXT("Type information for %s doesn't represent subsystem traits"), *GetNameSafe(SubsystemClass))) { return SystemTraits->bGameThreadOnly; } } // using `true` as default as the safer one of the options // since it's safer to run everything on GT rather than on an arbitrary thread return true; } //----------------------------------------------------------------------------- // FMassFragmentRequirements //----------------------------------------------------------------------------- FMassFragmentRequirements::FMassFragmentRequirements(const TSharedPtr& EntityManager) { if (ensure(EntityManager)) { Initialize(EntityManager.ToSharedRef()); } } FMassFragmentRequirements::FMassFragmentRequirements(const TSharedRef& EntityManager) { Initialize(EntityManager); } void FMassFragmentRequirements::Initialize(const TSharedRef& EntityManager) { UE_CLOG(CachedEntityManager && (CachedEntityManager != EntityManager), LogMass, Warning , TEXT("Trying to initialize FMassFragmentRequirements with another entity manager")); if (bInitialized) { return; } CachedEntityManager = EntityManager; bInitialized = true; } FMassFragmentRequirements& FMassFragmentRequirements::ClearTagRequirements(const FMassTagBitSet& TagsToRemoveBitSet) { RequiredAllTags.Remove(TagsToRemoveBitSet); RequiredAnyTags.Remove(TagsToRemoveBitSet); RequiredNoneTags.Remove(TagsToRemoveBitSet); RequiredOptionalTags.Remove(TagsToRemoveBitSet); return *this; } uint32 GetTypeHash(const FMassFragmentRequirements& Instance) { // @todo consider calculating hash only for non-empty elements, or any other optimization uint32 Hash = FCrc::MemCrc32(Instance.FragmentRequirements.GetData(), Instance.FragmentRequirements.Num() * sizeof(FMassFragmentRequirementDescription)); Hash = HashCombine(Hash, FCrc::MemCrc32(Instance.ChunkFragmentRequirements.GetData(), Instance.ChunkFragmentRequirements.Num() * sizeof(FMassFragmentRequirementDescription))); Hash = HashCombine(Hash, FCrc::MemCrc32(Instance.ConstSharedFragmentRequirements.GetData(), Instance.ConstSharedFragmentRequirements.Num() * sizeof(FMassFragmentRequirementDescription))); Hash = HashCombine(Hash, FCrc::MemCrc32(Instance.SharedFragmentRequirements.GetData(), Instance.SharedFragmentRequirements.Num() * sizeof(FMassFragmentRequirementDescription))); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredAllTags)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredAnyTags)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredNoneTags)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredOptionalTags)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredAllFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredAnyFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredOptionalFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredNoneFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredAllChunkFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredOptionalChunkFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredNoneChunkFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredAllSharedFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredOptionalSharedFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredNoneSharedFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredAllConstSharedFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredOptionalConstSharedFragments)); Hash = HashCombine(Hash, GetTypeHash(Instance.RequiredNoneConstSharedFragments)); return HashCombine(Hash, PointerHash(Instance.CachedEntityManager.Get())); } void FMassFragmentRequirements::SortRequirements() { // we're sorting the Requirements the same way ArchetypeData's FragmentConfig is sorted (see FMassArchetypeData::Initialize) // so that when we access ArchetypeData.FragmentConfigs in FMassArchetypeData::BindRequirementsWithMapping // (via GetFragmentData call) the access is sequential (i.e. not random) and there's a higher chance the memory // FragmentConfigs we want to access have already been fetched and are available in processor cache. FragmentRequirements.Sort(FScriptStructSortOperator()); ChunkFragmentRequirements.Sort(FScriptStructSortOperator()); ConstSharedFragmentRequirements.Sort(FScriptStructSortOperator()); SharedFragmentRequirements.Sort(FScriptStructSortOperator()); } bool FMassFragmentRequirements::IsGameThreadOnlySharedFragment(TNotNull SharedFragmentType) const { checkf(CachedEntityManager, TEXT("Not having a cached EntityManager at this point is not expected.")); const UE::Mass::FTypeInfo* TypeInfo = CachedEntityManager->GetTypeManager().GetTypeInfo(SharedFragmentType); if (ensureMsgf(TypeInfo, TEXT("Failed to find type information for %s"), *SharedFragmentType->GetName())) { const UE::Mass::FSharedFragmentTypeTraits* SharedFragmentTraits = TypeInfo->GetAsSharedFragmentTraits(); if (ensureMsgf(SharedFragmentTraits, TEXT("Type information for %s doesn't represent shared fragment traits"), *SharedFragmentType->GetName())) { return SharedFragmentTraits->bGameThreadOnly; } } // using `true` as default as the safer one of the options // since it's safer to run everything on GT rather than on an arbitrary thread return true; } FORCEINLINE void FMassFragmentRequirements::CacheProperties() const { if (bPropertiesCached == false) { bHasPositiveRequirements = !(RequiredAllTags.IsEmpty() && RequiredAnyTags.IsEmpty() && RequiredAllFragments.IsEmpty() && RequiredAnyFragments.IsEmpty() && RequiredAllChunkFragments.IsEmpty() && RequiredAllSharedFragments.IsEmpty() && RequiredAllConstSharedFragments.IsEmpty()); bHasNegativeRequirements = !(RequiredNoneTags.IsEmpty() && RequiredNoneFragments.IsEmpty() && RequiredNoneChunkFragments.IsEmpty() && RequiredNoneSharedFragments.IsEmpty() && RequiredNoneConstSharedFragments.IsEmpty()); bHasOptionalRequirements = !(RequiredOptionalFragments.IsEmpty() && RequiredOptionalTags.IsEmpty() && RequiredOptionalChunkFragments.IsEmpty() && RequiredOptionalSharedFragments.IsEmpty() && RequiredOptionalConstSharedFragments.IsEmpty()); bPropertiesCached = true; } } bool FMassFragmentRequirements::CheckValidity() const { CacheProperties(); // @todo we need to add more sophisticated testing somewhere to detect contradicting requirements - like having and not having a given tag. return bHasPositiveRequirements || bHasNegativeRequirements || bHasOptionalRequirements; } bool FMassFragmentRequirements::IsEmpty() const { CacheProperties(); // note that even though at the moment the following condition is the same as negation of current CheckValidity value // that will change in the future (with additional validity checks). return !bHasPositiveRequirements && !bHasNegativeRequirements && !bHasOptionalRequirements; } bool FMassFragmentRequirements::DoesMatchAnyOptionals(const FMassArchetypeCompositionDescriptor& ArchetypeComposition) const { return bHasOptionalRequirements && (ArchetypeComposition.Fragments.HasAny(RequiredOptionalFragments) || ArchetypeComposition.Tags.HasAny(RequiredOptionalTags) || ArchetypeComposition.ChunkFragments.HasAny(RequiredOptionalChunkFragments) || ArchetypeComposition.SharedFragments.HasAny(RequiredOptionalSharedFragments) || ArchetypeComposition.ConstSharedFragments.HasAny(RequiredOptionalConstSharedFragments)); } bool FMassFragmentRequirements::DoesArchetypeMatchRequirements(const FMassArchetypeHandle& ArchetypeHandle) const { check(ArchetypeHandle.IsValid()); const FMassArchetypeData* Archetype = FMassArchetypeHelper::ArchetypeDataFromHandle(ArchetypeHandle); CA_ASSUME(Archetype); return DoesArchetypeMatchRequirements(Archetype->GetCompositionDescriptor()); } bool FMassFragmentRequirements::DoesArchetypeMatchRequirements(const FMassArchetypeCompositionDescriptor& ArchetypeComposition) const { CacheProperties(); const bool bPassNegativeFilter = bHasNegativeRequirements == false || (ArchetypeComposition.Fragments.HasNone(RequiredNoneFragments) && ArchetypeComposition.Tags.HasNone(RequiredNoneTags) && ArchetypeComposition.ChunkFragments.HasNone(RequiredNoneChunkFragments) && ArchetypeComposition.SharedFragments.HasNone(RequiredNoneSharedFragments) && ArchetypeComposition.ConstSharedFragments.HasNone(RequiredNoneConstSharedFragments)); if (bPassNegativeFilter) { if (bHasPositiveRequirements) { return ArchetypeComposition.Fragments.HasAll(RequiredAllFragments) && (RequiredAnyFragments.IsEmpty() || ArchetypeComposition.Fragments.HasAny(RequiredAnyFragments)) && ArchetypeComposition.Tags.HasAll(RequiredAllTags) && (RequiredAnyTags.IsEmpty() || ArchetypeComposition.Tags.HasAny(RequiredAnyTags)) && ArchetypeComposition.ChunkFragments.HasAll(RequiredAllChunkFragments) && ArchetypeComposition.SharedFragments.HasAll(RequiredAllSharedFragments) && ArchetypeComposition.ConstSharedFragments.HasAll(RequiredAllConstSharedFragments); } else if (bHasOptionalRequirements) { return DoesMatchAnyOptionals(ArchetypeComposition); } // else - it's fine, we passed all the filters that have been set up return true; } return false; } void FMassFragmentRequirements::ExportRequirements(FMassExecutionRequirements& OutRequirements) const { using UE::Mass::Private::ExportRequirements; ExportRequirements(FragmentRequirements, OutRequirements.Fragments); ExportRequirements(ChunkFragmentRequirements, OutRequirements.ChunkFragments); ExportRequirements(SharedFragmentRequirements, OutRequirements.SharedFragments); ExportRequirements(ConstSharedFragmentRequirements, OutRequirements.ConstSharedFragments); OutRequirements.RequiredAllTags = RequiredAllTags; OutRequirements.RequiredAnyTags = RequiredAnyTags; OutRequirements.RequiredNoneTags = RequiredNoneTags; // not exporting optional tags by design } void FMassFragmentRequirements::Reset() { FragmentRequirements.Reset(); ChunkFragmentRequirements.Reset(); ConstSharedFragmentRequirements.Reset(); SharedFragmentRequirements.Reset(); RequiredAllTags.Reset(); RequiredAnyTags.Reset(); RequiredNoneTags.Reset(); RequiredAllFragments.Reset(); RequiredAnyFragments.Reset(); RequiredOptionalFragments.Reset(); RequiredNoneFragments.Reset(); RequiredAllChunkFragments.Reset(); RequiredOptionalChunkFragments.Reset(); RequiredNoneChunkFragments.Reset(); RequiredAllSharedFragments.Reset(); RequiredOptionalSharedFragments.Reset(); RequiredNoneSharedFragments.Reset(); RequiredAllConstSharedFragments.Reset(); RequiredOptionalConstSharedFragments.Reset(); RequiredNoneConstSharedFragments.Reset(); IncrementalChangesCount = 0; // note that we're not resetting bInitialized nor CachedEntityManager, on purpose // the point of this function is to just reset the contents while still being able // to add elements to it. This "requirements" instance is now "empty" but still valid } //----------------------------------------------------------------------------- // DEPRECATED //----------------------------------------------------------------------------- FMassFragmentRequirements::FMassFragmentRequirements(std::initializer_list InitList) { for (const UScriptStruct* FragmentType : InitList) { AddRequirement(FragmentType, EMassFragmentAccess::ReadWrite, EMassFragmentPresence::All); } } FMassFragmentRequirements::FMassFragmentRequirements(TConstArrayView InitList) { for (const UScriptStruct* FragmentType : InitList) { AddRequirement(FragmentType, EMassFragmentAccess::ReadWrite, EMassFragmentPresence::All); } }