Files
UnrealEngine/Engine/Source/Runtime/MassEntity/Public/MassCommandBuffer.h
2025-05-18 13:04:45 +08:00

249 lines
8.5 KiB
C++

// 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... TArgs> typename TCommand, typename... TArgs >
void PushCommand(const FMassEntityHandle Entity, TArgs&&... InArgs)
{
COMMAND_PUSHING_CHECK();
LLM_SCOPE_BYNAME(TEXT("Mass/PushCommand"));
TCommand<TArgs...>& Instance = CreateOrAddCommand<TCommand<TArgs...>>();
Instance.Add(Entity, Forward<TArgs>(InArgs)...);
++ActiveCommandsCounter;
}
template<typename TCommand, typename... TArgs>
void PushCommand(TArgs&&... InArgs)
{
COMMAND_PUSHING_CHECK();
LLM_SCOPE_BYNAME(TEXT("Mass/PushCommand"));
TCommand& Instance = CreateOrAddCommand<TCommand>();
Instance.Add(Forward<TArgs>(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<TCommand>().Add(Entity);
++ActiveCommandsCounter;
}
template< typename TCommand>
void PushCommand(TConstArrayView<FMassEntityHandle> Entities)
{
COMMAND_PUSHING_CHECK();
LLM_SCOPE_BYNAME(TEXT("Mass/PushCommand"));
CreateOrAddCommand<TCommand>().Add(Entities);
++ActiveCommandsCounter;
}
template<typename T>
void AddFragment(FMassEntityHandle Entity)
{
static_assert(UE::Mass::CFragment<T>, "Given struct type is not a valid fragment type.");
PushCommand<FMassCommandAddFragmentsInternal<EMassCommandCheckTime::CompileTimeCheck, T>>(Entity);
}
template<typename T>
void AddFragment_RuntimeCheck(FMassEntityHandle Entity)
{
checkf(UE::Mass::IsA<FMassFragment>(T::StaticStruct()), TEXT("Given struct type is not a valid fragment type."));
PushCommand<FMassCommandAddFragmentsInternal<EMassCommandCheckTime::RuntimeCheck, T>>(Entity);
}
template<typename T>
void RemoveFragment(FMassEntityHandle Entity)
{
static_assert(UE::Mass::CFragment<T>, "Given struct type is not a valid fragment type.");
PushCommand<FMassCommandRemoveFragmentsInternal<EMassCommandCheckTime::CompileTimeCheck, T>>(Entity);
}
template<typename T>
void RemoveFragment_RuntimeCheck(FMassEntityHandle Entity)
{
checkf(UE::Mass::IsA<FMassFragment>(T::StaticStruct()), TEXT("Given struct type is not a valid fragment type."));
PushCommand<FMassCommandRemoveFragmentsInternal<EMassCommandCheckTime::RuntimeCheck, T>>(Entity);
}
/** the convenience function equivalent to calling PushCommand<FMassCommandAddTag<T>>(Entity) */
template<typename T>
void AddTag(FMassEntityHandle Entity)
{
static_assert(UE::Mass::CTag<T>, "Given struct type is not a valid tag type.");
PushCommand<FMassCommandAddTagsInternal<EMassCommandCheckTime::CompileTimeCheck, T>>(Entity);
}
template<typename T>
void AddTag_RuntimeCheck(FMassEntityHandle Entity)
{
checkf(UE::Mass::IsA<FMassTag>(T::StaticStruct()), TEXT("Given struct type is not a valid tag type."));
PushCommand<FMassCommandAddTagsInternal<EMassCommandCheckTime::RuntimeCheck, T>>(Entity);
}
/** the convenience function equivalent to calling PushCommand<FMassCommandRemoveTag<T>>(Entity) */
template<typename T>
void RemoveTag(FMassEntityHandle Entity)
{
static_assert(UE::Mass::CTag<T>, "Given struct type is not a valid tag type.");
PushCommand<FMassCommandRemoveTagsInternal<EMassCommandCheckTime::CompileTimeCheck, T>>(Entity);
}
template<typename T>
void RemoveTag_RuntimeCheck(FMassEntityHandle Entity)
{
checkf(UE::Mass::IsA<FMassTag>(T::StaticStruct()), TEXT("Given struct type is not a valid tag type."));
PushCommand<FMassCommandRemoveTagsInternal<EMassCommandCheckTime::RuntimeCheck, T>>(Entity);
}
/** the convenience function equivalent to calling PushCommand<FMassCommandSwapTags<TOld, TNew>>(Entity) */
template<typename TOld, typename TNew>
void SwapTags(FMassEntityHandle Entity)
{
static_assert(UE::Mass::CTag<TOld>, "Given struct type is not a valid tag type.");
static_assert(UE::Mass::CTag<TNew>, "Given struct type is not a valid tag type.");
PushCommand<FMassCommandSwapTagsInternal<EMassCommandCheckTime::CompileTimeCheck, TOld, TNew>>(Entity);
}
template<typename TOld, typename TNew>
void SwapTags_RuntimeCheck(FMassEntityHandle Entity)
{
checkf(UE::Mass::IsA<FMassTag>(TOld::StaticStruct()), TEXT("Given struct type is not a valid tag type."));
checkf(UE::Mass::IsA<FMassTag>(TNew::StaticStruct()), TEXT("Given struct type is not a valid tag type."));
PushCommand<FMassCommandSwapTagsInternal<EMassCommandCheckTime::RuntimeCheck, TOld, TNew>>(Entity);
}
void DestroyEntity(FMassEntityHandle Entity)
{
PushCommand<FMassCommandDestroyEntities>(Entity);
}
void DestroyEntities(TConstArrayView<FMassEntityHandle> InEntitiesToDestroy)
{
PushCommand<FMassCommandDestroyEntities>(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<typename T>
T& CreateOrAddCommand()
{
const int32 Index = FMassBatchedCommand::GetCommandIndex<T>();
if (CommandInstances.IsValidIndex(Index) == false)
{
CommandInstances.AddZeroed(Index - CommandInstances.Num() + 1);
}
else if (CommandInstances[Index])
{
return (T&)(*CommandInstances[Index].Get());
}
CommandInstances[Index] = MakeUnique<T>();
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<TUniquePtr<FMassBatchedCommand>> 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<TUniquePtr<FMassBatchedCommand>> 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