// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "MassArchetypeTypes.h" #include "MassEntityConcepts.h" #include "MassEntityHandle.h" #include "MassEntityTypes.h" #define UE_API MASSENTITY_API struct FMassEntityManager; namespace UE::Mass { namespace Private { struct FEntityBuilderHelper; } /** * FEntityBuilder is a utility struct that provides a convenient way to create and configure entities * in the Mass framework. It bridges multiple APIs from FMassEntityManager, MassSpawnerSubsystem, * MassEntityTemplates, and other related components, allowing for streamlined entity creation and configuration. * * Key Features: * - Can be seamlessly used in place of FMassEntityHandle, allowing for consistent and intuitive usage. * - An entity only gets created once Commit() is called * - Copyable, but copied instances represent new entities without carrying over the reserved entity handle. * * Example Usage: * { * FEntityBuilder Builder(EntityManager); * Builder.Add(FTransform(FVector(100, 200, 300))) * .Commit(); // the entity gets reserved and built by this call * } * * { * FEntityBuilder Builder(EntityManager); * FMassEntityHandle ReservedEntity = Builder; // Entity handle reserved, can be used for commands. * Builder.Add_GetRef().GetMutableTransform().SetTranslation(FVector(100, 200, 300)); * Builder.Commit(); // Entity creation is finalized at this point. * } * * // Example of chaining with FMassEntityManager's MakeEntityBuilder() method: * FMassEntityHandle NewEntity = EntityManager.MakeEntityBuilder() * .Add() * .Add() * .Add(FAgentRadiusFragment{ .Radius = 35.f}) * .Add() * .Commit(); * * Current Limitations: * - Committing entities while Mass's processing is in progress is not yet supported; this functionality will be implemented in the near future. * - no support for entity grouping */ struct FEntityBuilder { /** Constructs a FEntityBuilder using a reference to a FMassEntityManager. */ UE_API explicit FEntityBuilder(FMassEntityManager& InEntityManager); /** Constructs a FEntityBuilder using a shared reference to a FMassEntityManager. */ UE_API explicit FEntityBuilder(const TSharedRef& InEntityManager); /** Copy constructor - copies-create a new instance that represents a new entity and does not carry over reserved handle. */ UE_API FEntityBuilder(const FEntityBuilder& Other); /** Assignment operator - copies represent new entities, with no carryover of reserved handle from the original. */ UE_API FEntityBuilder& operator=(const FEntityBuilder& Other); /** Move assignment operator - moves over all the data from Other, including the internal state (like whether the entity handle has already been reserved) */ UE_API FEntityBuilder& operator=(FEntityBuilder&& Other); /** Destructor - automatically commits entity creation if not explicitly aborted or committed beforehand. */ UE_API ~FEntityBuilder(); /** Creates an instance of FEntityBuilder and populates it with provided data */ static UE_API FEntityBuilder Make(const TSharedRef& InEntityManager , const FMassArchetypeCompositionDescriptor& Composition , TConstArrayView InitialFragmentValues = {} , TConstArrayView ConstSharedFragments = {} , TConstArrayView SharedFragments = {}); /** Creates an instance of FEntityBuilder and populates it with provided data, using move-semantics on said data */ static UE_API FEntityBuilder Make(const TSharedRef& InEntityManager , const FMassArchetypeCompositionDescriptor& Composition , TArray&& InitialFragmentValues , TArray&& ConstSharedFragments , TArray&& SharedFragments); /** * Finalizes the creation of the entity with the specified fragments and configurations. * Note that this function needs to be called manually, no automated entity creation will take place upon builder's destruction. */ UE_API FMassEntityHandle Commit(); /** * A wrapper for "Commit" call that, once that's done, prepares the builder for another commit, forgetting the * handle for the entity just created, and reverting the state back to "ReadyToCommit" * @see Commit */ UE_API FMassEntityHandle CommitAndReprepare(); /** if the builder is in "Committed" state it will roll back to ReadyToSubmit and reset the stored entity handle */ UE_API void Reprepare(); /** * Resets the builder to its initial state, discarding all previous entity configurations. * @param bReleaseEntityHandleIfReserved configures what to do with the reserved entity handle, if it's valid. */ UE_API void Reset(const bool bReleaseEntityHandleIfReserved = true); /** * Stores ReservedEntityHandle as the cached EntityHandle. The ReservedEntityHandle is expected to be valid * and represent a reserved entity. These expectations will be checked via ensures. * If the existing EntityHandle also represents a valid, reserved entity, that handle will be released. * @return whether the ReservedEntityHandle has been stored. */ UE_API bool SetReservedEntityHandle(const FMassEntityHandle ReservedEntityHandle); /** * Appends all element types and values stored by the entity indicated by SourceEntityHandle. * @param SourceEntityHandle valid handle for a fully constructed, built entity. * @return whether the operation was successful */ UE_API bool AppendDataFromEntity(const FMassEntityHandle SourceEntityHandle); /** * Copies all element types and values stored by the entity indicated by SourceEntityHandle. Any existing builder data will be overridden * @param SourceEntityHandle valid handle for a fully constructed, built entity. * @return whether the operation was successful */ UE_API bool CopyDataFromEntity(const FMassEntityHandle SourceEntityHandle); /** * Adds a fragment of type T to the entity and returns a reference to it, constructing it with the provided arguments. * The function will assert if an element of type T already exists. * @param T - The type of fragment to add. * @param InArgs - Constructor arguments for initializing the fragment. * @return A reference to the added fragment. */ template T& Add_GetRef(TArgs&&... InArgs) requires (CElement && !(CTag || CChunkFragment)); /** * Adds a fragment of type T to the entity and returns a reference to it, constructing it with the provided arguments. * If a fragment of the given type already exists then it will be overriden and its reference returned. * @return A reference to the added fragment. */ template T& GetOrCreate(TArgs&&... InArgs) requires (CElement && !(CTag || CChunkFragment)); /** * Adds a tag of type T to the entity. * @return Reference to this FEntityBuilder for method chaining. */ template FEntityBuilder& Add(); /** * Adds a chunk fragment of type T to the entity. * @return Reference to this FEntityBuilder for method chaining. */ template FEntityBuilder& Add(); /** * Adds a fragment of type T to the entity, constructing it with the provided arguments. * @return Reference to this FEntityBuilder for method chaining. */ template FEntityBuilder& Add(TArgs&&... InArgs) requires (CElement && !(CTag || CChunkFragment)); /** * Adds a fragment instance to the Entity Builder, treating the contents according to its type */ UE_API FEntityBuilder& Add(const FInstancedStruct& ElementInstance); UE_API FEntityBuilder& Add(FInstancedStruct&& ElementInstance); /** * Finds and retrieves a pointer to a fragment of type T if it exists. * @param T - The type of fragment to find. * @return Pointer to the fragment, or nullptr if it does not exist. */ template T* Find() requires (CElement && !(CTag || CChunkFragment)); /** * Advanced functionality. Can be used to provide additional parameters that will be used to * create the entity's target archetype. Note that these parameters will take effect only if * the target archetype doesn't exist yet. */ void ConfigureArchetypeCreation(const FMassArchetypeCreationParams& InCreationParams); /** Converts the builder to a FMassEntityHandle, reserving the entity handle if not already committed. */ [[nodiscard]] UE_API FMassEntityHandle GetEntityHandle() const; [[nodiscard]] UE_API FMassArchetypeHandle GetArchetypeHandle(); /** Checks whether the builder is in a valid, expected state */ bool IsValid() const; /** @return whether the builder has an entity handle reserved and the data has not been committed yet */ bool HasReservedEntityHandle() const; /** @return whether the builder has already committed the data */ bool IsCommitted() const; /** @return the EntityManager instance this entity builder is working for */ TSharedRef GetEntityManager(); protected: friend Private::FEntityBuilderHelper; UE_API void CacheSharedFragmentValue(); UE_API void CacheArchetypeHandle(); UE_API void InvalidateCachedData(); private: template FEntityBuilder& AddInternal(T&& ElementInstance); TSharedRef EntityManager; mutable FMassEntityHandle EntityHandle; FMassArchetypeCompositionDescriptor Composition; FMassArchetypeSharedFragmentValues CachedSharedFragmentValues; FMassArchetypeHandle CachedArchetypeHandle; /** stores optional FMassArchetypeCreationParams, that will be used if the target archetype doesn't exist yet */ FMassArchetypeCreationParams ArchetypeCreationParams; template TArray& GetInstancedStructContainerInternal() { return Fragments; } template TArray& GetInstancedStructContainerInternal() { // Resetting the cached shared values because this function is always called // with the intent to modify the contents of SharedFragments, invalidating the // cached data anyway CachedSharedFragmentValues.Reset(); return SharedFragments; } template TArray& GetInstancedStructContainerInternal() { CachedSharedFragmentValues.Reset(); return ConstSharedFragments; } template auto& GetBitSetBuilder() { return Composition.GetContainer(); } /** Releases reserved handle if it has not been committed yet */ void ConditionallyReleaseEntityHandle(); void CacheEntityHandle() const; TArray Fragments; TArray SharedFragments; TArray ConstSharedFragments; enum class EState : uint8 { Empty, ReadyToCommit, Committed, Invalid, }; EState State = EState::Empty; }; struct FScopedEntityBuilder : FEntityBuilder { UE_NONCOPYABLE(FScopedEntityBuilder); template FScopedEntityBuilder(TArgs&&... InArgs) : FEntityBuilder(Forward(InArgs)...) { } ~FScopedEntityBuilder() { Commit(); } }; //----------------------------------------------------------------------------- // Inlines and specializations //----------------------------------------------------------------------------- template FEntityBuilder& FEntityBuilder::Add() { Composition.Tags.Add(); State = EState::ReadyToCommit; CachedArchetypeHandle = FMassArchetypeHandle(); return *this; } template FEntityBuilder& FEntityBuilder::Add() { Composition.ChunkFragments.Add(); State = EState::ReadyToCommit; CachedArchetypeHandle = FMassArchetypeHandle(); return *this; } template FEntityBuilder& FEntityBuilder::Add(TArgs&&... InArgs) requires (CElement && !(CTag || CChunkFragment)) { Add_GetRef(InArgs...); return *this; } template T& FEntityBuilder::Add_GetRef(TArgs&&... InArgs) requires (CElement && !(CTag || CChunkFragment)) { using FElementType = UE::Mass::TElementType; State = EState::ReadyToCommit; if (ensureMsgf(GetBitSetBuilder().template Contains() == false, TEXT("Element of type %s has already been added"), *T::StaticStruct()->GetName())) { GetBitSetBuilder().template Add(); CachedArchetypeHandle = FMassArchetypeHandle(); return GetInstancedStructContainerInternal().Add_GetRef(FInstancedStruct::Make(InArgs...)).template GetMutable(); } return GetInstancedStructContainerInternal().FindByPredicate([](const FInstancedStruct& ExistingElement) { return ExistingElement.GetScriptStruct() == T::StaticStruct(); })->template GetMutable(); } template T& FEntityBuilder::GetOrCreate(TArgs&&... InArgs) requires (CElement && !(CTag || CChunkFragment)) { using FElementType = UE::Mass::TElementType; State = EState::ReadyToCommit; FInstancedStruct* ElementFound = nullptr; if (GetBitSetBuilder().template Contains()) { // replace ElementFound = GetInstancedStructContainerInternal().FindByPredicate([](const FInstancedStruct& ExistingElement) { return ExistingElement.GetScriptStruct() == T::StaticStruct(); }); checkf(ElementFound, TEXT("We expect the element to be found since we already tested the Composition")); ElementFound->GetMutable() = T(InArgs...); } else { GetBitSetBuilder().template Add(); CachedArchetypeHandle = FMassArchetypeHandle(); ElementFound = &GetInstancedStructContainerInternal().Add_GetRef(FInstancedStruct::Make(InArgs...)); } return ElementFound->GetMutable(); } template T* FEntityBuilder::Find() requires (CElement && !(CTag || CChunkFragment)) { using FElementType = UE::Mass::TElementType; if (GetBitSetBuilder().template Contains()) { FInstancedStruct* ElementFound = GetInstancedStructContainerInternal().FindByPredicate([](const FInstancedStruct& Element) { return Element.GetScriptStruct() == T::StaticStruct(); }); checkf(ElementFound, TEXT("We expect the element to be found since we already tested the Composition")); return ElementFound->GetMutablePtr(); } return nullptr; } inline bool FEntityBuilder::IsValid() const { return State != EState::Invalid; } inline bool FEntityBuilder::HasReservedEntityHandle() const { return State != EState::Committed && EntityHandle.IsValid(); } inline bool FEntityBuilder::IsCommitted() const { return State == EState::Committed; } inline TSharedRef FEntityBuilder::GetEntityManager() { return EntityManager; } inline void FEntityBuilder::ConfigureArchetypeCreation(const FMassArchetypeCreationParams& InCreationParams) { ArchetypeCreationParams = InCreationParams; } } // namespace UE::Mass #undef UE_API