// Copyright Epic Games, Inc. All Rights Reserved. #include "MassReplicationSubsystem.h" #include "Engine/World.h" #include "Engine/ChildConnection.h" #include "GameFramework/GameModeBase.h" #include "MassCommonTypes.h" #include "MassEntityManager.h" #include "MassClientBubbleHandler.h" #include "MassClientBubbleInfoBase.h" #include "MassReplicationSettings.h" #include "MassEntityUtils.h" #include "VisualLogger/VisualLogger.h" uint32 UMassReplicationSubsystem::CurrentNetMassCounter = 0; void UMassReplicationSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); World = GetWorld(); check(World); MassLODSubsystem = Collection.InitializeDependency(); check(MassLODSubsystem); EntityManager = UE::Mass::Utils::GetEntityManagerChecked(*World).AsShared(); OverrideSubsystemTraits(Collection); } void UMassReplicationSubsystem::Deinitialize() { // remove all Clients for (const FMassClientHandle& Handle : ClientHandleManager.GetHandles()) { if (Handle.IsValid()) { RemoveClient(Handle); } } // make sure all the other data is reset ClientHandleManager.Reset(); BubbleInfoArray.Reset(); ClientsReplicationInfo.Reset(); ClientToViewerHandleArray.Reset(); ViewerToClientHandleArray.Reset(); World = nullptr; MassLODSubsystem = nullptr; EntityManager.Reset(); Super::Deinitialize(); } UMassReplicationSubsystem::UMassReplicationSubsystem() : ReplicationGrid(GetDefault()->GetReplicationGridCellSize()) { } FMassNetworkID UMassReplicationSubsystem::GetNetIDFromHandle(const FMassEntityHandle Handle) const { check(EntityManager); const FMassNetworkIDFragment& Data = EntityManager->GetFragmentDataChecked(Handle); return Data.NetID; } namespace UE { namespace Mass { namespace Replication { // Checks the parent net connection has APlayerController OwningActor, unfortunately GetParentConnection() prevents UChildConnection being const here. APlayerController* GetValidParentNetConnection(UChildConnection& ChildConnection) { return (ChildConnection.GetParentConnection() != nullptr) ? Cast(ChildConnection.GetParentConnection()->OwningActor) : nullptr; } enum class ENetConnectionType : uint8 { None, Parent, Child, }; // Checks Controllers net connection is a parent net connection bool HasParentNetConnection(const APlayerController* Controller) { return (Controller != nullptr) && Controller->NetConnection && (Cast(Controller->NetConnection) == nullptr); } // If Controller has a UChildConnection then this gets the parent net connection's APlayerController OwningActor otherwise nullptr APlayerController* GetParentControllerFromChildNetConnection(const APlayerController* Controller) { //ChildConnection can't be const UChildConnection* ChildConnection = Controller ? Cast(Controller->NetConnection) : nullptr; return ChildConnection ? GetValidParentNetConnection(*ChildConnection) : nullptr; } // Gets the ENetConnectionType for Controller. Tests if Controller's net connection is a parent or if Controller's net connection is a child and its parent net connection has a APlayerController OwningActor, ENetConnectionType HasParentOrChildWithValidParentNetConnection(const APlayerController* Controller) { ENetConnectionType ConnectionType = ENetConnectionType::None; if (Controller && Controller->NetConnection) { UChildConnection* ChildConnection = Cast(Controller->NetConnection); // if the NetConnection is either a parent connection or a child connection with APlayerController owning actor its valid if (ChildConnection == nullptr) { ConnectionType = ENetConnectionType::Parent; } else { ConnectionType = (GetValidParentNetConnection(*ChildConnection) != nullptr) ? ENetConnectionType::Child : ENetConnectionType::None; } } return ConnectionType; } }}}; bool UMassReplicationSubsystem::SynchronizeClients(const TArray& Viewers) { typedef TMap FControllerMap; struct FClientAddData { FClientAddData(FMassViewerHandle InHandle, APlayerController* InController) : Handle(InHandle) , Controller(InController) {} FMassViewerHandle Handle; APlayerController* Controller = nullptr; }; bool bNeedShrinking = false; //we can only replicate if we have some BubbleInfos to use for replication if (BubbleInfoArray.Num() == 0) { return bNeedShrinking; } // Arbitrarily use index 0, if there are more items the corresponding InfoData, Bubbles will be the same length and corresponding indices between the Bubbles // will have the same player controller owner. const FMassClientBubbleInfoData& InfoData = BubbleInfoArray[0]; check(MassLODSubsystem); FControllerMap ClientConnectionMap; TArray ClientsToAdd; { // Go through the stored Clients, add valid ones to the ClientConnectionMap and remove invalids. // Note this is only valid until we next add a client handle. const TArray& ClientHandles = ClientHandleManager.GetHandles(); for (int32 Idx = 0; Idx < ClientHandles.Num(); ++Idx) { const FMassClientHandle& ClientHandle = ClientHandles[Idx]; if (ClientHandle.IsValid()) { check(ClientToViewerHandleArray.IsValidIndex(ClientHandle.GetIndex())); const FViewerClientPair ViewerClientPair = ClientToViewerHandleArray[ClientHandle.GetIndex()]; check(ViewerClientPair.ViewerHandle.IsValid()); check(ViewerClientPair.ClientHandle == ClientHandle); check(InfoData.Bubbles.IsValidIndex(ClientHandle.GetIndex())); const AMassClientBubbleInfoBase* ClientBubble = InfoData.Bubbles[ClientHandle.GetIndex()]; const APlayerController* Controller = ClientBubble ? Cast(ClientBubble->GetOwner()) : nullptr; //no need to check netconnection if UE_ALLOW_DEBUG_REPLICATION_BUBBLES_STANDALONE #if !UE_ALLOW_DEBUG_REPLICATION_BUBBLES_STANDALONE // must have a valid non child UNetConnection if (UE::Mass::Replication::HasParentNetConnection(Controller)) #endif { ClientConnectionMap.Add(ViewerClientPair.ViewerHandle, ClientHandle); } #if !UE_ALLOW_DEBUG_REPLICATION_BUBBLES_STANDALONE else { // this is safe as Clients and their handles are free list arrays RemoveClient(ClientHandle); bNeedShrinking |= Idx == ClientHandles.Num() - 1; } #endif } } // now go through all current Viewers and add if they do not exist for (const FViewerInfo& Viewer : Viewers) { // must have valid Viewer and parent net connection if (Viewer.Handle.IsValid()) { //no need to check netconnection if UE_ALLOW_DEBUG_REPLICATION_BUBBLES_STANDALONE #if !UE_ALLOW_DEBUG_REPLICATION_BUBBLES_STANDALONE APlayerController* ViewerAsPlayerController = Viewer.GetPlayerController(); if (UE::Mass::Replication::HasParentNetConnection(ViewerAsPlayerController)) #endif { // check if the controller already exists by trying to remove it from the map which was filled up with controllers we were tracking if (ClientConnectionMap.Remove(Viewer.Handle) == 0) { // If not add it to ClientsToAdd. Its important AddClient isn't called until necessary Clients are removed, as we may reuse // array indices that are already in use. ClientsToAdd.Emplace(Viewer.Handle, ViewerAsPlayerController); } } } } // anything left in the map needs to be removed from the list for (FControllerMap::TIterator Itr = ClientConnectionMap.CreateIterator(); Itr; ++Itr) { const int32 ViewerIdx = Itr->Value.GetIndex(); RemoveClient(Itr->Value); //InfoData must be valid here as ClientConnectionMap can only have items if ClientHandleManager.GetHandles() was greater than 0 //when we entered the function. bNeedShrinking |= (ViewerIdx == (InfoData.Bubbles.Num() - 1)); } } // resize the ViewerToClientHandleArray array to match the viewer handles array (for consistency) if (ViewerToClientHandleArray.Num() > Viewers.Num()) { ViewerToClientHandleArray.RemoveAt(Viewers.Num(), ViewerToClientHandleArray.Num() - Viewers.Num(), EAllowShrinking::No); } for (FClientAddData& ClientAdd : ClientsToAdd) { //TODO make AddClients (ie plural) functionality AddClient(ClientAdd.Handle, *ClientAdd.Controller); } return bNeedShrinking; } // synchronize the ClientViewers void UMassReplicationSubsystem::SynchronizeClientViewers(const TArray& Viewers) { check(MassLODSubsystem); struct FMassClientViewerHandle { FMassClientViewerHandle(FMassClientHandle InClientHandle, FMassViewerHandle InViewerHandle) : ClientHandle(InClientHandle) , ViewerHandle(InViewerHandle) {} FMassClientHandle ClientHandle; FMassViewerHandle ViewerHandle; }; // go through the ClientReplicationInfo and check validity and store the valid ones into a map typedef TMap FViewerMap; FViewerMap ClientViewerMap; { const TArray ClientHandles = ClientHandleManager.GetHandles(); for (const FMassClientHandle& ClientHandle : ClientHandles) { //as this is a fresh handle then we only need to check against !IsInvalid if (ClientHandle.IsValid()) { FMassClientReplicationInfo& ClientReplicationInfo = ClientsReplicationInfo[ClientHandle.GetIndex()]; int32 ViewerIdx = 0; while (ViewerIdx < ClientReplicationInfo.Handles.Num()) { const FMassViewerHandle& ClientViewer = ClientReplicationInfo.Handles[ViewerIdx]; APlayerController* Controller = MassLODSubsystem->GetPlayerControllerFromViewerHandle(ClientViewer); const UE::Mass::Replication::ENetConnectionType ConnectionType = UE::Mass::Replication::HasParentOrChildWithValidParentNetConnection(Controller); if (ConnectionType != UE::Mass::Replication::ENetConnectionType::None) { ++ViewerIdx; // we don't verify or remove Parent Connections here as that was done when we synchronized the client bubbles if (ConnectionType == UE::Mass::Replication::ENetConnectionType::Child) { ClientViewerMap.Add(Controller, FMassClientViewerHandle(ClientHandle, ClientViewer)); } } else //remove invalid ClientViewer, but dont increment the ViewerIdx { ClientReplicationInfo.Handles.RemoveAt(ViewerIdx, EAllowShrinking::No); } } } } // now go through all current Viewers and add if they are a valid ClientViewer with a child UNetconnection if they do not exist for (const FViewerInfo& Viewer : Viewers) { // we are only interested in valid Viewers if (Viewer.Handle.IsValid()) { // we are only processing child UNetConnections that have a valid APlayerController OwningActor APlayerController* ViewerAsPlayerController = Viewer.GetPlayerController(); const APlayerController* ParentController = UE::Mass::Replication::GetParentControllerFromChildNetConnection(ViewerAsPlayerController); // check if the parent controller is valid and already exists if (ParentController && (ClientViewerMap.Find(ViewerAsPlayerController) == nullptr)) { FMassViewerHandle ParentViewerHandle = MassLODSubsystem->GetViewerHandleFromActor(*ParentController); if (ensureMsgf(ParentViewerHandle.IsValid(), TEXT("MassLODSubsystem handles are out of sync with PlayerController NetConnections!"))) { // note all clients (parent NetConnections) should already be set up so we would expect valid handles here check(ViewerToClientHandleArray.IsValidIndex(ParentViewerHandle.GetIndex())); const FViewerClientPair& ParentViewerClientPair = ViewerToClientHandleArray[ParentViewerHandle.GetIndex()]; check(MassLODSubsystem->IsValidViewer(ParentViewerClientPair.ViewerHandle)); check(ClientHandleManager.IsValidHandle(ParentViewerClientPair.ClientHandle)); // remove APlayerController from the ClientViewerMap and Add the viewer to the ClientsReplicationInfo ClientViewerMap.Remove(ViewerAsPlayerController); FMassClientReplicationInfo& ClientReplicationInfo = ClientsReplicationInfo[ParentViewerClientPair.ClientHandle.GetIndex()]; ClientReplicationInfo.Handles.Add(Viewer.Handle); } } } } } { // anything left in the map needs to be removed from the list for (FViewerMap::TIterator Itr = ClientViewerMap.CreateIterator(); Itr; ++Itr) { const FMassClientViewerHandle& HandleData = Itr->Value; FMassClientReplicationInfo& ClientReplicationInfo = ClientsReplicationInfo[HandleData.ClientHandle.GetIndex()]; ClientReplicationInfo.Handles.RemoveSingle(HandleData.ViewerHandle); } } } void UMassReplicationSubsystem::SynchronizeClientsAndViewers() { // only execute this code at most once per frame if (LastSynchronizedFrame == GFrameCounter) { return; } LastSynchronizedFrame = GFrameCounter; if (MassLODSubsystem == nullptr) { checkNoEntry(); return; } // makes sure the LOD manager Viewers are synced before we process them const TArray& Viewers = MassLODSubsystem->GetSynchronizedViewers(); const bool bNeedShrinking = SynchronizeClients(Viewers); //only synchronize the client viewers outside of the debug functionality for now #if !UE_ALLOW_DEBUG_REPLICATION_BUBBLES_STANDALONE SynchronizeClientViewers(Viewers); #endif //UE_ALLOW_REPLICATION_DEBUG_BUBBLES_IN_STANDALONE if (bNeedShrinking) { const int32 NumItems = ClientHandleManager.ShrinkHandles(); ClientsReplicationInfo.RemoveAt(NumItems, ClientsReplicationInfo.Num() - NumItems, EAllowShrinking::No); ClientToViewerHandleArray.RemoveAt(NumItems, ClientToViewerHandleArray.Num() - NumItems, EAllowShrinking::No); for (FMassClientBubbleInfoData& InfoData : BubbleInfoArray) { InfoData.Bubbles.RemoveAt(NumItems, InfoData.Bubbles.Num() - NumItems, EAllowShrinking::No); } } } FMassBubbleInfoClassHandle UMassReplicationSubsystem::RegisterBubbleInfoClass(const TSubclassOf& BubbleInfoClass) { checkf(BubbleInfoClass.Get() != nullptr, TEXT("BubbleInfoClass must have been set!")); if (ClientHandleManager.GetHandles().Num() > 0) { checkf(false, TEXT("RegisterBubbleInfoClass() must not be called after AddClient, BubbleInfoClass has not been registered")); return FMassBubbleInfoClassHandle(); } const int32 IdxFound = BubbleInfoArray.IndexOfByPredicate([BubbleInfoClass](const FMassClientBubbleInfoData& Data) { return BubbleInfoClass == Data.BubbleClass; }); if (IdxFound != INDEX_NONE) { UE_LOG(LogMassReplication, Log, TEXT("UMassReplicationSubsystem: Trying to RegisterBubbleInfoClass() twice with the same BubbleInfoClass, Only one BubbleInfoClass will be registered for this type")); return FMassBubbleInfoClassHandle(IdxFound); } const int32 Idx = BubbleInfoArray.Emplace(BubbleInfoClass); return FMassBubbleInfoClassHandle(Idx); } FMassBubbleInfoClassHandle UMassReplicationSubsystem::GetBubbleInfoClassHandle(const TSubclassOf& BubbleInfoClass) const { FMassBubbleInfoClassHandle Handle; for (int32 Idx = 0; Idx < BubbleInfoArray.Num(); ++Idx) { const FMassClientBubbleInfoData& BubbleData = BubbleInfoArray[Idx]; if (BubbleData.BubbleClass == BubbleInfoClass) { Handle.SetIndex(Idx); break; } } UE_CVLOG_UELOG(Handle.IsValid() == false, this, LogMassReplication, Error, TEXT("%hs failed to find required Bubble Info class"), __FUNCTION__); return Handle; } #if UE_REPLICATION_COMPILE_CLIENT_CODE void UMassReplicationSubsystem::SetEntity(const FMassNetworkID NetworkID, const FMassEntityHandle Entity) { FMassReplicationEntityInfo* EntityInfo = FindMassEntityInfoMutable(NetworkID); check(EntityInfo && !EntityInfo->Entity.IsSet()); EntityInfo->Entity = Entity; OnMassAgentAdded.Broadcast(NetworkID, Entity); } #endif // UE_REPLICATION_COMPILE_CLIENT_CODE #if UE_REPLICATION_COMPILE_CLIENT_CODE FMassEntityHandle UMassReplicationSubsystem::ResetEntityIfValid(const FMassNetworkID NetworkID, int32 ReplicationID) { FMassEntityHandle EntityReset; FMassReplicationEntityInfo* EntityInfo = FindMassEntityInfoMutable(NetworkID); checkf(EntityInfo, TEXT("EntityData must have been added to EntityInfoMap!")); checkf(EntityInfo->ReplicationID >= ReplicationID, TEXT("We must not be removing an item we've never added!")); // Only reset 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 (EntityInfo->Entity.IsSet() && (EntityInfo->ReplicationID == ReplicationID)) { OnRemovingMassAgent.Broadcast(NetworkID, EntityReset); EntityReset = EntityInfo->Entity; //Unset the Entity handle, this indicates that its currently removed from the bubble EntityInfo->Entity = FMassEntityHandle(); } return EntityReset; } #endif // UE_REPLICATION_COMPILE_CLIENT_CODE #if UE_REPLICATION_COMPILE_CLIENT_CODE void UMassReplicationSubsystem::RemoveFromEntityInfoMap(const FMassNetworkID NetworkID) { check(NetworkID.IsValid()); FMassReplicationEntityInfo Info; ensureMsgf(EntityInfoMap.RemoveAndCopyValue(NetworkID, Info), TEXT("Removing a non-existant NetworkID(%s)!"), *NetworkID.Describe()); } #endif // UE_REPLICATION_COMPILE_CLIENT_CODE #if UE_REPLICATION_COMPILE_CLIENT_CODE UMassReplicationSubsystem::EFindOrAddMassEntityInfo UMassReplicationSubsystem::FindAndUpdateOrAddMassEntityInfo(const FMassNetworkID NetworkID, int32 ReplicationID, const FMassReplicationEntityInfo*& OutMassEntityInfo) { FMassReplicationEntityInfo* MassEntityInfo = FindMassEntityInfoMutable(NetworkID); EFindOrAddMassEntityInfo FindOrAddStatus = EFindOrAddMassEntityInfo::FoundOlderReplicationID; if (MassEntityInfo) { // Currently we don't think this should be needed, but are leaving it in for bomb proofing if (ensure(MassEntityInfo->ReplicationID < ReplicationID)) { // Update the replication ID to the latest MassEntityInfo->ReplicationID = ReplicationID; } else { FindOrAddStatus = EFindOrAddMassEntityInfo::FoundNewerReplicationID; } } else { MassEntityInfo = &EntityInfoMap.Add(NetworkID, FMassReplicationEntityInfo(FMassEntityHandle(), ReplicationID)); FindOrAddStatus = EFindOrAddMassEntityInfo::Added; } OutMassEntityInfo = MassEntityInfo; return FindOrAddStatus; } #endif // UE_REPLICATION_COMPILE_CLIENT_CODE #if UE_REPLICATION_COMPILE_CLIENT_CODE const FMassReplicationEntityInfo* UMassReplicationSubsystem::FindMassEntityInfo(const FMassNetworkID NetworkID) const { check(NetworkID.IsValid()); return EntityInfoMap.Find(NetworkID); } #endif // UE_REPLICATION_COMPILE_CLIENT_CODE #if UE_REPLICATION_COMPILE_CLIENT_CODE FMassReplicationEntityInfo* UMassReplicationSubsystem::FindMassEntityInfoMutable(const FMassNetworkID NetworkID) { check(NetworkID.IsValid()); return EntityInfoMap.Find(NetworkID); } #endif // UE_REPLICATION_COMPILE_CLIENT_CODE #if UE_REPLICATION_COMPILE_CLIENT_CODE FMassEntityHandle UMassReplicationSubsystem::FindEntity(const FMassNetworkID NetworkID) const { check(NetworkID.IsValid()); const FMassReplicationEntityInfo* Info = EntityInfoMap.Find(NetworkID); return Info ? Info->Entity : FMassEntityHandle(); } #endif // UE_REPLICATION_COMPILE_CLIENT_CODE void UMassReplicationSubsystem::DebugCheckArraysAreInSync() { #if UE_DEBUG_REPLICATION checkf((ClientToViewerHandleArray.Num() == ClientsReplicationInfo.Num()), TEXT("Client arrays out of sync with each other!")); const int32 NumEntries = (BubbleInfoArray.Num() > 0) ? BubbleInfoArray[0].Bubbles.Num() : 0; for (int32 IdxOuter = 0; IdxOuter < BubbleInfoArray.Num(); ++IdxOuter) { FMassClientBubbleInfoData& InfoDataOuter = BubbleInfoArray[IdxOuter]; checkf((InfoDataOuter.Bubbles.Num() == ClientsReplicationInfo.Num()), TEXT("BubbleInfoArray arrays out of sync with ClientsReplicationInfo!")); checkf(InfoDataOuter.Bubbles.Num() == NumEntries, TEXT("Bubbles have different numbers of items!")); for (int32 IdxInner = IdxOuter + 1; IdxInner < BubbleInfoArray.Num(); ++IdxInner) { FMassClientBubbleInfoData& InfoDataInner = BubbleInfoArray[IdxInner]; for (int32 IdxBubble = 0; IdxBubble < InfoDataOuter.Bubbles.Num(); ++IdxBubble) { AMassClientBubbleInfoBase* InfoOuter = InfoDataOuter.Bubbles[IdxBubble]; AMassClientBubbleInfoBase* InfoInner = InfoDataInner.Bubbles[IdxBubble]; const TArray& Handles = ClientHandleManager.GetHandles(); if (ClientHandleManager.IsValidHandle(Handles[IdxBubble])) { check(InfoOuter && InfoInner); APlayerController* OuterController = Cast(InfoOuter->GetOwner()); APlayerController* InnerController = Cast(InfoInner->GetOwner()); checkf((OuterController != nullptr) && (InnerController != nullptr), TEXT("Controller owners must be valid in BubbleInfoArray")); checkf(OuterController == InnerController, TEXT("Owner controllers at the same indices in different BubbleInfoArray items must be equal!")); } else { check(!InfoOuter && !InfoInner); } } } } #endif // UE_DEBUG_REPLICATION }; void UMassReplicationSubsystem::AddClient(FMassViewerHandle ViewerHandle, APlayerController& InController) { check(World); check(MassLODSubsystem); checkf(MassLODSubsystem->IsValidViewer(ViewerHandle), TEXT("ViewerHandle must be valid")); #if !UE_ALLOW_DEBUG_REPLICATION_BUBBLES_STANDALONE checkf(UE::Mass::Replication::HasParentNetConnection(&InController), TEXT("InController must have a parent net connection or replication will not occur!")); #endif if (!ensureMsgf(BubbleInfoArray.Num() > 0, TEXT("BubbleInfoClass has not been set Client will not be added to UMassReplicationSubsystem!"))) { return; } if (ViewerHandle.GetIndex() >= ViewerToClientHandleArray.Num()) { // making no assumptions about the order that ViewerHandles are added, make sure we add enough space to accomodate the new ViewerHandle index ViewerToClientHandleArray.AddDefaulted(ViewerHandle.GetIndex() - ViewerToClientHandleArray.Num() + 1); } else { checkf(ViewerToClientHandleArray[ViewerHandle.GetIndex()].ViewerHandle.IsValid() == false, TEXT("Adding a Client without removing the previous with the same Index!")); } FActorSpawnParameters SpawnParams; SpawnParams.Owner = &InController; const FMassClientHandle ClientHandle = ClientHandleManager.GetNextHandle(); const FViewerClientPair ViewerClientPair(ViewerHandle, ClientHandle); FViewerClientPair& ViewerToClientHandleItem = ViewerToClientHandleArray[ViewerHandle.GetIndex()]; checkf(ViewerToClientHandleItem.IsValid() == false, TEXT("Handles should have been reset when previous Viewer in this slot was removed")); ViewerToClientHandleItem = ViewerClientPair; checkf(ClientHandle.GetIndex() <= ClientToViewerHandleArray.Num(), TEXT("ClientHandle out of sync with ClientToViewerHandleArray")); // check if the handle is a new entry in the free list arrays or uses an existing entry if (ClientHandle.GetIndex() == ClientToViewerHandleArray.Num()) { ClientsReplicationInfo.AddDefaulted(); ClientToViewerHandleArray.Emplace(ViewerHandle, ClientHandle); } else { checkf(ClientsReplicationInfo[ClientHandle.GetIndex()].IsEmpty(), TEXT("ClientsReplicationInfo being replaced must have been reset prior to being reused!")); FViewerClientPair& ClientToViewerHandleItem = ClientToViewerHandleArray[ClientHandle.GetIndex()]; checkf(ClientToViewerHandleItem.IsValid() == false, TEXT("Handles should have been reset when previous Client in this slot was removed")); ClientToViewerHandleItem = ViewerClientPair; } FMassClientReplicationInfo& ClientReplicationInfo = ClientsReplicationInfo[ClientHandle.GetIndex()]; ClientReplicationInfo.Handles.Add(ViewerHandle); for (FMassClientBubbleInfoData& InfoData : BubbleInfoArray) { AMassClientBubbleInfoBase* ClientBubbleInfo = World->SpawnActor(InfoData.BubbleClass, SpawnParams); ClientBubbleInfo->SetClientHandle(ClientHandle); checkf(ClientHandle.GetIndex() <= InfoData.Bubbles.Num(), TEXT("ClientHandle out of sync with Bubbles")); // check if the handle is a new entry in the free list arrays or uses an existing entry if (ClientHandle.GetIndex() == InfoData.Bubbles.Num()) { InfoData.Bubbles.Push(ClientBubbleInfo); } else { TObjectPtr& BubbleUpdate = InfoData.Bubbles[ClientHandle.GetIndex()]; checkf(BubbleUpdate == nullptr, TEXT("ClientBubble being replaced must be nullptr it should have been removed first!")); BubbleUpdate = ClientBubbleInfo; } } DebugCheckArraysAreInSync(); } void UMassReplicationSubsystem::RemoveClient(FMassClientHandle ClientHandle) { check(World); checkf(ClientHandleManager.IsValidHandle(ClientHandle), TEXT("ClientHandle must be a valid non stale handle")); checkf(ClientToViewerHandleArray.IsValidIndex(ClientHandle.GetIndex()), TEXT("ClientHandle is out of sync with ClientToViewerHandleArray!")); FViewerClientPair& ClientToViewerHandleItem = ClientToViewerHandleArray[ClientHandle.GetIndex()]; checkf(ClientToViewerHandleItem.ViewerHandle.IsValid(), TEXT("Invalid ViewerHandle! ClientHandle is out of sync with ClientToViewerHandleArray!")); checkf(ClientToViewerHandleItem.ClientHandle == ClientHandle, TEXT("ClientHandle is out of sync with ClientToViewerHandleArray!")); { FMassClientReplicationInfo& ClientReplicationInfo = ClientsReplicationInfo[ClientHandle.GetIndex()]; checkf(ClientReplicationInfo.Handles.Num() > 0, TEXT("There should always be atleast one client viewer handle (the parent NetConnection)")); ClientReplicationInfo.Reset(); } checkf(ViewerToClientHandleArray.IsValidIndex(ClientToViewerHandleItem.ViewerHandle.GetIndex()), TEXT("ViewerHandle is out of sync with ViewerToClientHandleArray!")); FViewerClientPair& ViewerToClientHandleItem = ViewerToClientHandleArray[ClientToViewerHandleItem.ViewerHandle.GetIndex()]; checkf(ViewerToClientHandleItem == ClientToViewerHandleItem, TEXT("ClientToViewerHandleArray and ViewerToClientHandleArray out of sync!")); ViewerToClientHandleItem.Invalidate(); ClientToViewerHandleItem.Invalidate(); for (FMassClientBubbleInfoData& InfoData : BubbleInfoArray) { checkf(InfoData.Bubbles.IsValidIndex(ClientHandle.GetIndex()), TEXT("ClientHandle is out of sync with Bubbles!")); TObjectPtr& BubbleInfoItem = InfoData.Bubbles[ClientHandle.GetIndex()]; if ((World != nullptr) && (BubbleInfoItem != nullptr)) { World->DestroyActor(BubbleInfoItem); } BubbleInfoItem = nullptr; } ClientHandleManager.RemoveHandle(ClientHandle); DebugCheckArraysAreInSync(); }