Files
UnrealEngine/Engine/Source/Runtime/Datasmith/DirectLink/Private/DirectLinkScenePipe.cpp
2025-05-18 13:04:45 +08:00

528 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DirectLinkScenePipe.h"
#include "DirectLinkElementSnapshot.h"
#include "DirectLinkLog.h"
#include "DirectLinkMessages.h"
#include "DirectLinkSceneSnapshot.h"
#include "MessageEndpoint.h"
#include "Misc/Compression.h"
#include "Serialization/MemoryReader.h"
#include "Serialization/MemoryWriter.h"
namespace DirectLink
{
namespace Internal
{
int32 GetDeltaMessageTargetSizeByte()
{
static int32 i = 64*1024;
return i;
}
enum class ECompressionMethod
{
ECM_ZLib = 1,
ECM_Gzip = 2,
ECM_LZ4 = 3,
};
FName GetMethodName(ECompressionMethod MethodCode)
{
switch (MethodCode)
{
case ECompressionMethod::ECM_ZLib: return NAME_Zlib;
case ECompressionMethod::ECM_Gzip: return NAME_Gzip;
case ECompressionMethod::ECM_LZ4: return NAME_LZ4;
default: ensure(0); return NAME_None;
}
}
bool CompressInline(TArray<uint8>& UncompressedData, ECompressionMethod Method)
{
int32 UncompressedSize = UncompressedData.Num();
FName MethodName = GetMethodName(Method);
int32 CompressedSize = FCompression::CompressMemoryBound(MethodName, UncompressedSize);
TArray<uint8> CompressedData;
int32 HeaderSize = 5;
CompressedData.AddUninitialized(CompressedSize + HeaderSize);
// header
{
FMemoryWriter Ar(CompressedData);
uint8 MethodCode = uint8(Method);
Ar << MethodCode;
Ar << UncompressedSize;
check(HeaderSize == Ar.Tell());
}
if (FCompression::CompressMemory(MethodName, CompressedData.GetData() + HeaderSize, CompressedSize, UncompressedData.GetData(), UncompressedSize))
{
float CompressionRatio = float(CompressedSize) / UncompressedSize;
UE_LOG(LogDirectLinkNet, Verbose, TEXT("Message compression: %f%% (%d -> %d, %s)"), 100*CompressionRatio, UncompressedSize, CompressedSize, *MethodName.ToString());
CompressedData.SetNum(CompressedSize + HeaderSize, EAllowShrinking::Yes);
UncompressedData = MoveTemp(CompressedData);
return true;
}
UE_LOG(LogDirectLinkNet, Verbose, TEXT("Message compression failed"));
return false;
}
bool DecompressInline(TArray<uint8>& CompressedData)
{
ECompressionMethod Method;
uint8* BufferStart = nullptr;
int32 UncompressedSize = -1;
int32 CompressedSize = CompressedData.Num();
int32 HeaderSize = 0;
{
FMemoryReader Ar(CompressedData);
uint8 MethodCode = 0;
Ar << MethodCode;
Method = ECompressionMethod(MethodCode);
Ar << UncompressedSize;
HeaderSize = Ar.Tell();
}
FName MethodName = GetMethodName(Method);
if (MethodName != NAME_None)
{
TArray<uint8> UncompressedData;
UncompressedData.SetNumUninitialized(UncompressedSize);
bool Ok = FCompression::UncompressMemory(MethodName, UncompressedData.GetData(), UncompressedData.Num(), CompressedData.GetData() + HeaderSize, CompressedData.Num()-HeaderSize);
if (Ok)
{
CompressedData = MoveTemp(UncompressedData);
return true;
}
}
return false;
}
// returns true when the message is compressed
bool CompressInline(FDirectLinkMsg_DeltaMessage& UncompressedMessage)
{
check(UncompressedMessage.CompressedPayload == false);
if (UncompressedMessage.Payload.Num() > 100)
{
UncompressedMessage.CompressedPayload = CompressInline(UncompressedMessage.Payload, ECompressionMethod::ECM_ZLib);
}
return UncompressedMessage.CompressedPayload;
}
// returns true when the message is in an uncompressed state
bool DecompressInline(FDirectLinkMsg_DeltaMessage& Message)
{
if (Message.CompressedPayload)
{
if (DecompressInline(Message.Payload))
{
Message.CompressedPayload = false;
}
else
{
UE_LOG(LogDirectLinkNet, Error, TEXT("DeltaMessage: Decompression error"));
return false;
}
}
return true;
}
} // namespace Internal
template<typename MessageType>
void FPipeBase::SendInternal(MessageType* Message, int32 ByteSizeHint)
{
auto flag = EMessageFlags::Reliable;
ThisEndpoint->Send(Message, MessageType::StaticStruct(), flag, nullptr, TArrayBuilder<FMessageAddress>().Add(RemoteAddress), FTimespan::Zero(), FDateTime::MaxValue());
}
void FScenePipeToNetwork::SetupScene(FSetupSceneArg& SetupSceneArg)
{
FDirectLinkMsg_DeltaMessage* Message = FMessageEndpoint::MakeMessage<FDirectLinkMsg_DeltaMessage>(
FDirectLinkMsg_DeltaMessage::SetupScene, RemoteStreamPort, 0, 0
);
FMemoryWriter Ar(Message->Payload);
Ar << SetupSceneArg;
Send(Message);
}
void FScenePipeToNetwork::OpenDelta(FOpenDeltaArg& OpenDeltaArg)
{
++BatchNumber;
if (BatchNumber == 0) // skip 0, which would be considered invalid
{
++BatchNumber;
}
NextMessageNumber = 0;
FDirectLinkMsg_DeltaMessage* Message = FMessageEndpoint::MakeMessage<FDirectLinkMsg_DeltaMessage>(
FDirectLinkMsg_DeltaMessage::OpenDelta,
RemoteStreamPort, BatchNumber, NextMessageNumber++
);
FMemoryWriter Ar(Message->Payload);
Ar << OpenDeltaArg;
Send(Message);
}
void FScenePipeToNetwork::Send(FDirectLinkMsg_DeltaMessage* Message)
{
Internal::CompressInline(*Message);
SendInternal(Message, Message->Payload.Num());
}
void FScenePipeToNetwork::InitSetElementBuffer()
{
SetElementBuffer.Reset(Internal::GetDeltaMessageTargetSizeByte());
}
void FScenePipeToNetwork::OnSetElement(FSetElementArg& SetElementArg)
{
bool bAppend = true;
FMemoryWriter Ar(SetElementBuffer, false, bAppend);
ESerializationStatus Status = SetElementArg.Snapshot->Serialize(Ar);
Ar << SetElementArg.ElementIndexHint;
check(Status == ESerializationStatus::Ok); // write should never be an issue
if (SetElementBuffer.Num() >= Internal::GetDeltaMessageTargetSizeByte())
{
SendSetElementBuffer();
}
}
void FScenePipeToNetwork::SendSetElementBuffer()
{
FDirectLinkMsg_DeltaMessage* Message = FMessageEndpoint::MakeMessage<FDirectLinkMsg_DeltaMessage>(
FDirectLinkMsg_DeltaMessage::SetElements,
RemoteStreamPort, BatchNumber, NextMessageNumber++
);
Message->Payload = MoveTemp(SetElementBuffer);
Send(Message);
InitSetElementBuffer();
}
void FScenePipeToNetwork::RemoveElements(FRemoveElementsArg& RemoveElementsArg)
{
FDirectLinkMsg_DeltaMessage* Message = FMessageEndpoint::MakeMessage<FDirectLinkMsg_DeltaMessage>(
FDirectLinkMsg_DeltaMessage::RemoveElements,
RemoteStreamPort, BatchNumber, NextMessageNumber++
);
FMemoryWriter Ar(Message->Payload);
Ar << RemoveElementsArg.Elements;
Send(Message);
}
void FScenePipeToNetwork::OnCloseDelta(FCloseDeltaArg& CloseDeltaArg)
{
SendSetElementBuffer();
FDirectLinkMsg_DeltaMessage* Message = FMessageEndpoint::MakeMessage<FDirectLinkMsg_DeltaMessage>(
FDirectLinkMsg_DeltaMessage::CloseDelta,
RemoteStreamPort, BatchNumber, NextMessageNumber++
);
FMemoryWriter Ar(Message->Payload);
Ar << CloseDeltaArg;
Send(Message);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
void FScenePipeFromNetwork::HandleDeltaMessage(FDirectLinkMsg_DeltaMessage& Message)
{
UE_LOG(LogDirectLinkNet, Verbose, TEXT("Delta message received: b:%d m:%d k:%d"), Message.BatchCode, Message.MessageCode, Message.Kind);
if (Message.BatchCode == 0)
{
DelegateDeltaMessage(Message);
return;
}
if (CurrentBatchCode == 0)
{
CurrentBatchCode = Message.BatchCode;
NextTransmitableMessageIndex = 0;
}
if (Message.BatchCode != CurrentBatchCode)
{
UE_LOG(LogDirectLinkNet, Warning, TEXT("Dropped delta message (bad batch code %d, expected %d)"), Message.BatchCode, CurrentBatchCode);
return;
}
// consume as much as possible
if (Message.MessageCode == NextTransmitableMessageIndex)
{
DelegateDeltaMessage(Message);
++NextTransmitableMessageIndex;
FDirectLinkMsg_DeltaMessage NextMessage;
while (MessageBuffer.RemoveAndCopyValue(NextTransmitableMessageIndex, NextMessage))
{
DelegateDeltaMessage(NextMessage);
++NextTransmitableMessageIndex;
}
}
else
{
MessageBuffer.Add(Message.MessageCode, MoveTemp(Message));
}
}
void FScenePipeFromNetwork::OnOpenHaveList(const FSceneIdentifier& HaveSceneId, bool bKeepPreviousContent, int32 SyncCycle)
{
if (!ensure(BufferedHaveListContent == nullptr))
{
// we should not be opening a new have list without having the previous one closed
SendHaveElements();
}
BatchNumber = SyncCycle;
NextMessageNumber = 0;
FDirectLinkMsg_HaveListMessage* Message = FMessageEndpoint::MakeMessage<FDirectLinkMsg_HaveListMessage>(
FDirectLinkMsg_HaveListMessage::EKind::OpenHaveList,
RemoteStreamPort, BatchNumber, NextMessageNumber++
);
FMemoryWriter Ar(Message->Payload);
check(Ar.IsSaving());
Ar << const_cast<FSceneIdentifier&>(HaveSceneId);
Ar << bKeepPreviousContent;
UE_LOG(LogDirectLinkNet, Verbose, TEXT("Send OpenHave list b:%d m:%d k:%d"), Message->SyncCycle, Message->MessageCode, Message->Kind);
Send(Message);
}
void FScenePipeFromNetwork::OnHaveElement(FSceneGraphId NodeId, FElementHash HaveHash)
{
if (BufferedHaveListContent == nullptr)
{
BufferedHaveListContent = FMessageEndpoint::MakeMessage<FDirectLinkMsg_HaveListMessage>(
FDirectLinkMsg_HaveListMessage::EKind::HaveListElement,
RemoteStreamPort, 0, 0
);
BufferedHaveListContent->NodeIds.Reserve(BufferSize);
BufferedHaveListContent->Hashes.Reserve(BufferSize);
}
BufferedHaveListContent->NodeIds.Add(NodeId);
BufferedHaveListContent->Hashes.Add(HaveHash);
if (BufferedHaveListContent->NodeIds.Num() >= BufferSize)
{
SendHaveElements();
}
}
void FScenePipeFromNetwork::SendHaveElements()
{
if (BufferedHaveListContent)
{
BufferedHaveListContent->SyncCycle = BatchNumber;
BufferedHaveListContent->MessageCode = NextMessageNumber++;
ThisEndpoint->Send(BufferedHaveListContent, RemoteAddress);
BufferedHaveListContent = nullptr;
}
}
void FScenePipeFromNetwork::OnCloseHaveList()
{
SendHaveElements();
FDirectLinkMsg_HaveListMessage* Message = FMessageEndpoint::MakeMessage<FDirectLinkMsg_HaveListMessage>(
FDirectLinkMsg_HaveListMessage::EKind::CloseHaveList,
RemoteStreamPort, BatchNumber, NextMessageNumber++
);
UE_LOG(LogDirectLinkNet, Verbose, TEXT("Send OpenHave list b:%d m:%d k:%d"), Message->SyncCycle, Message->MessageCode, Message->Kind);
Send(Message);
}
void FScenePipeFromNetwork::DelegateDeltaMessage(FDirectLinkMsg_DeltaMessage& Message)
{
UE_LOG(LogDirectLinkNet, Verbose, TEXT("Delta message transmited: b:%d m:%d k:%d"), Message.BatchCode, Message.MessageCode, Message.Kind);
// acknowledge message (allows the sender to track communication progress)
auto* AckMessage = FMessageEndpoint::MakeMessage<FDirectLinkMsg_HaveListMessage>(
FDirectLinkMsg_HaveListMessage::EKind::AckDeltaMessage,
RemoteStreamPort, Message.BatchCode, Message.MessageCode
);
Send(AckMessage);
if (!Internal::DecompressInline(Message))
{
return; // log by DecompressInline
}
// process message
check(Consumer);
switch (Message.Kind)
{
case FDirectLinkMsg_DeltaMessage::SetupScene:
{
CurrentCommunicationStatus.bIsReceiving = true;
IDeltaConsumer::FSetupSceneArg SetupSceneArg;
FMemoryReader Ar(Message.Payload);
Ar << SetupSceneArg;
Consumer->SetupScene(SetupSceneArg);
break;
}
case FDirectLinkMsg_DeltaMessage::OpenDelta:
{
IDeltaConsumer::FOpenDeltaArg OpenDeltaArg;
FMemoryReader Ar(Message.Payload);
Ar << OpenDeltaArg;
CurrentCommunicationStatus.TaskTotal = OpenDeltaArg.ElementCountHint + 1; // "+1" in order to reach 100% artificially on the last message
Consumer->OpenDelta(OpenDeltaArg);
break;
}
case FDirectLinkMsg_DeltaMessage::SetElements:
{
FMemoryReader Ar(Message.Payload);
bool bSerialOk = true;
while (bSerialOk && Ar.Tell() < Message.Payload.Num())
{
IDeltaConsumer::FSetElementArg SetElementArg;
SetElementArg.Snapshot = MakeShared<FElementSnapshot>();
ESerializationStatus SerialResult = SetElementArg.Snapshot->Serialize(Ar);
Ar << SetElementArg.ElementIndexHint;
bSerialOk = false;
switch (SerialResult)
{
case ESerializationStatus::Ok:
Consumer->OnSetElement(SetElementArg);
CurrentCommunicationStatus.TaskCompleted = SetElementArg.ElementIndexHint;
bSerialOk = true;
break;
case ESerializationStatus::StreamError:
// #ue_directlink_syncprotocol notify sender of unrecoverable errors
UE_LOG(LogDirectLinkNet, Error, TEXT("Delta message issue: Stream Error"));
break;
case ESerializationStatus::VersionMinNotRespected:
UE_LOG(LogDirectLinkNet, Error, TEXT("Delta message issue: received message version no longer supported"));
break;
case ESerializationStatus::VersionMaxNotRespected:
UE_LOG(LogDirectLinkNet, Error, TEXT("Delta message issue: received message version unknown"));
break;
default:
ensure(false);
}
}
break;
}
case FDirectLinkMsg_DeltaMessage::RemoveElements:
{
IDeltaConsumer::FRemoveElementsArg RemoveElementsArg;
FMemoryReader Ar(Message.Payload);
Ar << RemoveElementsArg.Elements;
Consumer->RemoveElements(RemoveElementsArg);
break;
}
case FDirectLinkMsg_DeltaMessage::CloseDelta:
{
CurrentCommunicationStatus.bIsReceiving = false;
CurrentCommunicationStatus.TaskCompleted = CurrentCommunicationStatus.TaskTotal;
IDeltaConsumer::FCloseDeltaArg CloseDeltaArg;
FMemoryReader Ar(Message.Payload);
Ar << CloseDeltaArg;
Consumer->OnCloseDelta(CloseDeltaArg);
CurrentBatchCode = 0;
break;
}
case FDirectLinkMsg_DeltaMessage::None:
default:
ensure(false);
}
}
void FScenePipeFromNetwork::Send(FDirectLinkMsg_HaveListMessage* Message)
{
// #ue_directlink_sync compression ?
SendInternal(Message, Message->Payload.Num());
}
FArchive& operator<<(FArchive& Ar, FSceneIdentifier& SceneId)
{
Ar << SceneId.SceneGuid;
Ar << SceneId.DisplayName;
return Ar;
}
FArchive& operator<<(FArchive& Ar, IDeltaConsumer::FSetupSceneArg& SetupSceneArg)
{
Ar << SetupSceneArg.SceneId;
Ar << SetupSceneArg.bExpectHaveList;
Ar << SetupSceneArg.SyncCycle;
return Ar;
}
FArchive& operator<<(FArchive& Ar, IDeltaConsumer::FOpenDeltaArg& OpenDeltaArg)
{
Ar << OpenDeltaArg.bBasedOnNewScene;
Ar << OpenDeltaArg.ElementCountHint;
return Ar;
}
FArchive& operator<<(FArchive& Ar, IDeltaConsumer::FCloseDeltaArg& CloseDeltaArg)
{
Ar << CloseDeltaArg.bCancelled;
return Ar;
}
} // namespace DirectLink