-
Notifications
You must be signed in to change notification settings - Fork 190
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Progress on Content Tag Registry implementation, currently only with …
…manual tag add calls. Testing usage in ExampleMod to be removed later
- Loading branch information
Showing
7 changed files
with
181 additions
and
3 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
Mods/SML/Source/SML/Private/Registry/ContentTagRegistry.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
#include "Registry/ContentTagRegistry.h" | ||
|
||
#include "ModLoading/PluginModuleLoader.h" | ||
|
||
DEFINE_LOG_CATEGORY(LogContentTagRegistry); | ||
|
||
FFrame* UContentTagRegistry::ActiveScriptFramePtr{}; | ||
|
||
/** Makes sure provided object instance is valid, crashes with both script call stack and native stack trace if it's not */ | ||
#define NOTIFY_INVALID_REGISTRATION(Context) \ | ||
{ \ | ||
/* Attempt to use cached script frame pointer first, then fallback to global script callstack (which is not available in shipping by default) */ \ | ||
const FString ScriptCallstack = UContentTagRegistry::GetCallStackContext(); \ | ||
UE_LOG(LogContentTagRegistry, Error, TEXT("Attempt to modify content tags failed: %s"), Context); \ | ||
UE_LOG(LogContentTagRegistry, Error, TEXT("Script Callstack: %s"), *ScriptCallstack); \ | ||
ensureMsgf( false, TEXT("%s"), *Context ); \ | ||
} \ | ||
|
||
UContentTagRegistry::UContentTagRegistry() { | ||
} | ||
|
||
UContentTagRegistry* UContentTagRegistry::Get(const UObject* WorldContext) { | ||
if (const UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull)) { | ||
return World->GetSubsystem<UContentTagRegistry>(); | ||
} | ||
return nullptr; | ||
} | ||
|
||
const FGameplayTagContainer UContentTagRegistry::GetGameplayTagContainerFor(const UObject* content) { | ||
const auto record = TagContainerRegistry.Find(content); | ||
return (record == nullptr) ? FGameplayTagContainer() : *record; | ||
} | ||
|
||
bool UContentTagRegistry::CanModifyTagsOf(UObject* content, FString& OutMessage) { | ||
if (bRegistryFrozen) { | ||
OutMessage = FString::Printf(TEXT("Attempt to modify content tags of object '%s' in frozen registry. Make sure your tag changes are happening in the 'Initialization' Lifecycle Phase and not 'Post Initialization'. TODO update this message with the timing we decide on."), *GetPathNameSafe(content)); | ||
return false; | ||
} | ||
if (!IsValid(content)) { | ||
OutMessage = FString::Printf(TEXT("Attempt to modify content tags of an invalid object.")); | ||
return false; | ||
} | ||
// TODO checking to make sure a UClass is being registered and not a joe schmoe arbitrary uobject instance | ||
return true; | ||
} | ||
|
||
void UContentTagRegistry::AddGameplayTagsTo(UObject* content, const FGameplayTagContainer tags) { | ||
FString Context; | ||
if (!CanModifyTagsOf(content, Context)) { | ||
NOTIFY_INVALID_REGISTRATION(*Context); | ||
return; | ||
} | ||
auto& record = TagContainerRegistry.FindOrAdd(content); | ||
UE_LOG(LogContentTagRegistry, Verbose, TEXT("Adding tags %s for class %s"), *tags.ToString(), *GetFullNameSafe(content)); | ||
record.AppendTags(tags); | ||
} | ||
|
||
void UContentTagRegistry::FreezeRegistry() { | ||
bRegistryFrozen = true; | ||
} | ||
|
||
bool UContentTagRegistry::ShouldCreateSubsystem(UObject* Outer) const { | ||
UWorld* WorldOuter = CastChecked<UWorld>(Outer); | ||
return FPluginModuleLoader::ShouldLoadModulesForWorld(WorldOuter); | ||
} | ||
|
||
void UContentTagRegistry::OnActorPreSpawnInitialization(AActor* Actor) { | ||
OnWorldBeginPlayDelegate.AddWeakLambda(this, [&]() { | ||
FreezeRegistry(); | ||
}); | ||
} | ||
|
||
void UContentTagRegistry::OnWorldBeginPlay(UWorld& InWorld) { | ||
OnWorldBeginPlayDelegate.Broadcast(); | ||
} | ||
|
||
FString UContentTagRegistry::GetCallStackContext() { | ||
// Prefer script callstack to the native one | ||
if (ActiveScriptFramePtr != nullptr) { | ||
return ActiveScriptFramePtr->Node->GetPathName(); | ||
} | ||
|
||
// Attempt to capture stack trace | ||
TArray<FProgramCounterSymbolInfo> NativeStackTrace = FPlatformStackWalk::GetStack(1, 10); | ||
if (NativeStackTrace.IsEmpty()) { | ||
FProgramCounterSymbolInfo& Info = NativeStackTrace.Emplace_GetRef(); | ||
TCString<ANSICHAR>::Strcpy(Info.Filename, FProgramCounterSymbolInfo::MAX_NAME_LENGTH, "Unknown"); | ||
Info.LineNumber = 1; | ||
} | ||
|
||
// Find first frame that does not contain internal logic of content registry | ||
int32 FirstExternalFrameIndex = 0; | ||
while (FirstExternalFrameIndex + 1 < NativeStackTrace.Num() && | ||
FCStringAnsi::Strifind(NativeStackTrace[FirstExternalFrameIndex].Filename, __FILE__) != nullptr) { | ||
FirstExternalFrameIndex++; | ||
} | ||
return FString::Printf(TEXT("%hs:%d"), NativeStackTrace[FirstExternalFrameIndex].Filename, NativeStackTrace[FirstExternalFrameIndex].LineNumber); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
#pragma once | ||
|
||
#include "CoreMinimal.h" | ||
#include "Subsystems/WorldSubsystem.h" | ||
#include "GameplayTagContainer.h" | ||
#include "ContentTagRegistry.generated.h" | ||
|
||
DECLARE_LOG_CATEGORY_EXTERN(LogContentTagRegistry, Log, All); | ||
|
||
/** | ||
* Manages Gameplay Tag Containers for content classes. | ||
* Enables any mod to apply Unreal Gameplay Tags to any mod's content, | ||
* or the base game's content, at the UClass level. | ||
* | ||
* Add tags to content via this registry's provided avenues, | ||
* all returned tag containers are const. | ||
* | ||
* Tag associations can only be made before save is loaded, | ||
* after that moment the registry is frozen and no changes can be made after that. | ||
*/ | ||
UCLASS() | ||
class SML_API UContentTagRegistry : public UWorldSubsystem | ||
{ | ||
GENERATED_BODY() | ||
public: | ||
UContentTagRegistry(); | ||
|
||
/** Retrieves content tag registry instance */ | ||
UFUNCTION(BlueprintPure, Category = "Content Tag Registry", DisplayName = "GetContentTagRegistry", meta = (WorldContext = "WorldContext")) | ||
static UContentTagRegistry* Get(const UObject* WorldContext); | ||
|
||
/** | ||
* Get Gameplay Tag container for the supplied class. | ||
* Could be empty if there were no tags registered. | ||
* TODO outvar bool for found/not? | ||
*/ | ||
UFUNCTION(BlueprintPure, Category = "Content Tag Registry") | ||
const FGameplayTagContainer GetGameplayTagContainerFor(const UObject* content); | ||
|
||
/** | ||
* Register gameplay tags from the passed container to the passed class | ||
* TODO FName InRegistrationPluginName? | ||
*/ | ||
UFUNCTION(BlueprintCallable, Category = "Content Tag Registry") | ||
void AddGameplayTagsTo(UObject* content, FGameplayTagContainer tags); | ||
|
||
// TODO ability to remove tags | ||
|
||
// TODO auto register of stuff with ExtendedAttributeProvider being registered in mod content registry | ||
|
||
// Freezes the registry. No new registrations are accepted past this point. | ||
void FreezeRegistry(); | ||
|
||
void OnActorPreSpawnInitialization(AActor* Actor); | ||
|
||
// Begin USubsystem interface | ||
virtual bool ShouldCreateSubsystem(UObject* Outer) const override; | ||
//virtual void Initialize(FSubsystemCollectionBase& Collection) override; | ||
virtual void OnWorldBeginPlay(UWorld& InWorld) override; | ||
// End USubsystem interface | ||
|
||
private: | ||
UPROPERTY() | ||
TMap<UObject*, FGameplayTagContainer> TagContainerRegistry; | ||
|
||
bool bRegistryFrozen{ false }; | ||
|
||
DECLARE_MULTICAST_DELEGATE(FOnWorldBeginPlay); | ||
FOnWorldBeginPlay OnWorldBeginPlayDelegate; | ||
|
||
static FString GetCallStackContext(); | ||
|
||
/** Pointer to the currently active script callstack frame, used for debugging purposes */ | ||
static FFrame* ActiveScriptFramePtr; | ||
|
||
bool CanModifyTagsOf(UObject* content, FString& OutMessage); | ||
|
||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters