// Copyright Epic Games, Inc. All Rights Reserved. #include "UIExtensionSystem.h" #include "Blueprint/UserWidget.h" #include "Containers/UnrealString.h" #include "HAL/Platform.h" #include "LogUIExtension.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "Logging/LogVerbosity.h" #include "Misc/AssertionMacros.h" #include "Templates/Casts.h" #include "Templates/UnrealTemplate.h" #include "Trace/Detail/Channel.h" #include "UObject/Stack.h" #include "UObject/UObjectBaseUtility.h" class FSubsystemCollectionBase; //========================================================= void FUIExtensionPointHandle::Unregister() { if (UUIExtensionSubsystem* ExtensionSourcePtr = ExtensionSource.Get()) { ExtensionSourcePtr->UnregisterExtensionPoint(*this); } } //========================================================= void FUIExtensionHandle::Unregister() { if (UUIExtensionSubsystem* ExtensionSourcePtr = ExtensionSource.Get()) { ExtensionSourcePtr->UnregisterExtension(*this); } } //========================================================= bool FUIExtensionPoint::DoesExtensionPassContract(const FUIExtension* Extension) const { if (UObject* DataPtr = Extension->Data) { const bool bMatchesContext = (ContextObject.IsExplicitlyNull() && Extension->ContextObject.IsExplicitlyNull()) || ContextObject == Extension->ContextObject; // Make sure the contexts match. if (bMatchesContext) { // The data can either be the literal class of the data type, or a instance of the class type. const UClass* DataClass = DataPtr->IsA(UClass::StaticClass()) ? Cast(DataPtr) : DataPtr->GetClass(); for (const UClass* AllowedDataClass : AllowedDataClasses) { if (DataClass->IsChildOf(AllowedDataClass) || DataClass->ImplementsInterface(AllowedDataClass)) { return true; } } } } return false; } //========================================================= void UUIExtensionSubsystem::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) { if (UUIExtensionSubsystem* ExtensionSubsystem = Cast(InThis)) { for (auto MapIt = ExtensionSubsystem->ExtensionPointMap.CreateIterator(); MapIt; ++MapIt) { for (const TSharedPtr& ValueElement : MapIt.Value()) { Collector.AddReferencedObjects(ValueElement->AllowedDataClasses); } } for (auto MapIt = ExtensionSubsystem->ExtensionMap.CreateIterator(); MapIt; ++MapIt) { for (const TSharedPtr& ValueElement : MapIt.Value()) { Collector.AddReferencedObject(ValueElement->Data); } } } } void UUIExtensionSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); } void UUIExtensionSubsystem::Deinitialize() { Super::Deinitialize(); } FUIExtensionPointHandle UUIExtensionSubsystem::RegisterExtensionPoint(const FGameplayTag& ExtensionPointTag, EUIExtensionPointMatch ExtensionPointTagMatchType, const TArray& AllowedDataClasses, FExtendExtensionPointDelegate ExtensionCallback) { return RegisterExtensionPointForContext(ExtensionPointTag, nullptr, ExtensionPointTagMatchType, AllowedDataClasses, ExtensionCallback); } FUIExtensionPointHandle UUIExtensionSubsystem::RegisterExtensionPointForContext(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, EUIExtensionPointMatch ExtensionPointTagMatchType, const TArray& AllowedDataClasses, FExtendExtensionPointDelegate ExtensionCallback) { if (!ExtensionPointTag.IsValid()) { UE_LOG(LogUIExtension, Warning, TEXT("Trying to register an invalid extension point.")); return FUIExtensionPointHandle(); } if (!ExtensionCallback.IsBound()) { UE_LOG(LogUIExtension, Warning, TEXT("Trying to register an invalid extension point.")); return FUIExtensionPointHandle(); } if (AllowedDataClasses.Num() == 0) { UE_LOG(LogUIExtension, Warning, TEXT("Trying to register an invalid extension point.")); return FUIExtensionPointHandle(); } FExtensionPointList& List = ExtensionPointMap.FindOrAdd(ExtensionPointTag); TSharedPtr& Entry = List.Add_GetRef(MakeShared()); Entry->ExtensionPointTag = ExtensionPointTag; Entry->ContextObject = ContextObject; Entry->ExtensionPointTagMatchType = ExtensionPointTagMatchType; Entry->AllowedDataClasses = AllowedDataClasses; Entry->Callback = MoveTemp(ExtensionCallback); UE_LOG(LogUIExtension, Verbose, TEXT("Extension Point [%s] Registered"), *ExtensionPointTag.ToString()); NotifyExtensionPointOfExtensions(Entry); return FUIExtensionPointHandle(this, Entry); } FUIExtensionHandle UUIExtensionSubsystem::RegisterExtensionAsWidget(const FGameplayTag& ExtensionPointTag, TSubclassOf WidgetClass, int32 Priority) { return RegisterExtensionAsData(ExtensionPointTag, nullptr, WidgetClass, Priority); } FUIExtensionHandle UUIExtensionSubsystem::RegisterExtensionAsWidgetForContext(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, TSubclassOf WidgetClass, int32 Priority) { return RegisterExtensionAsData(ExtensionPointTag, ContextObject, WidgetClass, Priority); } FUIExtensionHandle UUIExtensionSubsystem::RegisterExtensionAsData(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, UObject* Data, int32 Priority) { if (!ExtensionPointTag.IsValid()) { UE_LOG(LogUIExtension, Warning, TEXT("Trying to register an invalid extension.")); return FUIExtensionHandle(); } if (!Data) { UE_LOG(LogUIExtension, Warning, TEXT("Trying to register an invalid extension.")); return FUIExtensionHandle(); } FExtensionList& List = ExtensionMap.FindOrAdd(ExtensionPointTag); TSharedPtr& Entry = List.Add_GetRef(MakeShared()); Entry->ExtensionPointTag = ExtensionPointTag; Entry->ContextObject = ContextObject; Entry->Data = Data; Entry->Priority = Priority; if (ContextObject) { UE_LOG(LogUIExtension, Verbose, TEXT("Extension [%s] @ [%s] Registered"), *GetNameSafe(Data), *ExtensionPointTag.ToString()); } else { UE_LOG(LogUIExtension, Verbose, TEXT("Extension [%s] for [%s] @ [%s] Registered"), *GetNameSafe(Data), *GetNameSafe(ContextObject), *ExtensionPointTag.ToString()); } NotifyExtensionPointsOfExtension(EUIExtensionAction::Added, Entry); return FUIExtensionHandle(this, Entry); } void UUIExtensionSubsystem::NotifyExtensionPointOfExtensions(TSharedPtr& ExtensionPoint) { for (FGameplayTag Tag = ExtensionPoint->ExtensionPointTag; Tag.IsValid(); Tag = Tag.RequestDirectParent()) { if (const FExtensionList* ListPtr = ExtensionMap.Find(Tag)) { // Copy in case there are removals while handling callbacks FExtensionList ExtensionArray(*ListPtr); for (const TSharedPtr& Extension : ExtensionArray) { if (ExtensionPoint->DoesExtensionPassContract(Extension.Get())) { FUIExtensionRequest Request = CreateExtensionRequest(Extension); ExtensionPoint->Callback.ExecuteIfBound(EUIExtensionAction::Added, Request); } } } if (ExtensionPoint->ExtensionPointTagMatchType == EUIExtensionPointMatch::ExactMatch) { break; } } } void UUIExtensionSubsystem::NotifyExtensionPointsOfExtension(EUIExtensionAction Action, TSharedPtr& Extension) { bool bOnInitialTag = true; for (FGameplayTag Tag = Extension->ExtensionPointTag; Tag.IsValid(); Tag = Tag.RequestDirectParent()) { if (const FExtensionPointList* ListPtr = ExtensionPointMap.Find(Tag)) { // Copy in case there are removals while handling callbacks FExtensionPointList ExtensionPointArray(*ListPtr); for (const TSharedPtr& ExtensionPoint : ExtensionPointArray) { if (bOnInitialTag || (ExtensionPoint->ExtensionPointTagMatchType == EUIExtensionPointMatch::PartialMatch)) { if (ExtensionPoint->DoesExtensionPassContract(Extension.Get())) { FUIExtensionRequest Request = CreateExtensionRequest(Extension); ExtensionPoint->Callback.ExecuteIfBound(Action, Request); } } } } bOnInitialTag = false; } } void UUIExtensionSubsystem::UnregisterExtension(const FUIExtensionHandle& ExtensionHandle) { if (ExtensionHandle.IsValid()) { checkf(ExtensionHandle.ExtensionSource == this, TEXT("Trying to unregister an extension that's not from this extension subsystem.")); TSharedPtr Extension = ExtensionHandle.DataPtr; if (FExtensionList* ListPtr = ExtensionMap.Find(Extension->ExtensionPointTag)) { if (Extension->ContextObject.IsExplicitlyNull()) { UE_LOG(LogUIExtension, Verbose, TEXT("Extension [%s] @ [%s] Unregistered"), *GetNameSafe(Extension->Data), *Extension->ExtensionPointTag.ToString()); } else { UE_LOG(LogUIExtension, Verbose, TEXT("Extension [%s] for [%s] @ [%s] Unregistered"), *GetNameSafe(Extension->Data), *GetNameSafe(Extension->ContextObject.Get()), *Extension->ExtensionPointTag.ToString()); } NotifyExtensionPointsOfExtension(EUIExtensionAction::Removed, Extension); ListPtr->RemoveSwap(Extension); if (ListPtr->Num() == 0) { ExtensionMap.Remove(Extension->ExtensionPointTag); } } } else { UE_LOG(LogUIExtension, Warning, TEXT("Trying to unregister an invalid Handle.")); } } void UUIExtensionSubsystem::UnregisterExtensionPoint(const FUIExtensionPointHandle& ExtensionPointHandle) { if (ExtensionPointHandle.IsValid()) { check(ExtensionPointHandle.ExtensionSource == this); const TSharedPtr ExtensionPoint = ExtensionPointHandle.DataPtr; if (FExtensionPointList* ListPtr = ExtensionPointMap.Find(ExtensionPoint->ExtensionPointTag)) { UE_LOG(LogUIExtension, Verbose, TEXT("Extension Point [%s] Unregistered"), *ExtensionPoint->ExtensionPointTag.ToString()); ListPtr->RemoveSwap(ExtensionPoint); if (ListPtr->Num() == 0) { ExtensionPointMap.Remove(ExtensionPoint->ExtensionPointTag); } } } else { UE_LOG(LogUIExtension, Warning, TEXT("Trying to unregister an invalid Handle.")); } } FUIExtensionRequest UUIExtensionSubsystem::CreateExtensionRequest(const TSharedPtr& Extension) { FUIExtensionRequest Request; Request.ExtensionHandle = FUIExtensionHandle(this, Extension); Request.ExtensionPointTag = Extension->ExtensionPointTag; Request.Priority = Extension->Priority; Request.Data = Extension->Data; Request.ContextObject = Extension->ContextObject.Get(); return Request; } FUIExtensionPointHandle UUIExtensionSubsystem::K2_RegisterExtensionPoint(FGameplayTag ExtensionPointTag, EUIExtensionPointMatch ExtensionPointTagMatchType, const TArray& AllowedDataClasses, FExtendExtensionPointDynamicDelegate ExtensionCallback) { return RegisterExtensionPoint(ExtensionPointTag, ExtensionPointTagMatchType, AllowedDataClasses, FExtendExtensionPointDelegate::CreateWeakLambda(ExtensionCallback.GetUObject(), [this, ExtensionCallback](EUIExtensionAction Action, const FUIExtensionRequest& Request) { ExtensionCallback.ExecuteIfBound(Action, Request); })); } FUIExtensionHandle UUIExtensionSubsystem::K2_RegisterExtensionAsWidget(FGameplayTag ExtensionPointTag, TSubclassOf WidgetClass, int32 Priority) { return RegisterExtensionAsWidget(ExtensionPointTag, WidgetClass, Priority); } FUIExtensionHandle UUIExtensionSubsystem::K2_RegisterExtensionAsWidgetForContext(FGameplayTag ExtensionPointTag, TSubclassOf WidgetClass, UObject* ContextObject, int32 Priority) { if (ContextObject) { return RegisterExtensionAsWidgetForContext(ExtensionPointTag, ContextObject, WidgetClass, Priority); } else { FFrame::KismetExecutionMessage(TEXT("A null ContextObject was passed to Register Extension (Widget For Context)"), ELogVerbosity::Error); return FUIExtensionHandle(); } } FUIExtensionHandle UUIExtensionSubsystem::K2_RegisterExtensionAsData(FGameplayTag ExtensionPointTag, UObject* Data, int32 Priority) { return RegisterExtensionAsData(ExtensionPointTag, nullptr, Data, Priority); } FUIExtensionHandle UUIExtensionSubsystem::K2_RegisterExtensionAsDataForContext(FGameplayTag ExtensionPointTag, UObject* ContextObject, UObject* Data, int32 Priority) { if (ContextObject) { return RegisterExtensionAsData(ExtensionPointTag, ContextObject, Data, Priority); } else { FFrame::KismetExecutionMessage(TEXT("A null ContextObject was passed to Register Extension (Data For Context)"), ELogVerbosity::Error); return FUIExtensionHandle(); } } //========================================================= void UUIExtensionHandleFunctions::Unregister(FUIExtensionHandle& Handle) { Handle.Unregister(); } bool UUIExtensionHandleFunctions::IsValid(FUIExtensionHandle& Handle) { return Handle.IsValid(); } //========================================================= void UUIExtensionPointHandleFunctions::Unregister(FUIExtensionPointHandle& Handle) { Handle.Unregister(); } bool UUIExtensionPointHandleFunctions::IsValid(FUIExtensionPointHandle& Handle) { return Handle.IsValid(); }