This project has been working great for 3 months with minimal crashes. I closed the project yesterday after working on it all day with no issues. Today, I went to try to open it and it randomly crashes and shows an error message that I’ve attached below.
I tried verifying and reinstalling the engine but I get the same crash. I have a complete backup from 5 days ago and I have builds from every day since then, but I’m unsure if I can get the editor files back from the built game.
We could resort to the version from 5 days ago, but we’ve accomplished so much since then working around the clock and it would really set us back to go back to that version. I can provide any dump files, just not sure what is needed. Thanks very much in advance, this is a huge setback for us.
archive.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Serialization/Archive.h"
#include "Serialization/ArchiveProxy.h"
#include "Math/UnrealMathUtility.h"
#include "HAL/UnrealMemory.h"
#include "Containers/Array.h"
#include "Containers/UnrealString.h"
#include "UObject/NameTypes.h"
#include "Logging/LogMacros.h"
#include "Misc/Parse.h"
#include "UObject/ObjectVersion.h"
#include "Serialization/NameAsStringProxyArchive.h"
#include "Misc/CommandLine.h"
#include "Internationalization/Text.h"
#include "Stats/StatsMisc.h"
#include "Stats/Stats.h"
#include "Async/AsyncWork.h"
#include "Serialization/CustomVersion.h"
#include "Misc/EngineVersion.h"
#include "Misc/NetworkVersion.h"
#include "Interfaces/ITargetPlatform.h"
#include "Serialization/CompressedChunkInfo.h"
#include "Serialization/ArchiveSerializedPropertyChain.h"
PRAGMA_DISABLE_UNSAFE_TYPECAST_WARNINGS
namespace ArchiveUtil
{
template<typename T>
FArchive& SerializeByteOrderSwapped(FArchive& Ar, T& Value)
{
static_assert(!TIsSigned<T>::Value, "To reduce the number of template instances, cast 'Value' to a uint16&, uint32& or uint64& prior to the call.");
if (Ar.IsLoading())
{
// Read and swap.
Ar.Serialize(&Value, sizeof(T));
Value = ByteSwap(Value);
}
else // Saving
{
// Swap and write.
T SwappedValue = ByteSwap(Value);
Ar.Serialize(&SwappedValue, sizeof(T));
}
return Ar;
}
} // namespace ArchiveUtil
/*-----------------------------------------------------------------------------
FArchive implementation.
-----------------------------------------------------------------------------*/
FArchiveState::FArchiveState()
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
ActiveFPLB = &InlineFPLB;
#endif
SerializedPropertyChain = nullptr;
#if USE_STABLE_LOCALIZATION_KEYS
LocalizationNamespacePtr = nullptr;
#endif // USE_STABLE_LOCALIZATION_KEYS
Reset();
}
FArchiveState::FArchiveState(const FArchiveState& ArchiveToCopy)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
ActiveFPLB = &InlineFPLB;
#endif
#if USE_STABLE_LOCALIZATION_KEYS
LocalizationNamespacePtr = nullptr;
#endif // USE_STABLE_LOCALIZATION_KEYS
CopyTrivialFArchiveStatusMembers(ArchiveToCopy);
SerializedPropertyChain = nullptr;
SetSerializedPropertyChain(ArchiveToCopy.SerializedPropertyChain, ArchiveToCopy.SerializedProperty);
// Don't know why this is set to false, but this is what the original copying code did
ArIsFilterEditorOnly = false;
bCustomVersionsAreReset = ArchiveToCopy.bCustomVersionsAreReset;
if (ArchiveToCopy.CustomVersionContainer)
{
CustomVersionContainer = new FCustomVersionContainer(*ArchiveToCopy.CustomVersionContainer);
}
else
{
CustomVersionContainer = nullptr;
}
}
FArchiveState& FArchiveState::operator=(const FArchiveState& ArchiveToCopy)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
ActiveFPLB = &InlineFPLB;
ActiveFPLB->Reset();
#endif
CopyTrivialFArchiveStatusMembers(ArchiveToCopy);
SetSerializedPropertyChain(ArchiveToCopy.SerializedPropertyChain, ArchiveToCopy.SerializedProperty);
// Don't know why this is set to false, but this is what the original copying code did
ArIsFilterEditorOnly = false;
bCustomVersionsAreReset = ArchiveToCopy.bCustomVersionsAreReset;
if (ArchiveToCopy.CustomVersionContainer)
{
if (!CustomVersionContainer)
{
CustomVersionContainer = new FCustomVersionContainer(*ArchiveToCopy.CustomVersionContainer);
}
else
{
*CustomVersionContainer = *ArchiveToCopy.CustomVersionContainer;
}
}
else if (CustomVersionContainer)
{
delete CustomVersionContainer;
CustomVersionContainer = nullptr;
}
return *this;
}
FArchiveState::~FArchiveState()
{
checkf(NextProxy == nullptr, TEXT("Archive destroyed before its proxies"));
delete CustomVersionContainer;
delete SerializedPropertyChain;
#if USE_STABLE_LOCALIZATION_KEYS
delete LocalizationNamespacePtr;
#endif // USE_STABLE_LOCALIZATION_KEYS
}
void FArchiveState::Reset()
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
ActiveFPLB->Reset();
#endif
ArUE4Ver = GPackageFileUE4Version;
ArLicenseeUE4Ver = GPackageFileLicenseeUE4Version;
ArEngineVer = FEngineVersion::Current();
ArEngineNetVer = FNetworkVersion::GetEngineNetworkProtocolVersion();
ArGameNetVer = FNetworkVersion::GetGameNetworkProtocolVersion();
ArIsLoading = false;
ArIsSaving = false;
ArIsTransacting = false;
ArIsTextFormat = false;
ArWantBinaryPropertySerialization = false;
ArUseUnversionedPropertySerialization = false;
ArForceUnicode = false;
ArIsPersistent = false;
ArIsError = false;
ArIsCriticalError = false;
ArContainsCode = false;
ArContainsMap = false;
ArRequiresLocalizationGather = false;
ArForceByteSwapping = false;
ArSerializingDefaults = false;
ArIgnoreArchetypeRef = false;
ArNoDelta = false;
ArNoIntraPropertyDelta = false;
ArIgnoreOuterRef = false;
ArIgnoreClassGeneratedByRef = false;
ArIgnoreClassRef = false;
ArAllowLazyLoading = false;
ArIsObjectReferenceCollector = false;
ArIsModifyingWeakAndStrongReferences= false;
ArIsCountingMemory = false;
ArPortFlags = 0;
ArShouldSkipBulkData = false;
ArMaxSerializeSize = 0;
ArIsFilterEditorOnly = false;
ArIsSaveGame = false;
ArIsNetArchive = false;
ArCustomPropertyList = nullptr;
ArUseCustomPropertyList = false;
CookingTargetPlatform = nullptr;
SerializedProperty = nullptr;
delete SerializedPropertyChain;
SerializedPropertyChain = nullptr;
#if USE_STABLE_LOCALIZATION_KEYS
SetBaseLocalizationNamespace(FString());
#endif // USE_STABLE_LOCALIZATION_KEYS
#if WITH_EDITOR
ArDebugSerializationFlags = 0;
#endif
// Reset all custom versions to the current registered versions.
ResetCustomVersions();
}
void FArchiveState::CopyTrivialFArchiveStatusMembers(const FArchiveState& ArchiveToCopy)
{
ArUE4Ver = ArchiveToCopy.ArUE4Ver;
ArLicenseeUE4Ver = ArchiveToCopy.ArLicenseeUE4Ver;
ArEngineVer = ArchiveToCopy.ArEngineVer;
ArEngineNetVer = ArchiveToCopy.ArEngineNetVer;
ArGameNetVer = ArchiveToCopy.ArGameNetVer;
ArIsLoading = ArchiveToCopy.ArIsLoading;
ArIsSaving = ArchiveToCopy.ArIsSaving;
ArIsTransacting = ArchiveToCopy.ArIsTransacting;
ArIsTextFormat = ArchiveToCopy.ArIsTextFormat;
ArWantBinaryPropertySerialization = ArchiveToCopy.ArWantBinaryPropertySerialization;
ArUseUnversionedPropertySerialization = ArchiveToCopy.ArUseUnversionedPropertySerialization;
ArForceUnicode = ArchiveToCopy.ArForceUnicode;
ArIsPersistent = ArchiveToCopy.ArIsPersistent;
ArIsError = ArchiveToCopy.ArIsError;
ArIsCriticalError = ArchiveToCopy.ArIsCriticalError;
ArContainsCode = ArchiveToCopy.ArContainsCode;
ArContainsMap = ArchiveToCopy.ArContainsMap;
ArRequiresLocalizationGather = ArchiveToCopy.ArRequiresLocalizationGather;
ArForceByteSwapping = ArchiveToCopy.ArForceByteSwapping;
ArSerializingDefaults = ArchiveToCopy.ArSerializingDefaults;
ArIgnoreArchetypeRef = ArchiveToCopy.ArIgnoreArchetypeRef;
ArNoDelta = ArchiveToCopy.ArNoDelta;
ArNoIntraPropertyDelta = ArchiveToCopy.ArNoIntraPropertyDelta;
ArIgnoreOuterRef = ArchiveToCopy.ArIgnoreOuterRef;
ArIgnoreClassGeneratedByRef = ArchiveToCopy.ArIgnoreClassGeneratedByRef;
ArIgnoreClassRef = ArchiveToCopy.ArIgnoreClassRef;
ArAllowLazyLoading = ArchiveToCopy.ArAllowLazyLoading;
ArIsObjectReferenceCollector = ArchiveToCopy.ArIsObjectReferenceCollector;
ArIsModifyingWeakAndStrongReferences = ArchiveToCopy.ArIsModifyingWeakAndStrongReferences;
ArIsCountingMemory = ArchiveToCopy.ArIsCountingMemory;
ArPortFlags = ArchiveToCopy.ArPortFlags;
ArShouldSkipBulkData = ArchiveToCopy.ArShouldSkipBulkData;
ArMaxSerializeSize = ArchiveToCopy.ArMaxSerializeSize;
ArIsFilterEditorOnly = ArchiveToCopy.ArIsFilterEditorOnly;
ArIsSaveGame = ArchiveToCopy.ArIsSaveGame;
ArIsNetArchive = ArchiveToCopy.ArIsNetArchive;
ArCustomPropertyList = ArchiveToCopy.ArCustomPropertyList;
ArUseCustomPropertyList = ArchiveToCopy.ArUseCustomPropertyList;
CookingTargetPlatform = ArchiveToCopy.CookingTargetPlatform;
SerializedProperty = ArchiveToCopy.SerializedProperty;
#if USE_STABLE_LOCALIZATION_KEYS
SetBaseLocalizationNamespace(ArchiveToCopy.GetBaseLocalizationNamespace());
#endif // USE_STABLE_LOCALIZATION_KEYS
}
void FArchiveState::LinkProxy(FArchiveState& Inner, FArchiveState& Proxy)
{
Proxy.NextProxy = Inner.NextProxy;
Inner.NextProxy = &Proxy;
}
void FArchiveState::UnlinkProxy(FArchiveState& Inner, FArchiveState& Proxy)
{
FArchiveState* Prev = &Inner;
while (Prev->NextProxy != &Proxy)
{
Prev = Prev->NextProxy;
checkf(Prev, TEXT("Proxy link not found - likely lifetime violation"));
}
Prev->NextProxy = Proxy.NextProxy;
Proxy.NextProxy = nullptr;
}
template<typename T>
FORCEINLINE void FArchiveState::ForEachState(T Func)
{
FArchiveState& RootState = GetInnermostState();
Func(RootState);
for (FArchiveState* Proxy = RootState.NextProxy; Proxy; Proxy = Proxy->NextProxy)
{
Func(*Proxy);
}
}
void FArchiveState::SetArchiveState(const FArchiveState& InState)
{
ForEachState([&InState](FArchiveState& State) { State = InState; });
}
void FArchiveState::SetError()
{
ForEachState([](FArchiveState& State) { State.ArIsError = true; });
}
void FArchiveState::SetCriticalError()
{
ForEachState([](FArchiveState& State) { State.ArIsError = State.ArIsCriticalError = true; });
}
void FArchiveState::ClearError()
{
ForEachState([](FArchiveState& State) { State.ArIsError = false; });
}
/**
* Returns the name of the Archive. Useful for getting the name of the package a struct or object
* is in when a loading error occurs.
*
* This is overridden for the specific Archive Types
**/
FString FArchiveState::GetArchiveName() const
{
return TEXT("FArchive");
}
void FArchiveState::GetSerializedPropertyChain(TArray<class FProperty*>& OutProperties) const
{
if (SerializedPropertyChain)
{
const int32 NumProperties = SerializedPropertyChain->GetNumProperties();
OutProperties.Reserve(NumProperties);
for (int32 PropertyIndex = 0; PropertyIndex < NumProperties; ++PropertyIndex)
{
OutProperties.Add(SerializedPropertyChain->GetPropertyFromStack(PropertyIndex));
}
}
}
void FArchiveState::SetSerializedPropertyChain(const FArchiveSerializedPropertyChain* InSerializedPropertyChain, class FProperty* InSerializedPropertyOverride)
{
if (InSerializedPropertyChain && InSerializedPropertyChain->GetNumProperties() > 0)
{
if (!SerializedPropertyChain)
{
SerializedPropertyChain = new FArchiveSerializedPropertyChain();
}
*SerializedPropertyChain = *InSerializedPropertyChain;
}
else
{
delete SerializedPropertyChain;
SerializedPropertyChain = nullptr;
}
if (InSerializedPropertyOverride)
{
SerializedProperty = InSerializedPropertyOverride;
}
else if (SerializedPropertyChain && SerializedPropertyChain->GetNumProperties() > 0)
{
SerializedProperty = SerializedPropertyChain->GetPropertyFromStack(0);
}
else
{
SerializedProperty = nullptr;
}
}
void FArchive::PushSerializedProperty(class FProperty* InProperty, const bool bIsEditorOnlyProperty)
{
if (InProperty)
{
// Push this property into the chain
if (!SerializedPropertyChain)
{
SerializedPropertyChain = new FArchiveSerializedPropertyChain();
}
SerializedPropertyChain->PushProperty(InProperty, bIsEditorOnlyProperty);
// Update the serialized property pointer with the new head
SerializedProperty = InProperty;
}
}
void FArchive::PopSerializedProperty(class FProperty* InProperty, const bool bIsEditorOnlyProperty)
{
if (InProperty)
{
// Pop this property from the chain
check(SerializedPropertyChain);
SerializedPropertyChain->PopProperty(InProperty, bIsEditorOnlyProperty);
// Update the serialized property pointer with the new head
if (SerializedPropertyChain->GetNumProperties() > 0)
{
SerializedProperty = SerializedPropertyChain->GetPropertyFromStack(0);
}
else
{
SerializedProperty = nullptr;
}
}
}
#if WITH_EDITORONLY_DATA
bool FArchiveState::IsEditorOnlyPropertyOnTheStack() const
{
return SerializedPropertyChain && SerializedPropertyChain->HasEditorOnlyProperty();
}
#endif
#if USE_STABLE_LOCALIZATION_KEYS
void FArchiveState::SetBaseLocalizationNamespace(const FString& InLocalizationNamespace)
{
if (InLocalizationNamespace.IsEmpty())
{
delete LocalizationNamespacePtr;
LocalizationNamespacePtr = nullptr;
}
else
{
if (!LocalizationNamespacePtr)
{
LocalizationNamespacePtr = new FString();
}
*LocalizationNamespacePtr = InLocalizationNamespace;
}
}
FString FArchiveState::GetBaseLocalizationNamespace() const
{
return LocalizationNamespacePtr ? *LocalizationNamespacePtr : FString();
}
void FArchiveState::SetLocalizationNamespace(const FString& InLocalizationNamespace)
{
SetBaseLocalizationNamespace(InLocalizationNamespace);
}
FString FArchiveState::GetLocalizationNamespace() const
{
return GetBaseLocalizationNamespace();
}
#endif // USE_STABLE_LOCALIZATION_KEYS
#if WITH_EDITOR
FArchive::FScopeAddDebugData::FScopeAddDebugData(FArchive& InAr, const FName& DebugData) : Ar(InAr)
{
Ar.PushDebugDataString(DebugData);
}
void FArchive::PushDebugDataString(const FName& DebugData)
{
}
#endif
FArchive& FArchive::operator<<( FText& Value )
{
FText::SerializeText(*this, Value);
return *this;
}
FArchive& FArchive::operator<<(struct FLazyObjectPtr& Value)
{
// The base FArchive does not implement this method. Use FArchiveUObject instead.
UE_LOG(LogSerialization, Fatal, TEXT("FArchive does not support FLazyObjectPtr serialization. Use FArchiveUObject instead."));
return *this;
}
FArchive& FArchive::operator<<(struct FSoftObjectPtr& Value)
{
// The base FArchive does not implement this method. Use FArchiveUObject instead.
UE_LOG(LogSerialization, Fatal, TEXT("FArchive does not support FSoftObjectPtr serialization. Use FArchiveUObject instead."));
return *this;
}
FArchive& FArchive::operator<<(struct FSoftObjectPath& Value)
{
// The base FArchive does not implement this method. Use FArchiveUObject instead.
UE_LOG(LogSerialization, Fatal, TEXT("FArchive does not support FSoftObjectPath serialization. Use FArchiveUObject instead."));
return *this;
}
FArchive& FArchive::operator<<(struct FWeakObjectPtr& Value)
{
// The base FArchive does not implement this method. Use FArchiveUObject instead.
UE_LOG(LogSerialization, Fatal, TEXT("FArchive does not support FWeakObjectPtr serialization. Use FArchiveUObject instead."));
return *this;
}
#if WITH_EDITOR
void FArchive::SerializeBool( bool& D )
{
// Serialize bool as if it were UBOOL (legacy, 32 bit int).
uint32 OldUBoolValue;
#if DEVIRTUALIZE_FLinkerLoad_Serialize
const uint8 * RESTRICT Src = this->ActiveFPLB->StartFastPathLoadBuffer;
if (Src + sizeof(uint32) <= this->ActiveFPLB->EndFastPathLoadBuffer)
{
OldUBoolValue = FPlatformMemory::ReadUnaligned<uint32>(Src);
this->ActiveFPLB->StartFastPathLoadBuffer += 4;
}
else
#endif
{
OldUBoolValue = D ? 1 : 0;
this->Serialize(&OldUBoolValue, sizeof(OldUBoolValue));
}
if (OldUBoolValue > 1)
{
UE_LOG(LogSerialization, Error, TEXT("Invalid boolean encountered while reading archive %s - stream is most likely corrupted."), *GetArchiveName());
this->SetError();
}
D = !!OldUBoolValue;
}
#endif
const FCustomVersionContainer& FArchiveState::GetCustomVersions() const
{
if (!CustomVersionContainer)
{
CustomVersionContainer = new FCustomVersionContainer;
}
if (bCustomVersionsAreReset)
{
bCustomVersionsAreReset = false;
// If the archive is for reading then we want to use currently registered custom versions, otherwise we expect
// serialization code to use UsingCustomVersion to populate the container.
if (this->IsLoading())
{
*CustomVersionContainer = FCurrentCustomVersions::GetAll();
}
else
{
CustomVersionContainer->Empty();
}
}
return *CustomVersionContainer;
}
void FArchiveState::SetCustomVersions(const FCustomVersionContainer& NewVersions)
{
if (!CustomVersionContainer)
{
CustomVersionContainer = new FCustomVersionContainer(NewVersions);
}
else
{
*CustomVersionContainer = NewVersions;
}
bCustomVersionsAreReset = false;
}
void FArchiveState::ResetCustomVersions()
{
bCustomVersionsAreReset = true;
}
void FArchive::UsingCustomVersion(const FGuid& Key)
{
// If we're loading, we want to use the version that the archive was serialized with, not register a new one.
if (IsLoading())
{
return;
}
FCustomVersion RegisteredVersion = FCurrentCustomVersions::Get(Key).GetValue();
const_cast<FCustomVersionContainer&>(GetCustomVersions()).SetVersion(Key, RegisteredVersion.Version, RegisteredVersion.GetFriendlyName());
}
int32 FArchiveState::CustomVer(const FGuid& Key) const
{
auto* CustomVersion = GetCustomVersions().GetVersion(Key);
// If this fails, you have forgotten to make an Ar.UsingCustomVersion call
// before serializing your custom version-dependent object.
check(IsLoading() || CustomVersion);
return CustomVersion ? CustomVersion->Version : -1;
}
void FArchiveState::SetCustomVersion(const FGuid& Key, int32 Version, FName FriendlyName)
{
const_cast<FCustomVersionContainer&>(GetCustomVersions()).SetVersion(Key, Version, FriendlyName);
}
FString FArchiveProxy::GetArchiveName() const
{
return InnerArchive.GetArchiveName();
}
#if USE_STABLE_LOCALIZATION_KEYS
void FArchiveProxy::SetLocalizationNamespace(const FString& InLocalizationNamespace)
{
InnerArchive.SetLocalizationNamespace(InLocalizationNamespace);
}
FString FArchiveProxy::GetLocalizationNamespace() const
{
return InnerArchive.GetLocalizationNamespace();
}
#endif // USE_STABLE_LOCALIZATION_KEYS
/**
* Serialize the given FName as an FString
*/
FArchive& FNameAsStringProxyArchive::operator<<( class FName& N )
{
if (IsLoading())
{
FString LoadedString;
InnerArchive << LoadedString;
N = FName(*LoadedString);
}
else
{
FString SavedString(N.ToString());
InnerArchive << SavedString;
}
return *this;
}
/** Accumulative time spent in IsSaving portion of SerializeCompressed. */
CORE_API double GArchiveSerializedCompressedSavingTime = 0;
// MT compression disabled on console due to memory impact and lack of beneficial usage case.
#define WITH_MULTI_THREADED_COMPRESSION (WITH_EDITORONLY_DATA)
#if WITH_MULTI_THREADED_COMPRESSION
// Helper structure to keep information about async chunks that are in-flight.
class FAsyncCompressionChunk : public FNonAbandonableTask
{
public:
/** Pointer to source (uncompressed) memory. */
void* UncompressedBuffer;
/** Pointer to destination (compressed) memory. */
void* CompressedBuffer;
/** Compressed size in bytes as passed to/ returned from compressor. */
int32 CompressedSize;
/** Uncompressed size in bytes as passed to compressor. */
int32 UncompressedSize;
/** Target platform for compressed data */
int32 BitWindow;
/** Format to compress with */
FName CompressionFormat;
/** Flags to control compression */
ECompressionFlags Flags;
/**
* Constructor, zeros everything
*/
FAsyncCompressionChunk()
: UncompressedBuffer(0)
, CompressedBuffer(0)
, CompressedSize(0)
, UncompressedSize(0)
, BitWindow(DEFAULT_ZLIB_BIT_WINDOW)
, CompressionFormat(NAME_Zlib)
, Flags(COMPRESS_NoFlags)
{
}
/**
* Performs the async compression
*/
void DoWork()
{
// upgrade old flag method
if ((Flags & COMPRESS_DeprecatedFormatFlagsMask) != 0)
{
UE_LOG(LogSerialization, Warning, TEXT("Old style compression flags are being used with FAsyncCompressionChunk, please update any code using this!"));
CompressionFormat = FCompression::GetCompressionFormatFromDeprecatedFlags(Flags);
}
// Compress from memory to memory.
verify( FCompression::CompressMemory(CompressionFormat, CompressedBuffer, CompressedSize, UncompressedBuffer, UncompressedSize, Flags, BitWindow) );
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncCompressionChunk, STATGROUP_ThreadPoolAsyncTasks);
}
};
#endif // WITH_MULTI_THREADED_COMPRESSION
void FArchive::SerializeCompressed(void* V, int64 Length, FName CompressionFormat, ECompressionFlags Flags, bool bTreatBufferAsFileReader)
{
if( IsLoading() )
{
if (CompressionFormat == NAME_Zlib && FPlatformProperties::GetZlibReplacementFormat() != nullptr)
{
// use this platform's replacement format in case it's not zlib
CompressionFormat = FPlatformProperties::GetZlibReplacementFormat();
}
// Serialize package file tag used to determine endianess.
FCompressedChunkInfo PackageFileTag;
PackageFileTag.CompressedSize = 0;
PackageFileTag.UncompressedSize = 0;
*this << PackageFileTag;
bool bWasByteSwapped = PackageFileTag.CompressedSize != PACKAGE_FILE_TAG;
// Read in base summary.
FCompressedChunkInfo Summary;
*this << Summary;
bool bHeaderWasValid = true;
if (bWasByteSwapped)
{
bHeaderWasValid = PackageFileTag.CompressedSize == PACKAGE_FILE_TAG_SWAPPED;
if (bHeaderWasValid)
{
Summary.CompressedSize = BYTESWAP_ORDER64(Summary.CompressedSize);
Summary.UncompressedSize = BYTESWAP_ORDER64(Summary.UncompressedSize);
PackageFileTag.UncompressedSize = BYTESWAP_ORDER64(PackageFileTag.UncompressedSize);
}
}
else
{
bHeaderWasValid = PackageFileTag.CompressedSize == PACKAGE_FILE_TAG; //-V547
}
if (!bHeaderWasValid)
{
UE_LOG(LogSerialization, Log, TEXT("ArchiveName: %s"), *GetArchiveName());
UE_LOG(LogSerialization, Log, TEXT("Archive UE4 Version: %d"), UE4Ver());
UE_LOG(LogSerialization, Log, TEXT("Archive Licensee Version: %d"), LicenseeUE4Ver());
UE_LOG(LogSerialization, Log, TEXT("Position: %lld"), Tell());
UE_LOG(LogSerialization, Log, TEXT("Read Size: %lld"), Length);
UE_LOG(LogSerialization, Fatal, TEXT("BulkData compressed header read error. This package may be corrupt!"));
}
// Handle change in compression chunk size in backward compatible way.
int64 LoadingCompressionChunkSize = PackageFileTag.UncompressedSize;
if (LoadingCompressionChunkSize == PACKAGE_FILE_TAG)
{
LoadingCompressionChunkSize = LOADING_COMPRESSION_CHUNK_SIZE;
}
// Figure out how many chunks there are going to be based on uncompressed size and compression chunk size.
int64 TotalChunkCount = (Summary.UncompressedSize + LoadingCompressionChunkSize - 1) / LoadingCompressionChunkSize;
// Allocate compression chunk infos and serialize them, keeping track of max size of compression chunks used.
FCompressedChunkInfo* CompressionChunks = new FCompressedChunkInfo[TotalChunkCount];
int64 MaxCompressedSize = 0;
for( int32 ChunkIndex=0; ChunkIndex<TotalChunkCount; ChunkIndex++ )
{
*this << CompressionChunks[ChunkIndex];
if (bWasByteSwapped)
{
CompressionChunks[ChunkIndex].CompressedSize = BYTESWAP_ORDER64( CompressionChunks[ChunkIndex].CompressedSize );
CompressionChunks[ChunkIndex].UncompressedSize = BYTESWAP_ORDER64( CompressionChunks[ChunkIndex].UncompressedSize );
}
MaxCompressedSize = FMath::Max( CompressionChunks[ChunkIndex].CompressedSize, MaxCompressedSize );
}
// Set up destination pointer and allocate memory for compressed chunk[s] (one at a time).
uint8* Dest = (uint8*) V;
void* CompressedBuffer = FMemory::Malloc( MaxCompressedSize );
// Iterate over all chunks, serialize them into memory and decompress them directly into the destination pointer
for( int64 ChunkIndex=0; ChunkIndex<TotalChunkCount; ChunkIndex++ )
{
const FCompressedChunkInfo& Chunk = CompressionChunks[ChunkIndex];
// Read compressed data.
Serialize( CompressedBuffer, Chunk.CompressedSize );
// Decompress into dest pointer directly.
bool bUncompressMemorySucceeded = FCompression::UncompressMemory( CompressionFormat, Dest, Chunk.UncompressedSize, CompressedBuffer, Chunk.CompressedSize, COMPRESS_NoFlags);
verifyf(bUncompressMemorySucceeded, TEXT("Failed to uncompress data in %s. Check log for details."), *GetArchiveName()); // And advance it by read amount.
Dest += Chunk.UncompressedSize;
}
// Free up allocated memory.
FMemory::Free( CompressedBuffer );
delete [] CompressionChunks;
}
else if( IsSaving() )
{
SCOPE_SECONDS_COUNTER(GArchiveSerializedCompressedSavingTime);
check( Length > 0 );
// if there's a cooking target, and it wants to replace Zlib compression with another format, use it. When loading,
// the platform will replace Zlib with that format above
if (CompressionFormat == NAME_Zlib && CookingTargetPlatform != nullptr)
{
// use the replacement format
CompressionFormat = CookingTargetPlatform->GetZlibReplacementFormat();
}
// Serialize package file tag used to determine endianess in LoadCompressedData.
FCompressedChunkInfo PackageFileTag;
PackageFileTag.CompressedSize = PACKAGE_FILE_TAG;
PackageFileTag.UncompressedSize = GSavingCompressionChunkSize;
*this << PackageFileTag;
// Figure out how many chunks there are going to be based on uncompressed size and compression chunk size.
int64 TotalChunkCount = (Length + GSavingCompressionChunkSize - 1) / GSavingCompressionChunkSize + 1;
// Keep track of current position so we can later seek back and overwrite stub compression chunk infos.
int64 StartPosition = Tell();
// Allocate compression chunk infos and serialize them so we can later overwrite the data.
FCompressedChunkInfo* CompressionChunks = new FCompressedChunkInfo[TotalChunkCount];
for( int64 ChunkIndex=0; ChunkIndex<TotalChunkCount; ChunkIndex++ )
{
*this << CompressionChunks[ChunkIndex];
}
// The uncompressd size is equal to the passed in length.
CompressionChunks[0].UncompressedSize = Length;
// Zero initialize compressed size so we can update it during chunk compression.
CompressionChunks[0].CompressedSize = 0;
#if WITH_MULTI_THREADED_COMPRESSION
#define MAX_COMPRESSION_JOBS (16)
// Don't scale more than 16x to avoid going overboard wrt temporary memory.
FAsyncTask<FAsyncCompressionChunk> AsyncChunks[MAX_COMPRESSION_JOBS];
// used to keep track of which job is the next one we need to retire
int32 AsyncChunkIndex[MAX_COMPRESSION_JOBS]={0};
static uint32 GNumUnusedThreads_SerializeCompressed = -1;
if (GNumUnusedThreads_SerializeCompressed == (uint32)-1)
{
// one-time initialization
GNumUnusedThreads_SerializeCompressed = 1;
// if we should use all available cores then we want to compress with all
if( FParse::Param(FCommandLine::Get(), TEXT("USEALLAVAILABLECORES")) == true )
{
GNumUnusedThreads_SerializeCompressed = 0;
}
}
// Maximum number of concurrent async tasks we're going to kick off. This is based on the number of processors
// available in the system.
int32 MaxConcurrentAsyncChunks = FMath::Clamp<int32>( FPlatformMisc::NumberOfCores() - GNumUnusedThreads_SerializeCompressed, 1, MAX_COMPRESSION_JOBS );
if (FParse::Param(FCommandLine::Get(), TEXT("MTCHILD")))
{
// throttle this back when doing MT cooks
MaxConcurrentAsyncChunks = FMath::Min<int32>( MaxConcurrentAsyncChunks,4 );
}
// Number of chunks left to finalize.
int64 NumChunksLeftToFinalize = (Length + GSavingCompressionChunkSize - 1) / GSavingCompressionChunkSize;
// Number of chunks left to kick off
int64 NumChunksLeftToKickOff = NumChunksLeftToFinalize;
// Start at index 1 as first chunk info is summary.
int64 CurrentChunkIndex = 1;
// Start at index 1 as first chunk info is summary.
int64 RetireChunkIndex = 1;
// Number of bytes remaining to kick off compression for.
int64 BytesRemainingToKickOff = Length;
// Pointer to src data if buffer is memory pointer, NULL if it's a FArchive.
uint8* SrcBuffer = bTreatBufferAsFileReader ? NULL : (uint8*)V;
check(!bTreatBufferAsFileReader || ((FArchive*)V)->IsLoading());
check(NumChunksLeftToFinalize);
// Loop while there is work left to do based on whether we have finalized all chunks yet.
while( NumChunksLeftToFinalize )
{
// If true we are waiting for async tasks to complete and should wait to complete some
// if there are no async tasks finishing this iteration.
bool bNeedToWaitForAsyncTask = false;
// Try to kick off async tasks if there are chunks left to kick off.
if( NumChunksLeftToKickOff )
{
// Find free index based on looking at uncompressed size. We can't use the thread counter
// for this as that might be a chunk ready for finalization.
int32 FreeIndex = INDEX_NONE;
for( int32 i=0; i<MaxConcurrentAsyncChunks; i++ )
{
if( !AsyncChunkIndex[i] )
{
FreeIndex = i;
check(AsyncChunks[FreeIndex].IsIdle()); // this is not supposed to be in use
break;
}
}
// Kick off async compression task if we found a chunk for it.
if( FreeIndex != INDEX_NONE )
{
FAsyncCompressionChunk& NewChunk = AsyncChunks[FreeIndex].GetTask();
// 2 times the uncompressed size should be more than enough; the compressed data shouldn't be that much larger
NewChunk.CompressedSize = 2 * GSavingCompressionChunkSize;
// Allocate compressed buffer placeholder on first use.
if( NewChunk.CompressedBuffer == NULL )
{
NewChunk.CompressedBuffer = FMemory::Malloc( NewChunk.CompressedSize );
}
// By default everything is chunked up into GSavingCompressionChunkSize chunks.
NewChunk.UncompressedSize = FMath::Min( BytesRemainingToKickOff, (int64)GSavingCompressionChunkSize );
check(NewChunk.UncompressedSize>0);
// Need to serialize source data if passed in pointer is an FArchive.
if( bTreatBufferAsFileReader )
{
// Allocate memory on first use. We allocate the maximum amount to allow reuse.
if( !NewChunk.UncompressedBuffer )
{
NewChunk.UncompressedBuffer = FMemory::Malloc(GSavingCompressionChunkSize);
}
((FArchive*)V)->Serialize(NewChunk.UncompressedBuffer, NewChunk.UncompressedSize);
}
// Advance src pointer by amount to be compressed.
else
{
NewChunk.UncompressedBuffer = SrcBuffer;
SrcBuffer += NewChunk.UncompressedSize;
}
// Update status variables for tracking how much work is left, what to do next.
BytesRemainingToKickOff -= NewChunk.UncompressedSize;
AsyncChunkIndex[FreeIndex] = CurrentChunkIndex++;
NewChunk.Flags = Flags;
NewChunk.CompressionFormat = CompressionFormat;
NumChunksLeftToKickOff--;
AsyncChunks[FreeIndex].StartBackgroundTask();
}
// No chunks were available to use, complete some
else
{
bNeedToWaitForAsyncTask = true;
}
}
// Wait for the oldest task to finish instead of spinning
if (NumChunksLeftToKickOff == 0)
{
bNeedToWaitForAsyncTask = true;
}
// Index of oldest chunk, needed as we need to serialize in order.
int32 OldestAsyncChunkIndex = INDEX_NONE;
for( int32 i=0; i<MaxConcurrentAsyncChunks; i++ )
{
check(AsyncChunkIndex[i] == 0 || AsyncChunkIndex[i] >= RetireChunkIndex);
check(AsyncChunkIndex[i] < RetireChunkIndex + MaxConcurrentAsyncChunks);
if (AsyncChunkIndex[i] == RetireChunkIndex)
{
OldestAsyncChunkIndex = i;
}
}
check(OldestAsyncChunkIndex != INDEX_NONE); // the retire chunk better be outstanding
bool ChunkReady;
if (bNeedToWaitForAsyncTask)
{
// This guarantees that the async work has finished, doing it on this thread if it hasn't been started
AsyncChunks[OldestAsyncChunkIndex].EnsureCompletion();
ChunkReady = true;
}
else
{
ChunkReady = AsyncChunks[OldestAsyncChunkIndex].IsDone();
}
if (ChunkReady)
{
FAsyncCompressionChunk& DoneChunk = AsyncChunks[OldestAsyncChunkIndex].GetTask();
// Serialize the data via archive.
Serialize( DoneChunk.CompressedBuffer, DoneChunk.CompressedSize );
// Update associated chunk.
int64 CompressionChunkIndex = RetireChunkIndex++;
check(CompressionChunkIndex<TotalChunkCount);
CompressionChunks[CompressionChunkIndex].CompressedSize = DoneChunk.CompressedSize;
CompressionChunks[CompressionChunkIndex].UncompressedSize = DoneChunk.UncompressedSize;
// Keep track of total compressed size, stored in first chunk.
CompressionChunks[0].CompressedSize += DoneChunk.CompressedSize;
// Clean up chunk. Src and dst buffer are not touched as the contain allocations we keep till the end.
AsyncChunkIndex[OldestAsyncChunkIndex] = 0;
DoneChunk.CompressedSize = 0;
DoneChunk.UncompressedSize = 0;
// Finalized one :)
NumChunksLeftToFinalize--;
bNeedToWaitForAsyncTask = false;
}
}
// Free intermediate buffer storage.
for( int32 i=0; i<MaxConcurrentAsyncChunks; i++ )
{
// Free temporary compressed buffer storage.
FMemory::Free( AsyncChunks[i].GetTask().CompressedBuffer );
AsyncChunks[i].GetTask().CompressedBuffer = NULL;
// Free temporary uncompressed buffer storage if data was serialized in.
if( bTreatBufferAsFileReader )
{
FMemory::Free( AsyncChunks[i].GetTask().UncompressedBuffer );
AsyncChunks[i].GetTask().UncompressedBuffer = NULL;
}
}
#else
// Set up source pointer amount of data to copy (in bytes)
uint8* Src;
// allocate memory to read into
if (bTreatBufferAsFileReader)
{
Src = (uint8*)FMemory::Malloc(GSavingCompressionChunkSize);
check(((FArchive*)V)->IsLoading());
}
else
{
Src = (uint8*) V;
}
int64 BytesRemaining = Length;
// Start at index 1 as first chunk info is summary.
int32 CurrentChunkIndex = 1;
// 2 times the uncompressed size should be more than enough; the compressed data shouldn't be that much larger
int64 CompressedBufferSize = 2 * GSavingCompressionChunkSize;
void* CompressedBuffer = FMemory::Malloc( CompressedBufferSize );
while( BytesRemaining > 0 )
{
int64 BytesToCompress = FMath::Min( BytesRemaining, (int64)GSavingCompressionChunkSize );
int64 CompressedSize = CompressedBufferSize;
// read in the next chunk from the reader
if (bTreatBufferAsFileReader)
{
((FArchive*)V)->Serialize(Src, BytesToCompress);
}
check(CompressedSize < INT_MAX);
int32 CompressedSizeInt = (int32)CompressedSize;
verify( FCompression::CompressMemory( CompressionFormat, CompressedBuffer, CompressedSizeInt, Src, BytesToCompress, Flags ) );
CompressedSize = CompressedSizeInt;
// move to next chunk if not reading from file
if (!bTreatBufferAsFileReader)
{
Src += BytesToCompress;
}
Serialize( CompressedBuffer, CompressedSize );
// Keep track of total compressed size, stored in first chunk.
CompressionChunks[0].CompressedSize += CompressedSize;
// Update current chunk.
check(CurrentChunkIndex<TotalChunkCount);
CompressionChunks[CurrentChunkIndex].CompressedSize = CompressedSize;
CompressionChunks[CurrentChunkIndex].UncompressedSize = BytesToCompress;
CurrentChunkIndex++;
BytesRemaining -= GSavingCompressionChunkSize;
}
// free the buffer we read into
if (bTreatBufferAsFileReader)
{
FMemory::Free(Src);
}
// Free allocated memory.
FMemory::Free( CompressedBuffer );
#endif
// Overrwrite chunk infos by seeking to the beginning, serializing the data and then
// seeking back to the end.
auto EndPosition = Tell();
// Seek to the beginning.
Seek( StartPosition );
// Serialize chunk infos.
for( int32 ChunkIndex=0; ChunkIndex<TotalChunkCount; ChunkIndex++ )
{
*this << CompressionChunks[ChunkIndex];
}
// Seek back to end.
Seek( EndPosition );
// Free intermediate data.
delete [] CompressionChunks;
}
}
void FArchive::ByteSwap(void* V, int32 Length)
{
uint8* Ptr = (uint8*)V;
int32 Top = Length - 1;
int32 Bottom = 0;
while (Bottom < Top)
{
Swap(Ptr[Top--], Ptr[Bottom++]);
}
}
FArchive& FArchive::SerializeByteOrderSwapped(void* V, int32 Length)
{
if (IsLoading())
{
Serialize(V, Length); // Read.
ByteSwap(V, Length); // Swap.
}
else // Writing
{
ByteSwap(V, Length); // Swap V.
Serialize(V, Length); // Write V.
ByteSwap(V, Length); // Swap V back to its original byte order to prevent caller from observing V swapped.
}
return *this;
}
FArchive& FArchive::SerializeByteOrderSwapped(uint16& Value)
{
return ArchiveUtil::SerializeByteOrderSwapped(*this, Value);
}
FArchive& FArchive::SerializeByteOrderSwapped(uint32& Value)
{
return ArchiveUtil::SerializeByteOrderSwapped(*this, Value);
}
FArchive& FArchive::SerializeByteOrderSwapped(uint64& Value)
{
return ArchiveUtil::SerializeByteOrderSwapped(*this, Value);
}
void FArchive::SerializeIntPacked(uint32& Value)
{
if (IsLoading())
{
Value = 0;
uint8 cnt = 0;
uint8 more = 1;
while(more)
{
uint8 NextByte;
Serialize(&NextByte, 1); // Read next byte
more = NextByte & 1; // Check 1 bit to see if theres more after this
NextByte = NextByte >> 1; // Shift to get actual 7 bit value
Value += NextByte << (7 * cnt++); // Add to total value
}
}
else
{
uint8 PackedBytes[5];
int32 PackedByteCount = 0;
uint32 Remaining = Value;
while(true)
{
uint8 nextByte = Remaining & 0x7f; // Get next 7 bits to write
Remaining = Remaining >> 7; // Update remaining
nextByte = nextByte << 1; // Make room for 'more' bit
if( Remaining > 0)
{
nextByte |= 1; // set more bit
PackedBytes[PackedByteCount++] = nextByte;
}
else
{
PackedBytes[PackedByteCount++] = nextByte;
break;
}
}
Serialize(PackedBytes, PackedByteCount); // Actually serialize the bytes we made
}
}
void FArchive::LogfImpl(const TCHAR* Fmt, ...)
{
// We need to use malloc here directly as GMalloc might not be safe, e.g. if called from within GMalloc!
int32 BufferSize = 1024;
TCHAR* Buffer = NULL;
int32 Result = -1;
while(Result == -1)
{
FMemory::SystemFree(Buffer);
Buffer = (TCHAR*) FMemory::SystemMalloc( BufferSize * sizeof(TCHAR) );
GET_VARARGS_RESULT( Buffer, BufferSize, BufferSize-1, Fmt, Fmt, Result );
BufferSize *= 2;
};
Buffer[Result] = 0;
// Convert to ANSI and serialize as ANSI char.
for( int32 i=0; i<Result; i++ )
{
ANSICHAR Char = CharCast<ANSICHAR>( Buffer[i] );
Serialize( &Char, 1 );
}
// Write out line terminator.
for( int32 i=0; LINE_TERMINATOR[i]; i++ )
{
ANSICHAR Char = LINE_TERMINATOR[i];
Serialize( &Char, 1 );
}
// Free temporary buffers.
FMemory::SystemFree( Buffer );
}
void FArchiveState::SetUE4Ver(int32 InVer)
{
ArUE4Ver = InVer;
}
void FArchiveState::SetLicenseeUE4Ver(int32 InVer)
{
ArLicenseeUE4Ver = InVer;
}
void FArchiveState::SetEngineVer(const FEngineVersionBase& InVer)
{
ArEngineVer = InVer;
}
void FArchiveState::SetEngineNetVer(const uint32 InEngineNetVer)
{
ArEngineNetVer = InEngineNetVer;
}
void FArchiveState::SetGameNetVer(const uint32 InGameNetVer)
{
ArGameNetVer = InGameNetVer;
}
void FArchiveState::SetIsLoading(bool bInIsLoading)
{
ArIsLoading = bInIsLoading;
}
void FArchiveState::SetIsSaving(bool bInIsSaving)
{
ArIsSaving = bInIsSaving;
}
void FArchiveState::SetIsTransacting(bool bInIsTransacting)
{
ArIsTransacting = bInIsTransacting;
}
void FArchiveState::SetIsTextFormat(bool bInIsTextFormat)
{
ArIsTextFormat = bInIsTextFormat;
}
void FArchiveState::SetWantBinaryPropertySerialization(bool bInWantBinaryPropertySerialization)
{
ArWantBinaryPropertySerialization = bInWantBinaryPropertySerialization;
}
void FArchiveState::SetUseUnversionedPropertySerialization(bool bInUseUnversioned)
{
ArUseUnversionedPropertySerialization = bInUseUnversioned;
}
void FArchiveState::SetForceUnicode(bool bInForceUnicode)
{
ArForceUnicode = bInForceUnicode;
}
void FArchiveState::SetIsPersistent(bool bInIsPersistent)
{
ArIsPersistent = bInIsPersistent;
}
static_assert(sizeof(FArchive) == sizeof(FArchiveState), "New FArchive members should be added to FArchiveState instead");
PRAGMA_ENABLE_UNSAFE_TYPECAST_WARNINGS
archive.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreTypes.h"
#include "Misc/VarArgs.h"
#include "Misc/AssertionMacros.h"
#include "Templates/EnableIf.h"
#include "Templates/IsEnumClass.h"
#include "Templates/Function.h"
#include "HAL/PlatformProperties.h"
#include "Misc/CompressionFlags.h"
#include "Misc/EngineVersionBase.h"
#include "Internationalization/TextNamespaceFwd.h"
#include "Templates/IsValidVariadicFunctionArg.h"
#include "Templates/AndOrNot.h"
#include "Templates/IsArrayOrRefOfType.h"
#include "Templates/IsSigned.h"
class FArchive;
class FCustomVersionContainer;
class FLinker;
class FName;
class FString;
class FText;
class ITargetPlatform;
class UObject;
class FProperty;
struct FUntypedBulkData;
struct FArchiveSerializedPropertyChain;
template<class TEnum> class TEnumAsByte;
typedef TFunction<bool (double RemainingTime)> FExternalReadCallback;
struct FUObjectSerializeContext;
class FField;
enum class EFileRegionType : uint8;
// Temporary while we shake out the EDL at boot
#define USE_EVENT_DRIVEN_ASYNC_LOAD_AT_BOOT_TIME (1)
#if USE_EVENT_DRIVEN_ASYNC_LOAD_AT_BOOT_TIME
#define EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME (1)
#else
#define EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME (!GIsInitialLoad)
#endif
#define DEVIRTUALIZE_FLinkerLoad_Serialize (!WITH_EDITORONLY_DATA)
// Helper macro to make serializing a bitpacked boolean in an archive easier
#define FArchive_Serialize_BitfieldBool(ARCHIVE, BITFIELD_BOOL) { bool TEMP_BITFIELD_BOOL = BITFIELD_BOOL; ARCHIVE << TEMP_BITFIELD_BOOL; BITFIELD_BOOL = TEMP_BITFIELD_BOOL; }
struct CORE_API FArchiveState
{
private:
// Only FArchive is allowed to instantiate this, by inheritance
friend class FArchive;
FArchiveState();
/** Copy constructor. */
FArchiveState(const FArchiveState&);
/**
* Copy assignment operator.
*
* @param ArchiveToCopy The archive to copy from.
*/
FArchiveState& operator=(const FArchiveState& ArchiveToCopy);
virtual ~FArchiveState() = 0;
protected:
static void LinkProxy(FArchiveState& Inner, FArchiveState& Proxy);
static void UnlinkProxy(FArchiveState& Inner, FArchiveState& Proxy);
public:
/**
* Returns lowest level archive state, proxy archives will override this.
*/
virtual FArchiveState& GetInnermostState()
{
return *this;
}
/**
* Modifies current archive state, can be used to override flags.
*/
void SetArchiveState(const FArchiveState& InState);
/**
* Sets ArIsError to true. Also sets error in the proxy archiver if one is wrapping this.
*/
void SetError();
/**
* Sets ArIsError to false, this does not clear any CriticalErrors
*/
void ClearError();
/**
* Sets the archiver IsCriticalError and IsError to true. Also sets CriticalError in the proxy archiver if one is wrapping this.
*/
void SetCriticalError();
/**
* Called to get the computed size from a size-detecting archive after it has finished serializing.
*/
virtual void CountBytes(SIZE_T InNum, SIZE_T InMax) { }
/**
* Returns the name of the Archive. Useful for getting the name of the package a struct or object
* is in when a loading error occurs.
*
* This is overridden for the specific Archive Types
*/
virtual FString GetArchiveName() const;
/**
* If this archive is a FLinkerLoad or FLinkerSave, returns a pointer to the ULinker portion.
*
* @return The linker, or nullptr if the archive is not a linker.
*/
virtual FLinker* GetLinker()
{
return nullptr;
}
/**
* Returns the current location within the backing data storage, which can possibly be passed to Seek later to restore a read/write location.
* If this returns -1, there is no backing data storage and Seek will not function.
*/
virtual int64 Tell()
{
return INDEX_NONE;
}
/** Returns total size of the backing data storage. */
virtual int64 TotalSize()
{
return INDEX_NONE;
}
/** Returns true if the current location within the backing data storage is at the end, always returns false if there is no storage. */
virtual bool AtEnd()
{
int64 Pos = Tell();
return ((Pos != INDEX_NONE) && (Pos >= TotalSize()));
}
/** Returns true if data larger than 1 byte should be swapped to deal with endian mismatches. */
FORCEINLINE bool IsByteSwapping()
{
#if PLATFORM_LITTLE_ENDIAN
bool SwapBytes = ArForceByteSwapping;
#else
bool SwapBytes = this->IsPersistent();
#endif
return SwapBytes;
}
/** Sets a flag indicating that this archive contains native or generated code. */
void ThisContainsCode()
{
ArContainsCode = true;
}
/** Sets a flag indicating that this archive contains a ULevel or UWorld object. */
void ThisContainsMap()
{
ArContainsMap = true;
}
/** Sets a flag indicating that this archive contains data required to be gathered for localization. */
void ThisRequiresLocalizationGather()
{
ArRequiresLocalizationGather = true;
}
/**
* Called to retrieve the archetype from the event driven loader.
* If this returns null, then call GetArchetype yourself.
*/
virtual UObject* GetArchetypeFromLoader(const UObject* Obj)
{
return nullptr;
}
/** Returns the global engine serialization version used for this archive. */
FORCEINLINE int32 UE4Ver() const
{
return ArUE4Ver;
}
/** Returns the licensee-specific version used for this archive, will be 0 by default. */
FORCEINLINE int32 LicenseeUE4Ver() const
{
return ArLicenseeUE4Ver;
}
/** Returns the compiled engine version used for this archive. */
FORCEINLINE FEngineVersionBase EngineVer() const
{
return ArEngineVer;
}
/** Returns the engine-global network protocol version for this archive. */
FORCEINLINE uint32 EngineNetVer() const
{
return ArEngineNetVer;
}
/** Returns the game-specific network protocol version for this archive. */
FORCEINLINE uint32 GameNetVer() const
{
return ArGameNetVer;
}
/**
* Queries a custom version from the archive. If the archive is being used to write, the custom version must have already been registered.
*
* @param Key The guid of the custom version to query.
* @return The version number, or 0 if the custom tag isn't stored in the archive.
*/
int32 CustomVer(const struct FGuid& Key) const;
/** Returns true if this archive is for loading data. */
FORCEINLINE bool IsLoading() const
{
return ArIsLoading;
}
/** Returns true if this archive is for saving data, this can also be a pre-save preparation archive. */
FORCEINLINE bool IsSaving() const
{
return ArIsSaving;
}
/** Returns true if this archive is transacting, which is used to keep track of changes to objects for things like the editor undo system. */
FORCEINLINE bool IsTransacting() const
{
if (FPlatformProperties::HasEditorOnlyData())
{
return ArIsTransacting;
}
else
{
return false;
}
}
/**
* Returns true if this archive serializes to a structured text format.
* Text format archives should use high level constructs from FStructuredArchive for delimiting data rather than manually seeking through the file.
*/
FORCEINLINE bool IsTextFormat() const
{
return (ArIsTextFormat && WITH_TEXT_ARCHIVE_SUPPORT);
}
/** Returns true if this archive wants properties to be serialized in binary form instead of safer but slower tagged form. */
FORCEINLINE bool WantBinaryPropertySerialization() const
{
return ArWantBinaryPropertySerialization;
}
/**
* Returns true if tagged property serialization should be replaced by faster unversioned serialization.
* This assumes writer and reader share the same property definitions.
*/
FORCEINLINE bool UseUnversionedPropertySerialization() const
{
return ArUseUnversionedPropertySerialization;
}
/** Returns true if this archive wants to always save strings in UTF16 format even if they are ANSI characters. */
FORCEINLINE bool IsForcingUnicode() const
{
return ArForceUnicode;
}
/**
* Returns true if this archive is saving or loading data destined for persistent storage and should skip transient data.
* This is also true for some intermediate archives for tasks like duplication that are eventually destined for persistent storage.
*/
FORCEINLINE bool IsPersistent() const
{
return ArIsPersistent;
}
/** Returns true if this archive contains errors, which means that further serialization is generally not safe. */
FORCEINLINE bool IsError() const
{
return ArIsError;
}
FORCEINLINE bool GetError() const
{
return ArIsError;
}
/** Returns true if this archive contains critical errors that cannot be recovered from. */
FORCEINLINE bool IsCriticalError() const
{
return ArIsCriticalError;
}
/** Returns true if this archive contains native or generated code. */
FORCEINLINE bool ContainsCode() const
{
return ArContainsCode;
}
/** Returns true if this archive contains a ULevel or UWorld object. */
FORCEINLINE bool ContainsMap() const
{
return ArContainsMap;
}
/** Returns true if this archive contains data required to be gathered for localization. */
FORCEINLINE bool RequiresLocalizationGather() const
{
return ArRequiresLocalizationGather;
}
/** Returns true if this archive should always swap bytes, ignoring endian rules. */
FORCEINLINE bool ForceByteSwapping() const
{
return ArForceByteSwapping;
}
/** Returns true if this archive is currently serializing class/struct default values. */
FORCEINLINE bool IsSerializingDefaults() const
{
return (ArSerializingDefaults > 0) ? true : false;
}
/** Returns true if this archive should ignore archetype references for structs and classes. */
FORCEINLINE bool IsIgnoringArchetypeRef() const
{
return ArIgnoreArchetypeRef;
}
/** Returns true if this archive should handle delta serialization for properties. */
FORCEINLINE bool DoDelta() const
{
return !ArNoDelta;
}
/** Returns true if this archive should perform delta serialization within properties (e.g. TMaps and TSets). */
FORCEINLINE bool DoIntraPropertyDelta() const
{
return !ArNoIntraPropertyDelta;
}
/** Returns true if this archive should ignore the Outer reference in UObject. */
FORCEINLINE bool IsIgnoringOuterRef() const
{
return ArIgnoreOuterRef;
}
/** Returns true if this archive should ignore the ClassGeneratedBy reference in UClass. */
FORCEINLINE bool IsIgnoringClassGeneratedByRef() const
{
return ArIgnoreClassGeneratedByRef;
}
/** Returns true if this archive should ignore the Class reference in UObject. */
FORCEINLINE bool IsIgnoringClassRef() const
{
return ArIgnoreClassRef;
}
/** Returns true if this archive sould allow lazy loading of bulk / secondary data. */
FORCEINLINE bool IsAllowingLazyLoading() const
{
return ArAllowLazyLoading;
}
/**
* Returns true if this archive is only looking for UObject references.
* This can be false for reference collectors looking for more general references.
*/
FORCEINLINE bool IsObjectReferenceCollector() const
{
return ArIsObjectReferenceCollector;
}
/** Returns true if this archive should modify/search weak object references as well as strong ones. */
FORCEINLINE bool IsModifyingWeakAndStrongReferences() const
{
return ArIsModifyingWeakAndStrongReferences;
}
/** Returns true if this archive is counting memory, normally CountBytes is called to get the size. */
FORCEINLINE bool IsCountingMemory() const
{
return ArIsCountingMemory;
}
/** Returns this archive's property serialization modifier flags. */
FORCEINLINE uint32 GetPortFlags() const
{
return ArPortFlags;
}
/** Checks to see if any of the passed in property serialization modifier flags are set. */
FORCEINLINE bool HasAnyPortFlags(uint32 Flags) const
{
return ((ArPortFlags & Flags) != 0);
}
/** Checks to see if all of the passed in property serialization modifier flags are set. */
FORCEINLINE bool HasAllPortFlags(uint32 Flags) const
{
return ((ArPortFlags & Flags) == Flags);
}
/** Returns the editor-only debug serialization flags. */
FORCEINLINE uint32 GetDebugSerializationFlags() const
{
#if WITH_EDITOR
return ArDebugSerializationFlags;
#else
return 0;
#endif
}
/** Returns true if this archive should ignore bulk data. */
FORCEINLINE bool ShouldSkipBulkData() const
{
return ArShouldSkipBulkData;
}
/** Returns the maximum size of data that this archive is allowed to serialize. */
FORCEINLINE int64 GetMaxSerializeSize() const
{
return ArMaxSerializeSize;
}
/**
* Gets the custom version numbers for this archive.
* These are used to check for system or game-specific version numbers.
*
* @return The container of custom versions in the archive.
*/
virtual const FCustomVersionContainer& GetCustomVersions() const;
/**
* Sets the custom version numbers for this archive.
*
* @param CustomVersionContainer - The container of custom versions to copy into the archive.
*/
virtual void SetCustomVersions(const FCustomVersionContainer& CustomVersionContainer);
/** Resets the custom version numbers for this archive. */
virtual void ResetCustomVersions();
/**
* Sets a specific custom version
*
* @param Key - The guid of the custom version to query.
* @param Version - The version number to set key to
* @param FriendlyName - Friendly name corresponding to the key
*/
void SetCustomVersion(const struct FGuid& Key, int32 Version, FName FriendlyName);
/**
* Toggle byte order swapping. This is needed in rare cases when we already know that the data
* swapping has already occurred or if we know that it will be handled later.
*
* @param Enabled set to true to enable byte order swapping
*/
void SetByteSwapping(bool Enabled)
{
ArForceByteSwapping = Enabled;
}
/**
* Sets the archive's property serialization modifier flags
*
* @param InPortFlags the new flags to use for property serialization
*/
void SetPortFlags(uint32 InPortFlags)
{
ArPortFlags = InPortFlags;
}
/**
* Sets the archives custom serialization modifier flags (nothing to do with PortFlags or Custom versions)
*
* @param InCustomFlags the new flags to use for custom serialization
*/
void SetDebugSerializationFlags(uint32 InCustomFlags)
{
#if WITH_EDITOR
ArDebugSerializationFlags = InCustomFlags;
#endif
}
/**
* Indicates whether this archive is filtering editor-only on save or contains data that had editor-only content stripped.
*
* @return true if the archive filters editor-only content, false otherwise.
*/
bool IsFilterEditorOnly() const
{
return ArIsFilterEditorOnly;
}
/**
* Sets a flag indicating that this archive needs to filter editor-only content.
*
* @param InFilterEditorOnly Whether to filter editor-only content.
*/
virtual void SetFilterEditorOnly(bool InFilterEditorOnly)
{
ArIsFilterEditorOnly = InFilterEditorOnly;
}
/**
* Indicates whether this archive is saving or loading game state
*
* @note This is intended for game-specific archives and is not true for any of the build in save methods
* @return true if the archive is dealing with save games, false otherwise.
*/
bool IsSaveGame() const
{
return ArIsSaveGame;
}
/**
* Whether or not this archive is serializing data being sent/received by the netcode
*/
FORCEINLINE bool IsNetArchive() const
{
return ArIsNetArchive;
}
/**
* Checks whether the archive is used for cooking.
*
* @return true if the archive is used for cooking, false otherwise.
*/
FORCEINLINE bool IsCooking() const
{
check(!CookingTargetPlatform || (!IsLoading() && !IsTransacting() && IsSaving()));
return !!CookingTargetPlatform;
}
/**
* Returns the cooking target platform.
*
* @return Target platform.
*/
FORCEINLINE const ITargetPlatform* CookingTarget() const
{
return CookingTargetPlatform;
}
/**
* Sets the cooking target platform.
*
* @param InCookingTarget The target platform to set.
*/
FORCEINLINE void SetCookingTarget(const ITargetPlatform* InCookingTarget)
{
CookingTargetPlatform = InCookingTarget;
}
/**
* Checks whether the archive is used to resolve out-of-date enum indexes
* If function returns true, the archive should be called only for objects containing user defined enum
*
* @return true if the archive is used to resolve out-of-date enum indexes
*/
virtual bool UseToResolveEnumerators() const
{
return false;
}
/**
* Checks whether the archive wants to skip the property independent of the other flags
*/
virtual bool ShouldSkipProperty(const FProperty* InProperty) const
{
return false;
}
/**
* Overrides the property that is currently being serialized
* @note: You likely want to call PushSerializedProperty/PopSerializedProperty instead
*
* @param InProperty Pointer to the property that is currently being serialized
*/
virtual void SetSerializedProperty(FProperty* InProperty)
{
SerializedProperty = InProperty;
}
/**
* Gets the property that is currently being serialized
*
* @return Pointer to the property that is currently being serialized
*/
FORCEINLINE class FProperty* GetSerializedProperty() const
{
return SerializedProperty;
}
/**
* Gets the chain of properties that are currently being serialized
* @note This populates the array in stack order, so the 0th entry in the array is the top of the stack of properties
*/
void GetSerializedPropertyChain(TArray<class FProperty*>& OutProperties) const;
/**
* Get the raw serialized property chain for this archive
* @note Accessing this directly can avoid an array allocation depending on your use-case
*/
FORCEINLINE const FArchiveSerializedPropertyChain* GetSerializedPropertyChain() const
{
return SerializedPropertyChain;
}
/**
* Set the raw serialized property chain for this archive, optionally overriding the serialized property too (or null to use the head of the property chain)
*/
virtual void SetSerializedPropertyChain(const FArchiveSerializedPropertyChain* InSerializedPropertyChain, class FProperty* InSerializedPropertyOverride = nullptr);
#if WITH_EDITORONLY_DATA
/** Returns true if the stack of currently serialized properties contains an editor-only property */
virtual bool IsEditorOnlyPropertyOnTheStack() const;
#endif
/** Sets the current UObject serialization context for this archive. */
virtual void SetSerializeContext(FUObjectSerializeContext* InLoadContext) {}
/** Gets the current UObject serialization context for this archive. */
virtual FUObjectSerializeContext* GetSerializeContext() { return nullptr; }
#if USE_STABLE_LOCALIZATION_KEYS
/**
* Set the localization namespace that this archive should use when serializing text properties.
* This is typically the namespace used by the package being serialized (if serializing a package, or an object within a package).
*/
virtual void SetLocalizationNamespace(const FString& InLocalizationNamespace);
/**
* Get the localization namespace that this archive should use when serializing text properties.
* This is typically the namespace used by the package being serialized (if serializing a package, or an object within a package).
*/
virtual FString GetLocalizationNamespace() const;
#endif // USE_STABLE_LOCALIZATION_KEYS
/** Resets all of the base archive members. */
virtual void Reset();
public:
#if DEVIRTUALIZE_FLinkerLoad_Serialize
/* These are used for fastpath inline serializers */
struct FFastPathLoadBuffer
{
const uint8* StartFastPathLoadBuffer;
const uint8* EndFastPathLoadBuffer;
const uint8* OriginalFastPathLoadBuffer;
FORCEINLINE FFastPathLoadBuffer()
{
Reset();
}
FORCEINLINE void Reset()
{
StartFastPathLoadBuffer = nullptr;
EndFastPathLoadBuffer = nullptr;
OriginalFastPathLoadBuffer = nullptr;
}
};
//@todoio FArchive is really a horrible class and the way it is proxied by FLinkerLoad is double terrible. It makes the fast path really hacky and slower than it would need to be.
FFastPathLoadBuffer* ActiveFPLB;
FFastPathLoadBuffer InlineFPLB;
#endif
// These will be private in FArchive
protected:
/** Copies all of the members except CustomVersionContainer */
void CopyTrivialFArchiveStatusMembers(const FArchiveState& ArchiveStatusToCopy);
/** Whether this archive is for loading data. */
uint8 ArIsLoading : 1;
/** Whether this archive is for saving data. */
uint8 ArIsSaving : 1;
/** Whether archive is transacting, which is used to keep track of changes to objects for things like the editor undo system. */
uint8 ArIsTransacting : 1;
/** Whether this archive serializes to a text format. Text format archives should use high level constructs from FStructuredArchive for delimiting data rather than manually seeking through the file. */
uint8 ArIsTextFormat : 1;
/** Whether this archive wants properties to be serialized in binary form instead of tagged. */
uint8 ArWantBinaryPropertySerialization : 1;
/** Whether tagged property serialization is replaced by faster unversioned serialization. This assumes writer and reader share the same property definitions. */
uint8 ArUseUnversionedPropertySerialization : 1;
/** Whether this archive wants to always save strings in UTF16 format even if they are ANSI characters */
uint8 ArForceUnicode : 1;
/** Whether this archive saves to persistent storage. This is also true for some intermediate archives like DuplicateObject that are expected to go to persistent storage but may be discarded */
uint8 ArIsPersistent : 1;
private:
/** Whether this archive contains errors, which means that further serialization is generally not safe */
uint8 ArIsError : 1;
/** Whether this archive contains critical errors that cannot be recovered from */
uint8 ArIsCriticalError : 1;
public:
/** Quickly tell if an archive contains script code. */
uint8 ArContainsCode : 1;
/** Used to determine whether FArchive contains a level or world. */
uint8 ArContainsMap : 1;
/** Used to determine whether FArchive contains data required to be gathered for localization. */
uint8 ArRequiresLocalizationGather : 1;
/** Whether we should forcefully swap bytes. */
uint8 ArForceByteSwapping : 1;
/** If true, we will not serialize archetype references for structs and classes. */
uint8 ArIgnoreArchetypeRef : 1;
/** If true, do not perform delta serialization of properties. */
uint8 ArNoDelta : 1;
/** If true, do not perform delta serialization within properties (e.g. TMaps and TSets). */
uint8 ArNoIntraPropertyDelta : 1;
/** If true, we will not serialize the Outer reference in UObject. */
uint8 ArIgnoreOuterRef : 1;
/** If true, we will not serialize ClassGeneratedBy reference in UClass. */
uint8 ArIgnoreClassGeneratedByRef : 1;
/** If true, UObject::Serialize will skip serialization of the Class property. */
uint8 ArIgnoreClassRef : 1;
/** Whether to allow lazy loading of bulk/secondary data. */
uint8 ArAllowLazyLoading : 1;
/** Whether this archive only cares about serializing object references. */
uint8 ArIsObjectReferenceCollector : 1;
/** Whether a reference collector is modifying the references and wants both weak and strong ones */
uint8 ArIsModifyingWeakAndStrongReferences : 1;
/** Whether this archive is counting memory. */
uint8 ArIsCountingMemory : 1;
/** Whether bulk data serialization should be skipped or not. */
uint8 ArShouldSkipBulkData : 1;
/** Whether editor only properties are being filtered from the archive (or has been filtered). */
uint8 ArIsFilterEditorOnly : 1;
/** Whether this archive is saving/loading game state */
uint8 ArIsSaveGame : 1;
/** Whether or not this archive is sending/receiving network data */
uint8 ArIsNetArchive : 1;
/** Set TRUE to use the custom property list attribute for serialization. */
uint8 ArUseCustomPropertyList : 1;
/** Whether we are currently serializing defaults. > 0 means yes, <= 0 means no. */
int32 ArSerializingDefaults;
/** Modifier flags that be used when serializing UProperties */
uint32 ArPortFlags;
/** Max size of data that this archive is allowed to serialize. */
int64 ArMaxSerializeSize;
/**
* Sets whether this archive is for loading data.
*
* @param bInIsLoading true if this archive is for loading, false otherwise.
*/
virtual void SetIsLoading(bool bInIsLoading);
/**
* Sets whether this archive is for saving data.
*
* @param bInIsSaving true if this archive is for saving, false otherwise.
*/
virtual void SetIsSaving(bool bInIsSaving);
/**
* Sets whether this archive is for transacting.
*
* @param bInIsTransacting true if this archive is for transacting, false otherwise.
*/
virtual void SetIsTransacting(bool bInIsTransacting);
/**
* Sets whether this archive is in text format.
*
* @param bInIsTextFormat true if this archive is in text format, false otherwise.
*/
virtual void SetIsTextFormat(bool bInIsTextFormat);
/**
* Sets whether this archive wants binary property serialization.
*
* @param bInWantBinaryPropertySerialization true if this archive wants binary serialization, false otherwise.
*/
virtual void SetWantBinaryPropertySerialization(bool bInWantBinaryPropertySerialization);
/** Sets whether tagged property serialization should be replaced by faster unversioned serialization. This assumes writer and reader share the same property definitions. */
virtual void SetUseUnversionedPropertySerialization(bool bInUseUnversioned);
/**
* Sets whether this archive wants to force saving as Unicode.
* This is needed when we need to make sure ANSI strings are saved as Unicode.
*
* @param bInForceUnicode true if this archive wants to force saving as Unicode, false otherwise.
*/
virtual void SetForceUnicode(bool bInForceUnicode);
/**
* Sets whether this archive is to persistent storage.
*
* @param bInIsPersistent true if this archive is to persistent storage, false otherwise.
*/
virtual void SetIsPersistent(bool bInIsPersistent);
/**
* Sets the archive version number. Used by the code that makes sure that FLinkerLoad's
* internal archive versions match the file reader it creates.
*
* @param UE4Ver new version number
*/
virtual void SetUE4Ver(int32 InVer);
/**
* Sets the archive licensee version number. Used by the code that makes sure that FLinkerLoad's
* internal archive versions match the file reader it creates.
*
* @param Ver new version number
*/
virtual void SetLicenseeUE4Ver(int32 InVer);
/**
* Sets the archive engine version. Used by the code that makes sure that FLinkerLoad's
* internal archive versions match the file reader it creates.
*
* @param InVer new version number
*/
virtual void SetEngineVer(const FEngineVersionBase& InVer);
/**
* Sets the archive engine network version.
*/
virtual void SetEngineNetVer(const uint32 InEngineNetVer);
/**
* Sets the archive game network version.
*/
virtual void SetGameNetVer(const uint32 InGameNetVer);
// These will be private in FArchive
protected:
/** Holds the archive version. */
int32 ArUE4Ver;
/** Holds the archive version for licensees. */
int32 ArLicenseeUE4Ver;
/** Holds the engine version. */
FEngineVersionBase ArEngineVer;
/** Holds the engine network protocol version. */
uint32 ArEngineNetVer;
/** Holds the game network protocol version. */
uint32 ArGameNetVer;
/**
* All the custom versions stored in the archive.
* Stored as a pointer to a heap-allocated object because of a 3-way dependency between TArray, FCustomVersionContainer and FArchive, which is too much work to change right now.
* Keeping it as a heap-allocated object also helps with performance in some cases as we don't need to construct it for archives that don't care about custom versions.
*/
mutable FCustomVersionContainer* CustomVersionContainer = nullptr;
public:
/** Custom property list attribute. If the flag below is set, only these properties will be iterated during serialization. If NULL, then no properties will be iterated. */
const struct FCustomPropertyListNode* ArCustomPropertyList;
#if WITH_EDITOR
/** Custom serialization modifier flags can be used for anything */
uint32 ArDebugSerializationFlags;
#endif
// These will be private in FArchive
protected:
/** Holds the cooking target platform. */
const ITargetPlatform* CookingTargetPlatform;
/** Holds the pointer to the property that is currently being serialized */
FProperty* SerializedProperty;
/** Holds the chain of properties that are currently being serialized */
FArchiveSerializedPropertyChain* SerializedPropertyChain;
#if USE_STABLE_LOCALIZATION_KEYS
/**
* The localization namespace that this archive should use when serializing text properties.
* This is typically the namespace used by the package being serialized (if serializing a package, or an object within a package).
* Stored as a pointer to a heap-allocated string because of a dependency between TArray (thus FString) and FArchive; null should be treated as an empty string.
*/
FString* LocalizationNamespacePtr;
/** See GetLocalizationNamespace */
FString GetBaseLocalizationNamespace() const;
/** See SetLocalizationNamespace */
void SetBaseLocalizationNamespace(const FString& InLocalizationNamespace);
#endif // USE_STABLE_LOCALIZATION_KEYS
/**
* Indicates if the custom versions container is in a 'reset' state. This will be used to defer the choice about how to
* populate the container until it is needed, where the read/write state will be known.
*/
mutable bool bCustomVersionsAreReset;
private:
/** Linked list to all proxies */
FArchiveState* NextProxy = nullptr;
template<typename T> void ForEachState(T Func);
};
/**
* TCheckedObjPtr
*
* Wrapper for UObject pointers, which checks that the base class is accurate, upon serializing (to prevent illegal casting)
*/
template<class T> class TCheckedObjPtr
{
friend class FArchive;
public:
TCheckedObjPtr()
: Object(nullptr)
, bError(false)
{
}
TCheckedObjPtr(T* InObject)
: Object(InObject)
, bError(false)
{
}
/**
* Assigns a value to the object pointer
*
* @param InObject The value to assign to the pointer
*/
FORCEINLINE TCheckedObjPtr& operator = (T* InObject)
{
Object = InObject;
return *this;
}
/**
* Returns the object pointer, for accessing members of the object
*
* @return Returns the object pointer
*/
FORCEINLINE T* operator -> () const
{
return Object;
}
/**
* Retrieves a writable/serializable reference to the pointer
*
* @return Returns a reference to the pointer
*/
FORCEINLINE T*& Get()
{
return Object;
}
/**
* Whether or not the pointer is valid/non-null
*
* @return Whether or not the pointer is valid
*/
FORCEINLINE bool IsValid() const
{
return Object != nullptr;
}
/**
* Whether or not there was an error during the previous serialization.
* This occurs if an object was successfully serialized, but with the wrong base class
* (which net serialization may have to recover from, if there was supposed to be data serialized along with the object)
*
* @return Whether or not there was an error
*/
FORCEINLINE bool IsError() const
{
return bError;
}
private:
/** The object pointer */
T* Object;
/** Whether or not there was an error upon serializing */
bool bError;
};
/**
* Base class for archives that can be used for loading, saving, and garbage
* collecting in a byte order neutral way.
*/
class CORE_API FArchive : private FArchiveState
{
public:
FArchive() = default;
FArchive(const FArchive&) = default;
FArchive& operator=(const FArchive& ArchiveToCopy) = default;
~FArchive() = default;
protected:
using FArchiveState::LinkProxy;
using FArchiveState::UnlinkProxy;
public:
/**
* Serializes an FName value from or into this archive.
*
* This operator can be implemented by sub-classes that wish to serialize FName instances.
*
* @param Value The value to serialize.
* @return This instance.
*/
virtual FArchive& operator<<(FName& Value)
{
return *this;
}
/**
* Serializes an FText value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
virtual FArchive& operator<<(FText& Value);
/**
* Serializes an UObject value from or into this archive.
*
* This operator can be implemented by sub-classes that wish to serialize UObject instances.
*
* @param Value The value to serialize.
* @return This instance.
*/
virtual FArchive& operator<<(UObject*& Value)
{
return *this;
}
/**
* Serializes a Field value from or into this archive.
*
* This operator can be implemented by sub-classes that wish to serialize UObject instances.
*
* @param Value The value to serialize.
* @return This instance.
*/
virtual FArchive& operator<<(FField*& Value)
{
return *this;
}
/**
* Serializes a UObject wrapped in a TCheckedObjPtr container, using the above operator,
* and verifies the serialized object is derived from the correct base class, to prevent illegal casting.
*
* @param Value The value to serialize.
* @return This instance.
*/
template<class T> FORCEINLINE FArchive& operator<<(TCheckedObjPtr<T>& Value)
{
Value.bError = false;
if (IsSaving())
{
UObject* SerializeObj = nullptr;
if (Value.IsValid())
{
if (Value.Get()->IsA(T::StaticClass()))
{
SerializeObj = Value.Get();
}
else
{
Value.bError = true;
}
}
*this << SerializeObj;
}
else
{
*this << Value.Get();
if (IsLoading() && Value.IsValid() && !Value.Get()->IsA(T::StaticClass()))
{
Value.bError = true;
Value = nullptr;
}
}
return *this;
}
/**
* Serializes a lazy object pointer value from or into this archive.
*
* Most of the time, FLazyObjectPtrs are serialized as UObject*, but some archives need to override this.
*
* @param Value The value to serialize.
* @return This instance.
*/
virtual FArchive& operator<<(struct FLazyObjectPtr& Value);
/**
* Serializes asset pointer from or into this archive.
*
* Most of the time, FSoftObjectPtr are serialized as UObject *, but some archives need to override this.
*
* @param Value The asset pointer to serialize.
* @return This instance.
*/
virtual FArchive& operator<<(struct FSoftObjectPtr& Value);
/**
* Serializes soft object paths from or into this archive.
*
* @param Value Soft object path to serialize.
* @return This instance.
*/
virtual FArchive& operator<<(struct FSoftObjectPath& Value);
/**
* Serializes FWeakObjectPtr value from or into this archive.
*
* This operator can be implemented by sub-classes that wish to serialize FWeakObjectPtr instances.
*
* @param Value The value to serialize.
* @return This instance.
*/
virtual FArchive& operator<<(struct FWeakObjectPtr& Value);
/**
* Inform the archive that a blueprint would like to force finalization, normally
* this is triggered by CDO load, but if there's no CDO we force finalization.
*/
virtual void ForceBlueprintFinalization() {}
public:
/**
* Serializes an ANSICHAR value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, ANSICHAR& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.Serialize(&Value, 1);
}
return Ar;
}
/**
* Serializes a WIDECHAR value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, WIDECHAR& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.ByteOrderSerialize(&Value, sizeof(Value));
}
return Ar;
}
/**
* Serializes an unsigned 8-bit integer value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, uint8& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.Serialize(&Value, 1);
}
return Ar;
}
/**
* Serializes an enumeration value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
template<class TEnum>
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, TEnumAsByte<TEnum>& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.Serialize(&Value, 1);
}
return Ar;
}
/**
* Serializes a signed 8-bit integer value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, int8& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.Serialize(&Value, 1);
}
return Ar;
}
/**
* Serializes an unsigned 16-bit integer value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, uint16& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.ByteOrderSerialize(Value);
}
return Ar;
}
/**
* Serializes a signed 16-bit integer value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, int16& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.ByteOrderSerialize(reinterpret_cast<uint16&>(Value));
}
return Ar;
}
/**
* Serializes an unsigned 32-bit integer value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, uint32& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.ByteOrderSerialize(Value);
}
return Ar;
}
/**
* Serializes a Boolean value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
#if WITH_EDITOR
protected:
virtual void SerializeBool( bool& D );
public:
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, bool& D)
{
Ar.SerializeBool(D);
return Ar;
}
#else
FORCEINLINE friend FArchive& operator<<( FArchive& Ar, bool& D )
{
// Serialize bool as if it were UBOOL (legacy, 32 bit int).
#if DEVIRTUALIZE_FLinkerLoad_Serialize
const uint8 * RESTRICT Src = Ar.ActiveFPLB->StartFastPathLoadBuffer;
if (Src + sizeof(uint32) <= Ar.ActiveFPLB->EndFastPathLoadBuffer)
{
D = !!FPlatformMemory::ReadUnaligned<uint32>(Src);
Ar.ActiveFPLB->StartFastPathLoadBuffer += 4;
}
else
#endif
{
uint32 OldUBoolValue = D ? 1 : 0;
Ar.Serialize(&OldUBoolValue, sizeof(OldUBoolValue));
D = !!OldUBoolValue;
}
return Ar;
}
#endif
/**
* Serializes a signed 32-bit integer value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, int32& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.ByteOrderSerialize(reinterpret_cast<uint32&>(Value));
}
return Ar;
}
#if PLATFORM_COMPILER_DISTINGUISHES_INT_AND_LONG
/**
* Serializes a long integer value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, long& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.ByteOrderSerialize(reinterpret_cast<unsigned long&>(Value));
}
return Ar;
}
#endif
/**
* Serializes a single precision floating point value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<( FArchive& Ar, float& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
static_assert(sizeof(float) == sizeof(uint32), "Expected float to be 4 bytes to swap as uint32");
Ar.ByteOrderSerialize(reinterpret_cast<uint32&>(Value));
}
return Ar;
}
/**
* Serializes a double precision floating point value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, double& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
static_assert(sizeof(double) == sizeof(uint64), "Expected double to be 8 bytes to swap as uint64");
Ar.ByteOrderSerialize(reinterpret_cast<uint64&>(Value));
}
return Ar;
}
/**
* Serializes a unsigned 64-bit integer value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
FORCEINLINE friend FArchive& operator<<(FArchive &Ar, uint64& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.ByteOrderSerialize(Value);
}
return Ar;
}
/**
* Serializes a signed 64-bit integer value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
/*FORCEINLINE*/friend FArchive& operator<<(FArchive& Ar, int64& Value)
{
#if DEVIRTUALIZE_FLinkerLoad_Serialize
if (!Ar.FastPathLoad<sizeof(Value)>(&Value))
#endif
{
Ar.ByteOrderSerialize(reinterpret_cast<uint64&>(Value));
}
return Ar;
}
/**
* Serializes enum classes as their underlying type.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
template <
typename EnumType,
typename = typename TEnableIf<TIsEnumClass<EnumType>::Value>::Type
>
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, EnumType& Value)
{
return Ar << (__underlying_type(EnumType)&)Value;
}
/**
* Serializes an FIntRect value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
friend FArchive& operator<<(FArchive& Ar, struct FIntRect& Value);
/**
* Serializes an FString value from or into an archive.
*
* @param Ar The archive to serialize from or to.
* @param Value The value to serialize.
*/
friend CORE_API FArchive& operator<<(FArchive& Ar, FString& Value);
public:
virtual void Serialize(void* V, int64 Length) { }
virtual void SerializeBits(void* V, int64 LengthBits)
{
Serialize(V, (LengthBits + 7) / 8);
if (IsLoading() && (LengthBits % 8) != 0)
{
((uint8*)V)[LengthBits / 8] &= ((1 << (LengthBits & 7)) - 1);
}
}
virtual void SerializeInt(uint32& Value, uint32 Max)
{
ByteOrderSerialize(Value);
}
/** Packs int value into bytes of 7 bits with 8th bit for 'more' */
virtual void SerializeIntPacked(uint32& Value);
/** Tells the archive to attempt to preload the specified object so data can be loaded out of it. */
virtual void Preload(UObject* Object) { }
/** Returns the low level archive state for this archive. */
FArchiveState& GetArchiveState()
{
return ImplicitConv<FArchiveState&>(*this);
}
const FArchiveState& GetArchiveState() const
{
return ImplicitConv<const FArchiveState&>(*this);
}
using FArchiveState::SetArchiveState;
using FArchiveState::SetError;
using FArchiveState::ClearError;
using FArchiveState::SetCriticalError;
using FArchiveState::GetInnermostState;
using FArchiveState::CountBytes;
using FArchiveState::GetArchiveName;
using FArchiveState::GetLinker;
using FArchiveState::Tell;
using FArchiveState::TotalSize;
using FArchiveState::AtEnd;
/** Attempts to set the current offset into backing data storage, this will do nothing if there is no storage. */
virtual void Seek(int64 InPos) { }
/**
* Attaches/ associates the passed in bulk data object with the linker.
*
* @param Owner UObject owning the bulk data
* @param BulkData Bulk data object to associate
*/
virtual void AttachBulkData(UObject* Owner, FUntypedBulkData* BulkData) { }
/**
* Detaches the passed in bulk data object from the linker.
*
* @param BulkData Bulk data object to detach
* @param bEnsureBulkDataIsLoaded Whether to ensure that the bulk data is loaded before detaching
*/
virtual void DetachBulkData(FUntypedBulkData* BulkData, bool bEnsureBulkDataIsLoaded) { }
/**
* Determine if the given archive is a valid "child" of this archive. In general, this means "is exactly the same" but
* this function allows a derived archive to support "child" or "internal" archives which are different objects that proxy the
* original one in some way.
*
* @param BulkData Bulk data object to detach
* @param bEnsureBulkDataIsLoaded Whether to ensure that the bulk data is loaded before detaching
*/
virtual bool IsProxyOf(FArchive* InOther) const
{
return InOther == this;
}
/**
* Hint the archive that the region starting at passed in offset and spanning the passed in size
* is going to be read soon and should be precached.
*
* The function returns whether the precache operation has completed or not which is an important
* hint for code knowing that it deals with potential async I/O. The archive is free to either not
* implement this function or only partially precache so it is required that given sufficient time
* the function will return true. Archives not based on async I/O should always return true.
*
* This function will not change the current archive position.
*
* @param PrecacheOffset Offset at which to begin precaching.
* @param PrecacheSize Number of bytes to precache
* @return false if precache operation is still pending, true otherwise
*/
virtual bool Precache(int64 PrecacheOffset, int64 PrecacheSize)
{
return true;
}
/**
* Flushes cache and frees internal data.
*/
virtual void FlushCache() { }
/**
* Sets mapping from offsets/ sizes that are going to be used for seeking and serialization to what
* is actually stored on disk. If the archive supports dealing with compression in this way it is
* going to return true.
*
* @param CompressedChunks Pointer to array containing information about [un]compressed chunks
* @param CompressionFlags Flags determining compression format associated with mapping
*
* @return true if archive supports translating offsets & uncompressing on read, false otherwise
*/
virtual bool SetCompressionMap(TArray<struct FCompressedChunk>* CompressedChunks, ECompressionFlags CompressionFlags)
{
return false;
}
/**
* Attempts to finish writing any buffered data to disk/permanent storage.
*/
virtual void Flush() { }
/**
* Attempts to close and finalize any handles used for backing data storage, returns true if it succeeded.
*/
virtual bool Close()
{
return !IsError();
}
/**
* Serializes and compresses/ uncompresses data. This is a shared helper function for compression
* support. The data is saved in a way compatible with FIOSystem::LoadCompressedData.
*
* @param V Data pointer to serialize data from/ to
* @param Length Length of source data if we're saving, unused otherwise
* @param Flags Flags to control what method to use for [de]compression and optionally control memory vs speed when compressing
* @param bTreatBufferAsFileReader true if V is actually an FArchive, which is used when saving to read data - helps to avoid single huge allocations of source data
* @param bUsePlatformBitWindow use a platform specific bitwindow setting
*/
void SerializeCompressed(void* V, int64 Length, FName CompressionFormat, ECompressionFlags Flags=COMPRESS_NoFlags, bool bTreatBufferAsFileReader=false);
using FArchiveState::IsByteSwapping;
/** Used to do byte swapping on small items. This does not happen usually, so we don't want it inline. */
void ByteSwap(void* V, int32 Length);
/** Serialize data of Length bytes, taking into account byte swapping if needed. */
FORCEINLINE FArchive& ByteOrderSerialize(void* V, int32 Length)
{
if (!IsByteSwapping()) // Most likely case (hot path)
{
Serialize(V, Length);
return *this;
}
return SerializeByteOrderSwapped(V, Length); // Slowest and unlikely path (should not be inlined)
}
using FArchiveState::ThisContainsCode;
using FArchiveState::ThisContainsMap;
using FArchiveState::ThisRequiresLocalizationGather;
/** Sets a flag indicating that this archive is currently serializing class/struct defaults. */
void StartSerializingDefaults()
{
ArSerializingDefaults++;
}
/** Indicate that this archive is no longer serializing class/struct defaults. */
void StopSerializingDefaults()
{
ArSerializingDefaults--;
}
/**
* Called when an object begins serializing property data using script serialization.
*/
virtual void MarkScriptSerializationStart(const UObject* Obj) { }
/**
* Called when an object stops serializing property data using script serialization.
*/
virtual void MarkScriptSerializationEnd(const UObject* Obj) { }
/**
* Called to register a reference to a specific name value, of type TypeObject (UEnum or UStruct normally). Const so it can be called from PostSerialize
*/
virtual void MarkSearchableName(const UObject* TypeObject, const FName& ValueName) const { }
using FArchiveState::GetArchetypeFromLoader;
private:
void VARARGS LogfImpl(const TCHAR* Fmt, ...);
public:
// Logf implementation for convenience.
template <typename FmtType, typename... Types>
void Logf(const FmtType& Fmt, Types... Args)
{
static_assert(TIsArrayOrRefOfType<FmtType, TCHAR>::Value, "Formatting string must be a TCHAR array.");
static_assert(TAnd<TIsValidVariadicFunctionArg<Types>...>::Value, "Invalid argument(s) passed to FArchive::Logf");
LogfImpl(Fmt, Args...);
}
using FArchiveState::UE4Ver;
using FArchiveState::LicenseeUE4Ver;
using FArchiveState::EngineVer;
using FArchiveState::EngineNetVer;
using FArchiveState::GameNetVer;
/**
* Registers the custom version to the archive. This is used to inform the archive that custom version information is about to be stored.
* There is no effect when the archive is being loaded from.
*
* @param Guid The guid of the custom version. This must have previously been registered with FCustomVersionRegistration.
*/
virtual void UsingCustomVersion(const struct FGuid& Guid);
using FArchiveState::CustomVer;
/**
* Returns a pointer to an archive that represents the same data that the current archive covers, but that can be cached and reused later
* In the case of standard archives, this function will just return a pointer to itself. If the archive is actually a temporary proxy to
* another archive, and has a shorter lifecycle than the source archive, it should return either a pointer to the underlying archive, or
* if the data becomes inaccessible when the proxy object disappears (as is the case with text format archives) then nullptr
*/
virtual FArchive* GetCacheableArchive()
{
return this;
}
using FArchiveState::IsLoading;
using FArchiveState::IsSaving;
using FArchiveState::IsTransacting;
using FArchiveState::IsTextFormat;
using FArchiveState::WantBinaryPropertySerialization;
using FArchiveState::UseUnversionedPropertySerialization;
using FArchiveState::IsForcingUnicode;
using FArchiveState::IsPersistent;
using FArchiveState::GetError;
using FArchiveState::IsError;
using FArchiveState::IsCriticalError;
using FArchiveState::ContainsCode;
using FArchiveState::ContainsMap;
using FArchiveState::RequiresLocalizationGather;
using FArchiveState::ForceByteSwapping;
using FArchiveState::IsSerializingDefaults;
using FArchiveState::IsIgnoringArchetypeRef;
using FArchiveState::DoDelta;
using FArchiveState::DoIntraPropertyDelta;
using FArchiveState::IsIgnoringOuterRef;
using FArchiveState::IsIgnoringClassGeneratedByRef;
using FArchiveState::IsIgnoringClassRef;
using FArchiveState::IsAllowingLazyLoading;
using FArchiveState::IsObjectReferenceCollector;
using FArchiveState::IsModifyingWeakAndStrongReferences;
using FArchiveState::IsCountingMemory;
using FArchiveState::GetPortFlags;
using FArchiveState::HasAnyPortFlags;
using FArchiveState::HasAllPortFlags;
using FArchiveState::GetDebugSerializationFlags;
using FArchiveState::ShouldSkipBulkData;
using FArchiveState::GetMaxSerializeSize;
using FArchiveState::GetCustomVersions;
using FArchiveState::SetCustomVersions;
using FArchiveState::ResetCustomVersions;
using FArchiveState::SetCustomVersion;
using FArchiveState::SetByteSwapping;
using FArchiveState::SetPortFlags;
using FArchiveState::SetDebugSerializationFlags;
using FArchiveState::IsFilterEditorOnly;
using FArchiveState::SetFilterEditorOnly;
using FArchiveState::IsSaveGame;
using FArchiveState::IsNetArchive;
using FArchiveState::IsCooking;
using FArchiveState::CookingTarget;
using FArchiveState::SetCookingTarget;
using FArchiveState::UseToResolveEnumerators;
using FArchiveState::ShouldSkipProperty;
using FArchiveState::SetSerializedProperty;
using FArchiveState::GetSerializedProperty;
using FArchiveState::GetSerializedPropertyChain;
using FArchiveState::SetSerializedPropertyChain;
/**
* Push a property that is currently being serialized onto the property stack
*
* @param InProperty Pointer to the property that is currently being serialized
* @param bIsEditorOnlyProperty True if the property is editor only (call FProperty::IsEditorOnlyProperty to work this out, as the archive can't since it can't access CoreUObject types)
*/
virtual void PushSerializedProperty(class FProperty* InProperty, const bool bIsEditorOnlyProperty);
/**
* Pop a property that was previously being serialized off the property stack
*
* @param InProperty Pointer to the property that was previously being serialized
* @param bIsEditorOnlyProperty True if the property is editor only (call FProperty::IsEditorOnlyProperty to work this out, as the archive can't since it can't access CoreUObject types)
*/
virtual void PopSerializedProperty(class FProperty* InProperty, const bool bIsEditorOnlyProperty);
#if WITH_EDITORONLY_DATA
using FArchiveState::IsEditorOnlyPropertyOnTheStack;
#endif
using FArchiveState::SetSerializeContext;
using FArchiveState::GetSerializeContext;
/**
* Adds external read dependency
*
* @return true if dependency has been added, false if Archive does not support them
*/
virtual bool AttachExternalReadDependency(FExternalReadCallback& ReadCallback) { return false; };
#if USE_STABLE_LOCALIZATION_KEYS
using FArchiveState::SetLocalizationNamespace;
using FArchiveState::GetLocalizationNamespace;
#endif // USE_STABLE_LOCALIZATION_KEYS
/** Resets all of the base archive members. */
using FArchiveState::Reset;
#if DEVIRTUALIZE_FLinkerLoad_Serialize
private:
template<SIZE_T Size>
FORCEINLINE bool FastPathLoad(void* InDest)
{
const uint8* RESTRICT Src = ActiveFPLB->StartFastPathLoadBuffer;
if (Src + Size <= ActiveFPLB->EndFastPathLoadBuffer)
{
if (Size == 2)
{
uint16 * RESTRICT Dest = (uint16 * RESTRICT)InDest;
*Dest = FPlatformMemory::ReadUnaligned<uint16>(Src);
}
else if (Size == 4)
{
uint32 * RESTRICT Dest = (uint32 * RESTRICT)InDest;
*Dest = FPlatformMemory::ReadUnaligned<uint32>(Src);
}
else if (Size == 8)
{
uint64 * RESTRICT Dest = (uint64 * RESTRICT)InDest;
*Dest = FPlatformMemory::ReadUnaligned<uint64>(Src);
}
else
{
uint8 * RESTRICT Dest = (uint8 * RESTRICT)InDest;
for (SIZE_T Index = 0; Index < Size; Index++)
{
Dest[Index] = Src[Index];
}
}
ActiveFPLB->StartFastPathLoadBuffer += Size;
return true;
}
return false;
}
public:
//@todoio FArchive is really a horrible class and the way it is proxied by FLinkerLoad is double terrible. It makes the fast path really hacky and slower than it would need to be.
using FArchiveState::ActiveFPLB;
using FArchiveState::InlineFPLB;
#else
template<SIZE_T Size>
FORCEINLINE bool FastPathLoad(void* InDest)
{
return false;
}
#endif
private:
// Used internally only to control the amount of generated code/type under control.
template<typename T>
FArchive& ByteOrderSerialize(T& Value)
{
static_assert(!TIsSigned<T>::Value, "To reduce the number of template instances, cast 'Value' to a uint16&, uint32& or uint64& prior to the call or use ByteOrderSerialize(void*, int32).");
if (!IsByteSwapping()) // Most likely case (hot path)
{
Serialize(&Value, sizeof(T));
return *this;
}
return SerializeByteOrderSwapped(Value); // Slowest and unlikely path (but fastest than SerializeByteOrderSwapped(void*, int32)).
}
// Not inlined to keep ByteOrderSerialize(), small and fast.
FArchive& SerializeByteOrderSwapped(void* V, int32 Length);
FArchive& SerializeByteOrderSwapped(uint16& Value);
FArchive& SerializeByteOrderSwapped(uint32& Value);
FArchive& SerializeByteOrderSwapped(uint64& Value);
#if PLATFORM_COMPILER_DISTINGUISHES_INT_AND_LONG
FArchive& SerializeByteOrderSwapped(unsigned long& Value)
{
static_assert(sizeof(unsigned long) == sizeof(uint64), "Wrong unsigned long size assuption.");
return SerializeByteOrderSwapped(reinterpret_cast<uint64&>(Value));
}
#endif
private:
using FArchiveState::ArIsLoading;
using FArchiveState::ArIsSaving;
using FArchiveState::ArIsTransacting;
using FArchiveState::ArIsTextFormat;
using FArchiveState::ArWantBinaryPropertySerialization;
using FArchiveState::ArUseUnversionedPropertySerialization;
using FArchiveState::ArForceUnicode;
using FArchiveState::ArIsPersistent;
public:
using FArchiveState::ArIsError;
using FArchiveState::ArIsCriticalError;
using FArchiveState::ArContainsCode;
using FArchiveState::ArContainsMap;
using FArchiveState::ArRequiresLocalizationGather;
using FArchiveState::ArForceByteSwapping;
using FArchiveState::ArIgnoreArchetypeRef;
using FArchiveState::ArNoDelta;
using FArchiveState::ArNoIntraPropertyDelta;
using FArchiveState::ArIgnoreOuterRef;
using FArchiveState::ArIgnoreClassGeneratedByRef;
using FArchiveState::ArIgnoreClassRef;
using FArchiveState::ArAllowLazyLoading;
using FArchiveState::ArIsObjectReferenceCollector;
using FArchiveState::ArIsModifyingWeakAndStrongReferences;
using FArchiveState::ArIsCountingMemory;
using FArchiveState::ArShouldSkipBulkData;
using FArchiveState::ArIsFilterEditorOnly;
using FArchiveState::ArIsSaveGame;
using FArchiveState::ArIsNetArchive;
using FArchiveState::ArUseCustomPropertyList;
using FArchiveState::ArSerializingDefaults;
using FArchiveState::ArPortFlags;
using FArchiveState::ArMaxSerializeSize;
public:
using FArchiveState::SetIsLoading;
using FArchiveState::SetIsSaving;
using FArchiveState::SetIsTransacting;
using FArchiveState::SetIsTextFormat;
using FArchiveState::SetWantBinaryPropertySerialization;
using FArchiveState::SetUseUnversionedPropertySerialization;
using FArchiveState::SetForceUnicode;
using FArchiveState::SetIsPersistent;
using FArchiveState::SetUE4Ver;
using FArchiveState::SetLicenseeUE4Ver;
using FArchiveState::SetEngineVer;
using FArchiveState::SetEngineNetVer;
using FArchiveState::SetGameNetVer;
private:
using FArchiveState::ArUE4Ver;
using FArchiveState::ArLicenseeUE4Ver;
using FArchiveState::ArEngineVer;
using FArchiveState::ArEngineNetVer;
using FArchiveState::ArGameNetVer;
using FArchiveState::CustomVersionContainer;
public:
/** Custom property list attribute. If the flag below is set, only these properties will be iterated during serialization. If NULL, then no properties will be iterated. */
using FArchiveState::ArCustomPropertyList;
class FScopeSetDebugSerializationFlags
{
private:
#if WITH_EDITOR
uint32 PreviousFlags;
FArchive& Ar;
#endif
public:
/**
* Initializes an object which will set flags for the scope of this code
*
* @param NewFlags new flags to set
* @param Remove should we add these flags or remove them default is to add
*/
#if WITH_EDITOR
FScopeSetDebugSerializationFlags(FArchive& InAr, uint32 NewFlags, bool Remove = false)
: Ar(InAr)
{
PreviousFlags = Ar.GetDebugSerializationFlags();
if (Remove)
{
Ar.SetDebugSerializationFlags( PreviousFlags & ~NewFlags);
}
else
{
Ar.SetDebugSerializationFlags( PreviousFlags | NewFlags);
}
}
~FScopeSetDebugSerializationFlags()
{
Ar.SetDebugSerializationFlags( PreviousFlags);
}
#else
FScopeSetDebugSerializationFlags(FArchive& InAr, uint32 NewFlags, bool Remove = false)
{}
~FScopeSetDebugSerializationFlags()
{}
#endif
};
#if WITH_EDITOR
/** Custom serialization modifier flags can be used for anything */
using FArchiveState::ArDebugSerializationFlags;
/** Debug stack storage if you want to add data to the archive for usage further down the serialization stack this should be used in conjunction with the FScopeAddDebugData struct */
virtual void PushDebugDataString(const FName& DebugData);
virtual void PopDebugDataString() { }
class FScopeAddDebugData
{
private:
FArchive& Ar;
public:
CORE_API FScopeAddDebugData(FArchive& InAr, const FName& DebugData);
~FScopeAddDebugData()
{
Ar.PopDebugDataString();
}
};
#endif
/** Called whilst cooking to provide file region hints to the cooker. */
virtual void PushFileRegionType(EFileRegionType Type) { }
virtual void PopFileRegionType() { }
private:
/** Holds the cooking target platform. */
using FArchiveState::CookingTargetPlatform;
/** Holds the pointer to the property that is currently being serialized */
using FArchiveState::SerializedProperty;
/** Holds the chain of properties that are currently being serialized */
using FArchiveState::SerializedPropertyChain;
#if USE_STABLE_LOCALIZATION_KEYS
/**
* The localization namespace that this archive should use when serializing text properties.
* This is typically the namespace used by the package being serialized (if serializing a package, or an object within a package).
* Stored as a pointer to a heap-allocated string because of a dependency between TArray (thus FString) and FArchive; null should be treated as an empty string.
*/
using FArchiveState::LocalizationNamespacePtr;
#endif // USE_STABLE_LOCALIZATION_KEYS
/**
* Indicates if the custom versions container is in a 'reset' state. This will be used to defer the choice about how to
* populate the container until it is needed, where the read/write state will be known.
*/
using FArchiveState::bCustomVersionsAreReset;
};
/**
* Template for archive constructors.
*/
template<class T> T Arctor(FArchive& Ar)
{
T Tmp;
Ar << Tmp;
return Tmp;
}