Files
UnrealEngine/Engine/Source/Programs/UnrealBuildAccelerator/Common/Private/UbaCacheEntry.cpp
2025-05-18 13:04:45 +08:00

836 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UbaCacheEntry.h"
#include "UbaCompactTables.h"
#include "UbaFile.h"
#include "UbaHashMap.h"
#include "UbaStorage.h"
#include <algorithm>
namespace uba
{
u64 CacheEntries::GetSharedSize()
{
return sizeof(u16)
+ Get7BitEncodedCount(sharedInputCasKeyOffsets.size()) + sharedInputCasKeyOffsets.size()
+ Get7BitEncodedCount(sharedLogLines.size()) + sharedLogLines.size();
}
u64 CacheEntries::GetEntrySize(CacheEntry& entry, u32 clientVersion, bool toDisk)
{
u64 size = 0;
if (toDisk)
{
size += Get7BitEncodedCount(entry.creationTime) + Get7BitEncodedCount(entry.lastUsedTime);
if (clientVersion >= 5 && entry.logLinesType == LogLinesType_Owned)
size += Get7BitEncodedCount(entry.logLines.size()) + entry.logLines.size();
}
else
{
size += Get7BitEncodedCount(entry.id);
}
if (clientVersion >= 5)
++size; // logLinesType
auto& extra = entry.extraInputCasKeyOffsets;
size += Get7BitEncodedCount(extra.size()) + extra.size();
auto& ranges = entry.sharedInputCasKeyOffsetRanges;
size += Get7BitEncodedCount(ranges.size()) + ranges.size();
auto& outputs = entry.outputCasKeyOffsets;
size += Get7BitEncodedCount(outputs.size()) + outputs.size();
return size;
}
u64 CacheEntries::GetTotalSize(u32 clientVersion, bool toDisk)
{
u64 size = GetSharedSize();
for (auto& entry : entries)
size += GetEntrySize(entry, clientVersion, toDisk);
if (toDisk)
size += sizeof(u32) + inputsThatAreOutputs.size()*sizeof(u32);
return size;
}
bool CacheEntries::Write(BinaryWriter& writer, u32 clientVersion, bool toDisk)
{
u16& entryCount = *(u16*)writer.AllocWrite(2);
entryCount = 0;
if (clientVersion == 3)
{
UBA_ASSERT(!toDisk);
Vector<u8> flattenInputs;
for (auto& entry : entries)
{
Flatten(flattenInputs, entry);
auto& inputs = flattenInputs;
auto& outputs = entry.outputCasKeyOffsets;
u64 neededSize = Get7BitEncodedCount(inputs.size()) + inputs.size() + Get7BitEncodedCount(outputs.size()) + outputs.size();
if (neededSize > writer.GetCapacityLeft())
return true;
writer.Write7BitEncoded(inputs.size());
writer.WriteBytes(inputs.data(), inputs.size());
writer.Write7BitEncoded(outputs.size());
writer.WriteBytes(outputs.data(), outputs.size());
++entryCount;
}
return true;
}
{
auto& sharedOffsets = sharedInputCasKeyOffsets;
if (!toDisk)
{
u64 neededSize = Get7BitEncodedCount(sharedOffsets.size()) + sharedOffsets.size();
neededSize += Get7BitEncodedCount(sharedLogLines.size()) + sharedLogLines.size();
if (neededSize > writer.GetCapacityLeft())
return true;
}
writer.Write7BitEncoded(sharedOffsets.size());
writer.WriteBytes(sharedOffsets.data(), sharedOffsets.size());
if (clientVersion >= 5)
{
writer.Write7BitEncoded(sharedLogLines.size());
writer.WriteBytes(sharedLogLines.data(), sharedLogLines.size());
}
}
for (auto& entry : entries)
{
auto& extra = entry.extraInputCasKeyOffsets;
auto& ranges = entry.sharedInputCasKeyOffsetRanges;
auto& outputs = entry.outputCasKeyOffsets;
if (toDisk)
{
writer.Write7BitEncoded(entry.creationTime);
writer.Write7BitEncoded(entry.lastUsedTime);
}
else
{
u64 neededSize = Get7BitEncodedCount(entry.id) + Get7BitEncodedCount(extra.size()) + extra.size();
neededSize += Get7BitEncodedCount(ranges.size()) + ranges.size();
neededSize += Get7BitEncodedCount(outputs.size()) + outputs.size();
if (clientVersion >= 5)
neededSize += 1; // hasLogLines
if (neededSize > writer.GetCapacityLeft())
return true;
writer.Write7BitEncoded(entry.id);
}
writer.Write7BitEncoded(extra.size());
writer.WriteBytes(extra.data(), extra.size());
writer.Write7BitEncoded(ranges.size());
writer.WriteBytes(ranges.data(), ranges.size());
writer.Write7BitEncoded(outputs.size());
writer.WriteBytes(outputs.data(), outputs.size());
// Log lines are not included in network data.
if (toDisk)
{
writer.WriteByte(entry.logLinesType);
if (entry.logLinesType == LogLinesType_Owned)
{
writer.Write7BitEncoded(entry.logLines.size());
writer.WriteBytes(entry.logLines.data(), entry.logLines.size());
}
}
else if (clientVersion >= 5)
{
writer.WriteByte(entry.logLinesType);
}
++entryCount;
}
if (toDisk)
{
writer.WriteU32(u32(inputsThatAreOutputs.size()));
for (u32 offset : inputsThatAreOutputs)
writer.WriteU32(offset);
}
return true;
}
bool CacheEntries::ReadFromDisk(Logger& logger, BinaryReader& reader, u32 databaseVersion, StorageImpl& storage, CompactCasKeyTable& table)
{
if (databaseVersion == 3)
{
u32 cacheEntryCount = reader.ReadU32();
Vector<u32> temp;
Vector<u8> temp2;
while (cacheEntryCount--)
{
auto& cacheEntry = entries.emplace_back();
cacheEntry.id = idCounter++;
reader.ReadU64();
cacheEntry.creationTime = GetSystemTimeAsFileTime();
cacheEntry.lastUsedTime = GetSystemTimeAsFileTime();
u32 inputSize = reader.ReadU32();
u64 inputEnd = reader.GetPosition() + inputSize;
temp.clear();
while (reader.GetPosition() < inputEnd)
temp.push_back(u32(reader.Read7BitEncoded()));
BuildInputsT(cacheEntry, temp, entries.size() == 1, temp2);
u32 outputSize = reader.ReadU32();
cacheEntry.outputCasKeyOffsets.resize(outputSize);
reader.ReadBytes(cacheEntry.outputCasKeyOffsets.data(), outputSize);
}
return true;
}
u16 entryCount = reader.ReadU16();
u64 sharedSize = reader.Read7BitEncoded();
sharedInputCasKeyOffsets.resize(sharedSize);
reader.ReadBytes(sharedInputCasKeyOffsets.data(), sharedSize);
if (databaseVersion >= 6)
{
u64 sharedLogLinesSize = reader.Read7BitEncoded();
sharedLogLines.resize(sharedLogLinesSize);
reader.ReadBytes(sharedLogLines.data(), sharedLogLinesSize);
}
while (entryCount--)
{
auto& entry = entries.emplace_back();
entry.id = idCounter++;
entry.creationTime = reader.Read7BitEncoded();
entry.lastUsedTime = reader.Read7BitEncoded();
u64 extraSize = reader.Read7BitEncoded();
entry.extraInputCasKeyOffsets.resize(extraSize);
reader.ReadBytes(entry.extraInputCasKeyOffsets.data(), extraSize);
u64 rangeSize = reader.Read7BitEncoded();
entry.sharedInputCasKeyOffsetRanges.resize(rangeSize);
reader.ReadBytes(entry.sharedInputCasKeyOffsetRanges.data(), rangeSize);
u64 outputSize = reader.Read7BitEncoded();
entry.outputCasKeyOffsets.resize(outputSize);
reader.ReadBytes(entry.outputCasKeyOffsets.data(), outputSize);
if (databaseVersion >= 6)
{
entry.logLinesType = LogLinesType(reader.ReadByte());
if (entry.logLinesType == LogLinesType_Owned)
{
u64 logLinesSize = reader.Read7BitEncoded();
entry.logLines.resize(logLinesSize);
reader.ReadBytes(entry.logLines.data(), logLinesSize);
}
}
}
if (databaseVersion < 8)
{
if (databaseVersion == 7)
reader.ReadBool();
PopulateInputsThatAreOutputs(sharedInputCasKeyOffsets, storage, table);
for (auto& entry : entries)
PopulateInputsThatAreOutputs(entry.extraInputCasKeyOffsets, storage, table);
}
if (databaseVersion >= 8)
{
u32 count = reader.ReadU32();
inputsThatAreOutputs.reserve(count);
while (count--)
inputsThatAreOutputs.insert(reader.ReadU32());
}
return true;
}
template<typename Container>
void CacheEntries::BuildInputsT(CacheEntry& entry, const Container& sortedInputs, bool populateShared, Vector<u8>& temp)
{
StackBinaryWriter<256*1024> rangeWriter;
auto g = MakeGuard([&]()
{
entry.sharedInputCasKeyOffsetRanges.resize(rangeWriter.GetPosition());
memcpy(entry.sharedInputCasKeyOffsetRanges.data(), rangeWriter.GetData(), rangeWriter.GetPosition());
});
auto writeRange = [&](u64 begin, u64 end) { rangeWriter.Write7BitEncoded(begin); rangeWriter.Write7BitEncoded(end); };
if (populateShared)
{
u64 bytes = 0;
for (u32 i : sortedInputs)
bytes += Get7BitEncodedCount(i);
sharedInputCasKeyOffsets.resize(bytes);
BinaryWriter writer(sharedInputCasKeyOffsets.data(), 0, sharedInputCasKeyOffsets.size());
for (u32 i : sortedInputs)
writer.Write7BitEncoded(i);
UBA_ASSERT(bytes == writer.GetPosition());
writeRange(0, bytes);
return;
}
auto inputsIt = sortedInputs.begin();
auto inputsEnd = sortedInputs.end();
BinaryReader sharedReader(sharedInputCasKeyOffsets);
u32 sharedOffset = ~0u;
u32 offset = ~0u;
u32 rangeBegin = 0;
bool inRange = false;
bool previousWasExtra = false;
u32 lastSharedPos = ~0u;
Vector<u8>& extraOffsets = temp;
extraOffsets.resize(sortedInputs.size()*5);
BinaryWriter extraWriter(extraOffsets.data(), 0, extraOffsets.size());
while (true)
{
u32 sharedPos = u32(sharedReader.GetPosition());
if (!sharedReader.GetLeft())
{
bool extraWritten = false;
// Handle all the extra offsets that are lower than shared offset
if (previousWasExtra)
{
extraWritten = true;
while (inputsIt != inputsEnd)
{
offset = *inputsIt++;
if (offset >= sharedOffset)
{
extraWritten = false;
break;
}
extraWriter.Write7BitEncoded(offset);
}
}
// Add current range if there is one going
if (inRange)
{
writeRange(rangeBegin, offset == sharedOffset ? sharedPos : lastSharedPos);
if (previousWasExtra && offset > sharedOffset)
extraWriter.Write7BitEncoded(offset);
}
else if (offset == sharedOffset) // We want to use a range just to make it easier to debug
writeRange(lastSharedPos, sharedPos);
else if (!extraWritten)
extraWriter.Write7BitEncoded(offset);
// Populate rest in extraInputCasKeyOffsets
for (;inputsIt != inputsEnd ;++inputsIt)
if (*inputsIt != sharedOffset)
extraWriter.Write7BitEncoded(*inputsIt);
break;
}
if (inputsIt == inputsEnd)
{
// Add current range if there is one going
if (inRange)
{
writeRange(rangeBegin, previousWasExtra ? lastSharedPos : sharedPos);
}
else if (offset > sharedOffset)
{
lastSharedPos = sharedPos;
sharedOffset = u32(sharedReader.Read7BitEncoded());
continue;
}
else
{
if (sharedOffset == offset)
writeRange(lastSharedPos, u32(sharedReader.GetPosition()));
else if (!previousWasExtra)
extraWriter.Write7BitEncoded(offset);
}
break;
}
previousWasExtra = false;
if (sharedOffset < offset)
{
lastSharedPos = sharedPos;
sharedOffset = u32(sharedReader.Read7BitEncoded());
}
else if (offset < sharedOffset)
{
offset = *inputsIt++;
sharedPos = lastSharedPos;
}
else
{
lastSharedPos = sharedPos;
sharedOffset = u32(sharedReader.Read7BitEncoded());
offset = *inputsIt++;
}
if (sharedOffset == offset)
{
if (!inRange)
{
rangeBegin = sharedPos;
inRange = true;
}
}
else
{
if (offset < sharedOffset)
{
extraWriter.Write7BitEncoded(offset);
previousWasExtra = true;
}
else if (inRange)
{
inRange = false;
writeRange(rangeBegin, sharedPos);
}
}
}
entry.extraInputCasKeyOffsets.resize(extraWriter.GetPosition());
memcpy(entry.extraInputCasKeyOffsets.data(), extraWriter.GetData(), extraWriter.GetPosition());
}
template<typename Container>
void CacheEntries::BuildRangesFromExcludedT(CacheEntry& entry, const Container& sortedExcludedInputs)
{
StackBinaryWriter<256*1024> rangeWriter;
auto g = MakeGuard([&]()
{
entry.sharedInputCasKeyOffsetRanges.resize(rangeWriter.GetPosition());
memcpy(entry.sharedInputCasKeyOffsetRanges.data(), rangeWriter.GetData(), rangeWriter.GetPosition());
});
auto writeRange = [&](u64 begin, u64 end) { rangeWriter.Write7BitEncoded(begin); rangeWriter.Write7BitEncoded(end); };
auto excludedInputsIt = sortedExcludedInputs.begin();
auto excludedInputsEnd = sortedExcludedInputs.end();
BinaryReader sharedReader(sharedInputCasKeyOffsets);
u32 sharedOffset = ~0u;
u32 offset = ~0u;
u32 excludeRangeEnd = 0;
u32 excludeRangeBegin = 0;
bool inExcludeRange = false;
u32 lastSharedPos = ~0u;
while (true)
{
u32 sharedPos = u32(sharedReader.GetPosition());
if (!sharedReader.GetLeft())
{
if (!inExcludeRange)
writeRange(excludeRangeEnd, excludeRangeBegin);
break;
}
if (offset <= sharedOffset && excludedInputsIt == excludedInputsEnd)
{
if (!inExcludeRange)
writeRange(excludeRangeEnd, sharedInputCasKeyOffsets.size());
else
writeRange(sharedPos, sharedInputCasKeyOffsets.size());
break;
}
if (sharedOffset < offset)
{
lastSharedPos = sharedPos;
sharedOffset = u32(sharedReader.Read7BitEncoded());
}
else if (offset < sharedOffset)
{
offset = *excludedInputsIt++;
sharedPos = lastSharedPos;
}
else
{
lastSharedPos = sharedPos;
sharedOffset = u32(sharedReader.Read7BitEncoded());
offset = *excludedInputsIt++;
}
if (sharedOffset == offset)
{
if (!inExcludeRange)
{
if (excludeRangeEnd != lastSharedPos)
writeRange(excludeRangeEnd, lastSharedPos);
excludeRangeBegin = sharedPos;
inExcludeRange = true;
}
}
else
{
if (inExcludeRange)
{
inExcludeRange = false;
excludeRangeEnd = sharedPos;
}
}
}
}
void CacheEntries::BuildInputs(CacheEntry& entry, const Set<u32>& inputs)
{
Vector<u8> temp;
BuildInputsT(entry, inputs, sharedInputCasKeyOffsets.empty(), temp);
}
void CacheEntries::UpdateEntries(Logger& logger, const HashMap2<u32, u32>& oldToNewCasKeyOffset, Vector<u32>& temp, Vector<u8>& temp2, Vector<u8>& temp3)
{
if (entries.empty())
return;
UnorderedSet<u32> itao(inputsThatAreOutputs.size());
for (u32 offset : inputsThatAreOutputs)
if (auto o = oldToNewCasKeyOffset.Find(offset))
itao.insert(*o);
inputsThatAreOutputs.swap(itao);
auto convertOffsets = [&](Vector<u8>& offsets)
{
temp.clear();
u32 newOffsetsSize = 0;
BinaryReader reader(offsets);
while (reader.GetLeft())
{
u32 newOffset = u32(reader.Read7BitEncoded());
if (auto o = oldToNewCasKeyOffset.Find(newOffset))
newOffset = *o;
temp.push_back(newOffset);
newOffsetsSize += Get7BitEncodedCount(newOffset);
}
std::sort(temp.begin(), temp.end());
offsets.resize(newOffsetsSize);
BinaryWriter writer(offsets.data(), 0, newOffsetsSize);
for (u32 offset : temp)
writer.Write7BitEncoded(offset);
};
for (auto& entry : entries)
convertOffsets(entry.outputCasKeyOffsets);
auto writePrimaryRange = [&](CacheEntry& entry, u64 newSize)
{
u64 rangeSize = 1 + Get7BitEncodedCount(newSize);
entry.sharedInputCasKeyOffsetRanges.resize(rangeSize);
BinaryWriter rangeWriter(entry.sharedInputCasKeyOffsetRanges.data(), 0, rangeSize);
rangeWriter.Write7BitEncoded(0);
rangeWriter.Write7BitEncoded(newSize);
UBA_ASSERT(rangeWriter.GetPosition() == rangeSize);
};
// If primary id is not set we use first entry as primaryId and base shared offsets off primary entry
if (entries.size() == 1 || primaryId == ~0u)
{
auto& oldShared = temp2;
oldShared = sharedInputCasKeyOffsets;
bool isFirst = true;
for (auto& entry : entries)
{
if (isFirst)
{
primaryId = entry.id;
// Flatten first entry into temp
Flatten(temp, entry, oldShared);
// Update temp with new offsets
u64 newSize = 0;
for (auto& offset : temp)
{
if (auto o = oldToNewCasKeyOffset.Find(offset))
offset = *o;
newSize += Get7BitEncodedCount(offset);
}
// Sort temp now when it likely is out of order
std::sort(temp.begin(), temp.end());
// Write new shared
sharedInputCasKeyOffsets.resize(newSize);
BinaryWriter writer(sharedInputCasKeyOffsets.data(), 0, newSize);
for (auto& offset : temp)
writer.Write7BitEncoded(offset);
// Clear extra and set entire shared to range
entry.extraInputCasKeyOffsets.clear();
writePrimaryRange(entry, newSize);
isFirst = false;
}
else
{
// Flatten using old shared and rebuild it with new shared
Flatten(temp, entry, oldShared);
for (auto& offset : temp)
if (auto o = oldToNewCasKeyOffset.Find(offset))
offset = *o;
// Sort temp now when it likely is out of order
std::sort(temp.begin(), temp.end());
entry.extraInputCasKeyOffsets.clear();
entry.sharedInputCasKeyOffsetRanges.clear();
BuildInputsT(entry, temp, false, temp3);
}
}
}
else
{
// This approach should be faster if there are more than one entry since we expect entries to be very similar to each other
// It instead tracks removed offsets when calculating the shared offsets and build the ranges from that.
auto& oldShared = temp2;
oldShared = sharedInputCasKeyOffsets;
convertOffsets(sharedInputCasKeyOffsets);
for (auto& entry : entries)
{
// Collect all inputs that are removed from the shared inputs
auto collectInputs = [&](Vector<u32>& out, u32 rangeBegin, u32 rangeEnd)
{
BinaryReader excludedReader(oldShared.data(), rangeBegin, rangeEnd);
while (excludedReader.GetLeft())
{
u32 offset = u32(excludedReader.Read7BitEncoded());
if (auto o = oldToNewCasKeyOffset.Find(offset))
offset = *o;
out.push_back(offset);
}
};
temp.clear();
auto& excludedOffsets = temp;
BinaryReader rangeReader(entry.sharedInputCasKeyOffsetRanges);
u32 lastEnd = 0;
while (rangeReader.GetLeft())
{
u32 begin = u32(rangeReader.Read7BitEncoded());
collectInputs(excludedOffsets, lastEnd, begin);
lastEnd = u32(rangeReader.Read7BitEncoded());
}
collectInputs(excludedOffsets, lastEnd, u32(oldShared.size()));
if (excludedOffsets.empty() && entry.extraInputCasKeyOffsets.empty())
{
writePrimaryRange(entry, sharedInputCasKeyOffsets.size());
}
else
{
// Sort excluded inputs..
std::sort(excludedOffsets.begin(), excludedOffsets.end());
// Build new ranges based on shared and excluded offsets from shared
BuildRangesFromExcludedT(entry, excludedOffsets);
// Create new extras
convertOffsets(entry.extraInputCasKeyOffsets);
}
}
}
}
bool CacheEntries::Validate(Logger& logger)
{
Set<u32> sharedOffsets;
{
BinaryReader sharedReader(sharedInputCasKeyOffsets);
while (sharedReader.GetLeft())
{
u64 offset;
if (!sharedReader.TryRead7BitEncoded(offset))
return false;
if (!sharedOffsets.insert(u32(offset)).second)
return false;
}
}
for (auto& entry : entries)
{
Set<u32> entryOffsets;
u32 rangeCount = 0;
BinaryReader rangeReader(entry.sharedInputCasKeyOffsetRanges);
while (rangeReader.GetLeft())
{
++rangeCount;
u64 begin = rangeReader.Read7BitEncoded();
u64 end = rangeReader.Read7BitEncoded();
BinaryReader sharedReader(sharedInputCasKeyOffsets.data(), begin, end);
while (sharedReader.GetLeft())
{
u64 offset;
if (!sharedReader.TryRead7BitEncoded(offset))
return false;
if (!entryOffsets.insert(u32(offset)).second)
return false;
}
}
u32 extraCount = 0;
BinaryReader extraReader(entry.extraInputCasKeyOffsets);
while (extraReader.GetLeft())
{
++extraCount;
u64 offset;
if (!extraReader.TryRead7BitEncoded(offset))
return false;
if (sharedOffsets.find(u32(offset)) != sharedOffsets.end())
return false;
if (!entryOffsets.insert(u32(offset)).second)
return false;
}
}
return true;
}
void CacheEntries::PopulateInputsThatAreOutputs(const Vector<u8>& inputData, StorageImpl& storage, CompactCasKeyTable& table)
{
// one entry that is ~0u is equal to test all inputs
if (inputsThatAreOutputs.size() == 1 && *inputsThatAreOutputs.begin() == ~0u)
return;
u32 insertCount = 0;
BinaryReader inputReader(inputData);
while (inputReader.GetLeft())
{
u32 casKeyOffset = u32(inputReader.Read7BitEncoded());
CasKey casKey;
table.GetKey(casKey, casKeyOffset);
if (storage.HasBeenSeen(casKey))
if (inputsThatAreOutputs.insert(casKeyOffset).second)
++insertCount;
}
// If there are lots of inputs that are outputs this is most likely a link step and we switch to always check inputs
if (insertCount > 5)
{
inputsThatAreOutputs.clear();
inputsThatAreOutputs.insert(~0u);
}
}
void CacheEntries::Flatten(Vector<u8>& out, const CacheEntry& entry)
{
u64 size = entry.extraInputCasKeyOffsets.size();
{
BinaryReader rangeReader(entry.sharedInputCasKeyOffsetRanges);
while (rangeReader.GetLeft())
{
u64 begin = rangeReader.Read7BitEncoded();
u64 end = rangeReader.Read7BitEncoded();
size += end - begin;
}
}
out.resize(size);
BinaryWriter writer(out.data(), 0, out.size());
BinaryReader extraReader(entry.extraInputCasKeyOffsets);
u32 nextExtra = ~0u;
if (extraReader.GetLeft())
nextExtra = u32(extraReader.Read7BitEncoded());
auto writeExtra = [&](u32 prevOffset)
{
while (nextExtra < prevOffset)
{
writer.Write7BitEncoded(nextExtra);
nextExtra = ~0u;
if (extraReader.GetLeft())
nextExtra = u32(extraReader.Read7BitEncoded());
};
};
BinaryReader rangeReader(entry.sharedInputCasKeyOffsetRanges);
while (rangeReader.GetLeft())
{
u64 begin = rangeReader.Read7BitEncoded();
u64 end = rangeReader.Read7BitEncoded();
BinaryReader inputReader(sharedInputCasKeyOffsets.data(), begin, end);
while (inputReader.GetLeft())
{
u32 offset = u32(inputReader.Read7BitEncoded());
writeExtra(offset);
writer.Write7BitEncoded(offset);
}
}
writeExtra(~0u);
}
void CacheEntries::Flatten(Vector<u32>& out, const CacheEntry& entry, const Vector<u8>& sharedOffsets)
{
out.clear();
BinaryReader extraReader(entry.extraInputCasKeyOffsets);
u32 nextExtra = ~0u;
if (extraReader.GetLeft())
nextExtra = u32(extraReader.Read7BitEncoded());
auto writeExtra = [&](u32 prevOffset)
{
while (nextExtra < prevOffset)
{
out.push_back(nextExtra);
nextExtra = ~0u;
if (extraReader.GetLeft())
nextExtra = u32(extraReader.Read7BitEncoded());
};
};
BinaryReader rangeReader(entry.sharedInputCasKeyOffsetRanges.data(), 0, entry.sharedInputCasKeyOffsetRanges.size());
while (rangeReader.GetLeft())
{
u64 begin = rangeReader.Read7BitEncoded();
u64 end = rangeReader.Read7BitEncoded();
BinaryReader inputReader(sharedOffsets.data(), begin, end);
while (inputReader.GetLeft())
{
u32 offset = u32(inputReader.Read7BitEncoded());
writeExtra(offset);
out.push_back(offset);
}
}
writeExtra(~0u);
}
}