Files
UnrealEngine/Engine/Plugins/Runtime/MassGameplay/Source/MassSpawner/Public/MassEntityTemplateRegistry.h
2025-05-18 13:04:45 +08:00

422 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6
#include "CoreMinimal.h"
#endif // UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6
#include "UObject/Object.h"
#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6
#include "MassCommonTypes.h"
#endif // UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6
#include "MassEntityTemplate.h"
#include "MassTranslator.h"
#include "MassEntityTemplateRegistry.generated.h"
#define ENSURE_SUPPORTED_TRAIT_OPERATION() ensureMsgf(bBuildInProgress == false, TEXT("This method is not expected to be called as "\
"part of trait's BuildTemplate call. Traits are not supposed to add elements based on other traits due to arbitrary trait ordering."));
class UWorld;
class UMassEntityTraitBase;
USTRUCT()
struct FMassMissingTraitMessage
{
GENERATED_BODY()
#if WITH_EDITORONLY_DATA
explicit FMassMissingTraitMessage(const UMassEntityTraitBase* InRequestingTrait = nullptr, const UStruct* InMissingType = nullptr, const UMassEntityTraitBase* InRemovedByTrait = nullptr)
: RequestingTrait(InRequestingTrait), MissingType(InMissingType), RemovedByTrait(InRemovedByTrait)
{}
const UMassEntityTraitBase* RequestingTrait = nullptr;
const UStruct* MissingType = nullptr;
// if set indicates that the missing type has been explicitly removed by given trait.
const UMassEntityTraitBase* RemovedByTrait = nullptr;
#endif // WITH_EDITORONLY_DATA
};
USTRUCT()
struct FMassDuplicateElementsMessage
{
GENERATED_BODY()
#if WITH_EDITORONLY_DATA
const UMassEntityTraitBase* DuplicatingTrait = nullptr;
const UMassEntityTraitBase* OriginalTrait = nullptr;
const UStruct* Element = nullptr;
#endif // WITH_EDITORONLY_DATA
};
#if WITH_EDITORONLY_DATA
namespace UE::Mass::Debug
{
extern MASSSPAWNER_API const FName TraitFailedValidation;
extern MASSSPAWNER_API const FName TraitIgnored;
}
#endif // WITH_EDITORONLY_DATA
enum class EFragmentInitialization : uint8
{
DefaultInitializer,
NoInitializer
};
struct FMassEntityTemplateBuildContext
{
explicit FMassEntityTemplateBuildContext(FMassEntityTemplateData& InTemplate, FMassEntityTemplateID InTemplateID = FMassEntityTemplateID())
: TemplateData(InTemplate)
, TemplateID(InTemplateID)
{}
void SetTemplateName(const FString& Name)
{
TemplateData.SetTemplateName(Name);
}
//----------------------------------------------------------------------//
// Fragments
//----------------------------------------------------------------------//
template<typename T>
T& AddFragment_GetRef()
{
TypeAdded(*T::StaticStruct());
return TemplateData.AddFragment_GetRef<T>();
}
template<typename T>
void AddFragment()
{
TypeAdded(*T::StaticStruct());
TemplateData.AddFragment<T>();
}
void AddFragment(FConstStructView InFragment)
{
checkf(InFragment.GetScriptStruct(), TEXT("Expecting a valid fragment type"));
TypeAdded(*InFragment.GetScriptStruct());
TemplateData.AddFragment(InFragment);
}
template<typename T>
void AddTag()
{
// Tags can be added by multiple traits, so they do not follow the same rules as fragments
TemplateData.AddTag<T>();
TypeAdded(*T::StaticStruct());
}
void AddTag(const UScriptStruct& TagType)
{
// Tags can be added by multiple traits, so they do not follow the same rules as fragments
TemplateData.AddTag(TagType);
TypeAdded(TagType);
}
template<typename T>
void AddChunkFragment()
{
TypeAdded(*T::StaticStruct());
TemplateData.AddChunkFragment<T>();
}
void AddConstSharedFragment(const FConstSharedStruct& InSharedFragment)
{
checkf(InSharedFragment.GetScriptStruct(), TEXT("Expecting a valid shared fragment type"));
TypeAdded(*InSharedFragment.GetScriptStruct());
TemplateData.AddConstSharedFragment(InSharedFragment);
}
void AddSharedFragment(const FSharedStruct& InSharedFragment)
{
checkf(InSharedFragment.GetScriptStruct(), TEXT("Expecting a valid shared fragment type"));
TypeAdded(*InSharedFragment.GetScriptStruct());
TemplateData.AddSharedFragment(InSharedFragment);
}
/**
* Removes given tag from collected data. More precisely: it will store the information and apply upon template creation (an optimization).
* WARNING: use with caution and only in cases where you know for certain what the given tag does and which processors rely on it.
* Using this functionality makes most sense for removing tags that specifically mean that entities having it are to be
* processed by a given processor.
*/
void RemoveTag(const UScriptStruct& TagType)
{
checkf(UE::Mass::IsA<FMassTag>(&TagType), TEXT("Given struct doesn't represent a valid mass tag type. Make sure to inherit from FMassTag or one of its child-types."));
RemovedTypes.Add({&TagType
#if WITH_EDITORONLY_DATA
, TraitsData.Last().Trait
#endif // WITH_EDITORONLY_DATA
});
}
template<typename T>
void RemoveTag()
{
RemoveTag(*T::StaticStruct());
}
template<typename T>
T* GetFragment()
{
return TemplateData.GetMutableFragment<T>();
}
template<typename T>
bool HasFragment() const
{
ENSURE_SUPPORTED_TRAIT_OPERATION();
return TemplateData.HasFragment<T>();
}
bool HasFragment(const UScriptStruct& ScriptStruct) const
{
ENSURE_SUPPORTED_TRAIT_OPERATION();
return TemplateData.HasFragment(ScriptStruct);
}
template<typename T>
bool HasTag() const
{
return TemplateData.HasTag<T>();
}
template<typename T>
bool HasChunkFragment() const
{
ENSURE_SUPPORTED_TRAIT_OPERATION();
return TemplateData.HasChunkFragment<T>();
}
template<typename T>
bool HasSharedFragment() const
{
ENSURE_SUPPORTED_TRAIT_OPERATION();
return TemplateData.HasSharedFragment<T>();
}
bool HasSharedFragment(const UScriptStruct& ScriptStruct) const
{
ENSURE_SUPPORTED_TRAIT_OPERATION();
return TemplateData.HasSharedFragment(ScriptStruct);
}
template<typename T>
bool HasConstSharedFragment() const
{
ENSURE_SUPPORTED_TRAIT_OPERATION();
return TemplateData.HasConstSharedFragment<T>();
}
bool HasConstSharedFragment(const UScriptStruct& ScriptStruct) const
{
ENSURE_SUPPORTED_TRAIT_OPERATION();
return TemplateData.HasConstSharedFragment(ScriptStruct);
}
//----------------------------------------------------------------------//
// Translators
//----------------------------------------------------------------------//
template<typename T>
void AddTranslator()
{
TypeAdded(*T::StaticClass());
GetDefault<T>()->AppendRequiredTags(TemplateData.GetMutableTags());
}
//----------------------------------------------------------------------//
// Dependencies
//----------------------------------------------------------------------//
template<typename T>
void RequireFragment()
{
static_assert(UE::Mass::CTag<T> == false, "Given struct type is a valid fragment type.");
AddDependency(T::StaticStruct());
}
template<typename T>
void RequireTag()
{
static_assert(UE::Mass::CTag<T>, "Given struct type is not a valid tag type.");
AddDependency(T::StaticStruct());
}
void AddDependency(const UStruct* Dependency)
{
TraitsData.Last().TypesRequired.Add(Dependency);
}
//----------------------------------------------------------------------//
// Template access
//----------------------------------------------------------------------//
FMassEntityTemplateID GetTemplateID() const { return TemplateID; }
TArray<FMassEntityTemplateData::FObjectFragmentInitializerFunction>& GetMutableObjectFragmentInitializers() { return TemplateData.GetMutableObjectFragmentInitializers(); }
//----------------------------------------------------------------------//
// Build methods
//----------------------------------------------------------------------//
/**
* Builds context from a list of traits
* @param Traits is the list of all the traits to build an entity
* @param World owning the MassEntitySubsystem for which the entity template is built
* @return true if there were no validation errors
*/
bool BuildFromTraits(TConstArrayView<UMassEntityTraitBase*> Traits, const UWorld& World);
/**
* The method that allows to distinguish between regular context use (using traits to build templates) and
* the "data investigation" mode (used for debugging and authoring purposes). Utilize this function to
* avoid UWorld-specific operations (like getting subsystems). This method should also be used when a trait
* contains conditional logic - in that case it's required for the trait to add all the types that are potentially
* added at runtime (even if seemingly conflicting information will be added).
*
* @return whether this context is in data inspection mode.
*/
#if WITH_EDITORONLY_DATA
bool IsInspectingData() const
{
return bIsInspectingData;
}
#else
constexpr bool IsInspectingData() const
{
return false;
}
#endif
#if WITH_EDITORONLY_DATA
void EnableDataInvestigationMode()
{
checkf(TemplateData.IsEmpty(), TEXT("Marking a FMassEntityTemplateBuildContext as being in 'investigation mode` is only supported before the context is first used."));
bIsInspectingData = true;
}
#endif // WITH_EDITORONLY_DATA
protected:
/**
* Validate the build context for fragment trait ownership and trait fragment missing dependency
* @param World owning the MassEntitySubsystem for which the entity template is validated against
* @return true if there were no validation errors
*/
bool ValidateBuildContext(const UWorld& World);
void TypeAdded(const UStruct& Type)
{
checkf(TraitsData.Num(), TEXT("Adding elements to the build context before BuildFromTraits or SetTraitBeingProcessed was called is unsupported"));
TraitsData.Last().TypesAdded.Add(&Type);
}
/**
* Return true if the given trait can be used. The function will fail if a trait instance of the given class has already
* been processed. The function will also fail the very same trait instance is used multiple times.
* Note that it's ok for Trait to be nullptr to indicate the subsequent additions to the build context are procedural
* in nature and are not associated with any traits. In that case it's ok to have multiple SetTraitBeingProcessed(nullptr)
* calls.
*/
MASSSPAWNER_API bool SetTraitBeingProcessed(const UMassEntityTraitBase* Trait);
void ResetBuildTimeData()
{
TraitsData.Reset();
TraitsProcessed.Reset();
IgnoredTraits.Reset();
RemovedTypes.Reset();
bBuildInProgress = false;
}
struct FTraitData
{
const UMassEntityTraitBase* Trait = nullptr;
TArray<const UStruct*> TypesAdded;
TArray<const UStruct*> TypesRequired;
};
TArray<FTraitData> TraitsData;
TSet<const UMassEntityTraitBase*> TraitsProcessed;
TSet<const UMassEntityTraitBase*> IgnoredTraits;
struct FRemovedType
{
const UStruct* TypeRemoved = nullptr;
#if WITH_EDITOR
const UMassEntityTraitBase* Remover = nullptr;
#endif // WITH_EDITOR
bool operator==(const FRemovedType& Other) const
{
return TypeRemoved == Other.TypeRemoved;
}
};
/**
* These tags will be removed from the resulting entity template
* @see RemoveTag for more details
*/
TArray<FRemovedType> RemovedTypes;
bool bBuildInProgress = false;
FMassEntityTemplateData& TemplateData;
FMassEntityTemplateID TemplateID;
#if WITH_EDITORONLY_DATA
private:
/**
* This being set to `true` indicates that the context is being used to gather information, not to create actual
* entity templates.
*/
bool bIsInspectingData = false;
#endif // WITH_EDITORONLY_DATA
};
/**
* Represents a repository storing all the FMassEntityTemplate that have been created and registered as part of FMassEntityConfig
* processing or via custom code (like we do in InstancedActors plugin).
*/
struct FMassEntityTemplateRegistry
{
// @todo consider TFunction instead
DECLARE_DELEGATE_ThreeParams(FStructToTemplateBuilderDelegate, const UWorld* /*World*/, const FConstStructView /*InStructInstance*/, FMassEntityTemplateBuildContext& /*BuildContext*/);
MASSSPAWNER_API explicit FMassEntityTemplateRegistry(UObject* InOwner = nullptr);
/** Initializes and stores the EntityManager the templates will be associated with. Needs to be called before any template operations.
* Note that the function will only let users set the EntityManager once. Once it's set the subsequent calls will
* have no effect. If attempting to set a different EntityManaget an ensure will trigger. */
MASSSPAWNER_API void Initialize(const TSharedPtr<FMassEntityManager>& InEntityManager);
MASSSPAWNER_API void ShutDown();
MASSSPAWNER_API UWorld* GetWorld() const;
static MASSSPAWNER_API FStructToTemplateBuilderDelegate& FindOrAdd(const UScriptStruct& DataType);
/** Removes all the cached template instances */
MASSSPAWNER_API void DebugReset();
MASSSPAWNER_API const TSharedRef<FMassEntityTemplate>* FindTemplateFromTemplateID(FMassEntityTemplateID TemplateID) const;
/**
* Adds a template based on TemplateData
*/
MASSSPAWNER_API const TSharedRef<FMassEntityTemplate>& FindOrAddTemplate(FMassEntityTemplateID TemplateID, FMassEntityTemplateData&& TemplateData);
MASSSPAWNER_API void DestroyTemplate(FMassEntityTemplateID TemplateID);
FMassEntityManager& GetEntityManagerChecked() { check(EntityManager); return *EntityManager; }
protected:
static MASSSPAWNER_API TMap<const UScriptStruct*, FStructToTemplateBuilderDelegate> StructBasedBuilders;
TMap<FMassEntityTemplateID, TSharedRef<FMassEntityTemplate>> TemplateIDToTemplateMap;
/**
* EntityManager the hosted templates are associated with. Storing instead of fetching at runtime to ensure all
* templates are tied to the same EntityManager
*/
TSharedPtr<FMassEntityManager> EntityManager;
TWeakObjectPtr<UObject> Owner;
};
#undef ENSURE_SUPPORTED_TRAIT_OPERATION