// Copyright Epic Games, Inc. All Rights Reserved. #include "LiveLinkCompression.h" #include "Misc/App.h" #include "Misc/Compression.h" #include "UObject/StructOnScope.h" #include "HAL/IConsoleManager.h" #include "Serialization/MemoryReader.h" #include "Serialization/MemoryWriter.h" #include "StructSerializer.h" #include "StructDeserializer.h" #include "Backends/CborStructSerializerBackend.h" #include "Backends/CborStructDeserializerBackend.h" namespace UE::LiveLink::Compression { static TAutoConsoleVariable CVarCompressionType( TEXT("LiveLink.SetCompressionType"), 1, TEXT("Specify the type of compression to use when serializing data. A value of 0 means compression is off. A value of 1 = Oodle. All other values = Zlib.")); static TAutoConsoleVariable CVarCompressionFlags( TEXT("LiveLink.SetCompressionFlags"), 0, TEXT("Specify the flags to use when compression is enabled. A value of 0 means no flags. A value of 1 favors smaller sizes. Any other value favors faster encoding.")); int32 GetConsoleVariableCompressionType() { return CVarCompressionType.GetValueOnAnyThread(); } int32 GetConsoleVariableCompressionFlags() { return CVarCompressionFlags.GetValueOnAnyThread(); } } namespace PayloadDetail { bool ShouldCompress(const FLiveLinkSerializedFrameData& InPayload, ELiveLinkPayloadCompressionType CompressionType) { if (CompressionType == ELiveLinkPayloadCompressionType::None) { return false; } if (CompressionType == ELiveLinkPayloadCompressionType::Heuristic) { return UE::LiveLink::Compression::ShouldCompress(InPayload.PayloadSize); } check(CompressionType == ELiveLinkPayloadCompressionType::Always); // Otherwise we are always compressing return InPayload.PayloadSize > 0; } bool TryCompressImpl(const UScriptStruct* InEventType, const void* InEventData, FLiveLinkSerializedFrameData& InOutPayload, ELiveLinkPayloadCompressionType CompressionType) { InOutPayload.PayloadSize = InOutPayload.PayloadBytes.Bytes.Num(); // if we serialized something, compress it if (ShouldCompress(InOutPayload, CompressionType)) { TRACE_CPUPROFILER_EVENT_SCOPE(LiveLink::TryCompressImpl); TArray& InBytes = InOutPayload.PayloadBytes.Bytes; TArray OutCompressedData; // Compress the result to send on the wire FName NamedCompressionAlgo = UE::LiveLink::Compression::GetCompressionAlgorithm(); ECompressionFlags CompressFlags = UE::LiveLink::Compression::GetCompressionFlags(); int32 CompressedSize = FCompression::CompressMemoryBound(NamedCompressionAlgo, InOutPayload.PayloadSize, CompressFlags); OutCompressedData.SetNumUninitialized(CompressedSize); if (FCompression::CompressMemory(NamedCompressionAlgo, OutCompressedData.GetData(), CompressedSize, InBytes.GetData(), InBytes.Num(), CompressFlags)) { OutCompressedData.SetNum(CompressedSize, EAllowShrinking::No); InOutPayload.PayloadBytes.Bytes = MoveTemp(OutCompressedData); InOutPayload.CompressionMethod = UE::LiveLink::Compression::GetCompressionMethod(NamedCompressionAlgo); InOutPayload.CompressionBias = UE::LiveLink::Compression::GetCompressionBias(CompressFlags); } else { UE_LOG(LogTemp, Warning, TEXT("Unable to compress data for %s!"), *InEventType->GetName()); InOutPayload.CompressionMethod = ELiveLinkCompressionMethod::Uncompressed; } } else { InOutPayload.CompressionMethod = ELiveLinkCompressionMethod::Uncompressed; } // Since we can support uncompressed or compressed data this is always successful. return true; } static TOptional> DecompressImpl(const FLiveLinkSerializedFrameData& InPayload) { ELiveLinkCompressionMethod CompressMethod = InPayload.CompressionMethod; if (CompressMethod != ELiveLinkCompressionMethod::Uncompressed) { TRACE_CPUPROFILER_EVENT_SCOPE(LiveLink::TryDecompress); const TArray& InBytes = InPayload.PayloadBytes.Bytes; TArray UncompressedData; UncompressedData.SetNumUninitialized(InPayload.PayloadSize); ECompressionFlags CompressFlags = UE::LiveLink::Compression::GetCoreCompressionFlags(InPayload.CompressionBias); FName CompressType = UE::LiveLink::Compression::GetCompressionAlgorithm(InPayload.CompressionMethod); if (FCompression::UncompressMemory(CompressType, UncompressedData.GetData(), UncompressedData.Num(), InBytes.GetData(), InBytes.Num(), CompressFlags)) { return TOptional>(MoveTemp(UncompressedData)); } else { UE_LOG(LogTemp, Warning, TEXT("Unable to uncompress data for %s!"), *InPayload.PayloadTypeName.ToString()); } } return TOptional>{}; } bool SerializeImpl(const UScriptStruct* InSourceEventType, const void* InSourceEventData, FLiveLinkSerializedFrameData& OutSerializedData) { if (OutSerializedData.SerializationMethod == ELiveLinkPayloadSerializationMethod::Cbor) { FMemoryWriter Writer(OutSerializedData.PayloadBytes.Bytes); FCborStructSerializerBackend Serializer(Writer, EStructSerializerBackendFlags::Default); FStructSerializer::Serialize(InSourceEventData, *const_cast(InSourceEventType), Serializer); return !Writer.GetError(); } FMemoryWriter Archive(OutSerializedData.PayloadBytes.Bytes); Archive.SetWantBinaryPropertySerialization(true); const_cast(InSourceEventType)->SerializeItem(Archive, (uint8*)InSourceEventData, nullptr); return !Archive.GetError(); } bool DeserializeImpl(const UScriptStruct* InTargetEventType, void* InOutTargetEventData, ELiveLinkPayloadSerializationMethod SerializeMethod, const TArray& InBytes) { if (SerializeMethod == ELiveLinkPayloadSerializationMethod::Cbor) { FMemoryReader Reader(InBytes); FCborStructDeserializerBackend Deserializer(Reader); return FStructDeserializer::Deserialize(InOutTargetEventData, *const_cast(InTargetEventType), Deserializer) && !Reader.GetError(); } FMemoryReader Archive(InBytes); Archive.SetWantBinaryPropertySerialization(true); const_cast(InTargetEventType)->SerializeItem(Archive, (uint8*)InOutTargetEventData, nullptr); return !Archive.GetError(); } bool DeserializeAndDecompress(const UScriptStruct* InTargetEventType, void* InOutTargetEventData, const FLiveLinkSerializedFrameData& InPayload) { TOptional> DecompressedBytes = DecompressImpl(InPayload); ELiveLinkCompressionMethod CompressMethod = InPayload.CompressionMethod; if (CompressMethod != ELiveLinkCompressionMethod::Uncompressed && !DecompressedBytes.IsSet()) { return false; } const TArray& ByteStream = DecompressedBytes.IsSet() ? DecompressedBytes.GetValue() : InPayload.PayloadBytes.Bytes; return DeserializeImpl(InTargetEventType, InOutTargetEventData, InPayload.SerializationMethod, ByteStream); } bool SerializePreChecks(const UScriptStruct* InSourceEventType, const void* InSourceEventData, FLiveLinkSerializedFrameData& OutSerializedData) { OutSerializedData.PayloadSize = 0; OutSerializedData.PayloadBytes.Bytes.Reset(); return InSourceEventType && InSourceEventData; } bool DeserializePreChecks(const UScriptStruct* InEventType, void* InOutEventData, const FLiveLinkSerializedFrameData& Payload) { return InEventType && InOutEventData; } } // namespace PayloadDetail bool FLiveLinkSerializedFrameData::SetPayload(const FStructOnScope& InPayload, ELiveLinkPayloadCompressionType CompressionType) { const UStruct* PayloadStruct = InPayload.GetStruct(); check(PayloadStruct->IsA()); return SetPayload((UScriptStruct*)PayloadStruct, InPayload.GetStructMemory(), CompressionType); } bool FLiveLinkSerializedFrameData::SetPayload(const UScriptStruct* InPayloadType, const void* InPayloadData, ELiveLinkPayloadCompressionType CompressionType) { check(InPayloadType && InPayloadData); PayloadTypeName = *InPayloadType->GetPathName(); return PayloadDetail::SerializePreChecks(InPayloadType, InPayloadData, *this) && PayloadDetail::SerializeImpl(InPayloadType, InPayloadData, *this) && PayloadDetail::TryCompressImpl(InPayloadType, InPayloadData, *this, CompressionType); } bool FLiveLinkSerializedFrameData::GetPayload(FStructOnScope& OutPayload) const { UStruct* PayloadType = nullptr; { FGCScopeGuard Guard; PayloadType = FindObject(nullptr, *PayloadTypeName.ToString()); } if (PayloadType) { OutPayload.Initialize(PayloadType); const UStruct* PayloadStruct = OutPayload.GetStruct(); check(PayloadStruct->IsA()); return PayloadDetail::DeserializePreChecks((UScriptStruct*)PayloadStruct, OutPayload.GetStructMemory(), *this) && PayloadDetail::DeserializeAndDecompress((UScriptStruct*)PayloadStruct, OutPayload.GetStructMemory(), *this); } return false; } bool FLiveLinkSerializedFrameData::GetPayload(const UScriptStruct* InPayloadType, void* InOutPayloadData) const { check(InPayloadType && InOutPayloadData); return IsTypeChildOf(InPayloadType) && PayloadDetail::DeserializePreChecks((UScriptStruct*)InPayloadType, InOutPayloadData, *this) && PayloadDetail::DeserializeAndDecompress((UScriptStruct*)InPayloadType, InOutPayloadData, *this); } bool FLiveLinkSerializedFrameData::IsTypeChildOf(const UScriptStruct* InPayloadType) const { const UStruct* PayloadType = FindObject(nullptr, *PayloadTypeName.ToString()); return PayloadType && InPayloadType->IsChildOf(PayloadType); }