213 lines
6.4 KiB
C++
213 lines
6.4 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "NonBufferingReadOnlyArchive.h"
|
|
|
|
#include "AssetRegistryPrivate.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "HAL/PlatformFileManager.h"
|
|
#include "Logging/MessageLog.h"
|
|
#include "Logging/StructuredLog.h"
|
|
#include "Misc/PathViews.h"
|
|
|
|
FNonBufferingReadOnlyArchive::FNonBufferingReadOnlyArchive(int64 InBufferSize, uint32 InFlags)
|
|
: Filename()
|
|
, Size(0)
|
|
, Pos(0)
|
|
, BufferBase(0)
|
|
, Handle()
|
|
, Flags(InFlags)
|
|
{
|
|
// Todo: The buffer must be aligned to at least the size of the physical sector of the disk used.
|
|
// We don't have a generic API for this so use the page size as that will cover most common cases
|
|
BufferReadAlignment = FPlatformMemory::GetConstants().BinnedPageSize;
|
|
check(FMath::IsPowerOfTwo(BufferReadAlignment));
|
|
BufferSize = FMath::Max(InBufferSize, BufferReadAlignment);
|
|
check(FMath::IsPowerOfTwo(BufferSize));
|
|
Buffer = (uint8*) FPlatformMemory::BinnedAllocFromOS(BufferSize);
|
|
check(Buffer);
|
|
}
|
|
|
|
FNonBufferingReadOnlyArchive::~FNonBufferingReadOnlyArchive()
|
|
{
|
|
Close();
|
|
FPlatformMemory::BinnedFreeToOS(Buffer, BufferSize);
|
|
}
|
|
|
|
void FNonBufferingReadOnlyArchive::Seek(int64 InPos)
|
|
{
|
|
checkf(InPos >= 0, TEXT("Attempted to seek to a negative location (%lld/%lld), file: %s. The file is most likely corrupt."), InPos, Size, *Filename);
|
|
checkf(InPos <= Size, TEXT("Attempted to seek past the end of file (%lld/%lld), file: %s. The file is most likely corrupt."), InPos, Size, *Filename);
|
|
|
|
// Note, we don't do any platform specific seeking when asked. We instead defer until a read attempt is made where we will
|
|
// be forced to read in a new buffer's amount of data.
|
|
Pos = InPos;
|
|
}
|
|
|
|
void FNonBufferingReadOnlyArchive::ReadLowLevel(uint8* Dest, int64 CountToRead, int64& OutBytesRead)
|
|
{
|
|
int64 StartPos = Handle->Tell();
|
|
if (Handle->Read(Dest, CountToRead))
|
|
{
|
|
OutBytesRead = Handle->Tell() - StartPos;
|
|
}
|
|
else
|
|
{
|
|
OutBytesRead = 0;
|
|
}
|
|
}
|
|
|
|
bool FNonBufferingReadOnlyArchive::SeekLowLevel(int64 InPos)
|
|
{
|
|
return Handle->Seek(InPos);
|
|
}
|
|
|
|
void FNonBufferingReadOnlyArchive::OpenFileLowLevel(const TCHAR* InFilename)
|
|
{
|
|
Close();
|
|
|
|
FFileOpenResult FileResult = FPlatformFileManager::Get().GetPlatformFile().OpenReadNoBuffering(InFilename, IPlatformFile::EOpenReadFlags::NoBuffering);
|
|
if (!FileResult.HasError())
|
|
{
|
|
Handle = FileResult.StealValue();
|
|
}
|
|
else
|
|
{
|
|
UE_CLOG(!IsSilent(), LogAssetRegistry, Warning, TEXT("OpenFile failed: File:%s, Error:%s"),
|
|
InFilename, *FileResult.StealError().GetMessage());
|
|
}
|
|
}
|
|
|
|
void FNonBufferingReadOnlyArchive::CloseLowLevel()
|
|
{
|
|
Handle.Reset();
|
|
}
|
|
|
|
bool FNonBufferingReadOnlyArchive::Close()
|
|
{
|
|
CloseLowLevel();
|
|
return !IsError();
|
|
}
|
|
|
|
bool FNonBufferingReadOnlyArchive::Precache(int64 PrecacheOffset, int64 PrecacheSize)
|
|
{
|
|
// Archives not based on async I/O should always return true, so we return true whether or not the precache was successful.
|
|
// Returning false would imply that the caller should continue calling until it returns true.
|
|
if (PrecacheSize < 0)
|
|
{
|
|
return true;
|
|
}
|
|
InternalPrecache(PrecacheOffset, PrecacheSize);
|
|
return true;
|
|
}
|
|
|
|
bool FNonBufferingReadOnlyArchive::OpenFile(const TCHAR* InFilename)
|
|
{
|
|
OpenFileLowLevel(InFilename);
|
|
if (!Handle.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
Size = Handle->Size();
|
|
Filename = InFilename;
|
|
Pos = 0;
|
|
|
|
// Until we read in data we need a value that will force us to precache
|
|
BufferBase = -(BufferSize + 1);
|
|
|
|
this->SetIsLoading(true);
|
|
this->SetIsPersistent(true);
|
|
return true;
|
|
}
|
|
|
|
bool FNonBufferingReadOnlyArchive::InternalPrecache(int64 PrecacheOffset, int64 PrecacheSize)
|
|
{
|
|
check(Buffer);
|
|
|
|
// Only precache at current position and avoid work if precaching same offset twice.
|
|
if (Pos != PrecacheOffset)
|
|
{
|
|
// We are refusing to precache, but return true if at least one byte after the requested PrecacheOffset is in our existing buffer.
|
|
return BufferBase <= PrecacheOffset && PrecacheOffset < BufferBase + BufferSize;
|
|
}
|
|
|
|
bool bPosWithinBuffer = BufferBase <= Pos && Pos < BufferBase + BufferSize;
|
|
int64 ReadCount = BufferSize;
|
|
if (bPosWithinBuffer)
|
|
{
|
|
// At least one byte after the requested PrecacheOffset is in our existing buffer so do nothing and return true
|
|
return true;
|
|
}
|
|
|
|
// We must always read at a sector aligned address so move the position backwards as needed
|
|
BufferBase = AlignDown(Pos, BufferReadAlignment);
|
|
if (BufferBase + ReadCount < Pos)
|
|
{
|
|
UE_CLOG(!IsSilent(), LogAssetRegistry, Warning,
|
|
TEXT("Buffer is not large enough to satisfy the requested read. BufferBase=%lld BufferSize=%lld BufferReadAlignment=%lld ReadCount=%lld Pos=%lld"), BufferBase, BufferSize, BufferReadAlignment, ReadCount, Pos);
|
|
return false;
|
|
}
|
|
|
|
int64 Count = 0;
|
|
SeekLowLevel(BufferBase);
|
|
ReadLowLevel(Buffer, ReadCount, Count);
|
|
|
|
if (Count != ReadCount && Count != (Size - BufferBase))
|
|
{
|
|
uint32 LastError = FPlatformMisc::GetLastError();
|
|
TCHAR ErrorBuffer[1024];
|
|
UE_CLOG(!IsSilent(), LogAssetRegistry, Warning, TEXT("ReadFile failed: Count=%lld Pos=%lld Size=%lld BufferBase=%lld ReadCount=%lld BufferReadAlignment=%lld LastError=%u: %s"),
|
|
Count, Pos, Size, BufferBase, ReadCount, BufferReadAlignment, LastError, FPlatformMisc::GetSystemErrorMessage(ErrorBuffer, 1024, LastError));
|
|
|
|
return BufferBase + Count >= Pos;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FNonBufferingReadOnlyArchive::Serialize(void* V, int64 Length)
|
|
{
|
|
if (Pos + Length > Size)
|
|
{
|
|
SetError();
|
|
UE_CLOG(!IsSilent(), LogAssetRegistry, Error, TEXT("Requested read of %" INT64_FMT " bytes when %" INT64_FMT " bytes remain (file=%s, size=%" INT64_FMT ")"), Length, Size - Pos, *Filename, Size);
|
|
return;
|
|
}
|
|
|
|
bool bReadUncached = false;
|
|
const bool bIsOutsideBufferWindow = (Pos < BufferBase) || (Pos >= (BufferBase + BufferSize));
|
|
if (bIsOutsideBufferWindow)
|
|
{
|
|
bReadUncached = true;
|
|
}
|
|
|
|
while (Length > 0)
|
|
{
|
|
int64 Copy = FMath::Min(Length, BufferBase + BufferSize - Pos);
|
|
if (Copy <= 0 || bReadUncached)
|
|
{
|
|
if (!InternalPrecache(Pos, MAX_int32))
|
|
{
|
|
SetError();
|
|
UE_CLOG(!IsSilent(), LogAssetRegistry, Warning, TEXT("ReadFile failed during precaching for file %s"), *Filename);
|
|
return;
|
|
}
|
|
bReadUncached = false;
|
|
Copy = FMath::Min(Length, BufferBase + BufferSize - Pos);
|
|
if (Copy <= 0)
|
|
{
|
|
SetError();
|
|
UE_CLOG(!IsSilent(), LogAssetRegistry, Error, TEXT("ReadFile beyond EOF %lld+%lld/%lld for file %s"),
|
|
Pos, Length, Size, *Filename);
|
|
}
|
|
if (IsError())
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
FMemory::Memcpy(V, Buffer + Pos - BufferBase, Copy);
|
|
Pos += Copy;
|
|
Length -= Copy;
|
|
V = (uint8*)V + Copy;
|
|
}
|
|
}
|