Files
2025-05-18 13:04:45 +08:00

412 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.UMassSimulationSettings
#include "MassReplicationProcessor.h"
#include "MassClientBubbleHandler.h"
#include "MassLODSubsystem.h"
#include "MassCommonFragments.h"
#include "MassExecutionContext.h"
namespace UE::Mass::Replication
{
int32 DebugClientReplicationLOD = -1;
FAutoConsoleVariableRef CVarDebugReplicationViewerLOD(TEXT("mass.debug.ClientReplicationLOD"), DebugClientReplicationLOD, TEXT("Debug Replication LOD of the specified client index"), ECVF_Cheat);
} // UE::Mass::Crowd
//----------------------------------------------------------------------//
// UMassReplicationProcessor
//----------------------------------------------------------------------//
UMassReplicationProcessor::UMassReplicationProcessor()
: SyncClientData(*this)
, CollectViewerInfoQuery(*this)
, CalculateLODQuery(*this)
, AdjustLODDistancesQuery(*this)
, EntityQuery(*this)
{
#if !UE_ALLOW_DEBUG_REPLICATION_BUBBLES_STANDALONE
ExecutionFlags = int32(EProcessorExecutionFlags::Server);
#else
ExecutionFlags = int32(EProcessorExecutionFlags::AllNetModes);
#endif // UE_ALLOW_DEBUG_REPLICATION_BUBBLES_STANDALONE
ProcessingPhase = EMassProcessingPhase::PostPhysics;
// Processor might need to create UObjects when synchronizing clients and viewers
// (e.g. SpawnActor from UMassReplicationSubsystem::SynchronizeClientsAndViewers())
bRequiresGameThreadExecution = true;
}
void UMassReplicationProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
SyncClientData.AddRequirement<FMassReplicationLODFragment>(EMassFragmentAccess::ReadWrite);
SyncClientData.AddRequirement<FMassReplicatedAgentFragment>(EMassFragmentAccess::ReadWrite);
CollectViewerInfoQuery.AddRequirement<FTransformFragment>(EMassFragmentAccess::ReadOnly);
CollectViewerInfoQuery.AddRequirement<FMassReplicationViewerInfoFragment>(EMassFragmentAccess::ReadWrite);
CollectViewerInfoQuery.AddSharedRequirement<FMassReplicationSharedFragment>(EMassFragmentAccess::ReadWrite);
CalculateLODQuery.AddRequirement<FMassReplicationViewerInfoFragment>(EMassFragmentAccess::ReadOnly);
CalculateLODQuery.AddRequirement<FMassReplicationLODFragment>(EMassFragmentAccess::ReadWrite);
CalculateLODQuery.AddConstSharedRequirement<FMassReplicationParameters>();
CalculateLODQuery.AddSharedRequirement<FMassReplicationSharedFragment>(EMassFragmentAccess::ReadWrite);
AdjustLODDistancesQuery.AddRequirement<FMassReplicationViewerInfoFragment>(EMassFragmentAccess::ReadOnly);
AdjustLODDistancesQuery.AddRequirement<FMassReplicationLODFragment>(EMassFragmentAccess::ReadWrite);
AdjustLODDistancesQuery.AddSharedRequirement<FMassReplicationSharedFragment>(EMassFragmentAccess::ReadWrite);
AdjustLODDistancesQuery.SetChunkFilter([](const FMassExecutionContext& Context)
{
const FMassReplicationSharedFragment& LODSharedFragment = Context.GetSharedFragment<FMassReplicationSharedFragment>();
return LODSharedFragment.bHasAdjustedDistancesFromCount;
});
EntityQuery.AddRequirement<FMassNetworkIDFragment>(EMassFragmentAccess::ReadOnly);
EntityQuery.AddRequirement<FReplicationTemplateIDFragment>(EMassFragmentAccess::ReadOnly);
EntityQuery.AddRequirement<FMassReplicationLODFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddRequirement<FMassReplicatedAgentFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddConstSharedRequirement<FMassReplicationParameters>();
EntityQuery.AddSharedRequirement<FMassReplicationSharedFragment>(EMassFragmentAccess::ReadWrite);
ProcessorRequirements.AddSubsystemRequirement<UMassLODSubsystem>(EMassFragmentAccess::ReadOnly);
}
void UMassReplicationProcessor::InitializeInternal(UObject& Owner, const TSharedRef<FMassEntityManager>& EntityManager)
{
Super::InitializeInternal(Owner, EntityManager);
#if UE_REPLICATION_COMPILE_SERVER_CODE
UWorld* World = Owner.GetWorld();
ReplicationSubsystem = UWorld::GetSubsystem<UMassReplicationSubsystem>(World);
check(ReplicationSubsystem);
#endif //UE_REPLICATION_COMPILE_SERVER_CODE
}
void UMassReplicationProcessor::PrepareExecution(FMassEntityManager& EntityManager)
{
#if UE_REPLICATION_COMPILE_SERVER_CODE
check(ReplicationSubsystem);
//first synchronize clients and viewers
ReplicationSubsystem->SynchronizeClientsAndViewers();
EntityManager.ForEachSharedFragment<FMassReplicationSharedFragment>([this](FMassReplicationSharedFragment& RepSharedFragment)
{
if (!ensureMsgf(RepSharedFragment.BubbleInfoClassHandle.IsValid()
, TEXT("BubbleInfoClassHandle is not valid which means no class has been indicated or the class used has not been registered pre creation of the handle.")))
{
return;
}
if (!RepSharedFragment.bEntityQueryInitialized)
{
RepSharedFragment.EntityQuery = EntityQuery;
RepSharedFragment.EntityQuery.SetChunkFilter([&RepSharedFragment](const FMassExecutionContext& Context)
{
const FMassReplicationSharedFragment& CurRepSharedFragment = Context.GetSharedFragment<FMassReplicationSharedFragment>();
return &CurRepSharedFragment == &RepSharedFragment;
});
RepSharedFragment.CachedReplicator->AddRequirements(RepSharedFragment.EntityQuery);
RepSharedFragment.bEntityQueryInitialized = true;
}
const TArray<FMassClientHandle>& CurrentClientHandles = ReplicationSubsystem->GetClientReplicationHandles();
const int32 MinNumHandles = FMath::Min(RepSharedFragment.CachedClientHandles.Num(), CurrentClientHandles.Num()); // Why is this the min not the max?
//check to see if we don't have enough cached client handles
if (RepSharedFragment.CachedClientHandles.Num() < CurrentClientHandles.Num())
{
RepSharedFragment.CachedClientHandles.Reserve(CurrentClientHandles.Num());
RepSharedFragment.BubbleInfos.Reserve(CurrentClientHandles.Num());
for (int32 Idx = RepSharedFragment.CachedClientHandles.Num(); Idx < CurrentClientHandles.Num(); ++Idx)
{
const FMassClientHandle& CurrentClientHandle = CurrentClientHandles[Idx];
RepSharedFragment.CachedClientHandles.Add(CurrentClientHandle);
AMassClientBubbleInfoBase* Info = CurrentClientHandle.IsValid() ?
ReplicationSubsystem->GetClientBubbleChecked(RepSharedFragment.BubbleInfoClassHandle, CurrentClientHandle) :
nullptr;
check(Info);
RepSharedFragment.BubbleInfos.Add(Info);
}
}
//check to see if we have too many cached client handles
else if (RepSharedFragment.CachedClientHandles.Num() > CurrentClientHandles.Num())
{
const int32 NumRemove = RepSharedFragment.CachedClientHandles.Num() - CurrentClientHandles.Num();
RepSharedFragment.CachedClientHandles.RemoveAt(CurrentClientHandles.Num(), NumRemove, EAllowShrinking::No);
RepSharedFragment.BubbleInfos.RemoveAt(CurrentClientHandles.Num(), NumRemove, EAllowShrinking::No);
}
//check to see if any cached client handles have changed, if they have set the BubbleInfo[] appropriately
for (int32 Idx = 0; Idx < MinNumHandles; ++Idx)
{
const FMassClientHandle& CurrentClientHandle = CurrentClientHandles[Idx];
FMassClientHandle& CachedClientHandle = RepSharedFragment.CachedClientHandles[Idx];
const bool bChanged = (CurrentClientHandle != CachedClientHandle);
if (bChanged)
{
AMassClientBubbleInfoBase* Info = CurrentClientHandle.IsValid() ?
ReplicationSubsystem->GetClientBubbleChecked(RepSharedFragment.BubbleInfoClassHandle, CurrentClientHandle) :
nullptr;
RepSharedFragment.BubbleInfos[Idx] = Info;
CachedClientHandle = CurrentClientHandle;
}
}
});
#endif //UE_REPLICATION_COMPILE_SERVER_CODE
}
void UMassReplicationProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
#if UE_REPLICATION_COMPILE_SERVER_CODE
UWorld* World = EntityManager.GetWorld();
check(World);
check(ReplicationSubsystem);
{
QUICK_SCOPE_CYCLE_COUNTER(UMassReplicationProcessor_Preperation);
PrepareExecution(EntityManager);
}
const UMassLODSubsystem& LODSubsystem = Context.GetSubsystemChecked<UMassLODSubsystem>();
const TArray<FViewerInfo>& AllViewersInfo = LODSubsystem.GetViewers();
const TArray<FMassClientHandle>& ClientHandles = ReplicationSubsystem->GetClientReplicationHandles();
for (const FMassClientHandle ClientHandle : ClientHandles)
{
if (ReplicationSubsystem->IsValidClientHandle(ClientHandle) == false)
{
continue;
}
FMassClientReplicationInfo& ClientReplicationInfo = ReplicationSubsystem->GetMutableClientReplicationInfoChecked(ClientHandle);
// Figure out all viewer of this client
TArray<FViewerInfo> Viewers;
for (const FMassViewerHandle ClientViewerHandle : ClientReplicationInfo.Handles)
{
const FViewerInfo* ViewerInfo = AllViewersInfo.FindByPredicate([ClientViewerHandle](const FViewerInfo& ViewerInfo) { return ClientViewerHandle == ViewerInfo.Handle; });
if (ensureMsgf(ViewerInfo, TEXT("Expecting to find the client viewer handle in the all viewers info list")))
{
Viewers.Add(*ViewerInfo);
}
}
// Prepare LOD collector and calculator
// Remember the max LOD distance from each
float MaxLODDistance = 0.0f;
EntityManager.ForEachSharedFragment<FMassReplicationSharedFragment>([&Viewers,&MaxLODDistance](FMassReplicationSharedFragment& RepSharedFragment)
{
RepSharedFragment.LODCollector.PrepareExecution(Viewers);
RepSharedFragment.LODCalculator.PrepareExecution(Viewers);
MaxLODDistance = FMath::Max(MaxLODDistance, RepSharedFragment.LODCalculator.GetMaxLODDistance());
});
// Fetch all entities to process
const FVector HalfExtent(MaxLODDistance, MaxLODDistance, 0.0f);
TArray<FMassEntityHandle> EntitiesInRange;
for (const FViewerInfo& Viewer : Viewers)
{
FBox Bounds(Viewer.Location - HalfExtent, Viewer.Location + HalfExtent);
ReplicationSubsystem->GetGrid().Query(Bounds, EntitiesInRange);
}
EntityQuery.CacheArchetypes();
if (EntityQuery.GetArchetypes().Num() > 0)
{
// EntitySet stores array of entities per specified archetype, may contain duplicates.
struct FEntitySet
{
void Reset()
{
Entities.Reset();
}
FMassArchetypeHandle Archetype;
TArray<FMassEntityHandle> Entities;
};
TArray<FEntitySet> EntitySets;
for (const FMassArchetypeHandle& Archetype : EntityQuery.GetArchetypes())
{
FEntitySet& Set = EntitySets.AddDefaulted_GetRef();
Set.Archetype = Archetype;
}
auto BuildEntitySet = [&EntitySets, &EntityManager](const TArray<FMassEntityHandle>& Entities)
{
FEntitySet* PrevSet = Entities.Num() ? &EntitySets[0] : nullptr;
for (const FMassEntityHandle Entity : Entities)
{
// Add to set of supported archetypes. Dont process if we don't care about the type.
const FMassArchetypeHandle Archetype = EntityManager.GetArchetypeForEntity(Entity);
FEntitySet* Set = PrevSet && PrevSet->Archetype == Archetype ? PrevSet : EntitySets.FindByPredicate([&Archetype](const FEntitySet& Set) { return Archetype == Set.Archetype; });
if (Set != nullptr)
{
// We don't care about duplicates here, the FMassArchetypeEntityCollection creation below will handle it
Set->Entities.Add(Entity);
PrevSet = Set;
}
}
};
BuildEntitySet(ClientReplicationInfo.HandledEntities);
BuildEntitySet(EntitiesInRange);
for (FEntitySet& Set : EntitySets)
{
if (Set.Entities.Num() == 0)
{
continue;
}
Context.SetEntityCollection(FMassArchetypeEntityCollection(Set.Archetype, Set.Entities, FMassArchetypeEntityCollection::FoldDuplicates));
{
QUICK_SCOPE_CYCLE_COUNTER(UMassReplicationProcessor_SyncToMass);
SyncClientData.ForEachEntityChunk(Context, [&ClientReplicationInfo](FMassExecutionContext& Context)
{
const TArrayView<FMassReplicationLODFragment> ViewerLODList = Context.GetMutableFragmentView<FMassReplicationLODFragment>();
TArrayView<FMassReplicatedAgentFragment> ReplicatedAgentList = Context.GetMutableFragmentView<FMassReplicatedAgentFragment>();
for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
{
FMassEntityHandle EntityHandle = Context.GetEntity(EntityIt);
FMassReplicatedAgentFragment& AgentFragment = ReplicatedAgentList[EntityIt];
FMassReplicationLODFragment& LODFragment = ViewerLODList[EntityIt];
if (FMassReplicatedAgentData* AgentData = ClientReplicationInfo.AgentsData.Find(EntityHandle))
{
LODFragment.LOD = AgentData->LOD;
AgentFragment.AgentData = *AgentData;
}
else
{
LODFragment.LOD = EMassLOD::Off;
AgentFragment.AgentData.Invalidate();
}
}
});
}
{
QUICK_SCOPE_CYCLE_COUNTER(UMassReplicationProcessor_LODCollection);
CollectViewerInfoQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
{
const TConstArrayView<FTransformFragment> LocationList = Context.GetFragmentView<FTransformFragment>();
const TArrayView<FMassReplicationViewerInfoFragment> ViewersInfoList = Context.GetMutableFragmentView<FMassReplicationViewerInfoFragment>();
FMassReplicationSharedFragment& RepSharedFragment = Context.GetMutableSharedFragment<FMassReplicationSharedFragment>();
RepSharedFragment.LODCollector.CollectLODInfo(Context, LocationList, ViewersInfoList, ViewersInfoList);
});
}
{
QUICK_SCOPE_CYCLE_COUNTER(UMassReplicationProcessor_LODCaculation);
CalculateLODQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
{
const TConstArrayView<FMassReplicationViewerInfoFragment> ViewersInfoList = Context.GetFragmentView<FMassReplicationViewerInfoFragment>();
const TArrayView<FMassReplicationLODFragment> ViewerLODList = Context.GetMutableFragmentView<FMassReplicationLODFragment>();
FMassReplicationSharedFragment& RepSharedFragment = Context.GetMutableSharedFragment<FMassReplicationSharedFragment>();
RepSharedFragment.LODCalculator.CalculateLOD(Context, ViewersInfoList, ViewerLODList, ViewersInfoList);
});
}
Context.ClearEntityCollection();
}
{
QUICK_SCOPE_CYCLE_COUNTER(UMassReplicationProcessor_LODAdjustDistance);
EntityManager.ForEachSharedFragment<FMassReplicationSharedFragment>([](FMassReplicationSharedFragment& RepSharedFragment)
{
RepSharedFragment.bHasAdjustedDistancesFromCount = RepSharedFragment.LODCalculator.AdjustDistancesFromCount();
});
}
for (FEntitySet& Set : EntitySets)
{
if (Set.Entities.Num() == 0)
{
continue;
}
Context.SetEntityCollection(FMassArchetypeEntityCollection(Set.Archetype, Set.Entities, FMassArchetypeEntityCollection::FoldDuplicates));
{
QUICK_SCOPE_CYCLE_COUNTER(UMassReplicationProcessor_LODAdjustLODFromCount);
AdjustLODDistancesQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
{
const TConstArrayView<FMassReplicationViewerInfoFragment> ViewersInfoList = Context.GetFragmentView<FMassReplicationViewerInfoFragment>();
const TArrayView<FMassReplicationLODFragment> ViewerLODList = Context.GetMutableFragmentView<FMassReplicationLODFragment>();
FMassReplicationSharedFragment& RepSharedFragment = Context.GetMutableSharedFragment<FMassReplicationSharedFragment>();
RepSharedFragment.LODCalculator.AdjustLODFromCount(Context, ViewersInfoList, ViewerLODList, ViewersInfoList);
});
}
{
QUICK_SCOPE_CYCLE_COUNTER(UMassReplicationProcessor_ProcessClientReplication);
FMassReplicationContext ReplicationContext(*World, LODSubsystem, *ReplicationSubsystem);
EntityManager.ForEachSharedFragment<FMassReplicationSharedFragment>([&EntityManager, &Context, &ReplicationContext, &ClientHandle](FMassReplicationSharedFragment& RepSharedFragment)
{
RepSharedFragment.CurrentClientHandle = ClientHandle;
RepSharedFragment.EntityQuery.ForEachEntityChunk(Context, [&ReplicationContext, &RepSharedFragment](FMassExecutionContext& Context)
{
RepSharedFragment.CachedReplicator->ProcessClientReplication(Context, ReplicationContext);
});
});
}
{
QUICK_SCOPE_CYCLE_COUNTER(UMassReplicationProcessor_SyncFromMass);
SyncClientData.ForEachEntityChunk(Context, [&ClientReplicationInfo](FMassExecutionContext& Context)
{
TArrayView<FMassReplicatedAgentFragment> ReplicatedAgentList = Context.GetMutableFragmentView<FMassReplicatedAgentFragment>();
for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
{
FMassEntityHandle EntityHandle = Context.GetEntity(EntityIt);
FMassReplicatedAgentFragment& AgentFragment = ReplicatedAgentList[EntityIt];
ClientReplicationInfo.AgentsData.Add(EntityHandle, AgentFragment.AgentData);
}
});
}
#if WITH_MASSGAMEPLAY_DEBUG
// Optional debug display
if (UE::Mass::Replication::DebugClientReplicationLOD == ClientHandle.GetIndex())
{
EntityManager.ForEachSharedFragment<FMassReplicationSharedFragment>([World, &EntityManager, &Context](FMassReplicationSharedFragment& RepSharedFragment)
{
RepSharedFragment.EntityQuery.ForEachEntityChunk(Context, [World, &RepSharedFragment](FMassExecutionContext& Context)
{
const TConstArrayView<FTransformFragment> TransformList = Context.GetFragmentView<FTransformFragment>();
const TConstArrayView<FMassReplicationLODFragment> ViewerLODList = Context.GetFragmentView<FMassReplicationLODFragment>();
RepSharedFragment.LODCalculator.DebugDisplayLOD(Context, ViewerLODList, TransformList, World);
});
});
}
#endif // WITH_MASSGAMEPLAY_DEBUG
Context.ClearEntityCollection();
}
}
ClientReplicationInfo.HandledEntities = MoveTemp(EntitiesInRange);
// Cleanup any AgentData that isn't relevant anymore (that is EMassLOD::OFF)
for (FMassReplicationAgentDataMap::TIterator It = ClientReplicationInfo.AgentsData.CreateIterator(); It; ++It)
{
FMassReplicatedAgentData& AgentData = It.Value();
if (AgentData.LOD == EMassLOD::Off)
{
checkf(!AgentData.Handle.IsValid(), TEXT("This replicated agent should have been removed from this client and was not"));
It.RemoveCurrent();
}
}
}
#endif //UE_REPLICATION_COMPILE_SERVER_CODE
}