Files
UnrealEngine/Engine/Plugins/Runtime/MassGameplay/Source/MassReplication/Public/MassClientBubbleHandler.h
2025-05-18 13:04:45 +08:00

743 lines
28 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "MassCommonTypes.h"
#include "MassReplicationTypes.h"
#include "Containers/ArrayView.h"
#include "MassClientBubbleSerializerBase.h"
#include "MassSpawnerSubsystem.h"
#include "MassReplicationFragments.h"
#include "MassReplicationSubsystem.h"
#include "Engine/World.h"
#include "MassEntityTemplate.h"
#include "MassEntityView.h"
#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6
#include "MassEntityManager.h"
#include "MassSpawnerTypes.h"
#endif // UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6
#include "MassClientBubbleHandler.generated.h"
class UWorld;
struct FMassEntityManager;
namespace UE::Mass::Replication
{
constexpr float AgentRemoveInterval = 5.f;
}; //namespace UE::Mass::Replication
/**
* Base for fast array items. For replication of new entity types this type should be inherited from. FReplicatedAgentBase should also be inherited from
* and made a member variable of the FMassFastArrayItemBase derived struct, with the member variable called Agent.
*/
USTRUCT()
struct FMassFastArrayItemBase : public FFastArraySerializerItem
{
GENERATED_BODY()
FMassFastArrayItemBase() = default;
FMassFastArrayItemBase(FMassReplicatedAgentHandle InHandle)
: Handle(InHandle)
{};
FMassReplicatedAgentHandle GetHandle() const { return Handle; }
protected:
/** Only to be used on a server */
UPROPERTY(NotReplicated)
FMassReplicatedAgentHandle Handle;
};
/** Data that can be accessed from a FMassReplicatedAgentHandle on a server */
struct FMassAgentLookupData
{
FMassAgentLookupData(FMassEntityHandle InEntity, FMassNetworkID InNetID, int32 InAgentsIdx)
: Entity(InEntity)
, NetID(InNetID)
, AgentsIdx(InAgentsIdx)
{}
void Invalidate()
{
Entity = FMassEntityHandle();
NetID.Invalidate();
AgentsIdx = INDEX_NONE;
}
bool IsValid() const
{
return Entity.IsSet() && NetID.IsValid() && (AgentsIdx >= 0);
}
FMassEntityHandle Entity;
FMassNetworkID NetID;
int32 AgentsIdx = INDEX_NONE;
};
/**
* Data that is stored when an agent is removed from the bubble, when it times out its safe enough to remove entries in EntityInfoMap.
* The idea is that any out of order adds and removes will happen after this time.
*/
struct FMassAgentRemoveData
{
FMassAgentRemoveData() = default;
FMassAgentRemoveData(double InTimeLastRemoved)
: TimeLastRemoved(InTimeLastRemoved)
{}
double TimeLastRemoved = 0.;
};
/**
* Interface for the bubble handler classes. All the outside interaction with the FastArray logic should be done via the Handler interface
* or derived classes where possible.
* These virtual functions are either only called once each per frame on the client for a few struct instances
* or called at startup / shutdown.
*/
class IClientBubbleHandlerInterface
{
public:
virtual ~IClientBubbleHandlerInterface() {}
virtual void InitializeForWorld(UWorld& InWorld) = 0;
#if UE_REPLICATION_COMPILE_CLIENT_CODE
/** These functions are processed internally by TClientBubbleHandlerBase */
virtual void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize) = 0;
virtual void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize) = 0;
virtual void PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize) = 0;
#endif // UE_REPLICATION_COMPILE_CLIENT_CODE
virtual void Reset() = 0;
virtual void UpdateAgentsToRemove() = 0;
virtual void Tick(float DeltaTime) = 0;
virtual void SetClientHandle(FMassClientHandle InClientHandle) = 0;
virtual void DebugValidateBubbleOnServer() = 0;
virtual void DebugValidateBubbleOnClient() = 0;
};
/**
* Template client bubble functionality. Replication logic for specific agent types is provided by deriving from this class.
* Interaction with the FMassClientBubbleSerializerBase and derived classes should be done from this class
*/
template<typename AgentArrayItem>
class TClientBubbleHandlerBase : public IClientBubbleHandlerInterface
{
public:
template<typename T>
friend class TMassClientBubblePathHandler;
template<typename T>
friend class TMassClientBubbleTransformHandler;
typedef TFunctionRef<void(FMassEntityQuery&)> FAddRequirementsForSpawnQueryFunction;
typedef TFunctionRef<void(FMassExecutionContext&)> FCacheFragmentViewsForSpawnQueryFunction;
typedef TFunctionRef<void(const FMassEntityView&, const typename AgentArrayItem::FReplicatedAgentType&, const int32)> FSetSpawnedEntityDataFunction;
typedef TFunctionRef<void(const FMassEntityView&, const typename AgentArrayItem::FReplicatedAgentType&)> FSetModifiedEntityDataFunction;
/** This must be called from outside before InitializeForWorld() is called. Its called from agent specific bubble implementations */
virtual void Initialize(TArray<AgentArrayItem>& InAgents, FMassClientBubbleSerializerBase& InSerializer);
virtual void InitializeForWorld(UWorld& InWorld) override;
#if UE_REPLICATION_COMPILE_SERVER_CODE
FMassReplicatedAgentHandle AddAgent(FMassEntityHandle Entity, typename AgentArrayItem::FReplicatedAgentType& Agent);
bool RemoveAgent(FMassNetworkID NetID);
bool RemoveAgent(FMassReplicatedAgentHandle AgentHandle);
void RemoveAgentChecked(FMassReplicatedAgentHandle AgentHandle);
/** Gets an agent safely */
const typename AgentArrayItem::FReplicatedAgentType* GetAgent(FMassReplicatedAgentHandle Handle) const;
/** Faster version to get an agent that performs check()s for debugging */
const typename AgentArrayItem::FReplicatedAgentType& GetAgentChecked(FMassReplicatedAgentHandle Handle) const;
const TArray<AgentArrayItem>& GetAgents() const { return *Agents; }
#endif //UE_REPLICATION_COMPILE_SERVER_CODE
protected:
#if UE_REPLICATION_COMPILE_CLIENT_CODE
virtual void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize) override;
/** Called from TClientBubbleHandlerBase derived classes in PostReplicatedAd() */
void PostReplicatedAddHelper(const TArrayView<int32> AddedIndices, FAddRequirementsForSpawnQueryFunction AddRequirementsForSpawnQuery
, FCacheFragmentViewsForSpawnQueryFunction CacheFragmentViewsForSpawnQuery, FSetSpawnedEntityDataFunction SetSpawnedEntityData, FSetModifiedEntityDataFunction SetModifiedEntityData);
/** used by PostReplicatedAddHelper */
void PostReplicatedAddEntitiesHelper(const TArrayView<int32> AddedIndices, FAddRequirementsForSpawnQueryFunction AddRequirementsForSpawnQuery
, FCacheFragmentViewsForSpawnQueryFunction CacheFragmentViewsForSpawnQuery, FSetSpawnedEntityDataFunction SetSpawnedEntityData);
/** Called from TClientBubbleHandlerBase derived classes in PostReplicatedChange() */
void PostReplicatedChangeHelper(const TArrayView<int32> ChangedIndices, FSetModifiedEntityDataFunction SetModifiedEntityData);
#endif //UE_REPLICATION_COMPILE_SERVER_CODE
#if UE_REPLICATION_COMPILE_SERVER_CODE
void RemoveAgentImpl(FMassReplicatedAgentHandle Handle);
#endif //UE_REPLICATION_COMPILE_SERVER_CODE
virtual void SetClientHandle(FMassClientHandle InClientHandle) override;
virtual void Reset() override;
virtual void Tick(float DeltaTime) override;
virtual void UpdateAgentsToRemove() override;
virtual void DebugValidateBubbleOnClient() override;
virtual void DebugValidateBubbleOnServer() override;
protected:
/** Pointer to the Agents array in the associated Serializer class */
TArray<AgentArrayItem>* Agents = nullptr;
FMassClientHandle ClientHandle;
#if UE_REPLICATION_COMPILE_SERVER_CODE
FMassReplicatedAgentHandleManager AgentHandleManager;
/** Used to look up Agent data from a FMassReplicatedAgentHandle, the AgentsIdx member will be the Idx in to the Agents array */
TArray<FMassAgentLookupData> AgentLookupArray;
TMap<FMassNetworkID, FMassReplicatedAgentHandle> NetworkIDToAgentHandleMap;
#endif //UE_REPLICATION_COMPILE_SERVER_CODE
#if UE_REPLICATION_COMPILE_CLIENT_CODE
/**
* Data that is stored when an agent is removed from the bubble, when it times out its safe enough to remove entries in EntityInfoMap
* The idea is that any out of order adds and subsequent removes for this NetID will normally happen before FAgentRemoveData::TimeLastRemoved,
* Those that happen after will be on such a bad connection that it doest matter.
*/
TMap<FMassNetworkID, FMassAgentRemoveData> AgentsRemoveDataMap;
#endif //UE_REPLICATION_COMPILE_CLIENT_CODE
/** Base class pointer to the associated Serializer class */
FMassClientBubbleSerializerBase* Serializer = nullptr;
};
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::Initialize(TArray<AgentArrayItem>& InAgents, FMassClientBubbleSerializerBase& InSerializer)
{
Agents = &InAgents;
Serializer = &InSerializer;
Serializer->SetClientHandler(*this);
}
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::InitializeForWorld(UWorld& InWorld)
{
checkf(Agents, TEXT("Agents not set up. Call Initialize() before InitializeForWorld() gets called"));
checkf(Serializer, TEXT("Serializer not set up. Call Initialize() before InitializeForWorld() gets called"));
Serializer->InitializeForWorld(InWorld);
}
#if UE_REPLICATION_COMPILE_SERVER_CODE
template<typename AgentArrayItem>
FMassReplicatedAgentHandle TClientBubbleHandlerBase<AgentArrayItem>::AddAgent(FMassEntityHandle Entity, typename AgentArrayItem::FReplicatedAgentType& Agent)
{
checkf(Agent.GetNetID().IsValid(), TEXT("Agent.NetID must be valid!"));
checkf(NetworkIDToAgentHandleMap.Find(Agent.GetNetID()) == nullptr, TEXT("Only add agents once"));
FMassReplicatedAgentHandle AgentHandle = AgentHandleManager.GetNextHandle();
checkf(AgentHandle.GetIndex() <= AgentLookupArray.Num(), TEXT("AgentHandle is out of sync with the AgentLookupArray Array!"));
const int32 Idx = AgentHandle.GetIndex();
if (Idx == AgentLookupArray.Num())
{
AgentLookupArray.Emplace(Entity, Agent.GetNetID(), (*Agents).Num());
}
else
{
checkf(AgentLookupArray[Idx].IsValid() == false, TEXT("Agent being replaced must be Invalid (should have been removed first)!"));
AgentLookupArray[Idx] = FMassAgentLookupData(Entity, Agent.GetNetID(), (*Agents).Num());
}
AgentArrayItem& Item = (*Agents).Emplace_GetRef(Agent, AgentHandle);
Serializer->MarkItemDirty(Item);
NetworkIDToAgentHandleMap.Add(Agent.GetNetID(), AgentHandle);
return AgentHandle;
}
template<typename AgentArrayItem>
bool TClientBubbleHandlerBase<AgentArrayItem>::RemoveAgent(FMassNetworkID NetID)
{
const FMassReplicatedAgentHandle* const AgentHandle = NetworkIDToAgentHandleMap.Find(NetID);
return (AgentHandle != nullptr) ? RemoveAgent(*AgentHandle) : false;
}
template<typename AgentArrayItem>
bool TClientBubbleHandlerBase<AgentArrayItem>::RemoveAgent(FMassReplicatedAgentHandle AgentHandle)
{
bool bRemoved = false;
if (ensureMsgf(AgentHandleManager.IsValidHandle(AgentHandle), TEXT("FMassReplicatedAgentHandle should be Valid")))
{
bRemoved = true;
RemoveAgentImpl(AgentHandle);
}
return bRemoved;
}
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::RemoveAgentChecked(FMassReplicatedAgentHandle Handle)
{
checkf(AgentHandleManager.IsValidHandle(Handle), TEXT("FMassReplicatedAgentHandle must be Valid"));
RemoveAgentImpl(Handle);
}
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::RemoveAgentImpl(FMassReplicatedAgentHandle Handle)
{
FMassAgentLookupData& LookUpData = AgentLookupArray[Handle.GetIndex()];
const bool bDidSwap = LookUpData.AgentsIdx < ((*Agents).Num() - 1);
(*Agents).RemoveAtSwap(LookUpData.AgentsIdx, EAllowShrinking::No);
Serializer->MarkArrayDirty();
verifyf(NetworkIDToAgentHandleMap.Remove(LookUpData.NetID) == 1, TEXT("Failed to find 1 matching NetID in NetworkIDToAgentHandleMap"));
if (bDidSwap)
{
//we need to change the AgentsIdx of the Lookup data free list for the item that was swapped
const FMassReplicatedAgentHandle HandleSwap = (*Agents)[LookUpData.AgentsIdx].GetHandle();
checkf(AgentHandleManager.IsValidHandle(HandleSwap), TEXT("Handle of the Agent we RemoveAtSwap with must be valid as its in the Agents array"));
FMassAgentLookupData& LookUpDataSwap = AgentLookupArray[HandleSwap.GetIndex()];
LookUpDataSwap.AgentsIdx = LookUpData.AgentsIdx;
checkf(LookUpDataSwap.NetID.IsValid(), TEXT("NetID of item we are swaping with must be valid as its in the Agents array"));
}
AgentHandleManager.RemoveHandle(Handle);
LookUpData.Invalidate();
}
template<typename AgentArrayItem>
const typename AgentArrayItem::FReplicatedAgentType* TClientBubbleHandlerBase<AgentArrayItem>::GetAgent(FMassReplicatedAgentHandle Handle) const
{
if (AgentHandleManager.IsValidHandle(Handle))
{
const FMassAgentLookupData& LookUpData = AgentLookupArray[Handle.GetIndex()];
return &((*Agents)[LookUpData.AgentsIdx].Agent);
}
return nullptr;
}
template<typename AgentArrayItem>
const typename AgentArrayItem::FReplicatedAgentType& TClientBubbleHandlerBase<AgentArrayItem>::GetAgentChecked(FMassReplicatedAgentHandle Handle) const
{
check(AgentHandleManager.IsValidHandle(Handle));
const FMassAgentLookupData& LookUpData = AgentLookupArray[Handle.GetIndex()];
return (*Agents)[LookUpData.AgentsIdx].Agent;
}
#endif //UE_REPLICATION_COMPILE_SERVER_CODE
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::UpdateAgentsToRemove()
{
#if UE_REPLICATION_COMPILE_CLIENT_CODE
QUICK_SCOPE_CYCLE_COUNTER(MassProcessor_Replication_CalculateClientReplication);
check(Serializer->GetWorld());
UMassReplicationSubsystem* ReplicationSubsystem = Serializer->GetReplicationSubsystem();
check(ReplicationSubsystem);
const double TimeRemove = Serializer->GetWorld()->GetRealTimeSeconds() - UE::Mass::Replication::AgentRemoveInterval;
// @todo do this in a more efficient way, we may potentially be able to use ACK's and FReplicatedAgentBase::bRemovedFromServeSim
// Check to see if we should free any EntityInfoMap entries, this is to avoid gradually growing EntityInfoMap perpetually
for (TMap<FMassNetworkID, FMassAgentRemoveData>::TIterator Iter = AgentsRemoveDataMap.CreateIterator(); Iter; ++Iter)
{
const FMassAgentRemoveData& RemoveData = (*Iter).Value;
// The idea here is that AgentRemoveInterval represents a reasonable amount of time that if an out of order add and remove come in after this that we don't care, as the accuracy of the simulation
// must already be pretty awful.
if (RemoveData.TimeLastRemoved < TimeRemove)
{
const FMassNetworkID NetID = (*Iter).Key;
ReplicationSubsystem->RemoveFromEntityInfoMap(NetID);
Iter.RemoveCurrent();
}
}
#endif //UE_REPLICATION_COMPILE_CLIENT_CODE
}
#if UE_REPLICATION_COMPILE_CLIENT_CODE
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
{
TArray<FMassEntityHandle> EntitiesToDestroy;
UWorld* World = Serializer->GetWorld();
check(World);
UMassSpawnerSubsystem* SpawnerSubsystem = Serializer->GetSpawnerSubsystem();
check(SpawnerSubsystem);
UMassReplicationSubsystem* ReplicationSubsystem = Serializer->GetReplicationSubsystem();
check(ReplicationSubsystem);
for (int32 Idx : RemovedIndices)
{
const AgentArrayItem& RemovedItem = (*Agents)[Idx];
FMassEntityHandle Entity = ReplicationSubsystem->ResetEntityIfValid(RemovedItem.Agent.GetNetID(), RemovedItem.ReplicationID);
// Only remove the item if its currently Set / Valid and its the most recent ReplicationID. Stale removes after more recent adds are ignored
// We do need to check the ReplicationID in this case
if (Entity.IsSet())
{
EntitiesToDestroy.Add(Entity);
check(AgentsRemoveDataMap.Find(RemovedItem.Agent.GetNetID()) == nullptr);
AgentsRemoveDataMap.Add(RemovedItem.Agent.GetNetID(), FMassAgentRemoveData(World->GetRealTimeSeconds()));
}
}
SpawnerSubsystem->DestroyEntities(EntitiesToDestroy);
}
#endif //UE_REPLICATION_COMPILE_CLIENT_CODE
#if UE_REPLICATION_COMPILE_CLIENT_CODE
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::PostReplicatedAddHelper(const TArrayView<int32> AddedIndices, FAddRequirementsForSpawnQueryFunction AddRequirementsForSpawnQuery
, FCacheFragmentViewsForSpawnQueryFunction CacheFragmentViewsForSpawnQuery, FSetSpawnedEntityDataFunction SetSpawnedEntityData, FSetModifiedEntityDataFunction SetModifiedEntityData)
{
FMassEntityManager& EntityManager = Serializer->GetEntityManagerChecked();
UMassReplicationSubsystem* ReplicationSubsystem = Serializer->GetReplicationSubsystem();
check(ReplicationSubsystem);
TMap<FMassNetworkID, int32> AgentsToAddMap; //NetID to index in AgentsToAddArray
AgentsToAddMap.Reserve(AddedIndices.Num());
TArray<int32> AgentsToAddArray;
AgentsToAddArray.Reserve(AddedIndices.Num());
const FMassReplicationEntityInfo* EntityInfo;
for (int32 Idx : AddedIndices)
{
const AgentArrayItem& AddedItem = (*Agents)[Idx];
switch (ReplicationSubsystem->FindAndUpdateOrAddMassEntityInfo(AddedItem.Agent.GetNetID(), AddedItem.ReplicationID, EntityInfo))
{
case UMassReplicationSubsystem::EFindOrAddMassEntityInfo::FoundOlderReplicationID:
{
AgentsRemoveDataMap.Remove(AddedItem.Agent.GetNetID());
// If EntityData IsSet() it means we have had multiple Adds without a remove and we treat this add as modifying an existing agent.
if (EntityInfo->Entity.IsSet())
{
FMassEntityView EntityView(EntityManager, EntityInfo->Entity);
SetModifiedEntityData(EntityView, AddedItem.Agent);
}
else // This entity is not in the client simulation yet and needs adding.
{
const int32* IdxInAgentsToAddArray = AgentsToAddMap.Find(AddedItem.Agent.GetNetID());
// If IdxInAgentsToAddArray then we've had multiple Adds
if (IdxInAgentsToAddArray)
{
// Adjust the existing AgentsToAddArray index
AgentsToAddArray[*IdxInAgentsToAddArray] = Idx;
}
else // First time we've tried to add an entity with this FMassNetworkID this update
{
const int32 IdxAdd = AgentsToAddArray.Add(Idx);
AgentsToAddMap.Add(AddedItem.Agent.GetNetID(), IdxAdd);
}
}
}
break;
case UMassReplicationSubsystem::EFindOrAddMassEntityInfo::Added:
{
check(AgentsToAddMap.Find(AddedItem.Agent.GetNetID()) == nullptr);
const int32 IdxAdd = AgentsToAddArray.Add(Idx);
AgentsToAddMap.Add(AddedItem.Agent.GetNetID(), IdxAdd);
}
break;
case UMassReplicationSubsystem::EFindOrAddMassEntityInfo::FoundNewerReplicationID:
break;
default:
checkf(false, TEXT("Unhandled EFindOrAddMassEntityInfo type"));
break;
}
}
PostReplicatedAddEntitiesHelper(AgentsToAddArray, AddRequirementsForSpawnQuery, CacheFragmentViewsForSpawnQuery, SetSpawnedEntityData);
}
#endif // UE_REPLICATION_COMPILE_CLIENT_CODE
#if UE_REPLICATION_COMPILE_CLIENT_CODE
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::PostReplicatedAddEntitiesHelper(const TArrayView<int32> AddedIndices, FAddRequirementsForSpawnQueryFunction AddRequirementsForSpawnQuery
, FCacheFragmentViewsForSpawnQueryFunction CacheFragmentViewsForSpawnQuery, FSetSpawnedEntityDataFunction SetSpawnedEntityData)
{
check(Serializer);
FMassEntityManager& EntityManager = Serializer->GetEntityManagerChecked();
UMassSpawnerSubsystem* SpawnerSubsystem = Serializer->GetSpawnerSubsystem();
check(SpawnerSubsystem);
UMassReplicationSubsystem* ReplicationSubsystem = Serializer->GetReplicationSubsystem();
check(ReplicationSubsystem);
TMap<FMassEntityTemplateID, TArray<typename AgentArrayItem::FReplicatedAgentType*>> AgentsSpawnMap;
// Group together Agents per TemplateID
for (int32 Idx : AddedIndices)
{
typename AgentArrayItem::FReplicatedAgentType& Agent = (*Agents)[Idx].Agent;
TArray<typename AgentArrayItem::FReplicatedAgentType*>& AgentsArray = AgentsSpawnMap.FindOrAdd(Agent.GetTemplateID());
AgentsArray.Add(&Agent);
}
// Batch spawn per FMassEntityTemplateID
for (const TPair<FMassEntityTemplateID, TArray<typename AgentArrayItem::FReplicatedAgentType*>>& Item : AgentsSpawnMap)
{
const FMassEntityTemplateID& TemplateID = Item.Key;
const TArray <typename AgentArrayItem::FReplicatedAgentType*>& AgentsSpawn = Item.Value;
TArray<FMassEntityHandle> Entities;
SpawnerSubsystem->SpawnEntities(TemplateID, AgentsSpawn.Num(), FStructView(), TSubclassOf<UMassProcessor>(), Entities);
const FMassEntityTemplate* MassEntityTemplate = SpawnerSubsystem->GetMassEntityTemplate(TemplateID);
check(MassEntityTemplate);
const FMassArchetypeHandle& ArchetypeHandle = MassEntityTemplate->GetArchetype();
FMassExecutionContext ExecContext(EntityManager);
FMassEntityQuery Query(EntityManager.AsShared());
AddRequirementsForSpawnQuery(Query);
Query.AddRequirement<FMassNetworkIDFragment>(EMassFragmentAccess::ReadWrite);
int32 AgentsSpawnIdx = 0;
Query.ForEachEntityChunk(FMassArchetypeEntityCollection(ArchetypeHandle, Entities, FMassArchetypeEntityCollection::NoDuplicates)
, ExecContext, [&AgentsSpawn, &AgentsSpawnIdx, this, ReplicationSubsystem, &ExecContext, &CacheFragmentViewsForSpawnQuery
, &SetSpawnedEntityData, &EntityManager](FMassExecutionContext& Context)
{
CacheFragmentViewsForSpawnQuery(ExecContext);
const TArrayView<FMassNetworkIDFragment> NetworkIDList = Context.GetMutableFragmentView<FMassNetworkIDFragment>();
for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
{
const typename AgentArrayItem::FReplicatedAgentType& AgentSpawn = *AgentsSpawn[AgentsSpawnIdx];
const FMassEntityHandle Entity = Context.GetEntity(EntityIt);
FMassNetworkIDFragment& NetIDFragment = NetworkIDList[EntityIt];
NetIDFragment.NetID = AgentSpawn.GetNetID();
ReplicationSubsystem->SetEntity(NetIDFragment.NetID, Entity);
FMassEntityView EntityView(EntityManager, Entity);
SetSpawnedEntityData(EntityView, AgentSpawn, EntityIt);
++AgentsSpawnIdx;
}
});
}
}
#endif // UE_REPLICATION_COMPILE_CLIENT_CODE
#if UE_REPLICATION_COMPILE_CLIENT_CODE
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::PostReplicatedChangeHelper(const TArrayView<int32> ChangedIndices, FSetModifiedEntityDataFunction SetModifiedEntityData)
{
FMassEntityManager& EntityManager = Serializer->GetEntityManagerChecked();
UMassReplicationSubsystem* ReplicationSubsystem = Serializer->GetReplicationSubsystem();
check(ReplicationSubsystem);
// Go through the changed Entities and update their Mass data
for (int32 Idx : ChangedIndices)
{
const AgentArrayItem& ChangedItem = (*Agents)[Idx];
const FMassReplicationEntityInfo* EntityInfo = ReplicationSubsystem->FindMassEntityInfo(ChangedItem.Agent.GetNetID());
checkf(EntityInfo, TEXT("EntityInfo must be valid if the Agent has already been added (which it must have been to get PostReplicatedChange"));
checkf(EntityInfo->ReplicationID >= ChangedItem.ReplicationID, TEXT("ReplicationID out of sync, this should never happen!"));
// Currently we don't think this should be needed, but are leaving it in for bomb proofing.
if (ensure(EntityInfo->ReplicationID == ChangedItem.ReplicationID))
{
FMassEntityView EntityView(EntityManager, EntityInfo->Entity);
SetModifiedEntityData(EntityView, ChangedItem.Agent);
}
}
}
#endif //UE_REPLICATION_COMPILE_CLIENT_CODE
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::SetClientHandle(FMassClientHandle InClientHandle)
{
ClientHandle = InClientHandle;
}
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::Reset()
{
(*Agents).Reset();
#if UE_REPLICATION_COMPILE_SERVER_CODE
AgentHandleManager.Reset();
NetworkIDToAgentHandleMap.Reset();
AgentLookupArray.Reset();
#endif //UE_REPLICATION_COMPILE_SERVER_CODE
}
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::Tick(float DeltaTime)
{
UWorld* World = Serializer->GetWorld();
check(World);
const ENetMode NetMode = World->GetNetMode();
if (NetMode != NM_Client)
{
DebugValidateBubbleOnServer();
}
else
{
DebugValidateBubbleOnClient();
UpdateAgentsToRemove();
}
}
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::DebugValidateBubbleOnClient()
{
#if UE_ALLOW_DEBUG_REPLICATION
const FMassEntityManager& EntityManager = Serializer->GetEntityManagerChecked();
UMassReplicationSubsystem* ReplicationSubsystem = Serializer->GetReplicationSubsystem();
check(ReplicationSubsystem);
for (int32 Idx = 0; Idx < (*Agents).Num(); ++Idx)
{
const AgentArrayItem& Item = (*Agents)[Idx];
const typename AgentArrayItem::FReplicatedAgentType& Agent = Item.Agent;
check(Agent.GetTemplateID().IsValid());
check(Agent.GetNetID().IsValid());
const FMassReplicationEntityInfo* EntityInfo = ReplicationSubsystem->FindMassEntityInfo(Agent.GetNetID());
checkf(EntityInfo, TEXT("There should always be an EntityInfoMap entry for Agents that are in the Agents array!"));
if (EntityInfo)
{
if (EntityInfo->ReplicationID == Item.ReplicationID)
{
const bool bIsEntityValid = EntityManager.IsEntityValid(EntityInfo->Entity);
checkf(bIsEntityValid, TEXT("Must be valid entity if at latest ReplciationID"));
if (bIsEntityValid)
{
const FMassNetworkIDFragment& FragmentNetID = EntityManager.GetFragmentDataChecked<FMassNetworkIDFragment>(EntityInfo->Entity);
checkf(FragmentNetID.NetID == Agent.GetNetID(), TEXT("Fragment and Agent NetID do not match!"));
}
}
}
}
#if UE_ALLOW_DEBUG_SLOW_REPLICATION
const TMap<FMassNetworkID, FMassReplicationEntityInfo>& EntityInfoMap = ReplicationSubsystem->GetEntityInfoMap();
for (TMap<FMassNetworkID, FMassReplicationEntityInfo>::TConstIterator Iter = EntityInfoMap.CreateConstIterator(); Iter; ++Iter)
{
if (Iter->Value.Entity.IsSet())
{
checkf(AgentsRemoveDataMap.Find(Iter->Key) == nullptr, TEXT("If Entity.IsSet() we should not have an entry in AgentsRemoveDataMap!"));
}
}
for (TMap<FMassNetworkID, FMassAgentRemoveData>::TIterator Iter = AgentsRemoveDataMap.CreateIterator(); Iter; ++Iter)
{
const FMassReplicationEntityInfo* EntityInfo = EntityInfoMap.Find(Iter->Key);
check(EntityInfo);
checkf(EntityInfo->Entity.IsSet() == false, TEXT("AgentsRemoveDataMap NetIDs in EntityInfoMap should be !Entity.IsSet()"));
}
#endif // UE_ALLOW_DEBUG_SLOW_REPLICATION
#endif // UE_ALLOW_DEBUG_REPLICATION
}
template<typename AgentArrayItem>
void TClientBubbleHandlerBase<AgentArrayItem>::DebugValidateBubbleOnServer()
{
#if UE_ALLOW_DEBUG_REPLICATION
using namespace UE::Mass::Replication;
const FMassEntityManager& EntityManager = Serializer->GetEntityManagerChecked();
for (int32 OuterIdx = 0; OuterIdx < (*Agents).Num(); ++OuterIdx)
{
const AgentArrayItem& OuterItem = (*Agents)[OuterIdx];
//check there are no duplicate NetID's'
if (OuterItem.Agent.GetNetID().IsValid())
{
for (int32 InnerIdx = OuterIdx + 1; InnerIdx < (*Agents).Num(); ++InnerIdx)
{
const AgentArrayItem& InnerItem = (*Agents)[InnerIdx];
checkf(InnerItem.Agent.GetNetID() != OuterItem.Agent.GetNetID(), TEXT("Repeated NetIDs in the server Agents array"));
}
}
checkf(OuterItem.Agent.GetTemplateID().IsValid(), TEXT("Server Agent with Invalid TemplateID"));
checkf(AgentHandleManager.IsValidHandle(OuterItem.GetHandle()), TEXT("Server Agent with Invalid Handle"));
checkf(OuterItem.Agent.GetNetID().IsValid(), TEXT("Server Agent with Invalid NetID"));
const FMassAgentLookupData& LookupData = AgentLookupArray[OuterItem.GetHandle().GetIndex()];
checkf(LookupData.AgentsIdx == OuterIdx, TEXT("Agent index must match lookup data!"));
checkf(EntityManager.IsEntityValid(LookupData.Entity), TEXT("Must be valid entity"));
const FMassNetworkIDFragment& FragmentNetID = EntityManager.GetFragmentDataChecked<FMassNetworkIDFragment>(LookupData.Entity);
checkf(FragmentNetID.NetID == OuterItem.Agent.GetNetID(), TEXT("Fragment and Agent NetID do not match!"));
checkf(LookupData.NetID == OuterItem.Agent.GetNetID(), TEXT("LookupData and Agent NetID do not match!"));
const FReplicationTemplateIDFragment& FragmentTemplateID = EntityManager.GetFragmentDataChecked<FReplicationTemplateIDFragment>(LookupData.Entity);
checkf(FragmentTemplateID.ID == OuterItem.Agent.GetTemplateID(), TEXT("Agent TemplateID different to Fragment!"));
}
checkf(AgentHandleManager.CalcNumUsedHandles() == (*Agents).Num(), TEXT("Num used Agent handles must be the same as the size of the agents array!"));
#endif // UE_ALLOW_DEBUG_REPLICATION
}