mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-26 09:35:28 +00:00
Merge branch 'ref_id_tests' into 'master'
Add more tests for RefId and fix ESM3ExteriorCellRefId See merge request OpenMW/openmw!2905
This commit is contained in:
commit
42c40c875d
@ -82,6 +82,23 @@ namespace
|
||||
return generateSerializedRefIds(generateGeneratedRefIds(random), serialize);
|
||||
}
|
||||
|
||||
template <class Random>
|
||||
std::vector<ESM::RefId> generateESM3ExteriorCellRefIds(Random& random)
|
||||
{
|
||||
std::vector<ESM::RefId> result;
|
||||
result.reserve(refIdsCount);
|
||||
std::uniform_int_distribution<std::int32_t> distribution(-100, 100);
|
||||
std::generate_n(std::back_inserter(result), refIdsCount,
|
||||
[&] { return ESM::ESM3ExteriorCellRefId(distribution(random), distribution(random)); });
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class Random, class Serialize>
|
||||
std::vector<std::string> generateSerializedESM3ExteriorCellRefIds(Random& random, Serialize&& serialize)
|
||||
{
|
||||
return generateSerializedRefIds(generateESM3ExteriorCellRefIds(random), serialize);
|
||||
}
|
||||
|
||||
void serializeRefId(benchmark::State& state)
|
||||
{
|
||||
std::minstd_rand random;
|
||||
@ -189,6 +206,33 @@ namespace
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void serializeTextESM3ExteriorCellRefId(benchmark::State& state)
|
||||
{
|
||||
std::minstd_rand random;
|
||||
std::vector<ESM::RefId> refIds = generateESM3ExteriorCellRefIds(random);
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(refIds[i].serializeText());
|
||||
if (++i >= refIds.size())
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void deserializeTextESM3ExteriorCellRefId(benchmark::State& state)
|
||||
{
|
||||
std::minstd_rand random;
|
||||
std::vector<std::string> serializedRefIds
|
||||
= generateSerializedESM3ExteriorCellRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
|
||||
std::size_t i = 0;
|
||||
for (auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
|
||||
if (++i >= serializedRefIds.size())
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(serializeRefId)->RangeMultiplier(4)->Range(8, 64);
|
||||
@ -199,5 +243,7 @@ BENCHMARK(serializeTextGeneratedRefId);
|
||||
BENCHMARK(deserializeTextGeneratedRefId);
|
||||
BENCHMARK(serializeTextIndexRefId);
|
||||
BENCHMARK(deserializeTextIndexRefId);
|
||||
BENCHMARK(serializeTextESM3ExteriorCellRefId);
|
||||
BENCHMARK(deserializeTextESM3ExteriorCellRefId);
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
|
@ -230,6 +230,7 @@ namespace ESM
|
||||
{ RefId::formIdRefId(42), "0x2a" },
|
||||
{ RefId::generated(42), "0x2a" },
|
||||
{ RefId::index(REC_ARMO, 42), "ARMO:0x2a" },
|
||||
{ RefId::esm3ExteriorCell(-13, 42), "-13:42" },
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ESMRefIdToString, ESMRefIdToStringTest, ValuesIn(toStringParams));
|
||||
@ -262,6 +263,7 @@ namespace ESM
|
||||
{ RefId::formIdRefId(42), "FormId:0x2a" },
|
||||
{ RefId::generated(42), "Generated:0x2a" },
|
||||
{ RefId::index(REC_ARMO, 42), "Index:ARMO:0x2a" },
|
||||
{ RefId::esm3ExteriorCell(-13, 42), "Esm3ExteriorCell:-13:42" },
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ESMRefIdToDebugString, ESMRefIdToDebugStringTest, ValuesIn(toDebugStringParams));
|
||||
@ -297,6 +299,13 @@ namespace ESM
|
||||
{ RefId::index(REC_INGR, 1), "Index:INGR:0x1" },
|
||||
{ RefId::index(REC_INGR, 0x1f), "Index:INGR:0x1f" },
|
||||
{ RefId::index(REC_INGR, std::numeric_limits<std::uint32_t>::max()), "Index:INGR:0xffffffff" },
|
||||
{ RefId::esm3ExteriorCell(-13, 42), "Esm3ExteriorCell:-13:42" },
|
||||
{ RefId::esm3ExteriorCell(
|
||||
std::numeric_limits<std::int32_t>::min(), std::numeric_limits<std::int32_t>::min()),
|
||||
"Esm3ExteriorCell:-2147483648:-2147483648" },
|
||||
{ RefId::esm3ExteriorCell(
|
||||
std::numeric_limits<std::int32_t>::max(), std::numeric_limits<std::int32_t>::max()),
|
||||
"Esm3ExteriorCell:2147483647:2147483647" },
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ESMRefIdText, ESMRefIdTextTest, ValuesIn(serializedRefIds));
|
||||
@ -364,7 +373,10 @@ namespace ESM
|
||||
TYPED_TEST_P(ESMRefIdTypesTest, serializeTextThenDeserializeTextShouldProduceSameValue)
|
||||
{
|
||||
const RefId refId = GenerateRefId<TypeParam>::call();
|
||||
EXPECT_EQ(RefId::deserializeText(refId.serializeText()), refId);
|
||||
const std::string text = refId.serializeText();
|
||||
for (std::size_t i = 0; i < text.size(); ++i)
|
||||
ASSERT_TRUE(std::isprint(text[i])) << "index: " << i << ", int value: " << static_cast<int>(text[i]);
|
||||
EXPECT_EQ(RefId::deserializeText(text), refId);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(ESMRefIdTypesTest, shouldBeEqualToItself)
|
||||
|
@ -50,5 +50,38 @@ namespace ESM
|
||||
writer.save(stream);
|
||||
EXPECT_THROW(writer.writeMaybeFixedSizeString(generateRandomString(33), 32), std::runtime_error);
|
||||
}
|
||||
|
||||
struct Esm3EsmWriterRefIdSizeTest : TestWithParam<std::pair<RefId, std::size_t>>
|
||||
{
|
||||
};
|
||||
|
||||
// If this test failed probably there is a change in RefId format and CurrentSaveGameFormatVersion should be
|
||||
// incremented, current version should be handled.
|
||||
TEST_P(Esm3EsmWriterRefIdSizeTest, writeHRefIdShouldProduceCertainNubmerOfBytes)
|
||||
{
|
||||
const auto [refId, size] = GetParam();
|
||||
|
||||
std::ostringstream stream;
|
||||
|
||||
{
|
||||
ESMWriter writer;
|
||||
writer.setFormatVersion(CurrentSaveGameFormatVersion);
|
||||
writer.save(stream);
|
||||
writer.writeHRefId(refId);
|
||||
}
|
||||
|
||||
EXPECT_EQ(stream.view().size(), size);
|
||||
}
|
||||
|
||||
const std::vector<std::pair<RefId, std::size_t>> refIdSizes = {
|
||||
{ RefId(), 57 },
|
||||
{ RefId::stringRefId(std::string(32, 'a')), 89 },
|
||||
{ RefId::formIdRefId(0x1f), 61 },
|
||||
{ RefId::generated(0x1f), 65 },
|
||||
{ RefId::index(REC_INGR, 0x1f), 65 },
|
||||
{ RefId::esm3ExteriorCell(-42, 42), 65 },
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(RefIds, Esm3EsmWriterRefIdSizeTest, ValuesIn(refIdSizes));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "esm3exteriorcellrefid.hpp"
|
||||
#include "serializerefid.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
@ -9,28 +10,28 @@ namespace ESM
|
||||
std::string ESM3ExteriorCellRefId::toString() const
|
||||
{
|
||||
std::string result;
|
||||
std::size_t integralSizeX = getIntegralSize(mX);
|
||||
result.resize(integralSizeX + getIntegralSize(mY) + 3, '\0');
|
||||
serializeIntegral(mX, 0, result);
|
||||
result[integralSizeX] = ':';
|
||||
serializeIntegral(mY, integralSizeX + 1, result);
|
||||
result.resize(getDecIntegralCapacity(mX) + getDecIntegralCapacity(mY) + 3, '\0');
|
||||
const std::size_t endX = serializeDecIntegral(mX, 0, result);
|
||||
result[endX] = ':';
|
||||
const std::size_t endY = serializeDecIntegral(mY, endX + 1, result);
|
||||
result.resize(endY);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ESM3ExteriorCellRefId::toDebugString() const
|
||||
{
|
||||
std::string result;
|
||||
std::size_t integralSizeX = getIntegralSize(mX);
|
||||
|
||||
serializeRefIdPrefix(integralSizeX + getIntegralSize(mY) + 1, esm3ExteriorCellRefIdPrefix, result);
|
||||
serializeIntegral(mX, esm3ExteriorCellRefIdPrefix.size(), result);
|
||||
result[esm3ExteriorCellRefIdPrefix.size() + integralSizeX] = ':';
|
||||
serializeIntegral(mY, esm3ExteriorCellRefIdPrefix.size() + integralSizeX + 1, result);
|
||||
serializeRefIdPrefix(
|
||||
getDecIntegralCapacity(mX) + getDecIntegralCapacity(mY) + 1, esm3ExteriorCellRefIdPrefix, result);
|
||||
const std::size_t endX = serializeDecIntegral(mX, esm3ExteriorCellRefIdPrefix.size(), result);
|
||||
result[endX] = ':';
|
||||
const std::size_t endY = serializeDecIntegral(mY, endX + 1, result);
|
||||
result.resize(endY);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, ESM3ExteriorCellRefId value)
|
||||
{
|
||||
return stream << "Vec2i{" << value.mX << "," << value.mY << '}';
|
||||
return stream << value.toDebugString();
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ namespace ESM
|
||||
std::string FormIdRefId::toString() const
|
||||
{
|
||||
std::string result;
|
||||
result.resize(getIntegralSize(mValue) + 2, '\0');
|
||||
serializeIntegral(mValue, 0, result);
|
||||
result.resize(getHexIntegralSize(mValue) + 2, '\0');
|
||||
serializeHexIntegral(mValue, 0, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,8 @@ namespace ESM
|
||||
std::string GeneratedRefId::toString() const
|
||||
{
|
||||
std::string result;
|
||||
result.resize(getIntegralSize(mValue) + 2, '\0');
|
||||
serializeIntegral(mValue, 0, result);
|
||||
result.resize(getHexIntegralSize(mValue) + 2, '\0');
|
||||
serializeHexIntegral(mValue, 0, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -9,20 +9,20 @@ namespace ESM
|
||||
std::string IndexRefId::toString() const
|
||||
{
|
||||
std::string result;
|
||||
result.resize(sizeof(mRecordType) + getIntegralSize(mValue) + 3, '\0');
|
||||
result.resize(sizeof(mRecordType) + getHexIntegralSize(mValue) + 3, '\0');
|
||||
std::memcpy(result.data(), &mRecordType, sizeof(mRecordType));
|
||||
result[sizeof(mRecordType)] = ':';
|
||||
serializeIntegral(mValue, sizeof(mRecordType) + 1, result);
|
||||
serializeHexIntegral(mValue, sizeof(mRecordType) + 1, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string IndexRefId::toDebugString() const
|
||||
{
|
||||
std::string result;
|
||||
serializeRefIdPrefix(sizeof(mRecordType) + getIntegralSize(mValue) + 1, indexRefIdPrefix, result);
|
||||
serializeRefIdPrefix(sizeof(mRecordType) + getHexIntegralSize(mValue) + 1, indexRefIdPrefix, result);
|
||||
std::memcpy(result.data() + indexRefIdPrefix.size(), &mRecordType, sizeof(mRecordType));
|
||||
result[indexRefIdPrefix.size() + sizeof(mRecordType)] = ':';
|
||||
serializeIntegral(mValue, indexRefIdPrefix.size() + sizeof(mRecordType) + 1, result);
|
||||
serializeHexIntegral(mValue, indexRefIdPrefix.size() + sizeof(mRecordType) + 1, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -225,23 +225,29 @@ namespace ESM
|
||||
return ESM::RefId();
|
||||
|
||||
if (value.starts_with(formIdRefIdPrefix))
|
||||
return ESM::RefId::formIdRefId(deserializeIntegral<ESM4::FormId>(formIdRefIdPrefix.size(), value));
|
||||
return ESM::RefId::formIdRefId(deserializeHexIntegral<ESM4::FormId>(formIdRefIdPrefix.size(), value));
|
||||
|
||||
if (value.starts_with(generatedRefIdPrefix))
|
||||
return ESM::RefId::generated(deserializeIntegral<std::uint64_t>(generatedRefIdPrefix.size(), value));
|
||||
return ESM::RefId::generated(deserializeHexIntegral<std::uint64_t>(generatedRefIdPrefix.size(), value));
|
||||
|
||||
if (value.starts_with(indexRefIdPrefix))
|
||||
{
|
||||
ESM::RecNameInts recordType{};
|
||||
std::memcpy(&recordType, value.data() + indexRefIdPrefix.size(), sizeof(recordType));
|
||||
return ESM::RefId::index(recordType,
|
||||
deserializeIntegral<std::uint32_t>(indexRefIdPrefix.size() + sizeof(recordType) + 1, value));
|
||||
deserializeHexIntegral<std::uint32_t>(indexRefIdPrefix.size() + sizeof(recordType) + 1, value));
|
||||
}
|
||||
|
||||
if (value.starts_with(esm3ExteriorCellRefIdPrefix))
|
||||
{
|
||||
std::int32_t x = deserializeIntegral<std::int32_t>(esm3ExteriorCellRefIdPrefix.size(), value);
|
||||
std::int32_t y
|
||||
= deserializeIntegral<std::int32_t>(esm3ExteriorCellRefIdPrefix.size() + getIntegralSize(x) + 1, value);
|
||||
if (value.size() < esm3ExteriorCellRefIdPrefix.size() + 3)
|
||||
throw std::runtime_error("Invalid ESM3ExteriorCellRefId format: not enough size");
|
||||
const std::size_t separator = value.find(':', esm3ExteriorCellRefIdPrefix.size() + 1);
|
||||
if (separator == std::string_view::npos)
|
||||
throw std::runtime_error("Invalid ESM3ExteriorCellRefId format: coordinates separator is not found");
|
||||
const std::int32_t x
|
||||
= deserializeDecIntegral<std::int32_t>(esm3ExteriorCellRefIdPrefix.size(), separator, value);
|
||||
const std::int32_t y = deserializeDecIntegral<std::int32_t>(separator + 1, value.size(), value);
|
||||
return ESM::ESM3ExteriorCellRefId(x, y);
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,11 @@
|
||||
|
||||
#include <charconv>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
@ -15,8 +17,19 @@ namespace ESM
|
||||
constexpr std::string_view esm3ExteriorCellRefIdPrefix = "Esm3ExteriorCell:";
|
||||
|
||||
template <class T>
|
||||
std::size_t getIntegralSize(T value)
|
||||
std::size_t getDecIntegralCapacity(T value)
|
||||
{
|
||||
if (value == 0)
|
||||
return 1;
|
||||
if (value > 0)
|
||||
return static_cast<std::size_t>(std::numeric_limits<T>::digits10);
|
||||
return static_cast<std::size_t>(std::numeric_limits<T>::digits10) + 1;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::size_t getHexIntegralSize(T value)
|
||||
{
|
||||
static_assert(!std::is_signed_v<T>);
|
||||
std::size_t result = sizeof(T) * 2;
|
||||
while (true)
|
||||
{
|
||||
@ -34,30 +47,55 @@ namespace ESM
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void serializeIntegral(T value, std::size_t shift, std::string& out)
|
||||
std::size_t serializeDecIntegral(T value, std::size_t shift, std::string& out)
|
||||
{
|
||||
const auto r = std::to_chars(out.data() + shift, out.data() + out.size(), value, 10);
|
||||
if (r.ec != std::errc())
|
||||
throw std::system_error(std::make_error_code(r.ec), "Failed to serialize ESM::RefId dec integral value");
|
||||
return r.ptr - out.data();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void serializeHexIntegral(T value, std::size_t shift, std::string& out)
|
||||
{
|
||||
static_assert(!std::is_signed_v<T>);
|
||||
out[shift] = '0';
|
||||
out[shift + 1] = 'x';
|
||||
const auto r = std::to_chars(out.data() + shift + 2, out.data() + out.size(), value, 16);
|
||||
if (r.ec != std::errc())
|
||||
throw std::system_error(std::make_error_code(r.ec), "Failed to serialize ESM::RefId integral value");
|
||||
throw std::system_error(std::make_error_code(r.ec), "Failed to serialize ESM::RefId hex integral value");
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void serializeRefIdValue(T value, std::string_view prefix, std::string& out)
|
||||
{
|
||||
serializeRefIdPrefix(getIntegralSize(value), prefix, out);
|
||||
serializeIntegral(value, prefix.size(), out);
|
||||
static_assert(!std::is_signed_v<T>);
|
||||
serializeRefIdPrefix(getHexIntegralSize(value), prefix, out);
|
||||
serializeHexIntegral(value, prefix.size(), out);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T deserializeIntegral(std::size_t shift, std::string_view value)
|
||||
T deserializeDecIntegral(std::size_t shift, std::size_t end, std::string_view value)
|
||||
{
|
||||
T result{};
|
||||
const auto r = std::from_chars(value.data() + shift, value.data() + end, result, 10);
|
||||
if (r.ec != std::errc())
|
||||
throw std::system_error(std::make_error_code(r.ec),
|
||||
"Failed to deserialize ESM::RefId dec integral value: \""
|
||||
+ std::string(value.data() + shift, value.data() + end) + '"');
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T deserializeHexIntegral(std::size_t shift, std::string_view value)
|
||||
{
|
||||
static_assert(!std::is_signed_v<T>);
|
||||
T result{};
|
||||
const auto r = std::from_chars(value.data() + shift + 2, value.data() + value.size(), result, 16);
|
||||
if (r.ec != std::errc())
|
||||
throw std::system_error(std::make_error_code(r.ec),
|
||||
"Failed to deserialize ESM::RefId integral value: \"" + std::string(value) + '"');
|
||||
"Failed to deserialize ESM::RefId hex integral value: \""
|
||||
+ std::string(value.data() + shift + 2, value.data() + value.size()) + '"');
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user