// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "MassEntityTypes.h" #if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6 #include "MassEntityManager.h" #endif // UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6 #include "Misc/MTAccessDetector.h" #include "MassEntityUtils.h" #include "MassCommands.h" struct FMassEntityManager; //@TODO: Consider debug information in case there is an assert when replaying the command buffer // (e.g., which system added the command, or even file/line number in development builds for the specific call via a macro) #define COMMAND_PUSHING_CHECK() \ checkf(IsFlushing() == false, TEXT("Trying to push commands is not supported while the given buffer is being flushed")); \ checkf(OwnerThreadId == FPlatformTLS::GetCurrentThreadId(), TEXT("Commands can be pushed only in the same thread where the command buffer was created.")) struct FMassCommandBuffer { public: MASSENTITY_API FMassCommandBuffer(); MASSENTITY_API ~FMassCommandBuffer(); /** Adds a new entry to a given TCommand batch command instance */ template< template typename TCommand, typename... TArgs > void PushCommand(const FMassEntityHandle Entity, TArgs&&... InArgs) { COMMAND_PUSHING_CHECK(); LLM_SCOPE_BYNAME(TEXT("Mass/PushCommand")); TCommand& Instance = CreateOrAddCommand>(); Instance.Add(Entity, Forward(InArgs)...); ++ActiveCommandsCounter; } template void PushCommand(TArgs&&... InArgs) { COMMAND_PUSHING_CHECK(); LLM_SCOPE_BYNAME(TEXT("Mass/PushCommand")); TCommand& Instance = CreateOrAddCommand(); Instance.Add(Forward(InArgs)...); ++ActiveCommandsCounter; } /** Adds a new entry to a given TCommand batch command instance */ template< typename TCommand> void PushCommand(const FMassEntityHandle Entity) { COMMAND_PUSHING_CHECK(); LLM_SCOPE_BYNAME(TEXT("Mass/PushCommand")); CreateOrAddCommand().Add(Entity); ++ActiveCommandsCounter; } template< typename TCommand> void PushCommand(TConstArrayView Entities) { COMMAND_PUSHING_CHECK(); LLM_SCOPE_BYNAME(TEXT("Mass/PushCommand")); CreateOrAddCommand().Add(Entities); ++ActiveCommandsCounter; } template void AddFragment(FMassEntityHandle Entity) { static_assert(UE::Mass::CFragment, "Given struct type is not a valid fragment type."); PushCommand>(Entity); } template void AddFragment_RuntimeCheck(FMassEntityHandle Entity) { checkf(UE::Mass::IsA(T::StaticStruct()), TEXT("Given struct type is not a valid fragment type.")); PushCommand>(Entity); } template void RemoveFragment(FMassEntityHandle Entity) { static_assert(UE::Mass::CFragment, "Given struct type is not a valid fragment type."); PushCommand>(Entity); } template void RemoveFragment_RuntimeCheck(FMassEntityHandle Entity) { checkf(UE::Mass::IsA(T::StaticStruct()), TEXT("Given struct type is not a valid fragment type.")); PushCommand>(Entity); } /** the convenience function equivalent to calling PushCommand>(Entity) */ template void AddTag(FMassEntityHandle Entity) { static_assert(UE::Mass::CTag, "Given struct type is not a valid tag type."); PushCommand>(Entity); } template void AddTag_RuntimeCheck(FMassEntityHandle Entity) { checkf(UE::Mass::IsA(T::StaticStruct()), TEXT("Given struct type is not a valid tag type.")); PushCommand>(Entity); } /** the convenience function equivalent to calling PushCommand>(Entity) */ template void RemoveTag(FMassEntityHandle Entity) { static_assert(UE::Mass::CTag, "Given struct type is not a valid tag type."); PushCommand>(Entity); } template void RemoveTag_RuntimeCheck(FMassEntityHandle Entity) { checkf(UE::Mass::IsA(T::StaticStruct()), TEXT("Given struct type is not a valid tag type.")); PushCommand>(Entity); } /** the convenience function equivalent to calling PushCommand>(Entity) */ template void SwapTags(FMassEntityHandle Entity) { static_assert(UE::Mass::CTag, "Given struct type is not a valid tag type."); static_assert(UE::Mass::CTag, "Given struct type is not a valid tag type."); PushCommand>(Entity); } template void SwapTags_RuntimeCheck(FMassEntityHandle Entity) { checkf(UE::Mass::IsA(TOld::StaticStruct()), TEXT("Given struct type is not a valid tag type.")); checkf(UE::Mass::IsA(TNew::StaticStruct()), TEXT("Given struct type is not a valid tag type.")); PushCommand>(Entity); } void DestroyEntity(FMassEntityHandle Entity) { PushCommand(Entity); } void DestroyEntities(TConstArrayView InEntitiesToDestroy) { PushCommand(InEntitiesToDestroy); } MASSENTITY_API SIZE_T GetAllocatedSize() const; /** * Appends the commands from the passed buffer into this one * @param InOutOther the source buffer to copy the commands from. Note that after the call the InOutOther will be * emptied due to the function using Move semantics */ MASSENTITY_API void MoveAppend(FMassCommandBuffer& InOutOther); bool HasPendingCommands() const { return ActiveCommandsCounter > 0; } bool IsFlushing() const { return bIsFlushing; } /** * Removes any pending command instances * This could be required for CommandBuffers that are queued to * flush their commands on the game thread but the EntityManager is no longer available. * In such scenario we need to cancel commands to avoid an ensure for unprocessed commands * when the buffer gets destroyed. */ void CancelCommands() { CleanUp(); } bool IsInOwnerThread() const { return OwnerThreadId == FPlatformTLS::GetCurrentThreadId(); } private: friend FMassEntityManager; void ForceUpdateCurrentThreadID(); template T& CreateOrAddCommand() { const int32 Index = FMassBatchedCommand::GetCommandIndex(); if (CommandInstances.IsValidIndex(Index) == false) { CommandInstances.AddZeroed(Index - CommandInstances.Num() + 1); } else if (CommandInstances[Index]) { return (T&)(*CommandInstances[Index].Get()); } CommandInstances[Index] = MakeUnique(); return (T&)(*CommandInstances[Index].Get()); } /** * Executes all accumulated commands. * @return whether any commands have actually been executed */ bool Flush(FMassEntityManager& EntityManager); MASSENTITY_API void CleanUp(); FCriticalSection AppendingCommandsCS; UE_MT_DECLARE_RW_ACCESS_DETECTOR(PendingBatchCommandsDetector); /** * Commands created for this specific command buffer. All commands in the array are unique (by type) and reusable * with subsequent PushCommand calls */ TArray> CommandInstances; /** * Commands appended to this command buffer (via FMassCommandBuffer::MoveAppend). These commands are just naive list * of commands, potentially containing duplicates with multiple MoveAppend calls. Once appended these commands are * not being reused and consumed, destructively, during flushing */ TArray> AppendedCommandInstances; int32 ActiveCommandsCounter = 0; /** Indicates that this specific MassCommandBuffer is currently flushing its contents */ bool bIsFlushing = false; /** * Identifies the thread where given FMassCommandBuffer instance was created. Adding commands from other * threads is not supported and we use this value to check that. * Note that it could be const since we set it in the constructor, but we need to recache on server forking. */ uint32 OwnerThreadId; }; #undef COMMAND_PUSHING_CHECK