Files
UnrealEngine/Engine/Plugins/Runtime/MassGameplay/Source/MassRepresentation/Private/MassRepresentationProcessor.cpp
2025-05-18 13:04:45 +08:00

483 lines
24 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MassRepresentationProcessor.h"
#include "MassRepresentationDebug.h"
#include "MassRepresentationSubsystem.h"
#include "MassRepresentationUtils.h"
#include "MassActorSubsystem.h"
#include "MassCommonFragments.h"
#include "MassCommandBuffer.h"
#include "MassEntityManager.h"
#include "MassEntityView.h"
#include "Engine/World.h"
#include "MassRepresentationActorManagement.h"
#include "MassCommonUtils.h"
#include "MassEntityUtils.h"
#include "MassExecutionContext.h"
DECLARE_CYCLE_STAT(TEXT("Mass Visualization Execute"), STAT_Mass_VisProcessor_Execute, STATGROUP_Mass);
namespace UE::Mass::Representation
{
int32 bAllowKeepActorExtraFrame = 1;
FAutoConsoleVariableRef CVarAllowKeepActorExtraFrame(TEXT("ai.massrepresentation.AllowKeepActorExtraFrame"), bAllowKeepActorExtraFrame, TEXT("Allow the mass representation to keep actor an extra frame when switching to ISM"), ECVF_Default);
}
//----------------------------------------------------------------------//
// UMassRepresentationProcessor();
//----------------------------------------------------------------------//
UMassRepresentationProcessor::UMassRepresentationProcessor()
: EntityQuery(*this)
{
bAutoRegisterWithProcessingPhases = false;
ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Representation;
ExecutionOrder.ExecuteAfter.Add(UE::Mass::ProcessorGroupNames::LOD);
}
void UMassRepresentationProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
EntityQuery.AddRequirement<FTransformFragment>(EMassFragmentAccess::ReadOnly);
EntityQuery.AddRequirement<FMassRepresentationFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddRequirement<FMassRepresentationLODFragment>(EMassFragmentAccess::ReadOnly);
EntityQuery.AddRequirement<FMassActorFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddConstSharedRequirement<FMassRepresentationParameters>();
EntityQuery.AddSharedRequirement<FMassRepresentationSubsystemSharedFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddSubsystemRequirement<UMassActorSubsystem>(EMassFragmentAccess::ReadWrite);
}
void UMassRepresentationProcessor::UpdateRepresentation(FMassExecutionContext& Context, const FMassRepresentationUpdateParams& Params)
{
UMassRepresentationSubsystem* RepresentationSubsystem = Context.GetMutableSharedFragment<FMassRepresentationSubsystemSharedFragment>().RepresentationSubsystem;
check(RepresentationSubsystem);
const FMassRepresentationParameters& RepresentationParams = Context.GetConstSharedFragment<FMassRepresentationParameters>();
UMassRepresentationActorManagement* RepresentationActorManagement = RepresentationParams.CachedRepresentationActorManagement;
check(RepresentationActorManagement);
UMassActorSubsystem* MassActorSubsystem = Context.GetMutableSubsystem<UMassActorSubsystem>();
FMassEntityManager& CachedEntityManager = Context.GetEntityManagerChecked();
// Get Transform, Representation, RepresentationLOD and Actor fragments from Context
const TConstArrayView<FTransformFragment> TransformList = Context.GetFragmentView<FTransformFragment>();
const TArrayView<FMassRepresentationFragment> RepresentationList = Context.GetMutableFragmentView<FMassRepresentationFragment>();
const TConstArrayView<FMassRepresentationLODFragment> RepresentationLODList = Context.GetFragmentView<FMassRepresentationLODFragment>();
const TArrayView<FMassActorFragment> ActorList = Context.GetMutableFragmentView<FMassActorFragment>();
const bool bDoKeepActorExtraFrame = UE::Mass::Representation::bAllowKeepActorExtraFrame ? RepresentationParams.bKeepLowResActors : false;
// Iterate over all entities, and:
// 1. Find their current EMassRepresentationType value based on their current RepresentationLOD;
// 2. Change EMassRepresentationType value based on some configs (not all flows will care about this);
// 3. Switch the in-game instance representation depending on the EMassRepresentationType;
// a. If EMassRepresentationType == HighResSpawnedActor or LowResSpawnedActor, sends an Actor SpawnRequest to MassRepresentationActorManagement;
// b. If EMassRepresentationType == StaticMeshInstance, sends an Actor disable request to MassRepresentationActorManagement;
// c. If EMassRepresentationType == StaticMeshInstance, sends an Actor disable request to MassRepresentationActorManagement.
// NOTE: I guess this system assumes all instances are already represented by ISMs, which is why we're only dealing with Actor spawn/deactivation
for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
{
const FMassEntityHandle MassAgentEntityHandle = Context.GetEntity(EntityIt);
const FTransformFragment& TransformFragment = TransformList[EntityIt];
const FMassRepresentationLODFragment& RepresentationLOD = RepresentationLODList[EntityIt];
FMassRepresentationFragment& Representation = RepresentationList[EntityIt];
FMassActorFragment& ActorInfo = ActorList[EntityIt];
AActor* Actor = ActorInfo.GetMutable();
// Keeping a copy of the that last calculated previous representation
const EMassRepresentationType PrevRepresentationCopy = Representation.PrevRepresentation;
Representation.PrevRepresentation = Representation.CurrentRepresentation;
// === 1. Find the current EMassRepresentationType value based on their current RepresentationLOD
EMassRepresentationType WantedRepresentationType = RepresentationParams.LODRepresentation[FMath::Min((int32)RepresentationLOD.LOD, (int32)EMassLOD::Off)];
// === 1 end
// === 2. Change EMassRepresentationType value based on some configs (not all flows will care about this)
// Make sure we do not have actor spawned in areas not fully loaded
if (Params.bTestCollisionAvailibilityForActorVisualization
&& (WantedRepresentationType == EMassRepresentationType::HighResSpawnedActor || WantedRepresentationType == EMassRepresentationType::LowResSpawnedActor)
&& !RepresentationSubsystem->IsCollisionLoaded(RepresentationParams.WorldPartitionGridNameContainingCollision, TransformFragment.GetTransform()))
{
WantedRepresentationType = RepresentationParams.CachedDefaultRepresentationType;
}
// If bForceActorRepresentationForExternalActors is enabled and we have an Actor reference for this entity, forcibly use it
// by enforcing an actor representation as the WantedRepresentation. If we're coming from ISMC, we'll remove the instance
// and switch to this actor, commiting either LowResSpawnedActor or HighResSpawnedActor as the new CurrentRepresentation.
// Once the Actor is destroyed however, this override stops, allowing the natural WantedRepresentationType to return.
//
// Useful for server-authoritative Actor spawning, with replicated Actors inserting themselves into Mass whilst they're
// replicated, enforcing actor representation on clients whilst they're present.
//
// NOTE:
// IsOwnedByMass = Hydrated by mass
// !IsOwnedByMass = Hydrated by some external system
if (IsValid(Actor))
{
if (RepresentationParams.bForceActorRepresentationForExternalActors && !ActorInfo.IsOwnedByMass())
{
WantedRepresentationType = (Representation.CurrentRepresentation == EMassRepresentationType::LowResSpawnedActor)
? EMassRepresentationType::LowResSpawnedActor
: EMassRepresentationType::HighResSpawnedActor;
}
}
// Has Actor unexpectedly been unset / destroyed since we last ran?
else if (Representation.CurrentRepresentation == EMassRepresentationType::LowResSpawnedActor
|| Representation.CurrentRepresentation == EMassRepresentationType::HighResSpawnedActor)
{
// Set CurrentRepresentation = None so we get a chance to see CurrentRepresentation != WantedRepresentationType and spawn
// another actor.
Representation.CurrentRepresentation = EMassRepresentationType::None;
}
// === 2 end
auto DisableActorForISM = [&](AActor*& Actor)
{
if (!Actor || ActorInfo.IsOwnedByMass())
{
// Execute only if the high res is different than the low res Actor
// Or if we do not wish to keep the low res actor while in TransformList
if (Representation.HighResTemplateActorIndex != Representation.LowResTemplateActorIndex || !RepresentationParams.bKeepLowResActors)
{
// Try releasing the high actor or any high res spawning request
if (ReleaseActorOrCancelSpawning(*RepresentationSubsystem, MassActorSubsystem, MassAgentEntityHandle, ActorInfo, Representation.HighResTemplateActorIndex, Representation.ActorSpawnRequestHandle, Context.Defer()))
{
Actor = ActorInfo.GetOwnedByMassMutable();
}
// Do not do the same with low res if indicated so
if (!RepresentationParams.bKeepLowResActors && ReleaseActorOrCancelSpawning(*RepresentationSubsystem, MassActorSubsystem, MassAgentEntityHandle, ActorInfo, Representation.LowResTemplateActorIndex, Representation.ActorSpawnRequestHandle, Context.Defer()))
{
Actor = ActorInfo.GetOwnedByMassMutable();
}
}
// If we already queued spawn request but have changed our mind, continue with it but once we get the actor back, disable it immediately
if (Representation.ActorSpawnRequestHandle.IsValid())
{
Actor = RepresentationActorManagement->GetOrSpawnActor(*RepresentationSubsystem, CachedEntityManager, MassAgentEntityHandle, TransformFragment.GetTransform(), Representation.LowResTemplateActorIndex, Representation.ActorSpawnRequestHandle, RepresentationActorManagement->GetSpawnPriority(RepresentationLOD));
}
}
if (Actor != nullptr)
{
RepresentationActorManagement->SetActorEnabled(EMassActorEnabledType::Disabled, *Actor, EntityIt, Context.Defer());
}
};
// === 3. Switch the in-game instance representation depending on the EMassRepresentationType;
// Process switch between representation if there is a change in the representation or there is a pending spawning request
if (WantedRepresentationType != Representation.CurrentRepresentation || Representation.ActorSpawnRequestHandle.IsValid())
{
if (Representation.CurrentRepresentation == EMassRepresentationType::None)
{
Representation.PrevTransform = TransformFragment.GetTransform();
Representation.PrevLODSignificance = RepresentationLOD.LODSignificance;
}
switch (WantedRepresentationType)
{
case EMassRepresentationType::HighResSpawnedActor:
case EMassRepresentationType::LowResSpawnedActor:
{
const bool bHighResActor = WantedRepresentationType == EMassRepresentationType::HighResSpawnedActor;
// Reuse actor, if it is valid and not owned by mass or same representation as low res without a valid spawning request
AActor* NewActor = nullptr;
if (!Actor || ActorInfo.IsOwnedByMass())
{
const int16 WantedTemplateActorIndex = bHighResActor ? Representation.HighResTemplateActorIndex : Representation.LowResTemplateActorIndex;
// If the low res is different than the high res, cancel any pending spawn request that is the opposite of what is needed.
if (Representation.LowResTemplateActorIndex != Representation.HighResTemplateActorIndex)
{
ReleaseActorOrCancelSpawning(*RepresentationSubsystem, MassActorSubsystem, MassAgentEntityHandle, ActorInfo, bHighResActor ? Representation.LowResTemplateActorIndex : Representation.HighResTemplateActorIndex, Representation.ActorSpawnRequestHandle, Context.Defer(), /*bCancelSpawningOnly*/true);
Actor = ActorInfo.GetOwnedByMassMutable();
}
// If there isn't any actor yet or
// If the actor isn't matching the one needed or
// If there is still a pending spawn request
// Then try to retrieve/spawn the new actor
if (!Actor ||
!RepresentationSubsystem->DoesActorMatchTemplate(*Actor, WantedTemplateActorIndex) ||
Representation.ActorSpawnRequestHandle.IsValid())
{
NewActor = RepresentationActorManagement->GetOrSpawnActor(*RepresentationSubsystem, CachedEntityManager, MassAgentEntityHandle, TransformFragment.GetTransform(), WantedTemplateActorIndex, Representation.ActorSpawnRequestHandle, RepresentationActorManagement->GetSpawnPriority(RepresentationLOD));
}
else
{
NewActor = Actor;
}
}
else
{
NewActor = Actor;
}
if (NewActor)
{
// Make sure our (re)activated actor is at the simulated position
// Needs to be done before enabling the actor so the animation initialization can use the new values
if (Representation.CurrentRepresentation == EMassRepresentationType::StaticMeshInstance)
{
RepresentationActorManagement->TeleportActor(Representation.PrevTransform, *NewActor, Context.Defer());
}
RepresentationActorManagement->SetActorEnabled(bHighResActor ? EMassActorEnabledType::HighRes : EMassActorEnabledType::LowRes, *NewActor, EntityIt, Context.Defer());
Representation.CurrentRepresentation = WantedRepresentationType;
}
else if (!Actor)
{
Representation.CurrentRepresentation = RepresentationParams.CachedDefaultRepresentationType;
}
break;
}
case EMassRepresentationType::StaticMeshInstance:
if (!bDoKeepActorExtraFrame ||
(Representation.PrevRepresentation != EMassRepresentationType::HighResSpawnedActor && Representation.PrevRepresentation != EMassRepresentationType::LowResSpawnedActor))
{
DisableActorForISM(Actor);
}
Representation.CurrentRepresentation = EMassRepresentationType::StaticMeshInstance;
break;
case EMassRepresentationType::None:
if (!Actor || ActorInfo.IsOwnedByMass())
{
// Try releasing both, could have an high res spawned actor and a spawning request for a low res one
ReleaseActorOrCancelSpawning(*RepresentationSubsystem, MassActorSubsystem, MassAgentEntityHandle, ActorInfo, Representation.LowResTemplateActorIndex, Representation.ActorSpawnRequestHandle, Context.Defer());
ReleaseActorOrCancelSpawning(*RepresentationSubsystem, MassActorSubsystem, MassAgentEntityHandle, ActorInfo, Representation.HighResTemplateActorIndex, Representation.ActorSpawnRequestHandle, Context.Defer());
}
else
{
RepresentationActorManagement->SetActorEnabled(EMassActorEnabledType::Disabled, *Actor, EntityIt, Context.Defer());
}
Representation.CurrentRepresentation = EMassRepresentationType::None;
break;
default:
checkf(false, TEXT("Unsupported LOD type"));
break;
}
}
else if (bDoKeepActorExtraFrame
&& Representation.PrevRepresentation == EMassRepresentationType::StaticMeshInstance
&& (PrevRepresentationCopy == EMassRepresentationType::HighResSpawnedActor
|| PrevRepresentationCopy == EMassRepresentationType::LowResSpawnedActor))
{
DisableActorForISM(Actor);
}
// === 3 end
}
#if WITH_MASSGAMEPLAY_DEBUG
// Optional debug display
if (UE::Mass::Representation::Debug::DebugRepresentation == 1 || UE::Mass::Representation::Debug::DebugRepresentation >= 3)
{
TRACE_CPUPROFILER_EVENT_SCOPE(DebugDisplayRepresentation)
UWorld* World = CachedEntityManager.GetWorld();
UE::Mass::Representation::Debug::DebugDisplayRepresentation(Context, RepresentationLODList, RepresentationList, TransformList, World);
}
// Optional vislog
if (UE::Mass::Representation::Debug::DebugRepresentation >= 2)
{
TRACE_CPUPROFILER_EVENT_SCOPE(VisLogRepresentation)
UE::Mass::Representation::Debug::VisLogRepresentation(Context, RepresentationLODList, RepresentationList, TransformList, RepresentationSubsystem);
}
#endif
}
void UMassRepresentationProcessor::Execute(FMassEntityManager& InEntityManager, FMassExecutionContext& Context)
{
// Update entities representation
EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context)
{
UpdateRepresentation(Context, UpdateParams);
});
}
bool UMassRepresentationProcessor::ReleaseActorOrCancelSpawning(UMassRepresentationSubsystem& RepresentationSubsystem, UMassActorSubsystem* MassActorSubsystem
, const FMassEntityHandle MassAgent, FMassActorFragment& ActorInfo, const int16 TemplateActorIndex, FMassActorSpawnRequestHandle& SpawnRequestHandle
, FMassCommandBuffer& CommandBuffer, bool bCancelSpawningOnly /*= false*/)
{
if (TemplateActorIndex == INDEX_NONE)
{
// Nothing to release
return false;
}
check(!ActorInfo.IsValid() || ActorInfo.IsOwnedByMass());
AActor* Actor = ActorInfo.GetOwnedByMassMutable();
// note that it's fine for Actor to be null. That means the RepresentationSubsystem will try to stop
// the spawning of whatever SpawnRequestHandle reference to
const bool bSuccess = bCancelSpawningOnly ? RepresentationSubsystem.CancelSpawning(MassAgent, TemplateActorIndex, SpawnRequestHandle) :
RepresentationSubsystem.ReleaseTemplateActorOrCancelSpawning(MassAgent, TemplateActorIndex, Actor, SpawnRequestHandle);
if (bSuccess)
{
Actor = ActorInfo.GetOwnedByMassMutable();
if (Actor && RepresentationSubsystem.DoesActorMatchTemplate(*Actor, TemplateActorIndex))
{
ActorInfo.ResetNoHandleMapUpdate();
TObjectKey<const AActor> ActorKey(Actor);
if (MassActorSubsystem)
{
CommandBuffer.PushCommand<FMassDeferredSetCommand>([MassActorSubsystem, ActorKey](FMassEntityManager&)
{
MassActorSubsystem->RemoveHandleForActor(ActorKey);
});
}
}
return true;
}
return false;
}
//----------------------------------------------------------------------//
// UMassVisualizationProcessor
//----------------------------------------------------------------------//
void UMassVisualizationProcessor::UpdateVisualization(FMassExecutionContext& Context)
{
FMassVisualizationChunkFragment& ChunkData = UpdateChunkVisibility(Context);
if (!ChunkData.ShouldUpdateVisualization())
{
return;
}
UpdateRepresentation(Context, UpdateParams);
// Update entity visibility
const TArrayView<FMassRepresentationFragment> RepresentationList = Context.GetMutableFragmentView<FMassRepresentationFragment>();
const TConstArrayView<FMassRepresentationLODFragment> RepresentationLODList = Context.GetFragmentView<FMassRepresentationLODFragment>();
for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
{
const FMassEntityHandle Entity = Context.GetEntity(EntityIt);
FMassRepresentationFragment& Representation = RepresentationList[EntityIt];
const FMassRepresentationLODFragment& RepresentationLOD = RepresentationLODList[EntityIt];
UpdateEntityVisibility(Entity, Representation, RepresentationLOD, ChunkData, Context.Defer());
}
}
void UMassVisualizationProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
Super::ConfigureQueries(EntityManager);
EntityQuery.AddChunkRequirement<FMassVisualizationChunkFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddTagRequirement<FMassVisualizationProcessorTag>(EMassFragmentPresence::All);
}
FMassVisualizationChunkFragment& UMassVisualizationProcessor::UpdateChunkVisibility(FMassExecutionContext& Context) const
{
const FMassRepresentationParameters& RepresentationParams = Context.GetConstSharedFragment<FMassRepresentationParameters>();
bool bFirstUpdate = false;
// Setup chunk fragment data about visibility
FMassVisualizationChunkFragment& ChunkData = Context.GetMutableChunkFragment<FMassVisualizationChunkFragment>();
EMassVisibility ChunkVisibility = ChunkData.GetVisibility();
if (ChunkVisibility == EMassVisibility::Max)
{
// The visibility on the chunk fragment data isn't set yet, let see if the Archetype has an visibility tag and set it on the ChunkData
ChunkVisibility = UE::Mass::Representation::GetVisibilityFromArchetype(Context);
ChunkData.SetVisibility(ChunkVisibility);
bFirstUpdate = RepresentationParams.bSpreadFirstVisualizationUpdate;
}
else
{
checkfSlow(UE::Mass::Representation::IsVisibilityTagSet(Context, ChunkVisibility), TEXT("Expecting the same Visibility as what we saved in the chunk data, maybe external code is modifying the tags"))
}
if (ChunkVisibility == EMassVisibility::CulledByDistance)
{
float DeltaTime = ChunkData.GetDeltaTime();
if (bFirstUpdate)
{
// A DeltaTime of 0.0f means it will tick this frame.
// @todo: Add some randomization for deterministic runs too. The randomization is used to distribute the infrequent ticks evenly on different frames.
DeltaTime = UE::Mass::Utils::IsDeterministic() ? RepresentationParams.NotVisibleUpdateRate * 0.5f : FMath::RandRange(0.0f, RepresentationParams.NotVisibleUpdateRate);
}
else
{
if (DeltaTime < 0.0f)
{
// @todo: Add some randomization for deterministic runs too. The randomization is used to distribute the infrequent ticks evenly on different frames.
DeltaTime += UE::Mass::Utils::IsDeterministic() ? RepresentationParams.NotVisibleUpdateRate : (RepresentationParams.NotVisibleUpdateRate * (1.0f + FMath::RandRange(-0.1f, 0.1f)));
}
DeltaTime -= Context.GetDeltaTimeSeconds();
}
ChunkData.Update(DeltaTime);
}
return ChunkData;
}
void UMassVisualizationProcessor::UpdateEntityVisibility(const FMassEntityHandle Entity, const FMassRepresentationFragment& Representation, const FMassRepresentationLODFragment& RepresentationLOD, FMassVisualizationChunkFragment& ChunkData, FMassCommandBuffer& CommandBuffer)
{
// Move the visible entities together into same chunks so we can skip entire chunk when not visible as an optimization
const EMassVisibility Visibility = Representation.CurrentRepresentation != EMassRepresentationType::None ?
EMassVisibility::CanBeSeen : RepresentationLOD.Visibility;
const EMassVisibility ChunkVisibility = ChunkData.GetVisibility();
if (ChunkVisibility != Visibility)
{
UE::Mass::Representation::PushSwapTagsCommand(CommandBuffer, Entity, ChunkVisibility, Visibility);
ChunkData.SetContainsNewlyVisibleEntity(Visibility == EMassVisibility::CanBeSeen);
}
}
void UMassVisualizationProcessor::Execute(FMassEntityManager& InEntityManager, FMassExecutionContext& Context)
{
SCOPE_CYCLE_COUNTER(STAT_Mass_VisProcessor_Execute);
int32 TotalEntitiesProcessed = 0;
// Update entities visualization
EntityQuery.ForEachEntityChunk(Context, [this, &TotalEntitiesProcessed](FMassExecutionContext& Context)
{
TotalEntitiesProcessed += Context.GetNumEntities();
UpdateVisualization(Context);
});
UE_VLOG(this, LogMassRepresentation, Verbose, TEXT("UMassVisualizationProcessor::Execute processed %d entities"), TotalEntitiesProcessed);
}
//----------------------------------------------------------------------//
// UMassRepresentationFragmentDestructor
//----------------------------------------------------------------------//
UMassRepresentationFragmentDestructor::UMassRepresentationFragmentDestructor()
: EntityQuery(*this)
{
ObservedType = FMassRepresentationFragment::StaticStruct();
Operation = EMassObservedOperation::Remove;
ExecutionFlags = (int32)EProcessorExecutionFlags::AllNetModes;
bRequiresGameThreadExecution = true; // due to FMassRepresentationSubsystemSharedFragment.RepresentationSubsystem use
}
void UMassRepresentationFragmentDestructor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
EntityQuery.AddRequirement<FMassRepresentationFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddRequirement<FMassActorFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddConstSharedRequirement<FMassRepresentationParameters>();
EntityQuery.AddSharedRequirement<FMassRepresentationSubsystemSharedFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddSubsystemRequirement<UMassActorSubsystem>(EMassFragmentAccess::ReadWrite);
}
void UMassRepresentationFragmentDestructor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context)
{
UMassRepresentationSubsystem* RepresentationSubsystem = Context.GetMutableSharedFragment<FMassRepresentationSubsystemSharedFragment>().RepresentationSubsystem;
check(RepresentationSubsystem);
UMassActorSubsystem* ActorSubsystem = Context.GetMutableSubsystem<UMassActorSubsystem>();
const TArrayView<FMassRepresentationFragment> RepresentationList = Context.GetMutableFragmentView<FMassRepresentationFragment>();
const TArrayView<FMassActorFragment> ActorList = Context.GetMutableFragmentView<FMassActorFragment>();
for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
{
FMassRepresentationFragment& Representation = RepresentationList[EntityIt];
FMassActorFragment& ActorInfo = ActorList[EntityIt];
const FMassEntityHandle MassAgentEntityHandle = Context.GetEntity(EntityIt);
UMassRepresentationActorManagement::ReleaseAnyActorOrCancelAnySpawning(*RepresentationSubsystem, MassAgentEntityHandle, ActorInfo, Representation, ActorSubsystem);
}
});
}