// Copyright Epic Games, Inc. All Rights Reserved. #include "MassObserverManager.h" #include "MassEntityManager.h" #include "MassEntitySubsystem.h" #include "MassExecutor.h" #include "MassProcessingTypes.h" #include "MassObserverRegistry.h" #include "MassDebugger.h" #include "MassEntityCollection.h" #include "MassProcessingContext.h" #include "MassObserverNotificationTypes.h" #include "MassObserverProcessor.h" #include "VisualLogger/VisualLogger.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MassObserverManager) namespace UE::Mass::ObserverManager { namespace Tweakables { // Used as a template parameter for TInlineAllocator that we use when gathering UScriptStruct* of the observed types to process. constexpr int InlineAllocatorElementsForOverlapTypes = 8; } // Tweakables bool bCoalesceBufferedNotifications = false; namespace Private { FAutoConsoleVariableRef ConsoleVariables[] = { FAutoConsoleVariableRef(TEXT("mass.observers.CoalesceBufferedNotifications"), bCoalesceBufferedNotifications , TEXT("If enabled, when buffering new notification we'll check if it's the same type as the previously stored one, and if so then merge the two."), ECVF_Default) }; // a helper function to reduce code duplication in FMassObserverManager::Initialize template void AddRegisteredObserverProcessorInstances(FMassEntityManager& EntityManager, const EProcessorExecutionFlags WorldExecutionFlags, UObject& Owner , const UMassObserverRegistry::FObserverClassesMap& RegisteredObserverTypes, TBitSet& InOutObservedBitSet, FMassObserversMap& Observers , TMap, UMassProcessor*>& InOutExistingProcessorsMap) { for (auto It : RegisteredObserverTypes) { const UObject* Type = It.Key.ResolveObjectPtr(); if (Type == nullptr || It.Value.Num() == 0) { continue; } const UScriptStruct* StructType = CastChecked(Type); TArray> ObserverProcessors; for (const FSoftClassPath& SoftProcessorClass : It.Value) { if (TSubclassOf ProcessorClass = SoftProcessorClass.ResolveClass()) { if (UMassProcessor** ExistingProcessor = InOutExistingProcessorsMap.Find(ProcessorClass)) { ObserverProcessors.AddUnique(*ExistingProcessor); } else if (ProcessorClass->GetDefaultObject()->ShouldExecute(WorldExecutionFlags)) { UMassProcessor* ObserverInstance = NewObject(&Owner, ProcessorClass); ObserverProcessors.Add(ObserverInstance); InOutExistingProcessorsMap.Add(ProcessorClass, ObserverInstance); } } } if (ObserverProcessors.Num() > 0) { FMassRuntimePipeline& Pipeline = (*Observers).FindOrAdd(StructType); Pipeline.AppendProcessors(MoveTemp(ObserverProcessors)); Pipeline.SortByExecutionPriority(); Pipeline.Initialize(Owner, EntityManager.AsShared()); InOutObservedBitSet.Add(*StructType); } } }; } // Private struct FDeprecationHelper { /** determines which observed operation type the given HandlersContainerPtr implements */ static EMassObservedOperation DetermineOperationType(const FMassObserversMap* HandlersContainerPtr, const FMassObserversMap* MapArray) { if (HandlersContainerPtr == &MapArray[static_cast(EMassObservedOperation::AddElement)]) { return EMassObservedOperation::AddElement; } if (HandlersContainerPtr == &MapArray[static_cast(EMassObservedOperation::RemoveElement)]) { return EMassObservedOperation::RemoveElement; } return EMassObservedOperation::MAX; }; static void HandleSingleElement(TNotNull ObserverManager, const UScriptStruct& ElementType, const FMassArchetypeEntityCollection& EntityCollection, FMassObserversMap& HandlersContainer) { const bool bIsFragment = UE::Mass::IsA(&ElementType); check(bIsFragment || UE::Mass::IsA(&ElementType)); const EMassObservedOperation Operation = bIsFragment ? DetermineOperationType(&HandlersContainer, ObserverManager->EntityManager.GetObserverManager().FragmentObservers) : DetermineOperationType(&HandlersContainer, ObserverManager->EntityManager.GetObserverManager().TagObservers); HandleSingleElement(ObserverManager, ElementType, EntityCollection, Operation); } static void HandleSingleElement(TNotNull ObserverManager, const UScriptStruct& ElementType, const FMassArchetypeEntityCollection& EntityCollection, const EMassObservedOperation Operation) { const int32 OperationIndex = static_cast(Operation); const bool bIsFragment = UE::Mass::IsA(&ElementType); check(bIsFragment || UE::Mass::IsA(&ElementType)); const UScriptStruct* ElementTypePtr = &ElementType; UE::Mass::FProcessingContext ProcessingContext(ObserverManager->EntityManager); FMassObserverManager::HandleElementsImpl(ProcessingContext, MakeArrayView(&EntityCollection, 1) , {Operation, MakeArrayView(&ElementTypePtr, 1)} , bIsFragment ? ObserverManager->FragmentObservers[OperationIndex] : ObserverManager->TagObservers[OperationIndex]); } }; struct FNotificationContext { FMassObserverManager& ObserverManager; FProcessingContext& ProcessingContext; }; struct FBufferedNotificationExecutioner { FBufferedNotificationExecutioner(FNotificationContext& InNotificationContext) : NotificationContext(InNotificationContext) {} template void operator()(const FBufferedNotification::FEmptyComposition&, TEntities) { // no-op } void operator()(const FMassArchetypeCompositionDescriptor& Change, const FEntityCollection& Entities) { if (Change.GetFragments().IsEmpty() == false) { (*this)(Change.GetFragments(), Entities); } if (Change.GetTags().IsEmpty() == false) { (*this)(Change.GetTags(), Entities); } } void operator()(const FMassArchetypeCompositionDescriptor& Change, const FMassEntityHandle EntityHandle) { if (Change.GetFragments().IsEmpty() == false) { (*this)(Change.GetFragments(), EntityHandle); } if (Change.GetTags().IsEmpty() == false) { (*this)(Change.GetTags(), EntityHandle); } } void operator()(const FMassFragmentBitSet& Change, const FEntityCollection& Entities) { ObservedTypesOverlap.Reset(); Change.ExportTypes(ObservedTypesOverlap); FMassObserverManager::HandleElementsImpl(NotificationContext.ProcessingContext , Entities.GetUpToDatePerArchetypeCollections(NotificationContext.ObserverManager.EntityManager) , {OpType, ObservedTypesOverlap} , NotificationContext.ObserverManager.FragmentObservers[static_cast(OpType)]); } void operator()(const FMassFragmentBitSet& Change, const FMassEntityHandle EntityHandle) { ObservedTypesOverlap.Reset(); Change.ExportTypes(ObservedTypesOverlap); FMassArchetypeHandle ArchetypeHandle = NotificationContext.ObserverManager.GetEntityManager().GetArchetypeForEntity(EntityHandle); FMassArchetypeEntityCollection AsEntityCollection(MoveTemp(ArchetypeHandle), EntityHandle); NotificationContext.ObserverManager.HandleElementsImpl(NotificationContext.ProcessingContext , MakeArrayView(&AsEntityCollection, 1) , {OpType, ObservedTypesOverlap} , NotificationContext.ObserverManager.FragmentObservers[static_cast(OpType)]); } void operator()(const FMassTagBitSet& Change, const FEntityCollection& Entities) { ObservedTypesOverlap.Reset(); Change.ExportTypes(ObservedTypesOverlap); FMassObserverManager::HandleElementsImpl(NotificationContext.ProcessingContext , Entities.GetUpToDatePerArchetypeCollections(NotificationContext.ObserverManager.EntityManager) , {OpType, ObservedTypesOverlap} , NotificationContext.ObserverManager.TagObservers[static_cast(OpType)]); } void operator()(const FMassTagBitSet& Change, const FMassEntityHandle EntityHandle) { ObservedTypesOverlap.Reset(); Change.ExportTypes(ObservedTypesOverlap); FMassArchetypeHandle ArchetypeHandle = NotificationContext.ObserverManager.GetEntityManager().GetArchetypeForEntity(EntityHandle); FMassArchetypeEntityCollection AsEntityCollection(MoveTemp(ArchetypeHandle), EntityHandle); NotificationContext.ObserverManager.HandleElementsImpl(NotificationContext.ProcessingContext , MakeArrayView(&AsEntityCollection, 1) , {OpType, ObservedTypesOverlap} , NotificationContext.ObserverManager.TagObservers[static_cast(OpType)]); } TArray> ObservedTypesOverlap; FNotificationContext& NotificationContext; EMassObservedOperation OpType; }; struct FBufferedCreationNotificationExecutioner { explicit FBufferedCreationNotificationExecutioner(FNotificationContext& InNotificationContext) : NotificationContext(InNotificationContext) {} void operator()(const FEntityCollection& Entities) const { NotificationContext.ObserverManager.OnCollectionsCreatedImpl(NotificationContext.ProcessingContext , Entities.GetUpToDatePerArchetypeCollections(NotificationContext.ObserverManager.EntityManager)); } void operator()(const FMassEntityHandle EntityHandle) const { const FMassArchetypeHandle ArchetypeHandle = NotificationContext.ObserverManager.GetEntityManager().GetArchetypeForEntity(EntityHandle); const FMassArchetypeCompositionDescriptor& ArchetypeComposition = NotificationContext.ProcessingContext.GetEntityManager()->GetArchetypeComposition(ArchetypeHandle); NotificationContext.ObserverManager.OnCompositionChanged(EntityHandle, ArchetypeComposition, EMassObservedOperation::CreateEntity, &NotificationContext.ProcessingContext); } FNotificationContext& NotificationContext; }; /** Trivial type with a sole responsibility of initializing and incrementing FMassObserverExecutionContext.CurrentTypeIndex */ struct FObserverContextIterator { FObserverContextIterator(FMassObserverExecutionContext& InRuntimeContext) : RuntimeContext(InRuntimeContext) { RuntimeContext.CurrentTypeIndex = 0; } int32 operator++() { return ++RuntimeContext.CurrentTypeIndex; } operator bool() const { return RuntimeContext.IsValid(); } private: FMassObserverExecutionContext& RuntimeContext; }; } // UE::Mass::ObserverManager //----------------------------------------------------------------------// // FMassObserversMap //----------------------------------------------------------------------// void FMassObserversMap::DebugAddUniqueProcessors(TArray& OutProcessors) const { #if WITH_MASSENTITY_DEBUG for (const auto& MapElement : Container) { for (const UMassProcessor* Processor : MapElement.Value.GetProcessorsView()) { ensure(Processor); OutProcessors.AddUnique(Processor); } } #endif // WITH_MASSENTITY_DEBUG } //----------------------------------------------------------------------// // FMassObserverManager //----------------------------------------------------------------------// FMassArchetypeEntityCollection FMassObserverManager::FCollectionRefOrHandle::DummyCollection; FMassObserverManager::FMassObserverManager() : EntityManager(GetMutableDefault()->GetMutableEntityManager()) { } FMassObserverManager::FMassObserverManager(FMassEntityManager& Owner) : EntityManager(Owner) { } void FMassObserverManager::DebugGatherUniqueProcessors(TArray& OutProcessors) const { #if WITH_MASSENTITY_DEBUG for (int32 OperationIndex = 0; OperationIndex < static_cast(EMassObservedOperation::MAX); ++OperationIndex) { FragmentObservers[OperationIndex].DebugAddUniqueProcessors(OutProcessors); TagObservers[OperationIndex].DebugAddUniqueProcessors(OutProcessors); } #endif // WITH_MASSENTITY_DEBUG } void FMassObserverManager::Initialize() { // instantiate initializers const UMassObserverRegistry& Registry = UMassObserverRegistry::Get(); UObject* Owner = EntityManager.GetOwner(); check(Owner); const UWorld* World = Owner->GetWorld(); const EProcessorExecutionFlags WorldExecutionFlags = UE::Mass::Utils::DetermineProcessorExecutionFlags(World); TMap, UMassProcessor*> TransientProcessorsMap; using UE::Mass::ObserverManager::Private::AddRegisteredObserverProcessorInstances; for (int i = 0; i < (int)EMassObservedOperation::MAX; ++i) { AddRegisteredObserverProcessorInstances(EntityManager, WorldExecutionFlags, *Owner, Registry.FragmentObserverMaps[i], ObservedFragments[i], FragmentObservers[i], TransientProcessorsMap); AddRegisteredObserverProcessorInstances(EntityManager, WorldExecutionFlags, *Owner, Registry.TagObserverMaps[i], ObservedTags[i], TagObservers[i], TransientProcessorsMap); } #if WITH_MASSENTITY_DEBUG FMassDebugger::RegisterProcessorDataProvider(TEXT("Observers"), EntityManager.AsShared() , [WeakManager = EntityManager.AsWeak()](TArray& OutProcessors) { if (TSharedPtr SharedEntityManager = WeakManager.Pin()) { FMassObserverManager& ObserverManager = SharedEntityManager->GetObserverManager(); ObserverManager.DebugGatherUniqueProcessors(OutProcessors); } }); #endif // WITH_MASSENTITY_DEBUG if (!ModulesUnloadedHandle.IsValid()) { ModulesUnloadedHandle = FCoreUObjectDelegates::CompiledInUObjectsRemovedDelegate.AddRaw(this, &FMassObserverManager::OnModulePackagesUnloaded); } } void FMassObserverManager::DeInitialize() { if (ModulesUnloadedHandle.IsValid()) { FCoreUObjectDelegates::CompiledInUObjectsRemovedDelegate.Remove(ModulesUnloadedHandle); ModulesUnloadedHandle.Reset(); } for (int32 i = 0; i < (int32)EMassObservedOperation::MAX; ++i) { (*FragmentObservers[i]).Empty(); (*TagObservers[i]).Empty(); } } bool FMassObserverManager::OnPostEntitiesCreated(const FMassArchetypeEntityCollection& EntityCollection) { TRACE_CPUPROFILER_EVENT_SCOPE(MassObserver_OnPostEntitiesCreated_Collection); if (LocksCount > 0) { TSharedRef ObserverLock = ActiveObserverLock.Pin().ToSharedRef(); ObserverLock->AddCreatedEntitiesCollection(EntityCollection); return false; } const FMassArchetypeCompositionDescriptor& ArchetypeComposition = EntityManager.GetArchetypeComposition(EntityCollection.GetArchetype()); return OnCompositionChanged(EntityCollection, ArchetypeComposition, EMassObservedOperation::CreateEntity); } bool FMassObserverManager::OnPostEntityCreated(FMassEntityHandle EntityHandle, const FMassArchetypeCompositionDescriptor& Composition) { TRACE_CPUPROFILER_EVENT_SCOPE(MassObserver_OnPostEntitiesCreated_Collection); if (LocksCount > 0) { TSharedRef ObserverLock = ActiveObserverLock.Pin().ToSharedRef(); ObserverLock->AddCreatedEntity(EntityHandle); return false; } if (Composition.IsEmpty()) { const FMassArchetypeHandle ArchetypeHandle = EntityManager.GetArchetypeForEntity(EntityHandle); const FMassArchetypeCompositionDescriptor& ArchetypeComposition = EntityManager.GetArchetypeComposition(ArchetypeHandle); return OnCompositionChanged(EntityHandle, ArchetypeComposition, EMassObservedOperation::CreateEntity); } return OnCompositionChanged(EntityHandle, Composition, EMassObservedOperation::CreateEntity); } bool FMassObserverManager::OnPreEntitiesDestroyed(const FMassArchetypeEntityCollection& EntityCollection) { TRACE_CPUPROFILER_EVENT_SCOPE_STR("OnPreEntitiesDestroyed") const FMassArchetypeCompositionDescriptor& ArchetypeComposition = EntityManager.GetArchetypeComposition(EntityCollection.GetArchetype()); return OnCompositionChanged(EntityCollection, ArchetypeComposition, EMassObservedOperation::DestroyEntity); } bool FMassObserverManager::OnPreEntitiesDestroyed(UE::Mass::FProcessingContext& ProcessingContext, const FMassArchetypeEntityCollection& EntityCollection) { TRACE_CPUPROFILER_EVENT_SCOPE_STR("OnPreEntitiesDestroyed") const FMassArchetypeCompositionDescriptor& ArchetypeComposition = EntityManager.GetArchetypeComposition(EntityCollection.GetArchetype()); return OnCompositionChanged(EntityCollection, ArchetypeComposition, EMassObservedOperation::DestroyEntity, &ProcessingContext); } bool FMassObserverManager::OnPreEntityDestroyed(const FMassArchetypeCompositionDescriptor& ArchetypeComposition, const FMassEntityHandle Entity) { TRACE_CPUPROFILER_EVENT_SCOPE_STR("OnPreEntityDestroyed") return OnCompositionChanged(Entity, ArchetypeComposition, EMassObservedOperation::DestroyEntity); } bool FMassObserverManager::OnCompositionChanged(FCollectionRefOrHandle&& EntityCollection, const FMassArchetypeCompositionDescriptor& CompositionDelta , const EMassObservedOperation Operation, UE::Mass::FProcessingContext* ProcessingContext) { using UE::Mass::ObserverManager::Tweakables::InlineAllocatorElementsForOverlapTypes; ensureMsgf(EntityCollection.EntityHandle.IsValid() || EntityCollection.EntityCollection.IsUpToDate() , TEXT("Out-of-date FMassArchetypeEntityCollection used. Stored information is unreliable.")); if (CompositionDelta.IsEmpty()) { // nothing to do here. // @todo calling this function just to bail out would be a lot cheaper if we didn't have to create // FMassArchetypeCompositionDescriptor instances first - we usually just pass in tag or fragment bitsets. // like in FMassEntityManager::BatchChangeTagsForEntities return false; } TRACE_CPUPROFILER_EVENT_SCOPE(MassObserver_OnCompositionChanged); if (LocksCount > 0) { if (TSharedPtr CreationContext = GetCreationContext()) { // a composition mutating operation is taking place, while creation lock is active - this operation invalidates the stored collections CreationContext->MarkDirty(); return false; } UE_CVLOG_UELOG(Operation == EMassObservedOperation::RemoveElement || Operation == EMassObservedOperation::DestroyEntity , EntityManager.GetOwner(), LogMass, Log , TEXT("%hs: Remove operation while observers are locked - the remove-observer will be executed after the data has already been removed."), __FUNCTION__); } const int32 OperationIndex = static_cast(Operation); FMassFragmentBitSet FragmentOverlap = ObservedFragments[OperationIndex].GetOverlap(CompositionDelta.GetFragments()); FMassTagBitSet TagOverlap = ObservedTags[OperationIndex].GetOverlap(CompositionDelta.GetTags()); const bool bHasFragmentsOverlap = !FragmentOverlap.IsEmpty(); const bool bHasTagsOverlap = !TagOverlap.IsEmpty(); if (bHasFragmentsOverlap || bHasTagsOverlap) { if (LocksCount > 0) { TSharedRef ObserverLockRef = ActiveObserverLock.Pin().ToSharedRef(); // UE::Mass::FEntityCollection(EntityCollection) OR EntityHandle if (UE::Mass::ObserverManager::bCoalesceBufferedNotifications) { TRACE_CPUPROFILER_EVENT_SCOPE(MassObserver_Notify_Coalesced); if (EntityCollection.EntityHandle.IsSet()) { ObserverLockRef->AddNotification(Operation, EntityCollection.EntityHandle , bHasFragmentsOverlap, MoveTemp(FragmentOverlap) , bHasTagsOverlap, MoveTemp(TagOverlap)); } else { ObserverLockRef->AddNotification(Operation, EntityCollection.EntityCollection , bHasFragmentsOverlap, MoveTemp(FragmentOverlap) , bHasTagsOverlap, MoveTemp(TagOverlap)); } } else { TRACE_CPUPROFILER_EVENT_SCOPE(MassObserver_Notify_Emplace); FBufferedNotification::FEntitiesContainer Entities; if (EntityCollection.EntityHandle.IsSet()) { Entities.Emplace(EntityCollection.EntityHandle); } else { Entities.Emplace(EntityCollection.EntityCollection); } if (bHasFragmentsOverlap && bHasTagsOverlap) { FMassArchetypeCompositionDescriptor ChangedComposition(MoveTemp(FragmentOverlap), MoveTemp(TagOverlap), {}, {}, {}); ObserverLockRef->BufferedNotifications.Emplace(Operation, MoveTemp(ChangedComposition), MoveTemp(Entities)); } else if (bHasFragmentsOverlap) { ObserverLockRef->BufferedNotifications.Emplace(Operation, MoveTemp(FragmentOverlap), MoveTemp(Entities)); } else // bHasTagsOverlap { ObserverLockRef->BufferedNotifications.Emplace(Operation, MoveTemp(TagOverlap), MoveTemp(Entities)); } } } else { auto HandleElements = [&](const FMassArchetypeEntityCollection& Collection) { TArray> ObservedTypesOverlap; alignas(UE::Mass::FProcessingContext) uint8 LocalContextBuffer[sizeof(UE::Mass::FProcessingContext)]; UE::Mass::FProcessingContext* LocalProcessingContext = (ProcessingContext == nullptr) ? new(&LocalContextBuffer) UE::Mass::FProcessingContext(EntityManager, /*DeltaSeconds=*/0.f, /*bFlushCommandBuffer=*/false) : ProcessingContext; if (bHasFragmentsOverlap) { FragmentOverlap.ExportTypes(ObservedTypesOverlap); HandleElementsImpl(*LocalProcessingContext, {Collection}, {Operation, ObservedTypesOverlap}, FragmentObservers[OperationIndex]); } if (bHasTagsOverlap) { ObservedTypesOverlap.Reset(); TagOverlap.ExportTypes(ObservedTypesOverlap); HandleElementsImpl(*LocalProcessingContext, {Collection}, {Operation, ObservedTypesOverlap}, TagObservers[OperationIndex]); } if (ProcessingContext == nullptr) { LocalProcessingContext->~FProcessingContext(); } }; if (EntityCollection.EntityHandle.IsSet()) { const FMassArchetypeHandle ArchetypeHandle = EntityManager.GetArchetypeForEntity(EntityCollection.EntityHandle); HandleElements(FMassArchetypeEntityCollection(ArchetypeHandle, EntityCollection.EntityHandle)); } else { HandleElements(EntityCollection.EntityCollection); } return true; } } return false; } bool FMassObserverManager::OnCollectionsCreatedImpl(UE::Mass::FProcessingContext& ProcessingContext, const TConstArrayView EntityCollections) { using UE::Mass::ObserverManager::Tweakables::InlineAllocatorElementsForOverlapTypes; TRACE_CPUPROFILER_EVENT_SCOPE(MassObserver_OnCollectionsCreatedImpl_Collection); check(LocksCount == 0); constexpr EMassObservedOperation Operation = EMassObservedOperation::CreateEntity; constexpr int32 OperationIndex = static_cast(Operation); FMassFragmentBitSet FragmentOverlap; FMassTagBitSet TagOverlap; for (FMassArchetypeEntityCollection Collection : EntityCollections) { checkfSlow(Collection.IsUpToDate(), TEXT("Out-of-date FMassArchetypeEntityCollection used. Stored information is unreliable.")); const FMassArchetypeCompositionDescriptor& ArchetypeComposition = EntityManager.GetArchetypeComposition(Collection.GetArchetype()); FragmentOverlap += ArchetypeComposition.GetFragments(); TagOverlap += ArchetypeComposition.GetTags(); } FragmentOverlap = ObservedFragments[OperationIndex].GetOverlap(FragmentOverlap); TagOverlap = ObservedTags[OperationIndex].GetOverlap(TagOverlap); const bool bHasFragmentsOverlap = !FragmentOverlap.IsEmpty(); const bool bHasTagsOverlap = !TagOverlap.IsEmpty(); if (bHasFragmentsOverlap || bHasTagsOverlap) { TArray> ObservedTypesOverlap; if (bHasFragmentsOverlap) { FragmentOverlap.ExportTypes(ObservedTypesOverlap); HandleElementsImpl(ProcessingContext, EntityCollections, {Operation, ObservedTypesOverlap}, FragmentObservers[OperationIndex]); } if (bHasTagsOverlap) { ObservedTypesOverlap.Reset(); TagOverlap.ExportTypes(ObservedTypesOverlap); HandleElementsImpl(ProcessingContext, EntityCollections, {Operation, ObservedTypesOverlap}, TagObservers[OperationIndex]); } return true; } return false; } void FMassObserverManager::HandleElementsImpl(UE::Mass::FProcessingContext& ProcessingContext, TConstArrayView EntityCollections , FMassObserverExecutionContext&& ObserverContext, FMassObserversMap& HandlersContainer) { TRACE_CPUPROFILER_EVENT_SCOPE(MassObserver_HandleElementsImpl); check(ObserverContext.GetTypesInOperation().Num()); ensureMsgf(EntityCollections.Num(), TEXT("Empty collections array is unexpected at this point. Nothing bad will happen, but it's a waste of perf.")); FMassEntityManager::FScopedProcessing ProcessingScope = ProcessingContext.EntityManager->NewProcessingScope(); // @todo maybe we could make this type configurable, so that project-specific code can extend it? FMassObserverExecutionContext& RuntimeObserverContext = ProcessingContext.AuxData.InitializeAs(MoveTemp(ObserverContext)); for (UE::Mass::ObserverManager::FObserverContextIterator ContextIterator(RuntimeObserverContext); ContextIterator; ++ContextIterator) { FMassRuntimePipeline& Pipeline = (*HandlersContainer).FindChecked(RuntimeObserverContext.GetCurrentType()); UE::Mass::Executor::RunProcessorsView(Pipeline.GetMutableProcessors(), ProcessingContext, EntityCollections); } } void FMassObserverManager::AddObserverInstance(TNotNull ElementType, const EMassObservedOperationFlags OperationFlags, TNotNull ObserverProcessor) { const bool bIsFragment = UE::Mass::IsA(ElementType); checkSlow(bIsFragment || UE::Mass::IsA(ElementType)); FMassObserversMap* ObserversMap = bIsFragment ? FragmentObservers : TagObservers; bool bInitializeCalled = false; for (uint8 OperationIndex = 0; OperationIndex < static_cast(EMassObservedOperation::MAX); ++OperationIndex) { if (!!(OperationFlags & static_cast(1 << OperationIndex))) { if (bIsFragment) { ObservedFragments[OperationIndex].Add(*ElementType); } else { ObservedTags[OperationIndex].Add(*ElementType); } FMassRuntimePipeline& Pipeline = (*ObserversMap[OperationIndex]).FindOrAdd(ElementType); if (Pipeline.AppendUniqueProcessor(*ObserverProcessor) && !bInitializeCalled) { if (UObject* Owner = EntityManager.GetOwner()) { ObserverProcessor->CallInitialize(Owner, EntityManager.AsShared()); bInitializeCalled = true; } Pipeline.SortByExecutionPriority(); } } } } void FMassObserverManager::AddObserverInstance(const UScriptStruct& ElementType, const EMassObservedOperation Operation, UMassProcessor& ObserverProcessor) { ensureAlways(Operation < EMassObservedOperation::MAX); check(Operation != EMassObservedOperation::MAX); EMassObservedOperationFlags OperationFlag = (Operation < EMassObservedOperation::MAX) ? static_cast(1 << static_cast(Operation)) : (Operation == EMassObservedOperation::Add ? EMassObservedOperationFlags::Add : EMassObservedOperationFlags::Remove); AddObserverInstance(&ElementType, OperationFlag, &ObserverProcessor); } void FMassObserverManager::AddObserverInstance(TNotNull ObserverProcessor) { AddObserverInstance(ObserverProcessor->GetObservedTypeChecked(), ObserverProcessor->GetObservedOperations(), ObserverProcessor); } void FMassObserverManager::RemoveObserverInstance(TNotNull ElementType, const EMassObservedOperationFlags OperationFlags, TNotNull ObserverProcessor) { const bool bIsFragment = UE::Mass::IsA(ElementType); if (!ensure(bIsFragment || UE::Mass::IsA(ElementType))) { return; } FMassObserversMap* ObserversMap = bIsFragment ? FragmentObservers : TagObservers; for (uint8 OperationIndex = 0; OperationIndex < static_cast(EMassObservedOperation::MAX); ++OperationIndex) { if (!!(OperationFlags & static_cast(1 << OperationIndex))) { FMassRuntimePipeline* Pipeline = (*ObserversMap[OperationIndex]).Find(ElementType); if (ensureMsgf(Pipeline, TEXT("Trying to remove an observer for a fragment/tag that does not seem to be observed."))) { Pipeline->RemoveProcessor(*ObserverProcessor); if (Pipeline->Num() == 0) { (*ObserversMap[OperationIndex]).Remove(ElementType); if (bIsFragment) { ObservedFragments[OperationIndex].Remove(*ElementType); } else { ObservedTags[OperationIndex].Remove(*ElementType); } } } } } } void FMassObserverManager::RemoveObserverInstance(const UScriptStruct& ElementType, const EMassObservedOperation Operation, UMassProcessor& ObserverProcessor) { ensureAlways(Operation < EMassObservedOperation::MAX); check(Operation != EMassObservedOperation::MAX); EMassObservedOperationFlags OperationFlag = (Operation < EMassObservedOperation::MAX) ? static_cast(1 << static_cast(Operation)) : (Operation == EMassObservedOperation::Add ? EMassObservedOperationFlags::Add : EMassObservedOperationFlags::Remove); RemoveObserverInstance(&ElementType, OperationFlag, &ObserverProcessor); } TSharedRef FMassObserverManager::GetOrMakeObserverLock() { if (ActiveObserverLock.IsValid()) { return ActiveObserverLock.Pin().ToSharedRef(); } else { FObserverLock* ObserverLock = new FObserverLock(*this); TSharedRef SharedContext = MakeShareable(ObserverLock); ActiveObserverLock = SharedContext; return SharedContext; } } TSharedRef FMassObserverManager::GetOrMakeCreationContext() { if (ActiveCreationContext.IsValid()) { return ActiveCreationContext.Pin().ToSharedRef(); } else { FCreationContext* ObserverLock = new FCreationContext(GetOrMakeObserverLock()); #if WITH_MASSENTITY_DEBUG ObserverLock->CreationHandle.SerialNumber = LockedNotificationSerialNumber; #endif // WITH_MASSENTITY_DEBUG ObserverLock->CreationHandle.OpIndex = ObserverLock->Lock->AddCreatedEntities({}); TSharedRef SharedContext = MakeShareable(ObserverLock); ActiveCreationContext = SharedContext; return SharedContext; } } TSharedRef FMassObserverManager::GetOrMakeCreationContext(TConstArrayView ReservedEntities , FMassArchetypeEntityCollection&& EntityCollection) { if (TSharedPtr CreationContext = ActiveCreationContext.Pin()) { CreationContext->GetObserverLock()->AddCreatedEntities(ReservedEntities, Forward(EntityCollection)); return CreationContext.ToSharedRef(); } else { FCreationContext* ObserverLock = new FCreationContext(GetOrMakeObserverLock()); #if WITH_MASSENTITY_DEBUG ObserverLock->CreationHandle.SerialNumber = LockedNotificationSerialNumber; #endif // WITH_MASSENTITY_DEBUG ObserverLock->CreationHandle.OpIndex = ObserverLock->Lock->AddCreatedEntities(ReservedEntities, Forward(EntityCollection)); TSharedRef SharedContext = MakeShareable(ObserverLock); ActiveCreationContext = SharedContext; return SharedContext; } } void FMassObserverManager::OnPostFork(EForkProcessRole) { if (TSharedPtr ActiveContext = ActiveObserverLock.Pin()) { ActiveContext->ForceUpdateCurrentThreadID(); } } void FMassObserverManager::OnModulePackagesUnloaded(TConstArrayView Packages) { TRACE_CPUPROFILER_EVENT_SCOPE(FMassObserverManager::OnModulePackagesUnloaded); const auto ProcessObserversMap = [&Packages](FMassObserversMap& ObserversMap) { for (auto MapIt = (*ObserversMap).CreateIterator(); MapIt; ++MapIt) { TObjectPtr ObserverClass = MapIt->Key; const UPackage* ObserverPackage = ObserverClass->GetPackage(); if (Packages.Contains(ObserverPackage)) { UE_LOG(LogMass, Verbose, TEXT("%hs: removed observer %s (%s)"), __FUNCTION__, *ObserverClass->GetName(), *ObserverPackage->GetName()); MapIt.RemoveCurrent(); } } }; for (int32 OperationIndex = 0; OperationIndex < static_cast(EMassObservedOperation::MAX); ++OperationIndex) { ProcessObserversMap(FragmentObservers[OperationIndex]); ProcessObserversMap(TagObservers[OperationIndex]); } } void FMassObserverManager::ResumeExecution(FObserverLock& LockBeingReleased) { using namespace UE::Mass::ObserverManager; ensureMsgf(LocksCount == 0, TEXT("We only expect this function to be called if all locks are released.")); #if WITH_MASSENTITY_DEBUG ensureMsgf(LockBeingReleased.LockSerialNumber == LockedNotificationSerialNumber , TEXT("Lock's and ObserverManager's lock serial numbers are expected to match.")); ++LockedNotificationSerialNumber; #endif // WITH_MASSENTITY_DEBUG if (LockBeingReleased.BufferedNotifications.IsEmpty() == false) { TRACE_CPUPROFILER_EVENT_SCOPE(MassObserver_ResumeExecution); UE::Mass::FProcessingContext ProcessingContext(EntityManager); FNotificationContext NotificationContext{*this, ProcessingContext}; FBufferedNotificationExecutioner RegularOperationNotifier(NotificationContext); FBufferedCreationNotificationExecutioner CreationOpExecutioner(NotificationContext); for (FBufferedNotification& Op : LockBeingReleased.BufferedNotifications) { if (Op.Type == EMassObservedOperation::CreateEntity) { Visit(CreationOpExecutioner, Op.AffectedEntities); } else { RegularOperationNotifier.OpType = Op.Type; Visit(RegularOperationNotifier, Op.CompositionChange, Op.AffectedEntities); } } #if WITH_MASSENTITY_DEBUG ++DebugNonTrivialResumeExecutionCount; #endif // WITH_MASSENTITY_DEBUG } } void FMassObserverManager::ReleaseCreationHandle(FCreationNotificationHandle InCreationNotificationHandle) { ensureMsgf(InCreationNotificationHandle.IsSet(), TEXT("Invalid creation handle passed to %hs"), __FUNCTION__); #if WITH_MASSENTITY_DEBUG ensureMsgf(InCreationNotificationHandle.SerialNumber == LockedNotificationSerialNumber , TEXT("Creation handle's serial number doesn't match the ObserverManager's data")); #endif // WITH_MASSENTITY_DEBUG TSharedPtr LockInstance = ActiveObserverLock.Pin(); if (ensure(LockInstance)) { ensure(LockInstance->ReleaseCreationNotification(InCreationNotificationHandle)); ensure(ActiveCreationContext.IsValid() == false); } } //----------------------------------------------------------------------// // DEPRECATED //----------------------------------------------------------------------// bool FMassObserverManager::OnPostEntitiesCreated(UE::Mass::FProcessingContext&, const FMassArchetypeEntityCollection& EntityCollection) { return OnPostEntitiesCreated(EntityCollection); } bool FMassObserverManager::OnPostEntitiesCreated(UE::Mass::FProcessingContext&, TConstArrayView EntityCollections) { TRACE_CPUPROFILER_EVENT_SCOPE_STR("OnPostEntitiesCreated") bool bReturnValue = false; for (const FMassArchetypeEntityCollection& Collection : EntityCollections) { const FMassArchetypeCompositionDescriptor& ArchetypeComposition = EntityManager.GetArchetypeComposition(Collection.GetArchetype()); bReturnValue |= OnCompositionChanged(Collection, ArchetypeComposition, EMassObservedOperation::CreateEntity); } return bReturnValue; } bool FMassObserverManager::OnCompositionChanged(UE::Mass::FProcessingContext&, const FMassArchetypeEntityCollection& EntityCollection , const FMassArchetypeCompositionDescriptor& CompositionDelta, const EMassObservedOperation InOperation) { return OnCompositionChanged(EntityCollection, CompositionDelta, InOperation); } void FMassObserverManager::HandleSingleEntityImpl(const UScriptStruct& FragmentType, const FMassArchetypeEntityCollection& EntityCollection, FMassObserversMap& HandlersContainer) { UE::Mass::ObserverManager::FDeprecationHelper::HandleSingleElement(this, FragmentType, EntityCollection, HandlersContainer); } void FMassObserverManager::OnPostFragmentOrTagAdded(const UScriptStruct& FragmentOrTagType, const FMassArchetypeEntityCollection& EntityCollection) { UE::Mass::ObserverManager::FDeprecationHelper::HandleSingleElement(this, FragmentOrTagType, EntityCollection, EMassObservedOperation::AddElement); } void FMassObserverManager::OnPreFragmentOrTagRemoved(const UScriptStruct& FragmentOrTagType, const FMassArchetypeEntityCollection& EntityCollection) { UE::Mass::ObserverManager::FDeprecationHelper::HandleSingleElement(this, FragmentOrTagType, EntityCollection, EMassObservedOperation::RemoveElement); } void FMassObserverManager::OnFragmentOrTagOperation(const UScriptStruct& FragmentOrTagType, const FMassArchetypeEntityCollection& EntityCollection, const EMassObservedOperation Operation) { UE::Mass::ObserverManager::FDeprecationHelper::HandleSingleElement(this, FragmentOrTagType, EntityCollection, Operation); } TConstArrayView FMassObserverManager::FCreationContext::GetEntityCollections() const { return {}; } int32 FMassObserverManager::FCreationContext::GetSpawnedNum() const { return 0; } bool FMassObserverManager::FCreationContext::IsDirty() const { return true; } void FMassObserverManager::FCreationContext::AppendEntities(const TConstArrayView) { } void FMassObserverManager::FCreationContext::AppendEntities(const TConstArrayView, FMassArchetypeEntityCollection&&) { } FMassObserverManager::FCreationContext::FCreationContext(const int32) : FCreationContext() {} const FMassArchetypeEntityCollection& FMassObserverManager::FCreationContext::GetEntityCollection() const { static FMassArchetypeEntityCollection DummyInstance; return DummyInstance; } void FMassObserverManager::HandleElementsImpl(UE::Mass::FProcessingContext& ProcessingContext, TConstArrayView EntityCollections , TArrayView ObservedTypes, FMassObserversMap& HandlersContainer) { if (ObservedTypes.Num() == 0 || ObservedTypes[0] == nullptr) { return; } // Legacy support: we need to figure out which operation this is // and to do that we need to know whether we're handling fragments or tags const bool bIsFragment = UE::Mass::IsA(ObservedTypes[0]); check(bIsFragment || UE::Mass::IsA(ObservedTypes[0])); using UE::Mass::ObserverManager::FDeprecationHelper; const EMassObservedOperation Operation = bIsFragment ? FDeprecationHelper::DetermineOperationType(&HandlersContainer, ProcessingContext.GetEntityManager()->GetObserverManager().FragmentObservers) : FDeprecationHelper::DetermineOperationType(&HandlersContainer, ProcessingContext.GetEntityManager()->GetObserverManager().TagObservers); checkf(Operation < EMassObservedOperation::MAX, TEXT("Unable to determine the legacy operation type")); HandleElementsImpl(ProcessingContext, EntityCollections, {Operation, ObservedTypes}, HandlersContainer); } void FMassObserverManager::HandleFragmentsImpl(UE::Mass::FProcessingContext& ProcessingContext, const FMassArchetypeEntityCollection& EntityCollection , TArrayView ObservedTypes, FMassObserversMap& HandlersContainer) { PRAGMA_DISABLE_DEPRECATION_WARNINGS HandleElementsImpl(ProcessingContext, {EntityCollection}, ObservedTypes, HandlersContainer); PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool FMassObserverManager::OnCollectionsCreatedImpl(UE::Mass::FProcessingContext& ProcessingContext, TArray&& EntityCollections) { return OnCollectionsCreatedImpl(ProcessingContext, MakeArrayView(EntityCollections)); }