// 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& EntityManager) { SyncClientData.AddRequirement(EMassFragmentAccess::ReadWrite); SyncClientData.AddRequirement(EMassFragmentAccess::ReadWrite); CollectViewerInfoQuery.AddRequirement(EMassFragmentAccess::ReadOnly); CollectViewerInfoQuery.AddRequirement(EMassFragmentAccess::ReadWrite); CollectViewerInfoQuery.AddSharedRequirement(EMassFragmentAccess::ReadWrite); CalculateLODQuery.AddRequirement(EMassFragmentAccess::ReadOnly); CalculateLODQuery.AddRequirement(EMassFragmentAccess::ReadWrite); CalculateLODQuery.AddConstSharedRequirement(); CalculateLODQuery.AddSharedRequirement(EMassFragmentAccess::ReadWrite); AdjustLODDistancesQuery.AddRequirement(EMassFragmentAccess::ReadOnly); AdjustLODDistancesQuery.AddRequirement(EMassFragmentAccess::ReadWrite); AdjustLODDistancesQuery.AddSharedRequirement(EMassFragmentAccess::ReadWrite); AdjustLODDistancesQuery.SetChunkFilter([](const FMassExecutionContext& Context) { const FMassReplicationSharedFragment& LODSharedFragment = Context.GetSharedFragment(); return LODSharedFragment.bHasAdjustedDistancesFromCount; }); EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); EntityQuery.AddConstSharedRequirement(); EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadWrite); ProcessorRequirements.AddSubsystemRequirement(EMassFragmentAccess::ReadOnly); } void UMassReplicationProcessor::InitializeInternal(UObject& Owner, const TSharedRef& EntityManager) { Super::InitializeInternal(Owner, EntityManager); #if UE_REPLICATION_COMPILE_SERVER_CODE UWorld* World = Owner.GetWorld(); ReplicationSubsystem = UWorld::GetSubsystem(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([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(); return &CurRepSharedFragment == &RepSharedFragment; }); RepSharedFragment.CachedReplicator->AddRequirements(RepSharedFragment.EntityQuery); RepSharedFragment.bEntityQueryInitialized = true; } const TArray& 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(); const TArray& AllViewersInfo = LODSubsystem.GetViewers(); const TArray& 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 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([&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 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 Entities; }; TArray EntitySets; for (const FMassArchetypeHandle& Archetype : EntityQuery.GetArchetypes()) { FEntitySet& Set = EntitySets.AddDefaulted_GetRef(); Set.Archetype = Archetype; } auto BuildEntitySet = [&EntitySets, &EntityManager](const TArray& 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 ViewerLODList = Context.GetMutableFragmentView(); TArrayView ReplicatedAgentList = Context.GetMutableFragmentView(); 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 LocationList = Context.GetFragmentView(); const TArrayView ViewersInfoList = Context.GetMutableFragmentView(); FMassReplicationSharedFragment& RepSharedFragment = Context.GetMutableSharedFragment(); RepSharedFragment.LODCollector.CollectLODInfo(Context, LocationList, ViewersInfoList, ViewersInfoList); }); } { QUICK_SCOPE_CYCLE_COUNTER(UMassReplicationProcessor_LODCaculation); CalculateLODQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context) { const TConstArrayView ViewersInfoList = Context.GetFragmentView(); const TArrayView ViewerLODList = Context.GetMutableFragmentView(); FMassReplicationSharedFragment& RepSharedFragment = Context.GetMutableSharedFragment(); RepSharedFragment.LODCalculator.CalculateLOD(Context, ViewersInfoList, ViewerLODList, ViewersInfoList); }); } Context.ClearEntityCollection(); } { QUICK_SCOPE_CYCLE_COUNTER(UMassReplicationProcessor_LODAdjustDistance); EntityManager.ForEachSharedFragment([](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 ViewersInfoList = Context.GetFragmentView(); const TArrayView ViewerLODList = Context.GetMutableFragmentView(); FMassReplicationSharedFragment& RepSharedFragment = Context.GetMutableSharedFragment(); RepSharedFragment.LODCalculator.AdjustLODFromCount(Context, ViewersInfoList, ViewerLODList, ViewersInfoList); }); } { QUICK_SCOPE_CYCLE_COUNTER(UMassReplicationProcessor_ProcessClientReplication); FMassReplicationContext ReplicationContext(*World, LODSubsystem, *ReplicationSubsystem); EntityManager.ForEachSharedFragment([&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 ReplicatedAgentList = Context.GetMutableFragmentView(); 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([World, &EntityManager, &Context](FMassReplicationSharedFragment& RepSharedFragment) { RepSharedFragment.EntityQuery.ForEachEntityChunk(Context, [World, &RepSharedFragment](FMassExecutionContext& Context) { const TConstArrayView TransformList = Context.GetFragmentView(); const TConstArrayView ViewerLODList = Context.GetFragmentView(); 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 }