Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

376 lines
13 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/MTTransactionallySafeAccessDetector.h"
#include "Misc/TransactionallySafeCriticalSection.h"
#include "MassEntityUtils.h"
#include "MassDebuggerBreakpoints.h"
#include "MassCommands.h"
// required for UE::Mass::Deprecation
#include <type_traits>
#include "Concepts/ConvertibleTo.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."))
// The following is a temporary construct to help users update their code after FMassBatchedCommand::Execute deprecation
// @todo remove by 5.9
namespace UE::Mass::Deprecation
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
template <typename TCommand>
constexpr bool IsExecuteOverridden()
{
using TResolved = decltype(&TCommand::Execute);
using TBase = decltype(&FMassBatchedCommand::Execute);
return !std::is_same_v<TResolved, TBase>;
}
template <typename TCommand>
requires std::is_base_of_v<FMassBatchedCommand, TCommand>
struct TOverridesExecute : std::bool_constant<IsExecuteOverridden<TCommand>()>
{
};
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
#define ASSERT_EXECUTE_DEPRECATION(CommandType) \
static_assert(!UE::Mass::Deprecation::TOverridesExecute<CommandType>::value, "Mass Commands: CONST Execute function is deprecated in 5.7 and will be removed by 5.9. Use Run instead.");
namespace UE::Mass::Debug
{
template<typename TCommand, typename... Args>
concept HasCheckBreakpoints =
requires(Args&&... args) {
{ TCommand::CheckBreakpoints(Forward<Args>(args)...) }
-> CConvertibleTo<bool>;
};
template<typename TCommand, typename... Args>
concept HasCheckBreakpointsWithEntity =
requires(const FMassEntityHandle Entity, Args&&... args) {
{ TCommand::CheckBreakpoints(Entity, Forward<Args>(args)...) }
-> CConvertibleTo<bool>;
};
template<typename TCommand, typename... TArgs>
void CallCheckBreakpoints(TArgs&&... InArgs)
{
#if WITH_MASSENTITY_DEBUG
if constexpr (HasCheckBreakpoints<TCommand, TArgs...>)
{
if (TCommand::CheckBreakpoints(Forward<TArgs>(InArgs)...))
{
UE::Mass::Debug::FBreakpoint::DebugBreak();
}
}
#endif //WITH_MASSENTITY_DEBUG
}
template<typename TCommand, typename... TArgs >
void CallCheckBreakpointsByInstance(TArgs&&... InArgs)
{
#if WITH_MASSENTITY_DEBUG
if constexpr (HasCheckBreakpointsWithEntity<TCommand, TArgs...>)
{
if (TCommand::CheckBreakpointsByInstance(Forward<TArgs>(InArgs)...))
{
UE::Mass::Debug::FBreakpoint::DebugBreak();
}
}
#endif //WITH_MASSENTITY_DEBUG
}
} // namespace UE::Mass::Debug
struct FMassCommandBuffer
{
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();
UE::Mass::Debug::CallCheckBreakpointsByInstance<TCommand<TArgs...>>(Entity, Forward<TArgs>(InArgs)...);
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();
UE::Mass::Debug::CallCheckBreakpointsByInstance<TCommand>(Forward<TArgs>(InArgs)...);
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();
UE::Mass::Debug::CallCheckBreakpoints<TCommand>(Entity);
LLM_SCOPE_BYNAME(TEXT("Mass/PushCommand"));
CreateOrAddCommand<TCommand>().Add(Entity);
++ActiveCommandsCounter;
}
template< typename TCommand>
void PushCommand(TConstArrayView<FMassEntityHandle> Entities)
{
COMMAND_PUSHING_CHECK();
UE::Mass::Debug::CallCheckBreakpoints<TCommand>(Entities);
LLM_SCOPE_BYNAME(TEXT("Mass/PushCommand"));
CreateOrAddCommand<TCommand>().Add(Entities);
++ActiveCommandsCounter;
}
/**
* Ordinary PushCommand calls try to reuse existing command instances (as stored in CommandInstances)
* based on their type.
* This command lets callers add a manually configured command instance, that
* might not be distinguishable from other commands based solely on its type.
* For example, when you implement a command type that has member properties that
* control how the given command works or what exactly it does.
*/
void PushUniqueCommand(TUniquePtr<FMassBatchedCommand>&& CommandInstance)
{
COMMAND_PUSHING_CHECK();
LLM_SCOPE_BYNAME(TEXT("Mass/PushCommand"));
UE::TScopeLock Lock(AppendingCommandsCS);
UE_MT_SCOPED_WRITE_ACCESS(PendingBatchCommandsDetector);
AppendedCommandInstances.Add(MoveTemp(CommandInstance));
++ActiveCommandsCounter;
}
template<typename T>
void AddFragment(FMassEntityHandle Entity)
{
static_assert(UE::Mass::CFragment<T>, MASS_INVALID_FRAGMENT_MSG);
PushCommand<FMassCommandAddFragmentsInternal<EMassCommandCheckTime::CompileTimeCheck, T>>(Entity);
}
template<typename T>
void AddFragment_RuntimeCheck(FMassEntityHandle Entity)
{
checkf(UE::Mass::IsA<FMassFragment>(T::StaticStruct()), TEXT(MASS_INVALID_FRAGMENT_MSG_F), *T::StaticStruct()->GetName());
PushCommand<FMassCommandAddFragmentsInternal<EMassCommandCheckTime::RuntimeCheck, T>>(Entity);
}
template<typename T>
void RemoveFragment(FMassEntityHandle Entity)
{
static_assert(UE::Mass::CFragment<T>, MASS_INVALID_FRAGMENT_MSG);
PushCommand<FMassCommandRemoveFragmentsInternal<EMassCommandCheckTime::CompileTimeCheck, T>>(Entity);
}
template<typename T>
void RemoveFragment_RuntimeCheck(FMassEntityHandle Entity)
{
checkf(UE::Mass::IsA<FMassFragment>(T::StaticStruct()), TEXT(MASS_INVALID_FRAGMENT_MSG_F), *T::StaticStruct()->GetName());
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(TConstArrayView<FMassEntityHandle> Entities)
{
static_assert(UE::Mass::CTag<T>, "Given struct type is not a valid tag type.");
PushCommand<FMassCommandAddTagsInternal<EMassCommandCheckTime::CompileTimeCheck, T>>(Entities);
}
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(TConstArrayView<FMassEntityHandle> Entities)
{
static_assert(UE::Mass::CTag<T>, "Given struct type is not a valid tag type.");
PushCommand<FMassCommandRemoveTagsInternal<EMassCommandCheckTime::CompileTimeCheck, T>>(Entities);
}
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);
}
void DestroyEntities(TArray<FMassEntityHandle>&& InEntitiesToDestroy)
{
PushCommand<FMassCommandDestroyEntities>(MoveTemp(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();
}
/**
* Updates the OwnerThreadId which indicates that the given command buffer instance is being
* used in a different thread now. Use this with extreme caution, it's not a tool to be used
* every time we get "Commands can be pushed only in the same thread where the command buffer was created."
* error. It's meant to be used when there's a possibility the code owning the buffer has been
* moved to another thread (like in ParallelFor).
*/
void ForceUpdateCurrentThreadID();
private:
friend FMassEntityManager;
template<typename T>
T& CreateOrAddCommand()
{
ASSERT_EXECUTE_DEPRECATION(T);
static_assert(!UE::Mass::Command::TCommandTraits<T>::RequiresUniqueHandling, "This command type needs to be added via PushUniqueCommand");
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();
FTransactionallySafeCriticalSection AppendingCommandsCS;
UE_MT_DECLARE_TS_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