Files
PHY/Plugins/GGS/Source/GenericUISystem/Private/UIExtension/GUIS_GameUIExtensionSubsystem.cpp
2026-03-03 01:23:02 +08:00

415 lines
14 KiB
C++

// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "UIExtension/GUIS_GameUIExtensionSubsystem.h"
#include "GUIS_LogChannels.h"
#include "Blueprint/UserWidget.h"
#include "UObject/Stack.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_GameUIExtensionSubsystem)
class FSubsystemCollectionBase;
//=========================================================
void FGUIS_GameUIExtPointHandle::Unregister()
{
if (UGUIS_ExtensionSubsystem* ExtensionSourcePtr = ExtensionSource.Get())
{
ExtensionSourcePtr->UnregisterExtensionPoint(*this);
ExtensionSource = nullptr;
DataPtr.Reset();
}
}
//=========================================================
FGUIS_GameUIExtHandle::FGUIS_GameUIExtHandle()
{
}
FGUIS_GameUIExtHandle::FGUIS_GameUIExtHandle(UGUIS_ExtensionSubsystem* InExtensionSource, const TSharedPtr<FGUIS_GameUIExt>& InDataPtr)
{
ExtensionSource = InExtensionSource;
DataPtr = InDataPtr;
}
void FGUIS_GameUIExtHandle::Unregister()
{
if (UGUIS_ExtensionSubsystem* ExtensionSourcePtr = ExtensionSource.Get())
{
ExtensionSourcePtr->UnregisterExtension(*this);
ExtensionSource = nullptr;
DataPtr.Reset();
}
}
//=========================================================
bool FGUIS_GameUIExtPoint::DoesExtensionPassContract(const FGUIS_GameUIExt* 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<UClass>(DataPtr) : DataPtr->GetClass();
for (const UClass* AllowedDataClass : AllowedDataClasses)
{
if (DataClass->IsChildOf(AllowedDataClass) || DataClass->ImplementsInterface(AllowedDataClass))
{
return true;
}
}
}
}
return false;
}
FGUIS_GameUIExtPointHandle::FGUIS_GameUIExtPointHandle()
{
}
FGUIS_GameUIExtPointHandle::FGUIS_GameUIExtPointHandle(UGUIS_ExtensionSubsystem* InExtensionSource, const TSharedPtr<FGUIS_GameUIExtPoint>& InDataPtr)
{
ExtensionSource = InExtensionSource;
DataPtr = InDataPtr;
}
//=========================================================
void UGUIS_ExtensionSubsystem::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
{
Super::AddReferencedObjects(InThis, Collector);
if (UGUIS_ExtensionSubsystem* ExtensionSubsystem = Cast<UGUIS_ExtensionSubsystem>(InThis))
{
for (auto MapIt = ExtensionSubsystem->ExtensionPointMap.CreateIterator(); MapIt; ++MapIt)
{
for (const TSharedPtr<FGUIS_GameUIExtPoint>& ValueElement : MapIt.Value())
{
Collector.AddReferencedObjects(ValueElement->AllowedDataClasses);
}
}
for (auto MapIt = ExtensionSubsystem->ExtensionMap.CreateIterator(); MapIt; ++MapIt)
{
for (const TSharedPtr<FGUIS_GameUIExt>& ValueElement : MapIt.Value())
{
Collector.AddReferencedObject(ValueElement->Data);
}
}
}
}
void UGUIS_ExtensionSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
}
void UGUIS_ExtensionSubsystem::Deinitialize()
{
Super::Deinitialize();
}
FGUIS_GameUIExtPointHandle UGUIS_ExtensionSubsystem::RegisterExtensionPoint(const FGameplayTag& ExtensionPointTag, EGUIS_GameUIExtPointMatchType ExtensionPointTagMatchType,
const TArray<UClass*>& AllowedDataClasses, FExtendExtensionPointDelegate ExtensionCallback)
{
return RegisterExtensionPointForContext(ExtensionPointTag, nullptr, ExtensionPointTagMatchType, AllowedDataClasses, ExtensionCallback);
}
FGUIS_GameUIExtPointHandle UGUIS_ExtensionSubsystem::RegisterExtensionPointForContext(const FGameplayTag& ExtensionPointTag, UObject* ContextObject,
EGUIS_GameUIExtPointMatchType ExtensionPointTagMatchType,
const TArray<UClass*>& AllowedDataClasses, FExtendExtensionPointDelegate ExtensionCallback)
{
if (!ExtensionPointTag.IsValid())
{
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to register an invalid extension point."));
return FGUIS_GameUIExtPointHandle();
}
if (!ExtensionCallback.IsBound())
{
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to register an invalid extension point."));
return FGUIS_GameUIExtPointHandle();
}
if (AllowedDataClasses.Num() == 0)
{
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to register an invalid extension point."));
return FGUIS_GameUIExtPointHandle();
}
FExtensionPointList& List = ExtensionPointMap.FindOrAdd(ExtensionPointTag);
TSharedPtr<FGUIS_GameUIExtPoint>& Entry = List.Add_GetRef(MakeShared<FGUIS_GameUIExtPoint>());
Entry->ExtensionPointTag = ExtensionPointTag;
Entry->ContextObject = ContextObject;
Entry->ExtensionPointTagMatchType = ExtensionPointTagMatchType;
Entry->AllowedDataClasses = AllowedDataClasses;
Entry->Callback = MoveTemp(ExtensionCallback);
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension Point [%s] Registered"), *ExtensionPointTag.ToString());
NotifyExtensionPointOfExtensions(Entry);
return FGUIS_GameUIExtPointHandle(this, Entry);
}
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::RegisterExtensionAsWidget(const FGameplayTag& ExtensionPointTag, TSubclassOf<UUserWidget> WidgetClass, int32 Priority)
{
return RegisterExtensionAsData(ExtensionPointTag, nullptr, WidgetClass, Priority);
}
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::RegisterExtensionAsWidgetForContext(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, TSubclassOf<UUserWidget> WidgetClass, int32 Priority)
{
return RegisterExtensionAsData(ExtensionPointTag, ContextObject, WidgetClass, Priority);
}
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::RegisterExtensionAsData(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, UObject* Data, int32 Priority)
{
if (!ExtensionPointTag.IsValid())
{
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to register an invalid extension."));
return FGUIS_GameUIExtHandle();
}
if (!Data)
{
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to register an invalid extension."));
return FGUIS_GameUIExtHandle();
}
FExtensionList& List = ExtensionMap.FindOrAdd(ExtensionPointTag);
TSharedPtr<FGUIS_GameUIExt>& Entry = List.Add_GetRef(MakeShared<FGUIS_GameUIExt>());
Entry->ExtensionPointTag = ExtensionPointTag;
Entry->ContextObject = ContextObject;
Entry->Data = Data;
Entry->Priority = Priority;
if (ContextObject)
{
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension [%s] @ [%s] Registered"), *GetNameSafe(Data), *ExtensionPointTag.ToString());
}
else
{
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension [%s] for [%s] @ [%s] Registered"), *GetNameSafe(Data), *GetNameSafe(ContextObject), *ExtensionPointTag.ToString());
}
NotifyExtensionPointsOfExtension(EGUIS_GameUIExtAction::Added, Entry);
return FGUIS_GameUIExtHandle(this, Entry);
}
void UGUIS_ExtensionSubsystem::NotifyExtensionPointOfExtensions(TSharedPtr<FGUIS_GameUIExtPoint>& 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<FGUIS_GameUIExt>& Extension : ExtensionArray)
{
if (ExtensionPoint->DoesExtensionPassContract(Extension.Get()))
{
FGUIS_GameUIExtRequest Request = CreateExtensionRequest(Extension);
ExtensionPoint->Callback.ExecuteIfBound(EGUIS_GameUIExtAction::Added, Request);
}
}
}
if (ExtensionPoint->ExtensionPointTagMatchType == EGUIS_GameUIExtPointMatchType::ExactMatch)
{
break;
}
}
}
void UGUIS_ExtensionSubsystem::NotifyExtensionPointsOfExtension(EGUIS_GameUIExtAction Action, TSharedPtr<FGUIS_GameUIExt>& 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<FGUIS_GameUIExtPoint>& ExtensionPoint : ExtensionPointArray)
{
if (bOnInitialTag || (ExtensionPoint->ExtensionPointTagMatchType == EGUIS_GameUIExtPointMatchType::PartialMatch))
{
if (ExtensionPoint->DoesExtensionPassContract(Extension.Get()))
{
FGUIS_GameUIExtRequest Request = CreateExtensionRequest(Extension);
ExtensionPoint->Callback.ExecuteIfBound(Action, Request);
}
}
}
}
bOnInitialTag = false;
}
}
void UGUIS_ExtensionSubsystem::UnregisterExtension(const FGUIS_GameUIExtHandle& ExtensionHandle)
{
if (ExtensionHandle.IsValid())
{
checkf(ExtensionHandle.ExtensionSource == this, TEXT("Trying to unregister an extension that's not from this extension subsystem."));
TSharedPtr<FGUIS_GameUIExt> Extension = ExtensionHandle.DataPtr;
if (FExtensionList* ListPtr = ExtensionMap.Find(Extension->ExtensionPointTag))
{
if (Extension->ContextObject.IsExplicitlyNull())
{
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension [%s] @ [%s] Unregistered"), *GetNameSafe(Extension->Data), *Extension->ExtensionPointTag.ToString());
}
else
{
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension [%s] for [%s] @ [%s] Unregistered"), *GetNameSafe(Extension->Data), *GetNameSafe(Extension->ContextObject.Get()),
*Extension->ExtensionPointTag.ToString());
}
NotifyExtensionPointsOfExtension(EGUIS_GameUIExtAction::Removed, Extension);
ListPtr->RemoveSwap(Extension);
if (ListPtr->Num() == 0)
{
ExtensionMap.Remove(Extension->ExtensionPointTag);
}
}
}
else
{
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to unregister an invalid Handle."));
}
}
void UGUIS_ExtensionSubsystem::UnregisterExtensionPoint(const FGUIS_GameUIExtPointHandle& ExtensionPointHandle)
{
if (ExtensionPointHandle.IsValid())
{
check(ExtensionPointHandle.ExtensionSource == this);
const TSharedPtr<FGUIS_GameUIExtPoint> ExtensionPoint = ExtensionPointHandle.DataPtr;
if (FExtensionPointList* ListPtr = ExtensionPointMap.Find(ExtensionPoint->ExtensionPointTag))
{
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension Point [%s] Unregistered"), *ExtensionPoint->ExtensionPointTag.ToString());
ListPtr->RemoveSwap(ExtensionPoint);
if (ListPtr->Num() == 0)
{
ExtensionPointMap.Remove(ExtensionPoint->ExtensionPointTag);
}
}
}
else
{
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to unregister an invalid Handle."));
}
}
FGUIS_GameUIExtRequest UGUIS_ExtensionSubsystem::CreateExtensionRequest(const TSharedPtr<FGUIS_GameUIExt>& Extension)
{
FGUIS_GameUIExtRequest Request;
Request.ExtensionHandle = FGUIS_GameUIExtHandle(this, Extension);
Request.ExtensionPointTag = Extension->ExtensionPointTag;
Request.Priority = Extension->Priority;
Request.Data = Extension->Data;
Request.ContextObject = Extension->ContextObject.Get();
return Request;
}
FGUIS_GameUIExtPointHandle UGUIS_ExtensionSubsystem::K2_RegisterExtensionPoint(FGameplayTag ExtensionPointTag, EGUIS_GameUIExtPointMatchType ExtensionPointTagMatchType,
const TArray<TSoftClassPtr<UClass>>& AllowedDataClasses,
FExtendExtensionPointDynamicDelegate ExtensionCallback)
{
TArray<UClass*> LoadedClasses;
for (const TSoftClassPtr<UClass>& DataClass : AllowedDataClasses)
{
if (!DataClass.IsNull())
{
LoadedClasses.Add(DataClass.LoadSynchronous());
}
}
return RegisterExtensionPoint(ExtensionPointTag, ExtensionPointTagMatchType, LoadedClasses, FExtendExtensionPointDelegate::CreateWeakLambda(
ExtensionCallback.GetUObject(), [this, ExtensionCallback](EGUIS_GameUIExtAction Action, const FGUIS_GameUIExtRequest& Request)
{
ExtensionCallback.ExecuteIfBound(Action, Request);
}));
}
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::K2_RegisterExtensionAsWidget(FGameplayTag ExtensionPointTag, TSoftClassPtr<UUserWidget> WidgetClass, int32 Priority)
{
if (!WidgetClass.IsNull())
{
return RegisterExtensionAsWidget(ExtensionPointTag, WidgetClass.LoadSynchronous(), Priority);
}
return FGUIS_GameUIExtHandle();
}
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::K2_RegisterExtensionAsWidgetForContext(FGameplayTag ExtensionPointTag, TSoftClassPtr<UUserWidget> WidgetClass, UObject* ContextObject, int32 Priority)
{
if (ContextObject && !WidgetClass.IsNull())
{
return RegisterExtensionAsWidgetForContext(ExtensionPointTag, ContextObject, WidgetClass.LoadSynchronous(), Priority);
}
FFrame::KismetExecutionMessage(TEXT("A null ContextObject was passed to Register Extension (Widget For Context)"), ELogVerbosity::Error);
return FGUIS_GameUIExtHandle();
}
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::K2_RegisterExtensionAsData(FGameplayTag ExtensionPointTag, UObject* Data, int32 Priority)
{
return RegisterExtensionAsData(ExtensionPointTag, nullptr, Data, Priority);
}
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::K2_RegisterExtensionAsDataForContext(FGameplayTag ExtensionPointTag, UObject* ContextObject, UObject* Data, int32 Priority)
{
if (ContextObject)
{
return RegisterExtensionAsData(ExtensionPointTag, ContextObject, Data, Priority);
}
FFrame::KismetExecutionMessage(TEXT("A null ContextObject was passed to Register Extension (Data For Context)"), ELogVerbosity::Error);
return FGUIS_GameUIExtHandle();
}
//=========================================================
UGUIS_ExtensionFunctionLibrary::UGUIS_ExtensionFunctionLibrary()
{
}
void UGUIS_ExtensionFunctionLibrary::UnregisterExtension(FGUIS_GameUIExtHandle& Handle)
{
Handle.Unregister();
}
bool UGUIS_ExtensionFunctionLibrary::IsValidExtension(FGUIS_GameUIExtHandle& Handle)
{
return Handle.IsValid();
}
//=========================================================
void UGUIS_ExtensionFunctionLibrary::UnregisterExtensionPoint(FGUIS_GameUIExtPointHandle& Handle)
{
Handle.Unregister();
}
bool UGUIS_ExtensionFunctionLibrary::IsValidExtensionPoint(FGUIS_GameUIExtPointHandle& Handle)
{
return Handle.IsValid();
}