// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "MassEntityHandle.h" #include "MassArchetypeTypes.h" namespace UE::Mass { /** * Type represents a collection of arbitrary EntityHandles. Under the hood the type stores also * an array of FMassArchetypeEntityCollection instances. These cached collections can be tested for * being up to date, and re-created on demand, based on stored entity handles. * * The type is intended to be used to collect entities available through different means: individual * handles, handle arrays and or FMassArchetypeEntityCollection instances. Such accumulated handles * can at any moment be turned into an array of up-to-date FMassArchetypeEntityCollection instance, * which in turn is how entity sets are provided to MassEntityManager's batched API. * * The biggest win while using this type is that the user doesn't have to worry about FMassArchetypeEntityCollection * instances going out of date (which happens whenever the target archetype is touched in a way that * changes internal entity indices). The type automatically updates the collections and caches the result. */ struct FEntityCollection { FEntityCollection() = default; /** * The following constructor are equivalent to using the default constructor and subsequently * calling AppendCollection or AppendHandles. */ MASSENTITY_API explicit FEntityCollection(FMassArchetypeEntityCollection&& InEntityCollection); MASSENTITY_API explicit FEntityCollection(const FMassArchetypeEntityCollection& InEntityCollection); MASSENTITY_API explicit FEntityCollection(const TConstArrayView InEntityHandles); MASSENTITY_API FEntityCollection(const TConstArrayView InEntityHandles, FMassArchetypeEntityCollection&& InEntityCollection); //----------------------------------------------------------------------------- // mutating API //----------------------------------------------------------------------------- /** * Appends Handles to stored EntityHandles. Results in marking cached FMassArchetypeEntityCollection as dirty. */ MASSENTITY_API void AppendHandles(TConstArrayView Handles); /** * Appends Handles to stored EntityHandles. * The second parameter is relevant if, at the moment of calling, the cached FMassArchetypeEntityCollection instances * are in sync with stored entity handles (meaning all entities stored in EntityHandles are also captured by one of * FMassArchetypeEntityCollection). If that's the case then the InEntityCollection gets stored along with * existing collections. Otherwise, InEntityCollection will be ignored. */ MASSENTITY_API void AppendHandles(TConstArrayView Handles, FMassArchetypeEntityCollection&& InEntityCollection); /** * Appends Handles to stored EntityHandles. Results in marking cached FMassArchetypeEntityCollection as dirty. */ MASSENTITY_API void AppendHandles(TArray&& Handles); /** * Appends the Handle to stored EntityHandles. Results in marking cached FMassArchetypeEntityCollection as dirty. */ MASSENTITY_API void AddHandle(FMassEntityHandle Handle); /** * Based on the provided FMassArchetypeEntityCollection creates an array of entity handles and stores them. * If up to this point the cached FMassArchetypeEntityCollection-s are consistent with stored EntityHandles * then InEntityCollection gets stored as well, and stored collections are not marked as dirty. */ template requires std::is_same_v::Type, FMassArchetypeEntityCollection> void AppendCollection(T&& InEntityCollection) { if (UNLIKELY(InEntityCollection.IsEmpty())) { return; } const bool bWasEmpty = EntityHandles.IsEmpty(); if (InEntityCollection.ExportEntityHandles(EntityHandles)) { ConditionallyStoreCollection(bWasEmpty, Forward(InEntityCollection)); } } /** * Results in duplicate handles being removed from EntityHandles, the cached collections being up-to-date * and CollectionCreationDuplicatesHandling being set to FMassArchetypeEntityCollection::NoDuplicates * * @param bForceOperation by default the entity handles will be re-exported only if * CollectionCreationDuplicatesHandling == FMassArchetypeEntityCollection::FoldDuplicates * (which means we cannot rule out that there are duplicates). Using bForceOperation = true * will perform the operation regardless. * * @return whether any duplicates were detected */ MASSENTITY_API bool UpdateAndRemoveDuplicates(const FMassEntityManager& EntityManager, bool bForceOperation = false); //----------------------------------------------------------------------------- // state-querying API //----------------------------------------------------------------------------- void MarkDirty() { CachedCollections.Reset(); } bool IsEmpty() const { ensureMsgf(!(EntityHandles.Num() == 0 && CachedCollections.Num() != 0), TEXT("Stored entity array is empty while there are stored collections. This is unexpected.")); return EntityHandles.IsEmpty(); } /** * Checks if cached collection data is up to date * If CachedCollections are not up-to-date we reset them to cache the information (and make the subsequent tests cheaper) * Note that, depending on the contents, the test might be non-trivial. Use responsibly. */ MASSENTITY_API bool IsUpToDate() const; //----------------------------------------------------------------------------- // data reading API //----------------------------------------------------------------------------- TConstArrayView GetEntityHandlesView() const { return EntityHandles; } /** * Retrieves the view to current contents of CachedCollections, which may be out of date. * If you need valid, up-to-date collections call GetUpToDatePerArchetypeCollections instead. */ TConstArrayView GetCachedPerArchetypeCollections() const { return CachedCollections; } /** * Fetches up-to-date FMassArchetypeEntityCollection instances matching stored * entity handles. */ TConstArrayView GetUpToDatePerArchetypeCollections(const FMassEntityManager& EntityManager) const { ConditionallyUpdate(EntityManager); return CachedCollections; } /** * Updates cached archetype collections and returns the container with move semantics */ TArray ConsumeArchetypeCollections(const FMassEntityManager& EntityManager) && { ConditionallyUpdate(EntityManager); return MoveTemp(CachedCollections); } private: template requires std::is_same_v::Type, FMassArchetypeEntityCollection> void ConditionallyStoreCollection(const bool bWasEmpty, T&& InEntityCollection) { // if there was no previous data, or the data was "complete", meaning we had collections for stored handles // Note: this condition only works as expected if we make sure that adding handles without associated collections // resets the CachedCollections array // @todo add unit tests to ensure this behavior if (bWasEmpty || !CachedCollections.IsEmpty()) { if (!CachedCollections.IsEmpty() && CachedCollections.Last().IsSameArchetype(InEntityCollection)) { // merge with the last collection since it's the same archetype. CachedCollections.Last().Append(Forward(InEntityCollection)); } else { CachedCollections.Emplace(Forward(InEntityCollection)); } } else { CachedCollections.Reset(); } } MASSENTITY_API void ConditionallyUpdate(const FMassEntityManager& EntityManager) const; /** * these are the entities represented by given instance of FEntityCollection. * EntityHandles are the authority, source of truth regarding the contents */ TArray EntityHandles; /** * Cached per-archetype collections of entities. Can go our of date due to * operations performed on this FEntityCollection instance (in this case we * reset cached CachedCollections) or due to the stored entities being moved * between archetypes. */ mutable TArray CachedCollections; /** * Stores information whether we can expect duplicates in EntityHandles when building CachedCollections */ FMassArchetypeEntityCollection::EDuplicatesHandling CollectionCreationDuplicatesHandling = FMassArchetypeEntityCollection::NoDuplicates; }; }