Skip to content

Commit

Permalink
Progress on Content Tag Registry implementation, currently only with …
Browse files Browse the repository at this point in the history
…manual tag add calls. Testing usage in ExampleMod to be removed later
  • Loading branch information
budak7273 committed Aug 5, 2024
1 parent e2022aa commit 06f55a1
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 3 deletions.
Binary file modified Mods/ExampleMod/Content/RootGameWorld_ExampleMod.uasset
Binary file not shown.
Binary file not shown.
4 changes: 2 additions & 2 deletions Mods/SML/SML.uplugin
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"Version": 3,
"VersionName": "3.7.0",
"SemVersion": "3.7.0",
"GameVersion": "269772",
"GameVersion": ">=269772",
"FriendlyName": "Satisfactory Mod Loader",
"Description": "Mod loading and compatibility API for Satisfactory",
"Category": "Modding",
Expand Down Expand Up @@ -36,4 +36,4 @@
"BasePlugin": true
}
]
}
}
98 changes: 98 additions & 0 deletions Mods/SML/Source/SML/Private/Registry/ContentTagRegistry.cpp
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);
}
78 changes: 78 additions & 0 deletions Mods/SML/Source/SML/Public/Registry/ContentTagRegistry.h
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);

};
1 change: 1 addition & 0 deletions Mods/SML/Source/SML/Public/Registry/ModContentRegistry.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ class SML_API UModContentRegistry : public UWorldSubsystem {
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void OnWorldBeginPlay(UWorld& InWorld) override;
// End USubsystem interface

static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector);
private:
friend class FGameObjectRegistryState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class SML_API ISMLExtendedAttributeProvider {
GENERATED_BODY()
public:

/** Get the Gameplay Tags container for this asset.
/**
* Get the Gameplay Tags container for this asset.
* Remember, since it's a BlueprintNativeEvent, to call from C++ you must call via ISMLExtendedAttributeProvider::Execute_GetGameplayTagsContainer
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="SML|Tags")
Expand Down

0 comments on commit 06f55a1

Please sign in to comment.