// Copyright Epic Games, Inc. All Rights Reserved. #include "DirectLinkStreamSender.h" #include "DirectLinkElementSnapshot.h" #include "DirectLinkLog.h" namespace DirectLink { /** * Keeps tracks of what the remote have. * A Simple id -> Hash table. */ class FRemoteSceneView { public: const FSceneIdentifier& GetSceneId() const { return SceneId; } void SetSceneId(const FSceneIdentifier& InSceneId) { SceneId = InSceneId; } FElementHash& GetHashRef(FSceneGraphId NodeId) { return HaveList.FindOrAdd(NodeId, InvalidHash); } void GetHaveIdsSet(TSet& OutIds) const { HaveList.GetKeys(OutIds); } private: FSceneIdentifier SceneId; TMap HaveList; }; /** * Handle unordered incoming messages, build a usable haveList structure for the following diff algo. */ class FHaveListReceiver { public: FHaveListReceiver(int32 SyncCycle) : SyncCycle(SyncCycle) , RemoteScene(MakeUnique()) {} bool IsOver() const { return bClosed; } // accepts unordered messages void HandleMessage(const FDirectLinkMsg_HaveListMessage& Message); // update RemoteView TUniquePtr& GetRemoteSceneView() { return RemoteScene; } private: void HandleOrderedMessage(const FDirectLinkMsg_HaveListMessage& Message); // update RemoteView private: int32 SyncCycle = 0; int32 NextOrderedMessage = 0; TSortedMap Unordered; TUniquePtr RemoteScene; bool bClosed = false; }; FStreamSender::FStreamSender(TSharedPtr ThisEndpoint, const FMessageAddress& DestinationAddress, FStreamPort ReceiverStreamPort) : PipeToNetwork(ThisEndpoint, DestinationAddress, ReceiverStreamPort) { } // allows forward-decl. of TUniquePtrs inner classes FStreamSender::~FStreamSender() = default; void FStreamSender::HandleHaveListMessage(const FDirectLinkMsg_HaveListMessage& Message) { if (Message.Kind == FDirectLinkMsg_HaveListMessage::EKind::AckDeltaMessage) { UE_LOG(LogDirectLinkNet, Verbose, TEXT("Receiver ack message %d"), Message.MessageCode); CurrentCommunicationStatus.TaskCompleted = FMath::Max(CurrentCommunicationStatus.TaskCompleted, Message.MessageCode + 1); // max, because ack messages are unordered return; } if (!HaveListReceiver) { UE_LOG(LogDirectLinkNet, Warning, TEXT("dropped unexpected HaveList message.")); return; } HaveListReceiver->HandleMessage(Message); LastHaveListMessage_s = FPlatformTime::Seconds(); } void FStreamSender::SetSceneSnapshot(TSharedPtr SceneSnapshot) { FScopeLock _(&NextSnapshotLock); NextSnapshot = SceneSnapshot; } void FStreamSender::Tick(double Now_s) { switch (NextStep) { case EStep::Idle: { if (NextSnapshotLock.TryLock()) { Snapshot = NextSnapshot; NextSnapshot.Reset(); NextSnapshotLock.Unlock(); if (Snapshot) { ++SyncCycle; HaveListReceiver = MakeUnique(SyncCycle); CurrentCommunicationStatus = FCommunicationStatus(); NextStep = EStep::SetupScene; return Tick(Now_s); } } break; } case EStep::SetupScene: { CurrentCommunicationStatus.bIsSending = true; check(Snapshot.IsValid()); auto& Local = *Snapshot.Get(); IDeltaConsumer::FSetupSceneArg SetupSceneArg; SetupSceneArg.SceneId = Local.SceneId; SetupSceneArg.bExpectHaveList = true; SetupSceneArg.SyncCycle = SyncCycle; PipeToNetwork.SetupScene(SetupSceneArg); LastHaveListMessage_s = FPlatformTime::Seconds(); NextStep = EStep::ReceiveHaveList; break; } case EStep::ReceiveHaveList: // wait for completed have list { CurrentCommunicationStatus.bIsSending = false; CurrentCommunicationStatus.bIsReceiving = true; // waiting for the remote havelist. if (ensure(HaveListReceiver) && HaveListReceiver->IsOver()) { RemoteScene = MoveTemp(HaveListReceiver->GetRemoteSceneView()); NextStep = EStep::GenerateDelta; return Tick(Now_s); } else { // #ue_directlink_syncprotocol todo: timeout // !!! connectivity is not handled here !!! // we should never be in that situation as the stream should have been checked before (ping/pong exchange) // Sender object may receive a LostConnectivity msg though. double ElapsedSinceHaveListRequest_s = FPlatformTime::Seconds() - LastHaveListMessage_s; if (ElapsedSinceHaveListRequest_s > 0.1) { UE_LOG(LogDirectLinkNet, Warning, TEXT("connectivity issue: Have List not received. Retry")) NextStep = EStep::SetupScene; return; } } break; } case EStep::GenerateDelta: { CurrentCommunicationStatus.bIsSending = true; CurrentCommunicationStatus.bIsReceiving = false; check(Snapshot.IsValid()); FSceneSnapshot& SceneSnapshot = *Snapshot.Get(); IDeltaConsumer::FOpenDeltaArg OpenDeltaArg; OpenDeltaArg.bBasedOnNewScene = false; OpenDeltaArg.ElementCountHint = SceneSnapshot.Elements.Num(); PipeToNetwork.OpenDelta(OpenDeltaArg); TSet LocalIds; LocalIds.Reserve(SceneSnapshot.Elements.Num()); int32 CurrentElementIndex = -1; for (auto& RefPair : SceneSnapshot.Elements) { ++CurrentElementIndex; FSceneGraphId NodeId = RefPair.Key; LocalIds.Add(NodeId); FElementSnapshot& ElementSnapshot = RefPair.Value.Get(); FElementHash NodeHash = ElementSnapshot.GetHash(); if (NodeHash != InvalidHash) { FElementHash& HaveHash = RemoteScene->GetHashRef(NodeId); if (HaveHash == NodeHash) { UE_LOG(LogDirectLinkNet, Verbose, TEXT("diff: Skipped %d, have hash match"), NodeId); continue; } HaveHash = NodeHash; } IDeltaConsumer::FSetElementArg SetArg; SetArg.Snapshot = RefPair.Value; SetArg.ElementIndexHint = CurrentElementIndex; PipeToNetwork.OnSetElement(SetArg); } TSet RemoteIds; RemoteScene->GetHaveIdsSet(RemoteIds); TSet RemovableIds = RemoteIds.Difference(LocalIds); if (RemovableIds.Num()) { IDeltaConsumer::FRemoveElementsArg DelArg; DelArg.Elements = RemovableIds.Array(); PipeToNetwork.RemoveElements(DelArg); } IDeltaConsumer::FCloseDeltaArg CloseArg; PipeToNetwork.OnCloseDelta(CloseArg); int32 DeltaMessagesCount = PipeToNetwork.GetSentDeltaMessageCount(); CurrentCommunicationStatus.TaskTotal = DeltaMessagesCount; Snapshot.Reset(); NextStep = EStep::SendDelta; break; } case EStep::SendDelta: { if (CurrentCommunicationStatus.TaskTotal == CurrentCommunicationStatus.TaskCompleted) { NextStep = EStep::Synced; return Tick(Now_s); } else { UE_LOG(LogDirectLinkNet, Verbose, TEXT("diff: waiting for receiver ack (%d/%d)") , CurrentCommunicationStatus.TaskCompleted , CurrentCommunicationStatus.TaskTotal ); } break; } case EStep::Synced: { UE_LOG(LogDirectLinkNet, Verbose, TEXT("diff: sync complete! (cycle %d)"), SyncCycle); CurrentCommunicationStatus.bIsSending = false; CurrentCommunicationStatus.bIsReceiving = false; NextStep = EStep::Idle; return Tick(Now_s); } default: ensure(false); } } void FHaveListReceiver::HandleMessage(const FDirectLinkMsg_HaveListMessage& Message) { if (Message.SyncCycle != SyncCycle) { UE_LOG(LogDirectLinkNet, Warning, TEXT("Dropped FHaveListReceiver message, expected syncCycle:%d, Received SyncCycle:%d"), SyncCycle, Message.SyncCycle); return; } if (bClosed) { UE_LOG(LogDirectLinkNet, Warning, TEXT("Dropped FHaveListReceiver message, closed FHaveListReceiver struct")); return; } if (NextOrderedMessage == Message.MessageCode) { HandleOrderedMessage(Message); NextOrderedMessage++; FDirectLinkMsg_HaveListMessage NextMessage; while(Unordered.RemoveAndCopyValue(NextOrderedMessage, NextMessage)) { HandleOrderedMessage(MoveTemp(NextMessage)); NextOrderedMessage++; } } else { Unordered.Add(Message.MessageCode, Message); } } void FHaveListReceiver::HandleOrderedMessage(const FDirectLinkMsg_HaveListMessage& Message) { switch (FDirectLinkMsg_HaveListMessage::EKind(Message.Kind)) { case FDirectLinkMsg_HaveListMessage::EKind::OpenHaveList: { FSceneIdentifier HaveSceneId; bool bKeepPreviousContent = false; FMemoryReader Ar(Message.Payload); Ar << HaveSceneId; Ar << bKeepPreviousContent; RemoteScene->SetSceneId(HaveSceneId); break; } case FDirectLinkMsg_HaveListMessage::EKind::HaveListElement: { uint32 ElementCount = FMath::Min(Message.NodeIds.Num(), Message.Hashes.Num()); for (uint32 ElementIndex = 0; ElementIndex < ElementCount; ++ElementIndex) { RemoteScene->GetHashRef(Message.NodeIds[ElementIndex]) = Message.Hashes[ElementIndex]; } break; } case FDirectLinkMsg_HaveListMessage::EKind::CloseHaveList: { SyncCycle = 0; bClosed = true; break; } case FDirectLinkMsg_HaveListMessage::EKind::None: default: ensure(false); break; } } } // namespace DirectLink