// Copyright Epic Games, Inc. All Rights Reserved. #include "TestNetSerializerFixture.h" #include "Iris/Serialization/IntNetSerializers.h" #include namespace UE::Net::Private { static FTestMessage& PrintIntNetSerializerConfig(FTestMessage& Message, const FNetSerializerConfig& InConfig) { const FIntNetSerializerConfig& Config = static_cast(InConfig); return Message << "BitCount: " << Config.BitCount; } template class FTestIntNetSerializer : public TTestNetSerializerFixture { typedef TTestNetSerializerFixture Super; public: FTestIntNetSerializer(const FNetSerializer& Serializer) : Super(Serializer) {} void TestIsEqual(); void TestValidate(); void TestSerialize(); void TestSerializeDelta(); void TestDefaultConfig(); protected: static const SourceType Values[]; static const SIZE_T ValueCount; }; #define UE_NET_IMPLEMENT_INT_NETSERIALIZER_TEST(TestClassName, NetSerializerType, SourceType) \ class TestClassName : public FTestIntNetSerializer \ { \ public: \ TestClassName() : FTestIntNetSerializer(UE_NET_GET_SERIALIZER(NetSerializerType)) {} \ }; \ \ UE_NET_TEST_FIXTURE(TestClassName, HasTestValues) \ { \ UE_NET_ASSERT_GT_MSG(ValueCount, SIZE_T(0), "No test values found"); \ } \ \ UE_NET_TEST_FIXTURE(TestClassName, TestIsEqual) \ { \ TestIsEqual(); \ } \ \ UE_NET_TEST_FIXTURE(TestClassName, TestValidate) \ { \ TestValidate(); \ } \ \ UE_NET_TEST_FIXTURE(TestClassName, TestSerialize) \ { \ TestSerialize(); \ } \ \ UE_NET_TEST_FIXTURE(TestClassName, TestSerializeDelta) \ { \ TestSerializeDelta(); \ } \ \ UE_NET_TEST_FIXTURE(TestClassName, TestDefaultConfig) \ { \ TestDefaultConfig(); \ } \ UE_NET_IMPLEMENT_INT_NETSERIALIZER_TEST(FTestInt64NetSerializer, FInt64NetSerializer, int64); UE_NET_IMPLEMENT_INT_NETSERIALIZER_TEST(FTestInt32NetSerializer, FInt32NetSerializer, int32); UE_NET_IMPLEMENT_INT_NETSERIALIZER_TEST(FTestInt16NetSerializer, FInt16NetSerializer, int16); UE_NET_IMPLEMENT_INT_NETSERIALIZER_TEST(FTestInt8NetSerializer, FInt8NetSerializer, int8); #undef UE_NET_IMPLEMENT_INT_NETSERIALIZER_TEST // template const SourceType FTestIntNetSerializer::Values[] = { std::numeric_limits::min(), std::numeric_limits::max(), SourceType(0), SourceType(-1), SourceType(-16), SourceType(2048458 % std::numeric_limits::max()) }; template const SIZE_T FTestIntNetSerializer::ValueCount = sizeof(Values)/sizeof(Values[0]); template void FTestIntNetSerializer::TestIsEqual() { constexpr uint8 MinBitCount = 1; constexpr uint8 MaxBitCount = sizeof(SourceType)*8; SourceType CompareValues[2][sizeof(Values)/sizeof(Values[0])]; bool ExpectedResults[2][sizeof(Values)/sizeof(Values[0])]; for (SIZE_T ValueIt = 0; ValueIt < ValueCount; ++ValueIt) { CompareValues[0][ValueIt] = Values[ValueIt]; ExpectedResults[0][ValueIt] = true; } for (SIZE_T ValueIt = 0; ValueIt < ValueCount; ++ValueIt) { CompareValues[1][ValueIt] = ~Values[ValueIt]; ExpectedResults[1][ValueIt] = false; } // Do two rounds of testing, one where we compare each value with itself and one where we compare against a guaranteed non-equal value. // We will only do an unquantized compare since we know these serializers uses a default quantize implementation. for (SIZE_T TestRoundIt = 0, TestRoundEndIt = 2; TestRoundIt != TestRoundEndIt; ++TestRoundIt) { for (uint8 BitCount = MinBitCount; BitCount <= MaxBitCount; ++BitCount) { FIntNetSerializerConfig Config(BitCount); constexpr bool bQuantizedCompare = false; const bool bSuccess = Super::TestIsEqual(Values, CompareValues[TestRoundIt], ExpectedResults[TestRoundIt], ValueCount, Config, bQuantizedCompare); if (!bSuccess) { return; } } } } template void FTestIntNetSerializer::TestValidate() { constexpr uint8 MinBitCount = 1; constexpr uint8 MaxBitCount = sizeof(SourceType)*8; bool ExpectedResults[sizeof(Values)/sizeof(Values[0])]; for (uint8 BitCount = MinBitCount; BitCount <= MaxBitCount; ++BitCount) { constexpr SourceType MaxValue = std::numeric_limits::max(); const SourceType MaxValueForBitCount = MaxValue >> (sizeof(SourceType)*8 - BitCount); const SourceType MinValueForBitCount = -MaxValueForBitCount - SourceType(1); for (SIZE_T ValueIt = 0; ValueIt < ValueCount; ++ValueIt) { const SourceType Value = Values[ValueIt]; ExpectedResults[ValueIt] = (FMath::Clamp(Value, MinValueForBitCount, MaxValueForBitCount) == Value); if (BitCount == sizeof(SourceType)*8) { UE_NET_ASSERT_TRUE_MSG(ExpectedResults[ValueIt], "Clamping of value with full bit precision resulted in value being clamped. This is unexpected. Make sure no undefined behavior is used in code."); } } FIntNetSerializerConfig Config(BitCount); const bool bSuccess = Super::TestValidate(Values, ExpectedResults, ValueCount, Config); if (!bSuccess) { return; } } } template void FTestIntNetSerializer::TestSerialize() { constexpr uint8 MinBitCount = 1; constexpr uint8 MaxBitCount = sizeof(SourceType)*8; // Setup expected results. The IntPackers should only consider the BitCount least significant bits. Our test values may have bits set outside that range. // But values with bits sets that won't be serialized should be considered equal as long as the least significant bits are set. This is because serializing // either value will result in the same value being deserialized. It may not be euqal the the original value though. The NetSerializer's Validate function // is there to detect that kind of situations and warn the user that the result is undefined. SourceType ExpectedValues[sizeof(Values) / sizeof(Values[0])]; for (SIZE_T ValueIt = 0; ValueIt < ValueCount; ++ValueIt) { ExpectedValues[ValueIt] = Values[ValueIt]; } const auto& EqualityFunc = [](NetSerializerValuePointer Value0, NetSerializerValuePointer Value1) -> bool { return *reinterpret_cast(Value0) == *reinterpret_cast(Value1); }; for (uint8 BitCount = MinBitCount; BitCount <= MaxBitCount; ++BitCount) { FIntNetSerializerConfig Config(BitCount); constexpr bool bQuantizedCompare = false; TOptional> CompareFunc; if (BitCount == MaxBitCount) { CompareFunc = EqualityFunc; } const bool bSuccess = Super::TestSerialize(Values, ExpectedValues, ValueCount, Config, bQuantizedCompare, CompareFunc); if (!bSuccess) { return; } } } template void FTestIntNetSerializer::TestSerializeDelta() { constexpr uint8 MinBitCount = 1; constexpr uint8 MaxBitCount = sizeof(SourceType)*8; for (uint8 BitCount = MinBitCount; BitCount <= MaxBitCount; ++BitCount) { FIntNetSerializerConfig Config(BitCount); Super::TestSerializeDelta(Values, ValueCount, Config); } } template void FTestIntNetSerializer::TestDefaultConfig() { const FIntNetSerializerConfig* DefaultConfig = static_cast(FTestNetSerializerFixture::Serializer.DefaultConfig); UE_NET_ASSERT_NE(DefaultConfig, nullptr); UE_NET_ASSERT_EQ(DefaultConfig->BitCount, static_cast(sizeof(SourceType)*8u)); } }