Files
UnrealEngine/Engine/Plugins/Compression/OodleNetwork/Source/Public/OodleNetworkHandlerComponent.h
2025-05-18 13:04:45 +08:00

434 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
// @todo #JohnB: Separate module-based header code, from other class implementations, so that you can setup the PCH.h file correctly
#include "Net/Core/Analytics/NetAnalytics.h"
#include "UObject/CoreNet.h"
#include "PacketHandler.h"
#include "OodleNetworkArchives.h"
#include "OodleNetworkFaultHandler.h"
#include "oodle2net.h"
#include "OodleNetworkHandlerComponent.generated.h"
#define UE_API OODLENETWORKHANDLERCOMPONENT_API
struct FBitWriter;
struct FOodleNetAnalyticsData;
struct FOutPacketTraits;
DECLARE_LOG_CATEGORY_EXTERN(OodleNetworkHandlerComponentLog, Log, All);
// The maximum packet size that this component can handle - UNetConnection's should never allow MaxPacket to exceed MAX_PACKET_SIZE
#define MAX_OODLE_PACKET_BYTES MAX_PACKET_SIZE
// The maximum compress/decompress buffer size - overkill, as buffers are statically allocated, and can't use Oodle runtime buffer calc
#define MAX_OODLE_BUFFER (MAX_OODLE_PACKET_BYTES * 2)
/**
* Specifies when compression is enabled. Used to make compression optional, for some platforms/clients
*/
UENUM()
enum class EOodleNetworkEnableMode : uint8
{
AlwaysEnabled, // Oodle compression is always enabled - forces compression to be enabled remotely
WhenCompressedPacketReceived // Oodle compression is only enabled if remotely requested
};
#define CAPTURE_EXT TEXT(".ucap")
/** Stats */
#if !UE_BUILD_SHIPPING
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Reserved Oodle (bits)"), STAT_PacketReservedOodle, STATGROUP_Packet, );
#endif
DECLARE_STATS_GROUP(TEXT("OodleNetwork"), STATGROUP_OodleNetwork, STATCAT_Advanced)
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle Out Rate Raw (bytes)"), STAT_Oodle_OutRaw, STATGROUP_OodleNetwork, );
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle Out Rate Compressed (bytes)"), STAT_Oodle_OutCompressed, STATGROUP_OodleNetwork, );
DECLARE_FLOAT_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle Out Rate Savings %"), STAT_Oodle_OutSavings, STATGROUP_OodleNetwork, );
DECLARE_FLOAT_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle Out Total Savings %"), STAT_Oodle_OutTotalSavings, STATGROUP_OodleNetwork, );
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle In Rate Raw (bytes)"), STAT_Oodle_InRaw, STATGROUP_OodleNetwork, );
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle In Rate Compressed (bytes)"), STAT_Oodle_InCompressed, STATGROUP_OodleNetwork, );
DECLARE_FLOAT_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle In Rate Savings %"), STAT_Oodle_InSavings, STATGROUP_OodleNetwork, );
DECLARE_FLOAT_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle In Total Savings %"), STAT_Oodle_InTotalSavings, STATGROUP_OodleNetwork, );
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle Compress Fail Num (0% savings)"), STAT_Oodle_CompressFailSavings, STATGROUP_OodleNetwork, );
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle Compress Fail Num (size limit)"), STAT_Oodle_CompressFailSize, STATGROUP_OodleNetwork, );
// @todo #JohnB: Implement (e.g. deliberately skipping VOIP)
//DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle Compress Skip Num"), STAT_Oodle_CompressSkip, STATGROUP_OodleNetwork, );
#if !UE_BUILD_SHIPPING
DECLARE_CYCLE_STAT_EXTERN(TEXT("Oodle Out Compress Time"), STAT_Oodle_OutCompressTime, STATGROUP_OodleNetwork, );
DECLARE_CYCLE_STAT_EXTERN(TEXT("Oodle In Decompress Time"), STAT_Oodle_InDecompressTime, STATGROUP_OodleNetwork, );
#endif
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Oodle Dictionary Count"), STAT_Oodle_DictionaryCount, STATGROUP_OodleNetwork, );
DECLARE_MEMORY_STAT_EXTERN(TEXT("Oodle Dictionary Bytes"), STAT_Oodle_DictionaryBytes, STATGROUP_OodleNetwork, );
DECLARE_MEMORY_STAT_EXTERN(TEXT("Oodle Shared Bytes"), STAT_Oodle_SharedBytes, STATGROUP_OodleNetwork, );
DECLARE_MEMORY_STAT_EXTERN(TEXT("Oodle State Bytes"), STAT_Oodle_StateBytes, STATGROUP_OodleNetwork, );
/** Globals */
/** The directory Oodle packet captures are saved to */
extern FString GOodleSaveDir;
/** The directory Oodle dictionaries are saved/loaded to/from */
extern FString GOodleContentDir;
#if STATS
// @todo #JohnB: The stats collecting is a bit crude, and should probably be giving per-dictionary stats
/**
* Stores Oodle net traffic stats, accumulated over the past second, before passing it to the stats system
*/
class FOodleNetStats
{
private:
/** Accumulated stats since last update */
/** Input traffic compressed packet length */
uint32 InCompressedLength;
/** Input traffic decompressed packet length */
uint32 InDecompressedLength;
/** Output traffic compressed packet length */
uint32 OutCompressedLength;
/** Output traffic uncompressed packet length */
uint32 OutUncompressedLength;
/** Time of the last stats update */
double LastStatsUpdate;
/** Process lifetime stats */
/** Total compressed input packets length */
uint64 TotalInCompressedLength;
/** Total decompressed input packets length */
uint64 TotalInDecompressedLength;
/** Total compressed output packets length */
uint64 TotalOutCompressedLength;
/** Total uncompressed output packets length */
uint64 TotalOutUncompressedLength;
public:
/**
* Base constructor
*/
FOodleNetStats()
: InCompressedLength(0)
, InDecompressedLength(0)
, OutCompressedLength(0)
, OutUncompressedLength(0)
, LastStatsUpdate(0.0)
, TotalInCompressedLength(0)
, TotalInDecompressedLength(0)
, TotalOutCompressedLength(0)
, TotalOutUncompressedLength(0)
{
}
/**
* Process incoming packet stats
*
* @param CompressedLength The compressed size of the input packet
* @param DecompressedLength The decompressed size of the input packet
*/
FORCEINLINE void IncomingStats(uint32 CompressedLength, uint32 DecompressedLength)
{
InCompressedLength += CompressedLength;
TotalInCompressedLength += CompressedLength;
InDecompressedLength += DecompressedLength;
TotalInDecompressedLength += DecompressedLength;
CheckForUpdate();
}
/**
* Process outgoing packet stats
*
* @param CompressedLength The compressed size of the output packet
* @param UncompressedLength The uncompressed size of the output packets
*/
FORCEINLINE void OutgoingStats(uint32 CompressedLength, uint32 UncompressedLength)
{
OutCompressedLength += CompressedLength;
TotalOutCompressedLength += CompressedLength;
OutUncompressedLength += UncompressedLength;
TotalOutUncompressedLength += UncompressedLength;
CheckForUpdate();
}
/**
* Checks to see if the main stats are due an update, and triggers an update if so
*/
FORCEINLINE void CheckForUpdate()
{
float DeltaTime = static_cast<float>(FPlatformTime::Seconds() - LastStatsUpdate);
if (DeltaTime > 1.f)
{
UpdateStats(DeltaTime);
LastStatsUpdate = FPlatformTime::Seconds();
}
}
/**
* Passes up the accumulated stats, to the main engine stats tracking
*
* @param DeltaTime The exact time since last stats update
*/
void UpdateStats(float DeltaTime);
/**
* Resets the stat values
*/
void ResetStats();
};
#endif // STATS
/**
* The mode that the Oodle packet handler should operate in
*/
enum EOodleNetworkHandlerMode
{
Capturing, // Stores packet captures for the server
Release // Compresses packet data, based on the dictionary file
};
/**
* Encapsulates Oodle dictionary data loaded from file, to be wrapped in a shared pointer (auto-deleting when no longer in use)
*/
struct FOodleNetworkDictionary
{
/** Size of the hash table used for the dictionary */
uint32 HashTableSize;
/** The raw dictionary data */
uint8* DictionaryData;
/** The size of the dictionary */
uint32 DictionarySize;
/** Shared dictionary state */
OodleNetwork1_Shared* SharedDictionary;
/** The size of the shared dictionary data (stored only for memory accounting) */
uint32 SharedDictionarySize;
/** The uncompacted compressor state */
OodleNetwork1UDP_State* CompressorState;
/** The size of CompressorState */
uint32 CompressorStateSize;
private:
FOodleNetworkDictionary()
{
}
FOodleNetworkDictionary(const FOodleNetworkDictionary&) = delete;
FOodleNetworkDictionary& operator=(const FOodleNetworkDictionary&) = delete;
public:
/**
* Base constructor
*/
FOodleNetworkDictionary(uint32 InHashTableSize, uint8* InDictionaryData, uint32 InDictionarySize, OodleNetwork1_Shared* InSharedDictionary,
uint32 InSharedDictionarySize, OodleNetwork1UDP_State* InInitialCompressorState, uint32 InCompressorStateSize);
/**
* Base destructor
*/
~FOodleNetworkDictionary();
};
/**
* PacketHandler component for implementing Oodle support.
*
* Implementation uses trained/dictionary-based UDP compression.
*/
class OodleNetworkHandlerComponent : public HandlerComponent
{
public:
/** Initializes default data */
UE_API OodleNetworkHandlerComponent();
/** Default Destructor */
UE_API ~OodleNetworkHandlerComponent();
UE_API virtual void CountBytes(FArchive& Ar) const override;
/**
* Initializes first-run config settings
*/
static UE_API void InitFirstRunConfig();
/**
* Initializes all required dictionaries
*/
UE_API void InitializeDictionaries();
/**
* Lazy dictionary initialization, triggered by receiving a compressed packet from the remote connection
*/
UE_API void RemoteInitializeDictionaries();
/**
* Initializes FOodleNetworkDictionary data, from the specified dictionary file
*
* @param FilePath The dictionary file path
* @param OutDictionary The FOodleNetworkDictionary shared pointer to write to
*/
UE_API void InitializeDictionary(FString FilePath, TSharedPtr<FOodleNetworkDictionary>& OutDictionary);
/**
* Frees the local reference to FOodleNetworkDictionary data, and removes it from memory if it was the last reference
*
* @param InDictionary The FOodleNetworkDictionary shared pointer being freed
*/
UE_API void FreeDictionary(TSharedPtr<FOodleNetworkDictionary>& InDictionary);
/**
* Resolves and returns the default dictionary file paths.
*
* @param OutServerDictionary The server dictionary path
* @param OutClientDictionary The client dictionary path
* @param bFailFatal Whether or not failure to set the dictionary paths, should be fatal
* @return Whether or not the dictionary paths were successfully set
*/
UE_API bool GetDictionaryPaths(FString& OutServerDictionary, FString& OutClientDictionary, bool bFailFatal=true);
#if !UE_BUILD_SHIPPING || OODLE_DEV_SHIPPING
/**
* Searches the game directory for alternate/fallback dictionary files, using the *.udic file extension.
* NOTE: This is non-shipping-only, as release games MUST have well-determined dictionary files (for net-binary-compatibility)
*
* @param OutServerDictionary The server dictionary path
* @param OutClientDictionary The client dictionary path
* @param bTestOnly Whether this is being used to test for presence of alternate dictionaries (disables some logging)
* @return Whether or not alternate dictionaries were found
*/
UE_API bool FindFallbackDictionaries(FString& OutServerDictionary, FString& OutClientDictionary, bool bTestOnly=false);
/**
* Initializes the packet capture archives
*/
UE_API void InitializePacketLogs();
/**
* Frees the packet capture archives
*/
UE_API void FreePacketLogs();
#endif
/**
* Check if component is currently compressing packets
*
* @return Whether or not compression is active
*/
UE_API bool IsCompressionActive() const;
UE_API virtual void Initialize() override;
UE_API virtual void InitFaultRecovery(UE::Net::FNetConnectionFaultRecoveryBase* InFaultRecovery) override;
UE_API virtual bool IsValid() const override;
UE_API virtual void Incoming(FIncomingPacketRef PacketRef) override;
UE_API virtual void Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) override;
UE_API virtual int32 GetReservedPacketBits() const override;
UE_API virtual void NotifyAnalyticsProvider() override;
protected:
/** Whether or not Oodle, and its additions to the packet protocol, are enabled */
bool bEnableOodle;
/** When to enable compression on the server */
EOodleNetworkEnableMode ServerEnableMode;
/** When to enable compression on the client */
EOodleNetworkEnableMode ClientEnableMode;
#if !UE_BUILD_SHIPPING || OODLE_DEV_SHIPPING
/** File to log input packets to */
FPacketCaptureArchive* InPacketLog;
/** File to log output packets to */
FPacketCaptureArchive* OutPacketLog;
/** Search for dictionary files and use them if present - switching mode to Release in process - don't use in shipping */
bool bUseDictionaryIfPresent;
/** Whether or not packet capturing is currently enabled (outputs uncompressed packets to file) */
bool bCaptureMode;
#endif
/** The net analytics aggregator data, which will take the above locally tracked variables, once they are complete */
TNetAnalyticsDataPtr<FOodleNetAnalyticsData> NetAnalyticsData;
/** Whether or not Oodle analytics is enabled - cached from NetAnalyticsData, for fast checking */
bool bOodleNetworkAnalytics;
#if !UE_BUILD_SHIPPING
public:
#endif
/** Server (Outgoing) dictionary data */
TSharedPtr<FOodleNetworkDictionary> ServerDictionary;
/** Client (Incoming - relative to server) dictionary data */
TSharedPtr<FOodleNetworkDictionary> ClientDictionary;
/** Whether or not InitializeDictionaries was ever called */
bool bInitializedDictionaries;
private:
/** Fault handler for Oodle-Network-specific errors, that may trigger NetConnection Close */
FOodleNetworkFaultHandler OodleNetworkFaultHandler;
};
/**
* Oodle Module Interface
*/
class FOodleComponentModuleInterface : public FPacketHandlerComponentModuleInterface
{
private:
public:
FOodleComponentModuleInterface()
{
}
virtual TSharedPtr<HandlerComponent> CreateComponentInstance(FString& Options) override;
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
#undef UE_API