// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Async/TransactionallySafeMutex.h" #include "Containers/ChunkedArray.h" #include "Misc/TVariant.h" #include "Templates/SharedPointer.h" #include "MassProcessingTypes.h" struct FMassArchetypeData; struct FMassEntityHandle; /** * Initialization parameters to configure MassEntityManager to reserve entities only single threaded * Supported in all build configurations */ struct FMassEntityManager_InitParams_SingleThreaded {}; /** * Initialization parameters to configure MassEntityManager to concurrently reserve entities * Only supported in editor builds. * * Expected static memory requirement for array of Page pointers can be computed: * MaxPages = MaxEntityCount / MaxEntitiesPerPage * MemorySize = MaxPages * sizeof(Page**) * * For default values, expectation is 128kB */ struct FMassEntityManager_InitParams_Concurrent { /** * Maximum supported entities by the MassEntityManager * Must be multiple of 2 */ uint32 MaxEntityCount = 1 << 30; // 1 billion /** * Number of entities per chunk * Must be multiple of 2 */ uint32 MaxEntitiesPerPage = 1 << 16; // 65536 }; using FMassEntityManagerStorageInitParams = TVariant; namespace UE::Mass { /** * Interface that abstracts the storage system for Mass Entities in the EntityManager * This may be temporary until the concurrent mechanism has been vetted for performance */ class IEntityStorageInterface { public: enum class EEntityState { /** Entity index refers to an entity that is free to be reserved or created */ Free, /** Entity index refers to a reserved entity */ Reserved, /** Entity index refers to an entity assigned to an archetype */ Created }; virtual ~IEntityStorageInterface() = default; virtual FMassArchetypeData* GetArchetype(int32 Index) = 0; virtual const FMassArchetypeData* GetArchetype(int32 Index) const = 0; virtual TSharedPtr& GetArchetypeAsShared(int32 Index) = 0; virtual const TSharedPtr& GetArchetypeAsShared(int32 Index) const = 0; virtual void SetArchetypeFromShared(int32 Index, TSharedPtr& Archetype) = 0; virtual void SetArchetypeFromShared(int32 Index, const TSharedPtr& Archetype) = 0; /** * Returns true if the given entity at index is currently reserved * False if free or assigned an archetype */ virtual EEntityState GetEntityState(int32 Index) const = 0; virtual int32 GetSerialNumber(int32 Index) const = 0; /** Checks if index can be used to access entity data */ virtual bool IsValidIndex(int32 Index) const = 0; /** * Checks if the given handle is valid in the context od this storage, i.e. whether the * index is valid and the serial number associated with it matches the handle's */ virtual bool IsValidHandle(FMassEntityHandle EntityHandle) const = 0; virtual SIZE_T GetAllocatedSize() const = 0; /** Checks if entity at Index is built */ virtual bool IsValid(int32 Index) const = 0; /** Produce a single entity handle */ virtual FMassEntityHandle AcquireOne() = 0; /** * @return number of entities actually added */ int32 Acquire(const int32 Count, TArray& OutEntityHandles); virtual int32 Acquire(TArrayView OutEntityHandles) = 0; virtual int32 Release(TConstArrayView Handles) = 0; virtual int32 ReleaseOne(FMassEntityHandle Handles) = 0; /** * Bypasses Serial Number Check * Only use if caller has ensured serial number matches or for debug purposes */ virtual int32 ForceRelease(TConstArrayView Handles) = 0; virtual int32 ForceReleaseOne(FMassEntityHandle Handle) = 0; /** * Returns the number of entities that are not free * For debug purposes only. In multi-threaded environments, the result is going to be out of date */ virtual int32 Num() const = 0; /** * Returns the number of entities that are free * For debug purposes only. In multi-threaded environments, the result is going to be out of date */ virtual int32 ComputeFreeSize() const = 0; }; //----------------------------------------------------------------------------- // FSingleThreadedEntityStorage //----------------------------------------------------------------------------- /** * This storage backend should be used when the user of MassEntityManager can guarantee * that all entity management will be done on a single thread. */ class FSingleThreadedEntityStorage final : public IEntityStorageInterface { public: void Initialize(const FMassEntityManager_InitParams_SingleThreaded&); virtual FMassArchetypeData* GetArchetype(int32 Index) override; virtual const FMassArchetypeData* GetArchetype(int32 Index) const override; virtual TSharedPtr& GetArchetypeAsShared(int32 Index) override; virtual const TSharedPtr& GetArchetypeAsShared(int32 Index) const override; virtual void SetArchetypeFromShared(int32 Index, TSharedPtr&) override; virtual void SetArchetypeFromShared(int32 Index, const TSharedPtr&) override; virtual EEntityState GetEntityState(int32 Index) const override; virtual int32 GetSerialNumber(int32 Index) const override; virtual bool IsValidIndex(int32 Index) const override; virtual bool IsValidHandle(FMassEntityHandle EntityHandle) const override; virtual SIZE_T GetAllocatedSize() const override; virtual bool IsValid(int32 Index) const override; virtual FMassEntityHandle AcquireOne() override; using IEntityStorageInterface::Acquire; virtual int32 Acquire(TArrayView OutEntityHandles) override; virtual int32 Release(TConstArrayView Handles) override; virtual int32 ReleaseOne(FMassEntityHandle Handle) override; virtual int32 ForceRelease(TConstArrayView Handles) override; virtual int32 ForceReleaseOne(FMassEntityHandle Handle) override; virtual int32 Num() const override; virtual int32 ComputeFreeSize() const override; private: struct FEntityData { TSharedPtr CurrentArchetype; int32 SerialNumber = 0; ~FEntityData(); void Reset(); bool IsValid() const; }; std::atomic SerialNumberGenerator = 0; TChunkedArray Entities; TArray EntityFreeIndexList; }; //----------------------------------------------------------------------------- // FConcurrentEntityStorage //----------------------------------------------------------------------------- /** * This storage backend allows for entities to be concurrently reserved. Reserved entities can also * be concurrently freed. * Creation of entities (i.e. assignment of an archetype and addition of data into chunks) cannot be done * concurrently with this implementation. */ class FConcurrentEntityStorage final : public IEntityStorageInterface { public: void Initialize(const FMassEntityManager_InitParams_Concurrent& InInitializationParams); virtual ~FConcurrentEntityStorage() override; virtual FMassArchetypeData* GetArchetype(int32 Index) override; virtual const FMassArchetypeData* GetArchetype(int32 Index) const override; virtual TSharedPtr& GetArchetypeAsShared(int32 Index) override; virtual const TSharedPtr& GetArchetypeAsShared(int32 Index) const override; virtual void SetArchetypeFromShared(int32 Index, TSharedPtr& Archetype) override; virtual void SetArchetypeFromShared(int32 Index, const TSharedPtr& Archetype) override; virtual EEntityState GetEntityState(int32 Index) const override; virtual int32 GetSerialNumber(int32 Index) const override; virtual bool IsValidIndex(int32 Index) const override; virtual bool IsValidHandle(FMassEntityHandle EntityHandle) const override; virtual SIZE_T GetAllocatedSize() const override; virtual bool IsValid(int32 Index) const override; virtual FMassEntityHandle AcquireOne() override; using IEntityStorageInterface::Acquire; virtual int32 Acquire(TArrayView OutEntityHandles) override; virtual int32 Release(TConstArrayView Handles) override; virtual int32 ReleaseOne(FMassEntityHandle Handle) override; virtual int32 ForceRelease(TConstArrayView Handles) override; virtual int32 ForceReleaseOne(FMassEntityHandle Handle) override; virtual int32 Num() const override; virtual int32 ComputeFreeSize() const override; #if WITH_MASSENTITY_DEBUG /** @return whether the assumptions are still valid */ MASSENTITY_API static bool DebugAssumptionsSelfTest(); #endif // WITH_MASSENTITY_DEBUG private: struct FEntityData { static constexpr int MaxGenerationBits = 30; TSharedPtr CurrentArchetype; /** Generation ID or version of the entity in this slot */ uint32 GenerationId : MaxGenerationBits = 0; /** 1 if the entity is NOT free */ uint32 bIsAllocated : 1 = 0; ~FEntityData(); /** Converts EntityData state into a SerialNumber for public usage */ int32 GetSerialNumber() const; bool operator==(const FEntityData& Other) const; }; FEntityData& LookupEntity(int32 Index); const FEntityData& LookupEntity(int32 Index) const; /** Returns size of a page in bytes */ uint64 ComputePageSize() const; /** * @return whether the operation was successful. Will return false when OOM */ bool AddPage(); /** Number of allocated Entities (only used for viewing in the debugger). */ uint32 EntityCount = 0; uint32 MaximumEntityCountShift = 0; uint32 MaxEntitiesPerPage = 0; uint32 PageCount = 0; /** ALWAYS acquire FreeListMutex before this one */ UE::FTransactionallySafeMutex PageAllocateMutex; /** Pointer to array of pages */ FEntityData** EntityPages = nullptr; TArray EntityFreeIndexList; UE::FTransactionallySafeMutex FreeListMutex; }; } // namespace UE::Mass