// Copyright Epic Games, Inc. All Rights Reserved. #include "MassRelationManager.h" #include "MassEntityBuilder.h" #include "MassEntityTypes.h" #include "MassEntityManager.h" #include "MassRelationObservers.h" #include "MassExecutionContext.h" #include "Interfaces/ITextureFormat.h" #include "MassCommands.h" #include "MassArchetypeGroupCommands.h" #include "Algo/RemoveIf.h" DECLARE_CYCLE_STAT(TEXT("Mass Relation IsSubject"), STAT_Mass_IsSubject, STATGROUP_Mass); DECLARE_CYCLE_STAT(TEXT("Mass Relation IsSubject Recursive"), STAT_Mass_IsSubjectRecursive, STATGROUP_Mass); namespace UE::Mass { namespace Relations::Private { inline void HandleRole(const FMassEntityManager& EntityManager, FRelationData& RelationData, const FMassEntityHandle& OperatorEntity, UE::Mass::ERelationRole Role , const FMassEntityHandle& RoleEntity, const FMassEntityHandle& RelationEntity , TArray& InOutRelationEntitiesToDestroy) { const int32 RoleIndex = static_cast(Role); if (RelationData.Traits.RoleTraits[RoleIndex].RequiresExternalMapping == EExternalMappingRequired::Yes) { TArray& RoleData = RelationData.RoleMap.FindOrAdd(OperatorEntity)[RoleIndex]; const FMassRelationRoleInstanceHandle NewInstanceHandle = FMassRelationRoleInstanceHandle::Create(Role, RoleEntity, RelationEntity); // if objects are exclusive we need to destroy the previous instance if (RelationData.Traits.RoleTraits[RoleIndex].bExclusive && RoleData.Num() && RoleData[0] != NewInstanceHandle) { FMassRelationRoleInstanceHandle PreviousInstance = RoleData.Pop(EAllowShrinking::No); const FMassEntityHandle PreviousRelationEntity = PreviousInstance.GetRelationEntityHandle(EntityManager); InOutRelationEntitiesToDestroy.Add(PreviousRelationEntity); } #if WITH_MASSENTITY_DEBUG const int32 ExistingIndex = RoleData.Find(NewInstanceHandle); ensureMsgf(ExistingIndex == INDEX_NONE, TEXT("Given relation instance handle is already present in role mapping")); #endif // WITH_MASSENTITY_DEBUG RoleData.Add(NewInstanceHandle); } } void AddRoleInstance(const FMassEntityManager& EntityManager, FRelationData& RelationData, const FMassEntityHandle Subject, const FMassEntityHandle Object, const FMassEntityHandle RelationEntityHandle , TArray& InOutRelationEntitiesToDestroy) { HandleRole(EntityManager, RelationData, Subject, ERelationRole::Object, Object, RelationEntityHandle, InOutRelationEntitiesToDestroy); HandleRole(EntityManager, RelationData, Object, ERelationRole::Subject, Subject, RelationEntityHandle, InOutRelationEntitiesToDestroy); } } struct FScopedRecursiveLimit { FScopedRecursiveLimit(int32& Limit) : LimitRef(Limit) { ++LimitRef; check(LimitRef < 10); } ~FScopedRecursiveLimit() { --LimitRef; } int32& LimitRef; }; //----------------------------------------------------------------------------- // FRelationData //----------------------------------------------------------------------------- FRelationData::FRelationData(const FRelationTypeTraits& InTraits) : Traits(InTraits) { } TArray FRelationData::GetParticipants(const FMassEntityManager& EntityManager, const FMassEntityHandle RoleEntity, const ERelationRole QueriedRole) const { const int32 QueriedRoleIndex = static_cast(QueriedRole); ensureMsgf(Traits.RoleTraits[QueriedRoleIndex].RequiresExternalMapping == EExternalMappingRequired::Yes , TEXT("Fetching relation participants or role %s, while role traits explicitly prevent generic mapping") , *LexToString(QueriedRole)); TArray ReturnSubjects; if (const FRelationInstanceData* InstanceData = RoleMap.Find(RoleEntity)) { ReturnSubjects.Reserve((*InstanceData)[QueriedRoleIndex].Num()); for (const FMassRelationRoleInstanceHandle RelationInstanceHandle : (*InstanceData)[QueriedRoleIndex]) { const FMassEntityHandle RoleEntityHandle = RelationInstanceHandle.GetRoleEntityHandle(EntityManager); if (RoleEntityHandle.IsValid()) { ReturnSubjects.Add(RoleEntityHandle); } } } return ReturnSubjects; } //----------------------------------------------------------------------------- // FRelationManager::FHierarchyEntitiesContainer //----------------------------------------------------------------------------- void FRelationManager::FHierarchyEntitiesContainer::StoreUnique(const uint32 Depth, TArray& InOutArray) { for (int32 HandleIndex = InOutArray.Num() - 1; HandleIndex >= 0; --HandleIndex) { bool bIsAlreadyInSet = false; ExistingElements.FindOrAdd(InOutArray[HandleIndex], &bIsAlreadyInSet); if (bIsAlreadyInSet) { InOutArray.RemoveAtSwap(HandleIndex); } } if (InOutArray.Num()) { if (static_cast(ContainerPerLevel.Num()) <= Depth) { ContainerPerLevel.AddDefaulted(Depth - ContainerPerLevel.Num() + 1); } ContainerPerLevel[Depth].Append(InOutArray); } } void FRelationManager::FHierarchyEntitiesContainer::StoreUnique(const uint32 Depth, const FMassEntityHandle Handle) { bool bIsAlreadyInSet = false; ExistingElements.FindOrAdd(Handle, &bIsAlreadyInSet); if (bIsAlreadyInSet == false) { if (static_cast(ContainerPerLevel.Num()) <= Depth) { ContainerPerLevel.AddDefaulted(Depth - ContainerPerLevel.Num() + 1); } ContainerPerLevel[Depth].Add(Handle); } } //----------------------------------------------------------------------------- // FRelationManager //----------------------------------------------------------------------------- FRelationManager::FRelationManager(FMassEntityManager& InEntityManager) : EntityManager(InEntityManager) , TypeManager(InEntityManager.GetTypeManager()) { } TArray FRelationManager::CreateRelationInstances(const FTypeHandle RelationTypeHandle, TArrayView Subjects, TArrayView Objects) { if (!ensureMsgf(RelationTypeHandle.IsValid(), TEXT("Invalid RelationTypeHandle passed to %hs"), __FUNCTION__)) { return {}; } if (!ensureMsgf(Subjects.Num(), TEXT("Relation needs a valid Subjects entity")) || !ensureMsgf(Objects.Num(), TEXT("Relation needs a valid Objects entity")) // we allow number mismatch if there's a single object or subject, then we create 1-to-many relation || !testableEnsureMsgf(Objects.Num() == Subjects.Num(), TEXT("Relation Objects count need match Subjects"))) { return {}; } FRelationData& RelationData = GetRelationDataChecked(RelationTypeHandle); int32 NumOfRelations = Subjects.Num(); for (int32 PairIndex = 0; PairIndex < NumOfRelations; ) { const FMassEntityHandle Object = Objects[PairIndex]; const FMassEntityHandle Subject = Subjects[PairIndex]; bool bPairValid = true; if (!testableEnsureMsgf(Subject.IsValid(), TEXT("Relation needs a valid Subject entity")) || !testableEnsureMsgf(Object.IsValid(), TEXT("Relation needs a valid Object entity"))) { bPairValid = false; } if (!testableEnsureMsgf(RelationData.Traits.bHierarchical == false || Subject != Object , TEXT("Hierarchical relation requires the subject and object to be different"))) { bPairValid = false; } if (!testableEnsureMsgf(IsSubjectOfRelation(RelationData, Subject, Object) == false , TEXT("Relation between the two entities already exists"))) { bPairValid = false; } if (bPairValid) { ++PairIndex; } else { if (PairIndex + 1 < NumOfRelations) { Swap(Objects[PairIndex], Objects[NumOfRelations - 1]); Swap(Subjects[PairIndex], Subjects[NumOfRelations - 1]); } --NumOfRelations; } } if (NumOfRelations == 0) { return {}; } // some pairs have been removed, we need to update the views and pretend the invalid pairs were never there if (NumOfRelations != Subjects.Num()) { Objects = MakeArrayView(&Objects[0], NumOfRelations); Subjects = MakeArrayView(&Subjects[0], NumOfRelations); } FEntityBuilder EntityBuilder(EntityManager); FMassRelationFragment& RelationFragment = EntityBuilder.Add_GetRef(); TNotNull RelationTypeTag = RelationTypeHandle.GetScriptStruct(); EntityBuilder.Add(RelationTypeTag); TSharedRef ObserversLock = EntityManager.GetOrMakeObserversLock(); TArray CreatedRelationEntities; TArray RelationEntitiesToDestroy; for (int32 PairIndex = 0; PairIndex < NumOfRelations; ++PairIndex) { const FMassEntityHandle Object = Objects[FMath::Min(Objects.Num() - 1, PairIndex)]; const FMassEntityHandle Subject = Subjects[FMath::Min(Subjects.Num() - 1, PairIndex)]; RelationFragment.Subject = Subject; RelationFragment.Object = Object; CreatedRelationEntities.Add(EntityBuilder.CommitAndReprepare()); const FMassEntityHandle RelationEntityHandle = CreatedRelationEntities.Last(); Relations::Private::AddRoleInstance(EntityManager, RelationData, Subject, Object, RelationEntityHandle, RelationEntitiesToDestroy); } if (RelationEntitiesToDestroy.Num()) { EntityManager.Defer().DestroyEntities(MoveTemp(RelationEntitiesToDestroy)); } TNotNull SubjectElement = RelationData.Traits.RoleTraits[static_cast(ERelationRole::Subject)].Element ? TNotNull(RelationData.Traits.RoleTraits[static_cast(ERelationRole::Subject)].Element) : RelationTypeTag; TNotNull ObjectElement = RelationData.Traits.RoleTraits[static_cast(ERelationRole::Object)].Element ? TNotNull(RelationData.Traits.RoleTraits[static_cast(ERelationRole::Object)].Element) : RelationTypeTag; if (EntityManager.IsProcessing()) { TUniquePtr ElementAddingCommand = MakeUnique(SubjectElement); ElementAddingCommand->Add(Subjects); if (ObjectElement != SubjectElement) { EntityManager.Defer().PushUniqueCommand(MoveTemp(ElementAddingCommand)); ElementAddingCommand = MakeUnique(ObjectElement); } ElementAddingCommand->Add(Objects); EntityManager.Defer().PushUniqueCommand(MoveTemp(ElementAddingCommand)); } else { EntityManager.AddElementToEntities(Objects, ObjectElement); EntityManager.AddElementToEntities(Subjects, SubjectElement); } if (RelationData.Traits.bHierarchical) { FHierarchyEntitiesContainer SubSubjects; uint32 MinDepth = static_cast(-1); for (int32 PairIndex = 0; PairIndex < Subjects.Num(); ++PairIndex) { const FMassEntityHandle Object = Objects[FMath::Min(Objects.Num() - 1, PairIndex)]; const FMassEntityHandle Subject = Subjects[FMath::Min(Subjects.Num() - 1, PairIndex)]; FArchetypeGroupHandle ObjectsGroup = EntityManager.GetGroupForEntity(Object, RelationData.Traits.RegisteredGroupType); if (ObjectsGroup.IsValid() == false) { ObjectsGroup = FArchetypeGroupHandle(RelationData.Traits.RegisteredGroupType, 0); } const uint32 StartingDepth = ObjectsGroup.GetGroupID(); MinDepth = FMath::Min(StartingDepth, MinDepth); SubSubjects.StoreUnique(StartingDepth, Object); SubSubjects.StoreUnique(StartingDepth + 1, Subject); GatherHierarchy(RelationData, Subject, SubSubjects, StartingDepth + 2); } if (EntityManager.IsProcessing()) { for (uint32 Depth = MinDepth; Depth < SubSubjects.Num(); ++Depth) { FArchetypeGroupHandle GroupHandle(RelationData.Traits.RegisteredGroupType, Depth); EntityManager.Defer().PushCommand(GroupHandle, MoveTemp(SubSubjects[Depth])); } } else { for (uint32 Depth = MinDepth; Depth < SubSubjects.Num(); ++Depth) { FArchetypeGroupHandle GroupHandle(RelationData.Traits.RegisteredGroupType, Depth); TArray CollectionsAtLevel; UE::Mass::Utils::CreateEntityCollections(EntityManager, SubSubjects[Depth] , FMassArchetypeEntityCollection::EDuplicatesHandling::FoldDuplicates , CollectionsAtLevel); EntityManager.BatchGroupEntities(GroupHandle, CollectionsAtLevel); } } } return CreatedRelationEntities; } bool FRelationManager::DestroyRelationInstance(const FTypeHandle RelationTypeHandle, const FMassEntityHandle Subject, const FMassEntityHandle Object) { const FRelationData& RelationData = GetRelationDataChecked(RelationTypeHandle); ensure(RelationData.Traits.RoleTraits[0].RequiresExternalMapping != EExternalMappingRequired::No || RelationData.Traits.RoleTraits[1].RequiresExternalMapping != EExternalMappingRequired::No); if (const FRelationData::FRelationInstanceData* InstanceData = RelationData.RoleMap.Find(Subject)) { for (const FMassRelationRoleInstanceHandle RelationInstanceHandle : (*InstanceData)[static_cast(ERelationRole::Object)]) { if (RelationInstanceHandle.GetRoleEntityIndex() == Object.Index) { const FMassEntityHandle RelationEntityHandle = RelationInstanceHandle.GetRelationEntityHandle(EntityManager); if (RelationEntityHandle.IsValid()) { EntityManager.DestroyEntity(RelationEntityHandle); return true; } } } } return false; } bool FRelationManager::DestroyRelationInstance(const FMassRelationRoleInstanceHandle RelationHandle) const { const FMassEntityHandle RelationEntityHandle = RelationHandle.GetRelationEntityHandle(EntityManager); if (RelationEntityHandle.IsValid()) { EntityManager.DestroyEntity(RelationEntityHandle); return true; } return false; } TArray FRelationManager::GetRelationSubjects(const FRelationData& RelationData, const FMassEntityHandle ObjectEntity) const { return RelationData.GetParticipants(EntityManager, ObjectEntity, ERelationRole::Subject); } TArray FRelationManager::GetRelationObjects(const FRelationData& RelationData, const FMassEntityHandle SubjectEntity) const { return RelationData.GetParticipants(EntityManager, SubjectEntity, ERelationRole::Object); } void FRelationManager::GatherHierarchy(const FRelationData& RelationData, const FMassEntityHandle SubjectHandle, FHierarchyEntitiesContainer& SubSubjects, const uint32 Depth) const { // getting subject's subjects TArray LocalSubjects = GetRelationSubjects(RelationData, SubjectHandle); if (LocalSubjects.Num() > 0) { // this call will store the handles in LocalSubjects, but only the ones that have not been // stored earlier. This prevents the function getting stuck due to cycles. SubSubjects.StoreUnique(Depth, LocalSubjects); for (const FMassEntityHandle& SubSubjectHandle : LocalSubjects) { GatherHierarchy(RelationData, SubSubjectHandle, SubSubjects, Depth + 1); } } } void FRelationManager::GetRelationEntities(TConstArrayView RelationEntitiesContainer, TArray& InOutEntityHandles) const { InOutEntityHandles.Reserve(RelationEntitiesContainer.Num() + InOutEntityHandles.Num()); for (const FMassRelationRoleInstanceHandle& Handle : RelationEntitiesContainer) { InOutEntityHandles.Add(Handle.GetRelationEntityHandle(EntityManager)); } } TArray FRelationManager::GetRelationEntities(TConstArrayView RelationEntitiesContainer) const { TArray EntityHandles; GetRelationEntities(RelationEntitiesContainer, EntityHandles); return EntityHandles; } void FRelationManager::GetRoleEntities(TConstArrayView RelationEntitiesContainer, TArray& InOutEntityHandles) const { InOutEntityHandles.Reserve(RelationEntitiesContainer.Num() + InOutEntityHandles.Num()); for (const FMassRelationRoleInstanceHandle& Handle : RelationEntitiesContainer) { InOutEntityHandles.Add(Handle.GetRoleEntityHandle(EntityManager)); } } TArray FRelationManager::GetRoleEntities(TConstArrayView RelationEntitiesContainer) const { TArray EntityHandles; GetRoleEntities(RelationEntitiesContainer, EntityHandles); return EntityHandles; } FRelationData& FRelationManager::CreateRelationData(const FTypeHandle RelationTypeHandle) { FRelationData* RelationData = RelationsDataMap.Find(RelationTypeHandle); if (ensureMsgf(RelationData == nullptr , TEXT("%hs: relation of type %s already registered"), __FUNCTION__, *RelationTypeHandle.GetFName().ToString())) { FRelationTypeTraits Traits = TypeManager.GetRelationTypeChecked(RelationTypeHandle); // @todo we should check every role's traits and see if RequiresExternalMapping == EExternalMappingRequired::No, // and if so, make sure all the other required bits are provided (like "other role getter"). RelationData = &RelationsDataMap.Add(RelationTypeHandle, Traits); } return *RelationData; } bool FRelationManager::IsSubjectOfRelation(const FTypeHandle RelationTypeHandle, const FMassEntityHandle Subject, const FMassEntityHandle Object) const { check(RelationTypeHandle.IsValid()); if (const FRelationData* RelationData = GetRelationData(RelationTypeHandle)) { return IsSubjectOfRelation(*RelationData, Subject, Object); } return false; } bool FRelationManager::IsSubjectOfRelation(const FRelationData& RelationData, const FMassEntityHandle Subject, const FMassEntityHandle Object) const { SCOPE_CYCLE_COUNTER(STAT_Mass_IsSubject); if (Subject != Object && Subject.IsValid() && Object.IsValid()) { if (const FRelationData::FRelationInstanceData* SubjectData = RelationData.RoleMap.Find(Subject)) { for (const FMassRelationRoleInstanceHandle& RelationInstanceHandle : (*SubjectData)[static_cast(ERelationRole::Object)]) { if (RelationInstanceHandle.GetRoleEntityHandle(EntityManager) == Object) { return true; } } } } return false; } bool FRelationManager::IsSubjectOfRelationRecursive(const FTypeHandle RelationTypeHandle, const FMassEntityHandle Subject, const FMassEntityHandle Object) const { check(RelationTypeHandle.IsValid()); if (const FRelationData* RelationData = GetRelationData(RelationTypeHandle)) { return IsSubjectOfRelationRecursive(*RelationData, Subject, Object); } return false; } bool FRelationManager::IsSubjectOfRelationRecursive(const FRelationData& RelationData, const FMassEntityHandle Subject, const FMassEntityHandle Object) const { // @todo the function can be optimized based on information whether Subject or Object is exclusive. If both are, it can be optimized even further. // for now naive implementation SCOPE_CYCLE_COUNTER(STAT_Mass_IsSubjectRecursive); if (Subject != Object && Subject.IsValid() && Object.IsValid()) { TArray Subjects; TArray Objects; Subjects.Add(Subject); constexpr int32 IterationsLimit = 64; for (int32 SubjectIndex = 0; SubjectIndex < Subjects.Num() && ensure(SubjectIndex < IterationsLimit); ++SubjectIndex) { if (const FRelationData::FRelationInstanceData* SubjectData = RelationData.RoleMap.Find(Subjects[SubjectIndex])) { GetRoleEntities((*SubjectData)[static_cast(ERelationRole::Object)], Objects); if (Objects.Find(Object) != INDEX_NONE) { return true; } for (const FMassEntityHandle& ObjectHandle : Objects) { Subjects.AddUnique(ObjectHandle); } Objects.Reset(); } } } return false; } void FRelationManager::OnRelationTypeRegistered(const FTypeHandle InRegisteredTypeHandle, const FRelationTypeTraits& RelationTypeTraits) { FRelationData& RelationData = CreateRelationData(InRegisteredTypeHandle); ensureMsgf(RelationData.Traits.bHierarchical == false || RelationData.Traits.RegisteredGroupType.IsValid() , TEXT("Hierarchical relationships need a valid registered group size. Failed by relationship %s") , *RelationData.Traits.GetFName().ToString()); TArray> ObserversToCreate = { RelationTypeTraits.RelationEntityCreationObserverClass.Get() , RelationTypeTraits.RelationEntityDestructionObserverClass.Get() , UMassRelationEntityGuardDog::StaticClass() }; if (RelationTypeTraits.SubjectEntityDestructionObserverClass.Get() == UMassRelationRoleDestruction::StaticClass() && RelationTypeTraits.ObjectEntityDestructionObserverClass.Get() == UMassRelationRoleDestruction::StaticClass()) { UMassRelationRoleDestruction::AddObserverInstances(EntityManager.GetObserverManager(), InRegisteredTypeHandle, RelationData.Traits); } else { ObserversToCreate.Add(RelationTypeTraits.SubjectEntityDestructionObserverClass.Get()); ObserversToCreate.Add(RelationTypeTraits.ObjectEntityDestructionObserverClass.Get()); } for (const TSubclassOf& ObserverClass : ObserversToCreate) { if (ObserverClass) { UMassRelationObserver* ObserverProcessor = NewObject(EntityManager.GetOwner(), ObserverClass); if (ObserverProcessor->ConfigureRelationObserver(InRegisteredTypeHandle, RelationData.Traits)) { EntityManager.GetObserverManager().AddObserverInstance(ObserverProcessor); } } } } } // namespace UE::Mass #undef WITH_RELATIONSHIP_VALIDATION