/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "Tests.h" #include "TestProfiler.h" #include #include #include #include #include #include #include #include #define GM_REPLICA_TEST_SESSION_CHANNEL 1 using namespace GridMate; #if defined(max) #undef max #endif #if defined(min) #undef min #endif namespace UnitTest { //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class InterpolatorTest : public GridMateMPTestFixture { public: float m_zigVals[1000]; static const int k_actualSampleStart = 100; static const int k_offsetBetweenSamples = 10; //----------------------------------------------------------------------------- InterpolatorTest() { for (int a = 0; a < 100; ++a) { m_zigVals[a ] = static_cast< float >(rand() % 200 - 100); m_zigVals[a + 200] = static_cast< float >(rand() % 200 - 100); m_zigVals[a + 400] = static_cast< float >(rand() % 200 - 100); m_zigVals[a + 600] = static_cast< float >(rand() % 200 - 100); m_zigVals[a + 800] = static_cast< float >(rand() % 200 - 100); } for (int a = 100; a < 200; ++a) { m_zigVals[a] = 10.f; } for (int a = 300; a < 400; ++a) { m_zigVals[a] = static_cast< float >((a - 300) * (a - 300)); } for (int a = 500; a < 600; ++a) { m_zigVals[a] = static_cast< float >(a - 500) * 0.7f - 20.f; } for (int a = 700; a < 800; ++a) { m_zigVals[a] = sqrtf(static_cast< float >(a)); } for (int a = 900; a < 1000; ++a) { m_zigVals[a] = static_cast< float >(a - 900) * -5.f + 100.f; } } //----------------------------------------------------------------------------- template< typename T > void AddSamplesConstant(T& interpolator, int numSamples, const float k_constant) { for (int a = 0; a < numSamples; ++a) { interpolator.AddSample(k_constant, k_actualSampleStart + a * k_offsetBetweenSamples); } } //----------------------------------------------------------------------------- template< typename T > void AddSamplesLinear(T& interpolator, int numSamples, float slope, float yIntercept) { for (int a = 0; a < numSamples; ++a) { interpolator.AddSample(slope * static_cast< float >(a) + yIntercept, k_actualSampleStart + a * k_offsetBetweenSamples); } } //----------------------------------------------------------------------------- template< typename T > void AddSamplesZigZag(T& interpolator, int numSamples) { for (int a = 0; a < numSamples; ++a) { interpolator.AddSample(m_zigVals[a % AZ_ARRAY_SIZE(m_zigVals)], k_actualSampleStart + a * k_offsetBetweenSamples); } } void run() { ////////////////////////////////////////// // testing point sample EpsilonThrottle< float > epsilon; epsilon.SetThreshold(0.001f); float check = -1.f; (void)check; // ensure interpolator returns correct value when it only has one sample { const int k_time = 0; const int k_sample = 1337; PointSample< int > interpolator; interpolator.AddSample(k_sample, k_time); AZ_TEST_ASSERT(interpolator.GetInterpolatedValue(k_time) == k_sample); AZ_TEST_ASSERT(interpolator.GetLastValue() == k_sample); AZ_TEST_ASSERT(interpolator.GetSampleCount() == 1); SampleInfo< int > info = interpolator.GetSampleInfo(0); AZ_TEST_ASSERT(info.m_t == k_time); AZ_TEST_ASSERT(info.m_v == k_sample); } // sample set partway full (pattern constant) { const int k_sampleArraySize = 100; const int k_numSamples = k_sampleArraySize; const float k_constant = 5.f; PointSample< float, k_sampleArraySize > interpolator; interpolator.Clear(); AddSamplesConstant(interpolator, k_numSamples, k_constant); epsilon.SetBaseline(k_constant); for (int a = -k_offsetBetweenSamples; a < k_offsetBetweenSamples * (k_numSamples + 2); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set partway full (pattern linear) { const int k_numSamples = 500; const int k_sampleArraySize = 800; const float k_slope = 1.f; const float k_intercept = 10.f; PointSample< float, k_sampleArraySize > interpolator; AddSamplesLinear(interpolator, k_numSamples, k_slope, k_intercept); epsilon.SetBaseline(k_intercept); // interpolate to value before any samples for (int a = k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate after samples for (int a = 0; a < k_numSamples * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(k_slope * static_cast< float >(a / k_offsetBetweenSamples) + k_intercept); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate to value after last sample for (int a = k_numSamples * k_offsetBetweenSamples; a < (k_numSamples + 2) * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(k_slope * static_cast< float >(k_numSamples - 1) + k_intercept); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(k_slope * static_cast< float >(k_numSamples - 1) + k_intercept); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set partway full (pattern zigzag) { const int k_numSamples = 400; const int k_sampleArraySize = 800; PointSample< float, k_sampleArraySize > interpolator; AddSamplesZigZag(interpolator, k_numSamples); epsilon.SetBaseline(m_zigVals[0]); // interpolate to before earliest remaining sample record for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate from existing samples for (int a = 0; a < k_offsetBetweenSamples * k_numSamples; ++a) { int idxLower = (a / k_offsetBetweenSamples) % AZ_ARRAY_SIZE(m_zigVals); float target = m_zigVals[idxLower]; epsilon.SetBaseline(target); check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); } // interpolate after last known sample for (int a = k_offsetBetweenSamples * k_numSamples; a < k_offsetBetweenSamples * (1 + k_numSamples); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set full (pattern constant) { const int k_numSamples = 860; const int k_sampleArraySize = k_numSamples; const float k_constant = 5.f; PointSample< float, k_sampleArraySize > interpolator; AddSamplesConstant(interpolator, k_numSamples, k_constant); epsilon.SetBaseline(k_constant); for (int a = -k_offsetBetweenSamples; a < (k_numSamples + 2) * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set full (pattern linear) { const int k_sampleArraySize = 600; const int k_numSamples = k_sampleArraySize; const float k_slope = 1.f; const float k_intercept = 10.f; PointSample< float, k_sampleArraySize > interpolator; interpolator.Clear(); AddSamplesLinear(interpolator, k_numSamples, k_slope, k_intercept); epsilon.SetBaseline(k_intercept); // interpolate to value before any samples for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate after samples for (int a = 0; a < k_numSamples * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(k_slope * static_cast< float >(a / k_offsetBetweenSamples) + k_intercept); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate to value after last sample for (int a = k_numSamples * k_offsetBetweenSamples; a < (k_numSamples + 2) * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(k_slope * static_cast< float >(k_numSamples - 1) + k_intercept); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(k_slope * static_cast< float >(k_numSamples - 1) + k_intercept); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set full (pattern zigzag) { const int k_sampleArraySize = 1200; const int k_numSamples = k_sampleArraySize; PointSample< float, k_sampleArraySize > interpolator; AddSamplesZigZag(interpolator, k_numSamples); epsilon.SetBaseline(m_zigVals[0]); // interpolate to before earliest remaining sample record for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate from existing samples for (int a = 0; a < k_offsetBetweenSamples * k_numSamples; ++a) { int idxLower = (a / k_offsetBetweenSamples) % AZ_ARRAY_SIZE(m_zigVals); float target = m_zigVals[idxLower]; check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); } // interpolate after last known sample for (int a = k_offsetBetweenSamples * k_numSamples; a < k_offsetBetweenSamples * (1 + k_numSamples); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set wrapped around (pattern constant) { const int k_sampleArraySize = 80; const int k_numSamples = 120; const float k_constant = 5.f; PointSample< float, k_sampleArraySize > interpolator; AddSamplesConstant(interpolator, k_numSamples, k_constant); epsilon.SetBaseline(k_constant); for (int a = -k_offsetBetweenSamples; a < (k_numSamples + 2) * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set wrapped around (pattern linear) { const int k_numSamples = 1200; const int k_sampleArraySize = 80; const float k_slope = 1.f; const float k_intercept = 10.f; PointSample< float, k_sampleArraySize > interpolator; AddSamplesLinear(interpolator, k_numSamples, k_slope, k_intercept); // interpolate to value before any samples for (int a = -k_offsetBetweenSamples + (k_numSamples - k_sampleArraySize) * k_offsetBetweenSamples; a < (k_numSamples - k_sampleArraySize) * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(k_intercept + k_slope * static_cast< float >(k_numSamples - k_sampleArraySize)); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate after samples for (int a = (k_numSamples - k_sampleArraySize) * k_offsetBetweenSamples; a < k_numSamples * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(k_slope * static_cast< float >(a / k_offsetBetweenSamples) + k_intercept); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate to value after last sample for (int a = k_numSamples * k_offsetBetweenSamples; a < (k_numSamples + 2) * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(k_slope * static_cast< float >(k_numSamples - 1) + k_intercept); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(k_slope * static_cast< float >(k_numSamples - 1) + k_intercept); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set wrapped around (pattern zig-zag) { const int k_numSamples = 1500; const int k_sampleArraySize = 1000; PointSample< float, k_sampleArraySize > interpolator; interpolator.Clear(); AddSamplesZigZag(interpolator, k_numSamples); // interpolate to before earliest remaining sample record for (int a = -k_offsetBetweenSamples + k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize); a < k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(m_zigVals[ (k_numSamples - k_sampleArraySize) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate from existing samples for (int a = k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize); a < k_offsetBetweenSamples * k_numSamples; ++a) { int idxLower = (a / k_offsetBetweenSamples) % AZ_ARRAY_SIZE(m_zigVals); float target = m_zigVals[idxLower]; epsilon.SetBaseline(target); check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); } // interpolate after last known sample for (int a = k_offsetBetweenSamples * k_numSamples; a < k_offsetBetweenSamples * (1 + k_numSamples); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // test Break { PointSample< float > interpolator; interpolator.Break(); } // sample set max size 1 { PointSample< float, 1 > interpolator; // with empty set (uncomment to make sure asserts fire) //check = interpolator.GetLastValue(); //check = interpolator.GetInterpolatedValue( 210 ); // with populated set interpolator.AddSample(1.f, 100); check = interpolator.GetInterpolatedValue(90); epsilon.SetBaseline(1.f); AZ_TEST_ASSERT(epsilon.WithinThreshold(1.f)); check = interpolator.GetInterpolatedValue(100); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(110); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetLastValue(); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); // with the only sample replaced interpolator.AddSample(10.f, 200); epsilon.SetBaseline(10.f); check = interpolator.GetInterpolatedValue(190); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(200); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(210); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetLastValue(); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); // with the only sample replaced at the same time stamp interpolator.AddSample(20.f, 200); epsilon.SetBaseline(20.f); check = interpolator.GetInterpolatedValue(190); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(200); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(210); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetLastValue(); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); } // end testing point-sampling ////////////////////////////////////////// ////////////////////////////////////////// // testing linear interpolation // ensure interpolator returns correct value when it only has one sample { const int k_time = 0; const int k_sample = 1337; LinearInterp< int > interpolator; interpolator.AddSample(k_sample, k_time); AZ_TEST_ASSERT(interpolator.GetInterpolatedValue(k_time) == k_sample); AZ_TEST_ASSERT(interpolator.GetLastValue() == k_sample); AZ_TEST_ASSERT(interpolator.GetSampleCount() == 1); SampleInfo< int > info = interpolator.GetSampleInfo(0); AZ_TEST_ASSERT(info.m_t == k_time); AZ_TEST_ASSERT(info.m_v == k_sample); } // sample set partway full (pattern constant) { const int k_numSamples = 50; const int k_sampleArraySize = 100; const float k_constant = 10.f; LinearInterp< float, k_sampleArraySize > interpolator; AddSamplesConstant(interpolator, k_numSamples, k_constant); epsilon.SetBaseline(k_constant); // interpolate to before samples start / where there are samples to interpolate / past last sample // [-10,0) : before samples start // [0, 40] : where there are samples to interpolate // (40, 50]: past last sample for (int a = -k_offsetBetweenSamples; a < k_numSamples * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set partway full (pattern linear) { const float k_slope = 0.5f; const float k_intercept = 5.f; const int k_numSamples = 600; const int k_sampleArraySize = 800; LinearInterp< float, k_sampleArraySize > interpolator; interpolator.Clear(); AddSamplesLinear(interpolator, k_numSamples, k_slope, k_intercept); epsilon.SetBaseline(k_intercept); // interpolate to before samples start for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate where there are samples to interpolate for (int a = 0; a <= k_offsetBetweenSamples * (k_numSamples - 1); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target = k_slope * static_cast< float >(a) / static_cast< float >(k_offsetBetweenSamples) + k_intercept; epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate past last sample float target = k_slope * static_cast< float >(k_numSamples - 1) + k_intercept; epsilon.SetBaseline(target); for (int a = k_offsetBetweenSamples * (k_numSamples - 1) + 1; a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set partway full (pattern zigzag) { const int k_numSamples = 400; const int k_sampleArraySize = 500; LinearInterp< float, k_sampleArraySize > interpolator; AddSamplesZigZag(interpolator, k_numSamples); // interpolate to before samples start for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(m_zigVals[0]); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate to where there are samples to interpolate for (int a = 0; a <= k_offsetBetweenSamples * (k_numSamples - 1); ++a) { int idxLower = (a / k_offsetBetweenSamples) % AZ_ARRAY_SIZE(m_zigVals); int idxUpper = (idxLower + 1) % AZ_ARRAY_SIZE(m_zigVals); float target = m_zigVals[idxLower] + static_cast< float >(m_zigVals[idxUpper] - m_zigVals[idxLower]) * static_cast< float >(a - k_offsetBetweenSamples * (a / k_offsetBetweenSamples)) / static_cast< float >(k_offsetBetweenSamples); check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)target; (void)check; } // interpolate past last sample for (int a = k_offsetBetweenSamples * (k_numSamples - 1); a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set full (pattern constant) { const int k_numSamples = 500; const int k_sampleArraySize = k_numSamples; const float k_constant = 10.f; LinearInterp< float, k_sampleArraySize > interpolator; AddSamplesConstant(interpolator, k_numSamples, k_constant); epsilon.SetBaseline(k_constant); // interpolate to before samples start / where there are samples to interpolate / past last sample // [-10,0) : before samples start // [0, 40] : where there are samples to interpolate // (40, 50]: past last sample for (int a = -k_offsetBetweenSamples; a < k_numSamples * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set full (pattern linear) { const int k_numSamples = 850; const int k_sampleArraySize = k_numSamples; const float k_slope = 0.5f; const float k_intercept = 5.f; LinearInterp< float, k_sampleArraySize > interpolator; AddSamplesLinear(interpolator, k_numSamples, k_slope, k_intercept); epsilon.SetBaseline(k_intercept); // interpolate to before samples start for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate where there are samples to interpolate for (int a = 0; a <= k_offsetBetweenSamples * (k_numSamples - 1); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target = k_slope * static_cast< float >(a) / static_cast< float >(k_offsetBetweenSamples) + k_intercept; epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate past last sample float target = k_slope * static_cast< float >(k_numSamples - 1) + k_intercept; epsilon.SetBaseline(target); for (int a = k_offsetBetweenSamples * (k_numSamples - 1) + 1; a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } AZ_TracePrintf("GridMate", "this pointer: 0x%p\n", this); // sample set full (pattern zig-zag) { const int k_numSamples = 100; const int k_sampleArraySize = k_numSamples; LinearInterp< float, k_sampleArraySize > interpolator; interpolator.Clear(); AddSamplesZigZag(interpolator, k_numSamples); epsilon.SetBaseline(m_zigVals[0]); // interpolate to before samples start for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate to where there are samples to interpolate for (int a = 0; a <= k_offsetBetweenSamples * (k_numSamples - 1); ++a) { int idxLower = (a / k_offsetBetweenSamples) % AZ_ARRAY_SIZE(m_zigVals); int idxUpper = (idxLower + 1) % AZ_ARRAY_SIZE(m_zigVals); float target = m_zigVals[idxLower] + static_cast< float >(m_zigVals[idxUpper] - m_zigVals[idxLower]) * static_cast< float >(a - k_offsetBetweenSamples * (a / k_offsetBetweenSamples)) / static_cast< float >(k_offsetBetweenSamples); check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)target; (void)check; } // interpolate past last sample for (int a = k_offsetBetweenSamples * (k_numSamples - 1); a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set wrapped around (pattern constant) { const int k_sampleArraySize = 80; const int k_numSamples = 100; const float k_constant = 10.f; LinearInterp< float, k_sampleArraySize > interpolator; interpolator.Clear(); AddSamplesConstant(interpolator, k_numSamples, k_constant); epsilon.SetBaseline(k_constant); // interpolate to before samples start / where there are samples to interpolate / past last sample // [-10,0) : before samples start // [0, 40] : where there are samples to interpolate // (40, 50]: past last sample for (int a = -k_offsetBetweenSamples; a < k_numSamples * k_offsetBetweenSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set wrapped around (pattern linear) { const int k_numSamples = 140; const int k_sampleArraySize = 90; const float k_slope = 0.5f; const float k_intercept = 5.f; LinearInterp< float, k_sampleArraySize > interpolator; AddSamplesLinear(interpolator, k_numSamples, k_slope, k_intercept); // interpolate to before samples start for (int a = k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize - 1); a < k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(k_slope * static_cast< float >(k_numSamples - k_sampleArraySize) + k_intercept); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate where there are samples to interpolate for (int a = k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize); a <= k_offsetBetweenSamples * (k_numSamples - 1); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target = k_slope * static_cast< float >(a) / static_cast< float >(k_offsetBetweenSamples) + k_intercept; epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate past last sample float target = k_slope * static_cast< float >(k_numSamples - 1) + k_intercept; epsilon.SetBaseline(target); for (int a = k_offsetBetweenSamples * (k_numSamples - 1) + 1; a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set wrapped around (pattern zigzag) { const int k_numSamples = 250; const int k_sampleArraySize = 100; LinearInterp< float, k_sampleArraySize > interpolator; AddSamplesZigZag(interpolator, k_numSamples); // interpolate to before samples start for (int a = k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize - 1); a < k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(m_zigVals[ (k_numSamples - k_sampleArraySize) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate to where there are samples to interpolate for (int a = k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize); a <= k_offsetBetweenSamples * (k_numSamples - 1); ++a) { int idxLower = (a / k_offsetBetweenSamples) % AZ_ARRAY_SIZE(m_zigVals); int idxUpper = (idxLower + 1) % AZ_ARRAY_SIZE(m_zigVals); float target = m_zigVals[idxLower] + static_cast< float >(m_zigVals[idxUpper] - m_zigVals[idxLower]) * static_cast< float >(a - k_offsetBetweenSamples * (a / k_offsetBetweenSamples)) / static_cast< float >(k_offsetBetweenSamples); epsilon.SetBaseline(target); check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)target; (void)check; } // interpolate past last sample for (int a = k_offsetBetweenSamples * (k_numSamples - 1); a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // test Break { const int k_numSamples = 20; const int k_sampleArraySize = k_numSamples; const int k_break = 10; const float k_intercept = 0.f; const float k_slope = 1.f; LinearInterp< float, k_sampleArraySize > interpolator; for (int a = 0; a < k_numSamples; ++a) { if (a == k_break) { interpolator.Break(); } interpolator.AddSample(k_slope * static_cast< float >(a) + k_intercept, k_actualSampleStart + a * k_offsetBetweenSamples); } epsilon.SetBaseline(k_intercept); // interpolate to before samples start for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate where there are samples to interpolate for (int a = 0; a <= k_offsetBetweenSamples * (k_numSamples - 1); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target; if (a / k_offsetBetweenSamples + 1 == k_break) { target = k_slope * static_cast< float >(a / k_offsetBetweenSamples) + k_intercept; } else { target = k_slope * static_cast< float >(a) / static_cast< float >(k_offsetBetweenSamples) + k_intercept; } epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate past last sample float target = k_slope * static_cast< float >(k_numSamples - 1) + k_intercept; epsilon.SetBaseline(target); for (int a = k_offsetBetweenSamples * (k_numSamples - 1) + 1; a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set max size 1 { LinearInterp< float, 1 > interpolator; // with empty set (uncomment to make sure asserts fire) //interpolator.GetLastValue(); //interpolator.GetInterpolatedValue( 210 ); // with populated set interpolator.AddSample(1.f, 100); epsilon.SetBaseline(1.f); check = interpolator.GetInterpolatedValue(90); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(100); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(110); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetLastValue(); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); // with the only sample replaced interpolator.AddSample(10.f, 200); epsilon.SetBaseline(10.f); check = interpolator.GetInterpolatedValue(190); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(200); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(210); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetLastValue(); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); // with the only sample replaced at the same time stamp interpolator.AddSample(20.f, 200); epsilon.SetBaseline(20.f); check = interpolator.GetInterpolatedValue(190); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(200); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(210); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetLastValue(); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); } // end testing linear interpolation ////////////////////////////////////////// ////////////////////////////////////////// // testing linear interpolation and extrapolation { // ensure interpolator returns correct value when it only has one sample { const int k_time = 0; const int k_sample = 1337; LinearInterpExtrap< int > interpolator; interpolator.AddSample(k_sample, k_time); AZ_TEST_ASSERT(interpolator.GetInterpolatedValue(k_time) == k_sample); AZ_TEST_ASSERT(interpolator.GetLastValue() == k_sample); AZ_TEST_ASSERT(interpolator.GetSampleCount() == 1); SampleInfo< int > info = interpolator.GetSampleInfo(0); AZ_TEST_ASSERT(info.m_t == k_time); AZ_TEST_ASSERT(info.m_v == k_sample); } // sample set partway full (pattern constant) { const int k_numSamples = 35; const int k_sampleArraySize = 80; const float k_constant = 15.f; LinearInterpExtrap< float, k_sampleArraySize > interpolator; AddSamplesConstant(interpolator, k_numSamples, k_constant); epsilon.SetBaseline(k_constant); // interpolate to before samples start / where there are samples to interpolate / past last sample // [-10,0) : before samples start // [0, 70] : where there are samples to interpolate // (70, 80]: past last sample for (int a = -k_offsetBetweenSamples; a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set partway full (pattern linear) { const int k_numSamples = 600; const int k_sampleArraySize = 800; const float k_slope = 3.f; const float k_intercept = -15.f; LinearInterpExtrap< float, k_sampleArraySize > interpolator; AddSamplesLinear(interpolator, k_numSamples, k_slope, k_intercept); epsilon.SetBaseline(k_intercept); // interpolate to before samples start for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate where there are samples to interpolate for (int a = 0; a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target = k_slope * static_cast< float >(a) * 1.f / static_cast< float >(k_offsetBetweenSamples) + k_intercept; epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate past last sample for (int a = k_offsetBetweenSamples * (k_numSamples) + 1; a < k_offsetBetweenSamples * (k_numSamples + 1); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target = k_slope * static_cast< float >(a) / static_cast< float >(k_offsetBetweenSamples) + k_intercept; epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } } // sample set partway full (pattern zig-zag) { const int k_numSamples = 750; const int k_sampleArraySize = 1000; LinearInterpExtrap< float, k_sampleArraySize > interpolator; interpolator.Clear(); AddSamplesZigZag(interpolator, k_numSamples); epsilon.SetBaseline(m_zigVals[0]); // interpolate to before samples start for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate to where there are samples to interpolate for (int a = 0; a <= k_offsetBetweenSamples * (k_numSamples - 1); ++a) { int idxLower = (a / k_offsetBetweenSamples) % AZ_ARRAY_SIZE(m_zigVals); int idxUpper = (idxLower + 1) % AZ_ARRAY_SIZE(m_zigVals); float target = m_zigVals[idxLower] + static_cast< float >(m_zigVals[idxUpper] - m_zigVals[idxLower]) * static_cast< float >(a - k_offsetBetweenSamples * (a / k_offsetBetweenSamples)) / static_cast< float >(k_offsetBetweenSamples); check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)target; (void)check; } // interpolate past last sample for (int a = k_offsetBetweenSamples * (k_numSamples - 1); a < k_offsetBetweenSamples * k_numSamples; ++a) { int idxUpper = (a / k_offsetBetweenSamples) % AZ_ARRAY_SIZE(m_zigVals); int idxLower = (idxUpper - 1) % AZ_ARRAY_SIZE(m_zigVals); float target = m_zigVals[idxLower] + static_cast< float >(m_zigVals[idxUpper] - m_zigVals[idxLower]) * static_cast< float >(k_offsetBetweenSamples + a - k_offsetBetweenSamples * (a / k_offsetBetweenSamples)) / static_cast< float >(k_offsetBetweenSamples); check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set full (pattern constant) { const int k_numSamples = 350; const int k_sampleArraySize = k_numSamples; const float k_constant = 15.f; LinearInterpExtrap< float, k_sampleArraySize > interpolator; interpolator.Clear(); AddSamplesConstant(interpolator, k_numSamples, k_constant); epsilon.SetBaseline(k_constant); // interpolate to before samples start / where there are samples to interpolate / past last sample // [-10,0) : before samples start // [0, 70] : where there are samples to interpolate // (70, 80]: past last sample for (int a = -k_offsetBetweenSamples; a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set full (pattern linear) { const int k_numSamples = 700; const int k_sampleArraySize = k_numSamples; const float k_slope = 3.f; const float k_intercept = -15.f; LinearInterpExtrap< float, k_sampleArraySize > interpolator; AddSamplesLinear(interpolator, k_numSamples, k_slope, k_intercept); epsilon.SetBaseline(k_intercept); // interpolate to before samples start for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate where there are samples to interpolate for (int a = 0; a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target = k_slope * static_cast< float >(a) / static_cast< float >(k_offsetBetweenSamples) + k_intercept; epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate past last sample for (int a = k_offsetBetweenSamples * (k_numSamples) + 1; a < k_offsetBetweenSamples * (k_numSamples + 1); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target = k_slope * static_cast< float >(a) / static_cast< float >(k_offsetBetweenSamples) + k_intercept; epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } } // sample set full (pattern zigzag) { const int k_numSamples = 950; const int k_sampleArraySize = k_numSamples; LinearInterpExtrap< float, k_sampleArraySize > interpolator; AddSamplesZigZag(interpolator, k_numSamples); epsilon.SetBaseline(m_zigVals[0]); // interpolate to before samples start for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate to where there are samples to interpolate for (int a = 0; a <= k_offsetBetweenSamples * (k_numSamples - 1); ++a) { int idxLower = (a / k_offsetBetweenSamples) % AZ_ARRAY_SIZE(m_zigVals); int idxUpper = (idxLower + 1) % AZ_ARRAY_SIZE(m_zigVals); float target = m_zigVals[idxLower] + static_cast< float >(m_zigVals[idxUpper] - m_zigVals[idxLower]) * static_cast< float >(a - k_offsetBetweenSamples * (a / k_offsetBetweenSamples)) / static_cast< float >(k_offsetBetweenSamples); check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)target; (void)check; } // interpolate past last sample int idxUpper = (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals); int idxLower = (idxUpper - 1) % AZ_ARRAY_SIZE(m_zigVals); for (int a = k_offsetBetweenSamples * (k_numSamples - 1); a < k_offsetBetweenSamples * k_numSamples; ++a) { float target = m_zigVals[idxLower] + static_cast< float >(m_zigVals[idxUpper] - m_zigVals[idxLower]) * static_cast< float >(k_offsetBetweenSamples + a - k_offsetBetweenSamples * (a / k_offsetBetweenSamples)) / static_cast< float >(k_offsetBetweenSamples); check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set wrapped around (pattern constant) { const int k_numSamples = 150; const int k_sampleArraySize = 80; const float k_constant = 15.f; LinearInterpExtrap< float, k_sampleArraySize > interpolator; AddSamplesConstant(interpolator, k_numSamples, k_constant); epsilon.SetBaseline(k_constant); // interpolate to before samples start / where there are samples to interpolate / past last sample // [-10,0) : before samples start // [0, 70] : where there are samples to interpolate // (70, 80]: past last sample for (int a = -k_offsetBetweenSamples; a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // sample set wrapped around (linear) { const int k_numSamples = 800; const int k_sampleArraySize = 600; const float k_slope = 3.f; const float k_intercept = -15.f; LinearInterpExtrap< float, k_sampleArraySize > interpolator; interpolator.Clear(); AddSamplesLinear(interpolator, k_numSamples, k_slope, k_intercept); epsilon.SetBaseline(k_slope * static_cast< float >(k_numSamples - k_sampleArraySize) + k_intercept); // interpolate to before samples start for (int a = k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize - 1); a < k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate where there are samples to interpolate for (int a = k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize); a <= k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target = k_slope * static_cast< float >(a) / static_cast< float >(k_offsetBetweenSamples) + k_intercept; epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate past last sample for (int a = k_offsetBetweenSamples * (k_numSamples) + 1; a < k_offsetBetweenSamples * (k_numSamples + 1); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target = k_slope * static_cast< float >(a) / static_cast< float >(k_offsetBetweenSamples) + k_intercept; epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } } // sample set wrapped around (pattern zigzag) { const int k_numSamples = 1500; const int k_sampleArraySize = 1000; LinearInterpExtrap< float, k_sampleArraySize > interpolator; AddSamplesZigZag(interpolator, k_numSamples); epsilon.SetBaseline(m_zigVals[ (k_numSamples - k_sampleArraySize) % AZ_ARRAY_SIZE(m_zigVals) ]); // interpolate to before samples start for (int a = k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize - 1); a < k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate to where there are samples to interpolate for (int a = k_offsetBetweenSamples * (k_numSamples - k_sampleArraySize); a < k_offsetBetweenSamples * (k_numSamples - 1); ++a) { int idxLower = (a / k_offsetBetweenSamples) % AZ_ARRAY_SIZE(m_zigVals); int idxUpper = (idxLower + 1) % AZ_ARRAY_SIZE(m_zigVals); float target = m_zigVals[idxLower] + static_cast< float >(m_zigVals[idxUpper] - m_zigVals[idxLower]) * static_cast< float >(a - k_offsetBetweenSamples * (a / k_offsetBetweenSamples)) / static_cast< float >(k_offsetBetweenSamples); check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)target; (void)check; } // interpolate past last sample int idxUpper = (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals); int idxLower = (idxUpper - 1) % AZ_ARRAY_SIZE(m_zigVals); for (int a = k_offsetBetweenSamples * (k_numSamples - 1); a < k_offsetBetweenSamples * k_numSamples; ++a) { float target = m_zigVals[idxLower] + static_cast< float >(m_zigVals[idxUpper] - m_zigVals[idxLower]) * static_cast< float >(k_offsetBetweenSamples + a - k_offsetBetweenSamples * (a / k_offsetBetweenSamples)) / static_cast< float >(k_offsetBetweenSamples); check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } epsilon.SetBaseline(m_zigVals[ (k_numSamples - 1) % AZ_ARRAY_SIZE(m_zigVals) ]); AZ_TEST_ASSERT(epsilon.WithinThreshold(interpolator.GetLastValue())); } // test Break { const int k_numSamples = 20; const int k_sampleArraySize = k_numSamples; const int k_break = 10; const float k_intercept = 0.f; const float k_slope = 1.f; LinearInterpExtrap< float, k_sampleArraySize > interpolator; for (int a = 0; a < k_numSamples; ++a) { if (a == k_break) { interpolator.Break(); } interpolator.AddSample(k_slope * static_cast< float >(a) + k_intercept, k_actualSampleStart + a * k_offsetBetweenSamples); } // interpolate to before samples start for (int a = -k_offsetBetweenSamples; a < 0; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); epsilon.SetBaseline(k_intercept); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate where there are samples to interpolate for (int a = 0; a < k_offsetBetweenSamples * k_numSamples; ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target; if (a / k_offsetBetweenSamples + 1 == k_break) { target = k_slope * static_cast< float >(a / k_offsetBetweenSamples) + k_intercept; } else { target = k_slope * static_cast< float >(a) / static_cast< float >(k_offsetBetweenSamples) + k_intercept; } epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } // interpolate past last sample for (int a = k_offsetBetweenSamples * (k_numSamples) + 1; a < k_offsetBetweenSamples * (k_numSamples + 1); ++a) { check = interpolator.GetInterpolatedValue(k_actualSampleStart + a); float target = k_slope * static_cast< float >(a) / static_cast< float >(k_offsetBetweenSamples) + k_intercept; epsilon.SetBaseline(target); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); (void)check; } } // sample set max size 1 { LinearInterpExtrap< float, 1 > interpolator; // with empty set (uncomment to make sure asserts fire) //interpolator.GetLastValue(); //interpolator.GetInterpolatedValue( 210 ); // with populated set interpolator.AddSample(1.f, 100); epsilon.SetBaseline(1.f); check = interpolator.GetInterpolatedValue(90); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(100); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(110); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetLastValue(); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); // with the only sample replaced interpolator.AddSample(10.f, 200); epsilon.SetBaseline(10.f); check = interpolator.GetInterpolatedValue(190); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(200); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(210); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetLastValue(); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); // with the only sample replaced at the same time stamp interpolator.AddSample(20.f, 200); epsilon.SetBaseline(20.f); check = interpolator.GetInterpolatedValue(190); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(200); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetInterpolatedValue(210); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); check = interpolator.GetLastValue(); AZ_TEST_ASSERT(epsilon.WithinThreshold(check)); } AZ_TracePrintf("GridMate", "this pointer: 0x%p", this); } // end testing linear interpolation and extrapolation ////////////////////////////////////////// } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class MPSession : public CarrierEventBus::Handler { public: ReplicaManager& GetReplicaMgr() { return m_rm; } void SetTransport(Carrier* transport) { m_pTransport = transport; CarrierEventBus::Handler::BusConnect(transport->GetGridMate()); } Carrier* GetTransport() { return m_pTransport; } void SetClient(bool isClient) { m_client = isClient; } void AcceptConn(bool accept) { m_acceptConn = accept; } ~MPSession() { CarrierEventBus::Handler::BusDisconnect(); } void Update() { char buf[1500]; for (ConnectionSet::iterator iConn = m_connections.begin(); iConn != m_connections.end(); ++iConn) { ConnectionID conn = *iConn; Carrier::ReceiveResult result = m_pTransport->Receive(buf, 1500, conn, GM_REPLICA_TEST_SESSION_CHANNEL); if (result.m_state == Carrier::ReceiveResult::RECEIVED) { if (strcmp(buf, "IM_A_CLIENT") == 0) { m_rm.AddPeer(conn, Mode_Client); } else if (strcmp(buf, "IM_A_PEER") == 0) { m_rm.AddPeer(conn, Mode_Peer); } } } } template typename T::Ptr GetChunkFromReplica(ReplicaId id) { ReplicaPtr replica = GetReplicaMgr().FindReplica(id); if (!replica) { return nullptr; } return replica->FindReplicaChunk(); } ////////////////////////////////////////////////////////////////////////// // CarrierEventBus void OnConnectionEstablished(Carrier* carrier, ConnectionID id) override { if (carrier != m_pTransport) { return; // not for us } m_connections.insert(id); if (m_client) { m_pTransport->Send("IM_A_CLIENT", 12, id, Carrier::SEND_RELIABLE, Carrier::PRIORITY_NORMAL, GM_REPLICA_TEST_SESSION_CHANNEL); } else { m_pTransport->Send("IM_A_PEER", 10, id, Carrier::SEND_RELIABLE, Carrier::PRIORITY_NORMAL, GM_REPLICA_TEST_SESSION_CHANNEL); } } void OnDisconnect(Carrier* carrier, ConnectionID id, CarrierDisconnectReason /*reason*/) override { if (carrier != m_pTransport) { return; // not for us } m_rm.RemovePeer(id); m_connections.erase(id); } void OnDriverError(Carrier* carrier, ConnectionID id, const DriverError& error) override { (void)error; if (carrier != m_pTransport) { return; // not for us } m_pTransport->Disconnect(id); } void OnSecurityError(Carrier* carrier, ConnectionID id, const SecurityError& error) override { (void)carrier; (void)id; (void)error; //Ignore security warnings in unit tests } ////////////////////////////////////////////////////////////////////////// ReplicaManager m_rm; Carrier* m_pTransport; typedef unordered_set ConnectionSet; ConnectionSet m_connections; bool m_client; bool m_acceptConn; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class MyObj { public: GM_CLASS_ALLOCATOR(MyObj); MyObj() : m_f1(0.f) , m_b1(false) , m_i1(0) {} float m_f1; bool m_b1; int m_i1; }; //----------------------------------------------------------------------------- class MyCtorContext : public CtorContextBase { public: CtorDataSet m_f; MyCtorContext() : m_f(Float16Marshaler(0.f, 1.f)) {} }; //----------------------------------------------------------------------------- class MigratableReplica : public ReplicaChunk { public: class Descriptor : public ReplicaChunkDescriptor { public: Descriptor() : ReplicaChunkDescriptor(MigratableReplica::GetChunkName(), sizeof(MigratableReplica)) { } ReplicaChunkBase* CreateFromStream(UnmarshalContext& mc) override { MyCtorContext cc; cc.Unmarshal(*mc.m_iBuf); // Important hooks. Pre/Post construct allows us to detect all datasets. if (mc.m_rm->GetUserContext(12345)) { AZ_TracePrintf("GridMate", "Create with UserData:%p\n", mc.m_rm->GetUserContext(12345)); } ReplicaChunk* chunk = aznew MigratableReplica; return chunk; } void DiscardCtorStream(UnmarshalContext& mc) override { MyCtorContext cc; cc.Unmarshal(*mc.m_iBuf); } void DeleteReplicaChunk(ReplicaChunkBase* chunkInstance) override { delete chunkInstance; } void MarshalCtorData(ReplicaChunkBase*, WriteBuffer& wb) override { MyCtorContext cc; cc.m_f.Set(0.5f); cc.Marshal(wb); } }; class MigratableReplicaDebugMsgs : public AZ::Debug::DrillerEBusTraits { public: typedef AZ::EBus EBus; virtual void OnNewOwner(ReplicaId repId, ReplicaManager* repMgr) = 0; }; typedef AZStd::intrusive_ptr Ptr; GM_CLASS_ALLOCATOR(MigratableReplica); static const char* GetChunkName() {return "MigratableReplica"; } MigratableReplica(MyObj* pObj = nullptr) : MyHandler123Rpc("MyHandler123Rpc") , m_data1("Data1") , m_data2("Data2") , m_data3("Data3", 3.0f, Float16Marshaler(0.0f, 10.0f)) , m_data4("Data4") { Bind(pObj); } bool IsReplicaMigratable() override { return true; } bool MyHandler123(const float& f, const RpcContext& rc) { (void)f; (void)rc; AZ_TracePrintf("GridMate", "Executed MyHandler123 requested at %u with %g on %s at %u.\n", rc.m_timestamp, f, GetReplica()->IsMaster() ? "Master" : "Proxy", rc.m_realTime); return true; } Rpc >::BindInterface MyHandler123Rpc; void UpdateChunk(const ReplicaContext& rc) override { if (m_pLocalObj) { m_data1.Set(m_pLocalObj->m_f1); m_data1Interpolated.AddSample(m_pLocalObj->m_f1, rc.m_localTime); m_data2.Set(m_pLocalObj->m_i1); m_data3.Set(m_pLocalObj->m_f1); } AZStd::bitset<25> bits = m_data4.Get(); m_data4.Set(bits.flip()); } void UpdateFromChunk(const ReplicaContext& rc) override { // AZ_TracePrintf("GridMate", "Updating proxy 0x%x on peer %d coming from peer %d %s\n", GetRepId(), rc.rm->GetLocalPeerId(), rc.myPeer->GetId(), rc.myPeer->GetConnectionId() == InvalidConnectionID ? "(orphan)" : ""); if (m_pLocalObj) { m_data1Interpolated.AddSample(m_data1.Get(), m_data1.GetLastUpdateTime()); m_pLocalObj->m_f1 = m_data1Interpolated.GetInterpolatedValue(rc.m_localTime); m_pLocalObj->m_i1 = m_data2.Get(); } m_dummy = m_data3.Get(); } void OnReplicaActivate(const ReplicaContext& rc) override { (void)rc; if (rc.m_rm->GetUserContext(12345)) { AZ_TracePrintf("GridMate", "Activate %s with UserData:%p\n", GetReplica()->IsMaster() ? "master" : "proxy", rc.m_rm->GetUserContext(12345)); } if (IsProxy()) { Bind(aznew MyObj()); } if (IsMaster()) { EBUS_EVENT(MigratableReplicaDebugMsgs::EBus, OnNewOwner, GetReplicaId(), rc.m_rm); } } void OnReplicaDeactivate(const ReplicaContext& rc) override { (void)rc; if (m_pLocalObj) { delete m_pLocalObj; m_pLocalObj = NULL; } } void OnReplicaChangeOwnership(const ReplicaContext& rc) override { (void)rc; AZ_TracePrintf("GridMate", "Migratable replica 0x%x became %s on Peer %d\n", (int) GetReplicaId(), IsMaster() ? "master" : "proxy", (int) rc.m_rm->GetLocalPeerId()); if (IsMaster()) { EBUS_EVENT(MigratableReplicaDebugMsgs::EBus, OnNewOwner, GetReplicaId(), rc.m_rm); } } void Bind(MyObj* pObj) { m_pLocalObj = pObj; } private: DataSet m_data1; LinearInterpExtrap m_data1Interpolated; DataSet m_data2; DataSet m_data3; DataSet > m_data4; MyObj* m_pLocalObj; float m_dummy; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class NonMigratableReplica : public ReplicaChunk { public: enum EBla : AZ::u8 { e_Bla0, e_Bla1, }; typedef vector IntVectorType; bool m_unreliableCheck; protected: MyObj* m_pLocalObj; int m_prevUnreliableValue; bool MyHandler123(const float& f, const RpcContext& rc) { (void)f; (void)rc; AZ_TracePrintf("GridMate", "Executed MyHandler123 requested at %u with %g on %s at %u.\n", rc.m_timestamp, f, IsMaster() ? "Master" : "Proxy", rc.m_realTime); return true; } bool MyHandler2(const float& f, int p2, const RpcContext& rc) { (void)f; (void)p2; (void)rc; AZ_TracePrintf("GridMate", "Executed MyHandler2 requested at %u with %g,%d on %s at %u.\n", rc.m_timestamp, f, p2, IsMaster() ? "Master" : "Proxy", rc.m_realTime); return true; } bool MyHandler3(const float& f, int p2, EBla p3, const RpcContext& rc) { (void)f; (void)p2; (void)p3; (void)rc; AZ_TracePrintf("GridMate", "Executed MyHandler3 requested at %u with %g,%d,%d on %s at %u.\n", rc.m_timestamp, f, p2, p3, IsMaster() ? "Master" : "Proxy", rc.m_realTime); return true; } bool MyHandler4(const float& f, int p2, EBla p3, const IntVectorType& p4, const RpcContext& rc) { (void)f; (void)p2; (void)p3; (void)p4; (void)rc; AZ_TracePrintf("GridMate", "Executed MyHandler4 requested at %u with %g,%d,%d,%d,%d on %s at %u.\n", rc.m_timestamp, f, p2, p3, p4[0], p4[1], IsMaster() ? "Master" : "Proxy", rc.m_realTime); return true; } bool MyHandlerUnreliable(const int& i, const RpcContext& rc) { (void)rc; AZ_TracePrintf("GridMate", "Executed MyHandlerUnreliable requested at %u with %d on %s at %u.\n", rc.m_timestamp, i, IsMaster() ? "Master" : "Proxy", rc.m_realTime); AZ_TEST_ASSERT(i > m_prevUnreliableValue); if ((i - m_prevUnreliableValue) > 1) { m_unreliableCheck = true; } m_prevUnreliableValue = i; return true; } public: GM_CLASS_ALLOCATOR(NonMigratableReplica); typedef AZStd::intrusive_ptr Ptr; static const char* GetChunkName() { return "NonMigratableReplica"; } Rpc >::BindInterface MyHandler123Rpc; Rpc, RpcArg >::BindInterface MyHandler2Rpc; Rpc, RpcArg, RpcArg >::BindInterface MyHandler3Rpc; Rpc, RpcArg, RpcArg, RpcArg >::BindInterface MyHandler4Rpc; Rpc >::BindInterface MyHandlerUnreliableRpc; NonMigratableReplica(MyObj* pObj = NULL) : m_unreliableCheck(false) , m_prevUnreliableValue(0) , MyHandler123Rpc("MyHandler123Rpc") , MyHandler2Rpc("MyHandler2Rpc") , MyHandler3Rpc("MyHandler3Rpc") , MyHandler4Rpc("MyHandler4Rpc") , MyHandlerUnreliableRpc("MyHandlerUnreliableRpc") , m_data1("Data1") , m_data2("Data2") { Bind(pObj); } bool IsReplicaMigratable() override { return false; } ~NonMigratableReplica() { AZ_Assert(!m_pLocalObj, "Local object should be cleared"); } void UpdateChunk(const ReplicaContext& rc) override { m_data1.Set(m_pLocalObj->m_f1); m_data1Interpolated.AddSample(m_pLocalObj->m_f1, rc.m_localTime); m_data2.Set(m_pLocalObj->m_i1); } void UpdateFromChunk(const ReplicaContext& rc) override { m_data1Interpolated.AddSample(m_data1.Get(), m_data1.GetLastUpdateTime()); m_pLocalObj->m_f1 = m_data1Interpolated.GetInterpolatedValue(rc.m_localTime); m_pLocalObj->m_i1 = m_data2.Get(); } void OnReplicaActivate(const ReplicaContext& rc) override { (void)rc; if (rc.m_rm->GetUserContext(12345)) { AZ_TracePrintf("GridMate", "Activate %s with UserData:%p\n", IsMaster() ? "master" : "proxy", rc.m_rm->GetUserContext(12345)); } if (IsProxy()) { Bind(aznew MyObj()); } } void OnReplicaDeactivate(const ReplicaContext& rc) override { (void)rc; if (m_pLocalObj) { delete m_pLocalObj; m_pLocalObj = NULL; } } void OnReplicaChangeOwnership(const ReplicaContext& rc) override { (void)rc; AZ_TracePrintf("GridMate", "NonMigratable replica 0x%x became %s on Peer %d\n", (int) GetReplicaId(), IsMaster() ? "master" : "proxy", (int) rc.m_rm->GetLocalPeerId()); } void Bind(MyObj* pObj) { m_pLocalObj = pObj; } protected: DataSet m_data1; LinearInterpExtrap m_data1Interpolated; DataSet m_data2; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class MyDerivedReplica : public NonMigratableReplica { public: GM_CLASS_ALLOCATOR(MyDerivedReplica); MyDerivedReplica() : m_data3("Data3") { } typedef AZStd::intrusive_ptr Ptr; static const char* GetChunkName() { return "MyDerivedReplica"; } virtual void UpdateChunk(const ReplicaContext& rc) override { NonMigratableReplica::UpdateChunk(rc); m_data3.Set(m_pLocalObj->m_b1); } virtual void UpdateFromChunk(const ReplicaContext& rc) override { NonMigratableReplica::UpdateFromChunk(rc); m_pLocalObj->m_b1 = m_data3.Get(); } protected: DataSet m_data3; }; //----------------------------------------------------------------------------- class Integ_ReplicaGMTest : public UnitTest::GridMateMPTestFixture , public ::testing::Test {}; TEST_F(Integ_ReplicaGMTest, ReplicaTest) { ReplicaChunkDescriptorTable::Get().RegisterChunkType(); ReplicaChunkDescriptorTable::Get().RegisterChunkType(); ReplicaChunkDescriptorTable::Get().RegisterChunkType(); AZ_TracePrintf("GridMate", "\n"); enum { s1, s2, s3, nSessions }; const int k_delay = 100; // Setting up simulator with outgoing packet loss DefaultSimulator clientSimulator; clientSimulator.SetOutgoingPacketLoss(1, 1); MPSession sessions[nSessions]; MyObj* s1obj1 = NULL, * s1obj2 = NULL, * s2obj1 = NULL, * s3obj1 = NULL; MigratableReplica::Ptr s1rep1, s3rep1; NonMigratableReplica::Ptr s1rep2; MyDerivedReplica::Ptr s2rep1; ReplicaId s1rep1id = 0, s1rep2id = 0, s2rep1id = 0, s3rep1id = 0; // initialize transport int basePort = 4427; for (int i = 0; i < nSessions; ++i) { TestCarrierDesc desc; desc.m_port = basePort + i; desc.m_enableDisconnectDetection = false; if (i == s2) { desc.m_simulator = &clientSimulator; } // initialize replica managers // s2(p)<-->(p)s1(p)<-->(c)s3 sessions[i].SetTransport(DefaultCarrier::Create(desc, m_gridMate)); sessions[i].AcceptConn(true); sessions[i].SetClient(i == s3); sessions[i].GetReplicaMgr().Init(ReplicaMgrDesc(i + 1, sessions[i].GetTransport(), 0, i == 0 ? ReplicaMgrDesc::Role_SyncHost : 0)); sessions[i].GetReplicaMgr().RegisterUserContext(12345, reinterpret_cast(static_cast(i + 1))); } sessions[0].GetReplicaMgr().SetLocalLagAmt(50); // put something on s1 to get it going auto rep = Replica::CreateReplica(nullptr); s1rep1 = CreateAndAttachReplicaChunk(rep); s1rep1id = sessions[s1].GetReplicaMgr().AddMaster(rep); s1rep1->Bind(s1obj1 = aznew MyObj()); // connect s2 to s1 sessions[s2].GetTransport()->Connect("127.0.0.1", basePort); // main test loop static bool keepRunning = true; int tick = 0; while (keepRunning) { // perform some random actions on a timeline switch (tick) { case 5: { // connect s3 to s1 sessions[s3].GetTransport()->Connect("127.0.0.1", basePort); break; } case 25: // remove s1rep1 AZ_TEST_ASSERT(s1rep1id); s1rep1->GetReplica()->Destroy(); s1obj1 = NULL; break; case 35: //AZ_TracePrintf("GridMate", "No more updates.\n"); break; case 70: //AZ_TracePrintf("GridMate", "Restart updates.\n"); break; case 90: keepRunning = false; } // add an object on s2 if (sessions[s2].GetReplicaMgr().IsReady()) { if (!s2rep1id) { auto newReplica = Replica::CreateReplica(nullptr); s2rep1 = CreateAndAttachReplicaChunk(newReplica); s2rep1id = sessions[s2].GetReplicaMgr().AddMaster(newReplica); s2rep1->Bind(s2obj1 = aznew MyObj()); } else { static bool sends2rep1rpc = true; if (sends2rep1rpc && tick >= 20) { s2rep1->MyHandler123Rpc(5.f); s2rep1->MyHandler2Rpc(6.0f, 1); s2rep1->MyHandler3Rpc(7.0f, 2, NonMigratableReplica::e_Bla0); NonMigratableReplica::IntVectorType v; v.push_back(10); v.push_back(13); s2rep1->MyHandler4Rpc(8.0f, 3, NonMigratableReplica::e_Bla1, v); sends2rep1rpc = false; } } } // add object on s1 if (sessions[s1].GetReplicaMgr().IsReady()) { if (!s1rep2) { auto newReplica = Replica::CreateReplica(nullptr); s1rep2 = CreateAndAttachReplicaChunk(newReplica); s1rep2id = sessions[s1].GetReplicaMgr().AddMaster(newReplica); s1rep2->Bind(s1obj2 = aznew MyObj); } else { if (s1rep2id && tick >= 40) { s1rep2->GetReplica()->Destroy(); s1obj2 = NULL; s1rep2id = 0; } } } // add object on s3 if (sessions[s3].GetReplicaMgr().IsReady()) { if (!s3rep1) { auto newReplica = Replica::CreateReplica(nullptr); s3rep1 = CreateAndAttachReplicaChunk(newReplica); s3rep1id = sessions[s3].GetReplicaMgr().AddMaster(newReplica); s3rep1->Bind(s3obj1 = aznew MyObj()); } else { if (s3rep1id && tick >= 45) { s3rep1->MyHandler123Rpc(-1.f); s3rep1->GetReplica()->Destroy(); s3obj1 = NULL; s3rep1id = 0; } } } { // Testing unreliable rpcs: enabling network simulator with outgoing packetloss, // calling 10 rpcs with 1..10 int argument, checking if replicas got rpcs in an order, and have missing calls static bool requestrpc = true; if (s3rep1id && requestrpc) { if (ReplicaPtr pObj = sessions[s2].GetReplicaMgr().FindReplica(s3rep1id)) { pObj->FindReplicaChunk()->MyHandler123Rpc(2.0f); requestrpc = false; } } static bool unreliableRequest = true; static int numUnreliableRequests = 0; if (sessions[s2].GetReplicaMgr().IsReady() && s2rep1 && tick > 15 && unreliableRequest) { // Starting packet loss unreliableRequest = false; } if (!unreliableRequest && numUnreliableRequests < 10) { if (numUnreliableRequests == 4) { clientSimulator.Enable(); } else if (numUnreliableRequests == 5) { clientSimulator.Disable(); } s2rep1->MyHandlerUnreliableRpc(++numUnreliableRequests); } static bool checkUnreliableDelivery = true; if (checkUnreliableDelivery && tick >= 25) { // Stopping packet loss ReplicaPtr rep1 = sessions[s1].GetReplicaMgr().FindReplica(s2rep1->GetReplicaId()); ReplicaPtr rep3 = sessions[s3].GetReplicaMgr().FindReplica(s2rep1->GetReplicaId()); AZ_TEST_ASSERT(rep1); AZ_TEST_ASSERT(rep3); AZ_TEST_ASSERT(rep1->FindReplicaChunk()->m_unreliableCheck); AZ_TEST_ASSERT(rep3->FindReplicaChunk()->m_unreliableCheck); checkUnreliableDelivery = false; } } // modify local objects if (tick < 20 || tick > 70) { if (s1obj1) { s1obj1->m_f1 += 0.5f; s1obj1->m_i1 += 1; s1obj1->m_b1 = !s1obj1->m_b1; } if (s1obj2) { s1obj2->m_f1 += 1.0f; s1obj2->m_i1 -= 1; s1obj2->m_b1 = !s1obj2->m_b1; } if (s2obj1) { s2obj1->m_f1 += 0.1f; s2obj1->m_i1 += 2; s2obj1->m_b1 = !s2obj1->m_b1; } if (s3obj1) { s3obj1->m_f1 += 0.3f; s3obj1->m_i1 += 3; s3obj1->m_b1 = !s3obj1->m_b1; } } ++tick; // tick everything for (int i = 0; i < nSessions; ++i) { sessions[i].Update(); sessions[i].GetReplicaMgr().Unmarshal(); } for (int i = 0; i < nSessions; ++i) { sessions[i].GetReplicaMgr().UpdateReplicas(); } for (int i = 0; i < nSessions; ++i) { sessions[i].GetReplicaMgr().UpdateFromReplicas(); sessions[i].GetReplicaMgr().Marshal(); } for (int i = 0; i < nSessions; ++i) { sessions[i].GetTransport()->Update(); } AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(k_delay)); } for (int i = 0; i < nSessions; ++i) { sessions[i].GetReplicaMgr().Shutdown(); DefaultCarrier::Destroy(sessions[i].GetTransport()); } } class Integ_ForcedReplicaMigrationTest : public UnitTest::GridMateMPTestFixture , public ReplicaMgrCallbackBus::Handler , public MigratableReplica::MigratableReplicaDebugMsgs::EBus::Handler , public ::testing::Test { void OnNewHost(bool isHost, ReplicaManager* pMgr) override { if (isHost) { AZ_TracePrintf("GridMate", "Peer %d has completed host migration and is now the host.\n", (int)pMgr->GetLocalPeerId()); pMgr->SetSendTimeInterval(k_hostSendRateMs); m_newHostEventOnNewHostCount++; } else { AZ_TracePrintf("GridMate", "Peer %d has has received notification that host migration is complete.\n", (int)pMgr->GetLocalPeerId()); pMgr->SetSendTimeInterval(0); m_newHostEventOnPeersCount++; } } void OnNewOwner(ReplicaId repId, ReplicaManager* repMgr) override { AZ_TracePrintf("GridMate", "Replica 0x%08x got new owner %d on frame %d.\n", repId, (int)repMgr->GetLocalPeerId(), m_frameCount); m_replicaOwnership[repId] = repMgr; } public: Integ_ForcedReplicaMigrationTest() { ReplicaMgrCallbackBus::Handler::BusConnect(m_gridMate); } ~Integ_ForcedReplicaMigrationTest() { ReplicaMgrCallbackBus::Handler::BusDisconnect(); } enum { p1, p2, p3, p4, p5, nPeers }; static const int k_frameTimePerNodeMs = 10; static const int k_numFramesToRun = 300; static const int k_hostSendRateMs = k_frameTimePerNodeMs * nPeers * 2; // limiting host send rate x2 times int m_frameCount; int m_newHostEventOnNewHostCount; int m_newHostEventOnPeersCount; AZStd::unordered_map m_replicaOwnership; }; const int Integ_ForcedReplicaMigrationTest::k_frameTimePerNodeMs; const int Integ_ForcedReplicaMigrationTest::k_numFramesToRun; const int Integ_ForcedReplicaMigrationTest::k_hostSendRateMs; TEST_F(Integ_ForcedReplicaMigrationTest, ForcedReplicaMigrationTest) { ReplicaChunkDescriptorTable::Get().RegisterChunkType(); ReplicaChunkDescriptorTable::Get().RegisterChunkType(); MPSession peers[nPeers]; MigratableReplica::Ptr migrRep[nPeers]; NonMigratableReplica::Ptr nonMigrRep[nPeers]; m_newHostEventOnNewHostCount = 0; m_newHostEventOnPeersCount = 0; MigratableReplica::MigratableReplicaDebugMsgs::EBus::Handler::BusConnect(); // initialize full-mesh P2P session int basePort = 4427; for (int i = 0; i < nPeers; ++i) { TestCarrierDesc desc; desc.m_port = basePort + i; desc.m_enableDisconnectDetection = /*false*/ true; desc.m_threadUpdateTimeMS = k_frameTimePerNodeMs / 2; // initialize replica managers peers[i].SetTransport(DefaultCarrier::Create(desc, m_gridMate)); peers[i].AcceptConn(true); peers[i].SetClient(false); peers[i].GetReplicaMgr().Init(ReplicaMgrDesc(i + 1 , peers[i].GetTransport() , 0 , i == 0 ? ReplicaMgrDesc::Role_SyncHost : 0 , i == 0 ? k_hostSendRateMs : 0)); } AZ_TracePrintf("GridMate", "\n"); m_frameCount = 0; while (m_frameCount < k_numFramesToRun) { static bool allReady = false; // establish all connections if (m_frameCount < nPeers) { for (int i = 0; i < m_frameCount; ++i) { peers[m_frameCount].GetTransport()->Connect("127.0.0.1", basePort + i); } } if (!allReady) { allReady = true; for (int i = 0; i < nPeers; ++i) { if (!peers[i].GetReplicaMgr().IsReady()) { allReady = false; } } if (allReady) { AZ_TracePrintf("GridMate", "All peers ready at frame %d\n", m_frameCount); } } // perform tests if (allReady) { // add replicas static bool addReplicas = true; if (addReplicas) { for (int i = 0; i < nPeers; ++i) { { auto rep = Replica::CreateReplica(nullptr); migrRep[i] = CreateAndAttachReplicaChunk(rep, aznew MyObj()); peers[i].GetReplicaMgr().AddMaster(rep); AZ_TEST_ASSERT(m_replicaOwnership[migrRep[i]->GetReplicaId()] == &peers[i].GetReplicaMgr()); } { auto rep = Replica::CreateReplica(nullptr); nonMigrRep[i] = CreateAndAttachReplicaChunk(rep, aznew MyObj()); peers[i].GetReplicaMgr().AddMaster(rep); } } addReplicas = false; AZ_TracePrintf("GridMate", "Replicas added at frame %d\n", m_frameCount); } // disconnect p3 and trigger peer migration static bool dropP3 = true; if (m_frameCount > 50 && dropP3) { peers[p3].GetTransport()->Disconnect(AllConnections); dropP3 = false; AZ_TracePrintf("GridMate", "Dropped P3 at frame %d\n", m_frameCount); } // Check that p3's MigratableReplica has migrated to p1 (host) if (m_frameCount == 85) { AZ_TEST_ASSERT(m_replicaOwnership[migrRep[p3]->GetReplicaId()] == &peers[p1].GetReplicaMgr()); } // disconnect p1 and trigger host loss static bool dropP1 = true; if (m_frameCount > 100 && dropP1) { peers[p1].GetTransport()->Disconnect(AllConnections); dropP1 = false; AZ_TracePrintf("GridMate", "Dropped P1 at frame %d\n", m_frameCount); } // promote p2 to host static bool promoteP2 = true; if (m_frameCount > 150 && promoteP2) { peers[p2].GetReplicaMgr().Promote(); promoteP2 = false; AZ_TracePrintf("GridMate", "Promoted P2 at frame %d\n", m_frameCount); } } // tick int tickPeer = m_frameCount++ % nPeers; peers[tickPeer].Update(); peers[tickPeer].GetReplicaMgr().Unmarshal(); peers[tickPeer].GetReplicaMgr().UpdateReplicas(); peers[tickPeer].GetReplicaMgr().UpdateFromReplicas(); peers[tickPeer].GetReplicaMgr().Marshal(); peers[tickPeer].GetTransport()->Update(); AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(k_frameTimePerNodeMs)); } // Check that p1's MigratableReplicas (including the one from p3) have migrated to p2 (host) AZ_TEST_ASSERT(m_replicaOwnership[migrRep[p1]->GetReplicaId()] == &peers[p2].GetReplicaMgr()); AZ_TEST_ASSERT(m_replicaOwnership[migrRep[p3]->GetReplicaId()] == &peers[p2].GetReplicaMgr()); AZ_TEST_ASSERT(m_newHostEventOnNewHostCount == 1); // New host should have received OnNewHost event AZ_TEST_ASSERT(m_newHostEventOnPeersCount == 2); // 2 peers remaining should have received OnNewHost event // clean up for (int i = 0; i < nPeers; ++i) { peers[i].GetReplicaMgr().Shutdown(); DefaultCarrier::Destroy(peers[i].GetTransport()); } MigratableReplica::MigratableReplicaDebugMsgs::EBus::Handler::BusDisconnect(); } class Integ_ReplicaMigrationRequestTest : public UnitTest::GridMateMPTestFixture , public ::testing::Test { public: enum { Host, Peer1, Peer2, Client1, Client2, TotalNodes }; class AlwaysMigratable : public ReplicaChunk { public: GM_CLASS_ALLOCATOR(AlwaysMigratable); typedef AZStd::intrusive_ptr Ptr; static const char* GetChunkName() { return "AlwaysMigratable"; } AlwaysMigratable() : UpdateControlValue("UpdateControlValue") , m_requests(0) , m_accepted(0) , m_triggerNextTransfer(false) , m_owner("Owner") , m_control("Control") { } bool IsReplicaMigratable() override { return true; } bool AcceptChangeOwnership(PeerId requestor, const ReplicaContext& rc) override { AZ_TracePrintf("GridMate", "Node %d accepted transfer of AlwaysMigratable 0x%x to node %d.\n", rc.m_rm->GetLocalPeerId() - 1, GetReplicaId(), requestor - 1); m_requests++; m_accepted++; if (rc.m_rm->GetLocalPeerId() - 1 == Peer2 && requestor - 1 == Host) { m_triggerNextTransfer = true; } return true; } void OnReplicaActivate(const ReplicaContext& rc) override { if (IsMaster()) { m_owner.Set(rc.m_rm->GetLocalPeerId() - 1); m_control.Set(rc.m_rm->GetLocalPeerId() - 1); } } void OnReplicaChangeOwnership(const ReplicaContext& rc) override { if (IsMaster()) { AZ_TracePrintf("GridMate", "OnChangeOwnership: 0x%04x Became master on node %d\n", GetReplicaId(), rc.m_rm->GetLocalPeerId() - 1); m_owner.Set(rc.m_rm->GetLocalPeerId() - 1); } else { AZ_TracePrintf("GridMate", "OnChangeOwnership: 0x%04x Became proxy on node %d\n", GetReplicaId(), rc.m_rm->GetLocalPeerId() - 1); if (m_triggerNextTransfer) { GetReplica()->RequestChangeOwnership(Client2 + 1); m_triggerNextTransfer = false; } } } bool UpdateControlValueFn(const RpcContext& rc) { (void)rc; m_control.Set(GetReplicaManager()->GetLocalPeerId() - 1); return false; } Rpc<>::BindInterface UpdateControlValue; int m_requests; int m_accepted; bool m_triggerNextTransfer; DataSet m_owner; DataSet m_control; }; class NeverMigratable : public AlwaysMigratable { public: GM_CLASS_ALLOCATOR(NeverMigratable); typedef AZStd::intrusive_ptr Ptr; static const char* GetChunkName() { return "NeverMigratable"; } NeverMigratable() { } bool IsReplicaMigratable() override { return false; } }; class SometimesMigratable : public AlwaysMigratable { public: GM_CLASS_ALLOCATOR(SometimesMigratable); typedef AZStd::intrusive_ptr Ptr; static const char* GetChunkName() { return "SometimesMigratable"; } SometimesMigratable() { m_acceptMigrationRequests = false; } virtual bool AcceptChangeOwnership(PeerId requestor, const ReplicaContext& rc) override { (void)requestor; (void)rc; m_requests++; if (m_acceptMigrationRequests) { AZ_TracePrintf("GridMate", "Node %d accepted transfer of SometimesMigratable 0x%x to node %d.\n", rc.m_rm->GetLocalPeerId() - 1, GetReplicaId(), requestor - 1); m_accepted++; return true; } return false; } bool m_acceptMigrationRequests; }; struct Node { MPSession m_session; AlwaysMigratable::Ptr m_always; NeverMigratable::Ptr m_never; SometimesMigratable::Ptr m_sometimes; }; static const int k_frameTimePerNodeMs = 10; static const int k_hostSendTimeMs = k_frameTimePerNodeMs * TotalNodes * 4; // limiting host send rate to be x4 times slower than tick }; TEST_F(Integ_ReplicaMigrationRequestTest, ReplicaMigrationRequestTest) { /* Topology: P1---P2 \ / \ / H / \ / \ C1 C2 Migration pattern: AlwaysMigratable: P1 -> P2 P2 -> Host -> C2 (both at same time, with C2 arriving second) Host -> C1 C1 -> C2 -> Host C2 -> P1 NeverMigratable: P1 -> C1 (Forbidden) C2 -> P2 (Forbidden) SometimesMigratable: P1 -> Host C1 -> P1 P2 -> C2 (Forbidden) C2 -> Host (Forbidden) */ ReplicaChunkDescriptorTable::Get().RegisterChunkType(); ReplicaChunkDescriptorTable::Get().RegisterChunkType(); ReplicaChunkDescriptorTable::Get().RegisterChunkType(); Node nodes[TotalNodes]; int basePort = 4427; for (int i = 0; i < TotalNodes; ++i) { TestCarrierDesc desc; desc.m_port = basePort + i; desc.m_connectionTimeoutMS = 15000; // initialize replica managers nodes[i].m_session.SetTransport(DefaultCarrier::Create(desc, m_gridMate)); nodes[i].m_session.AcceptConn(true); nodes[i].m_session.SetClient(i == Client1 || i == Client2); nodes[i].m_session.GetReplicaMgr().Init(ReplicaMgrDesc(i + 1 , nodes[i].m_session.GetTransport() , 0 , i == Host ? ReplicaMgrDesc::Role_SyncHost : 0 , i == Host ? k_hostSendTimeMs : 0)); } // Connect all the nodes nodes[Peer1].m_session.GetTransport()->Connect("127.0.0.1", basePort + Host); nodes[Peer2].m_session.GetTransport()->Connect("127.0.0.1", basePort + Host); nodes[Client1].m_session.GetTransport()->Connect("127.0.0.1", basePort + Host); nodes[Client2].m_session.GetTransport()->Connect("127.0.0.1", basePort + Host); nodes[Peer1].m_session.GetTransport()->Connect("127.0.0.1", basePort + Peer2); int framesToRun = 800; for (int iTick = 0; iTick < framesToRun; ++iTick) { for (int iNode = 0; iNode < TotalNodes; ++iNode) { if (nodes[iNode].m_session.GetReplicaMgr().IsReady()) { if (nodes[iNode].m_always == nullptr) { { auto rep = Replica::CreateReplica(nullptr); nodes[iNode].m_always = CreateAndAttachReplicaChunk(rep); nodes[iNode].m_session.GetReplicaMgr().AddMaster(rep); } { auto rep = Replica::CreateReplica(nullptr); nodes[iNode].m_never = CreateAndAttachReplicaChunk(rep); nodes[iNode].m_session.GetReplicaMgr().AddMaster(rep); } { auto rep = Replica::CreateReplica(nullptr); nodes[iNode].m_sometimes = CreateAndAttachReplicaChunk(rep); nodes[iNode].m_sometimes->m_acceptMigrationRequests = iNode == Peer1 || iNode == Client1; nodes[iNode].m_session.GetReplicaMgr().AddMaster(rep); } } } } // First round of migrations if (iTick == 200) { // P1 -> P2 ReplicaPtr aP1onP2 = nodes[Peer2].m_session.GetReplicaMgr().FindReplica(nodes[Peer1].m_always->GetReplicaId()); AZ_TEST_ASSERT(aP1onP2); aP1onP2->RequestChangeOwnership(); // P2 -> Host -> C2 (both at same time, with C2 arriving second) ReplicaPtr aP2onH = nodes[Host].m_session.GetReplicaMgr().FindReplica(nodes[Peer2].m_always->GetReplicaId()); AZ_TEST_ASSERT(aP2onH); aP2onH->RequestChangeOwnership(); // Host -> C1 ReplicaPtr aHonC1 = nodes[Client1].m_session.GetReplicaMgr().FindReplica(nodes[Host].m_always->GetReplicaId()); AZ_TEST_ASSERT(aHonC1); aHonC1->RequestChangeOwnership(); // C1 -> C2 -> Host (first migration) ReplicaPtr aC1onC2 = nodes[Client2].m_session.GetReplicaMgr().FindReplica(nodes[Client1].m_always->GetReplicaId()); AZ_TEST_ASSERT(aC1onC2); aC1onC2->RequestChangeOwnership(); // C2 -> P1 ReplicaPtr aC2onP1 = nodes[Peer1].m_session.GetReplicaMgr().FindReplica(nodes[Client2].m_always->GetReplicaId()); AZ_TEST_ASSERT(aC2onP1); aC2onP1->RequestChangeOwnership(); // P1 -> C1 (Forbidden) ReplicaPtr nP1onC1 = nodes[Client1].m_session.GetReplicaMgr().FindReplica(nodes[Peer1].m_never->GetReplicaId()); AZ_TEST_ASSERT(nP1onC1); nP1onC1->RequestChangeOwnership(); // C2 -> P2 (Forbidden) ReplicaPtr nC2onP2 = nodes[Peer2].m_session.GetReplicaMgr().FindReplica(nodes[Client2].m_never->GetReplicaId()); AZ_TEST_ASSERT(nC2onP2); nC2onP2->RequestChangeOwnership(); // P1 -> Host ReplicaPtr sP1onH = nodes[Host].m_session.GetReplicaMgr().FindReplica(nodes[Peer1].m_sometimes->GetReplicaId()); AZ_TEST_ASSERT(sP1onH); sP1onH->RequestChangeOwnership(); // C1 -> P1 ReplicaPtr sC1onP1 = nodes[Peer1].m_session.GetReplicaMgr().FindReplica(nodes[Client1].m_sometimes->GetReplicaId()); AZ_TEST_ASSERT(sC1onP1); sC1onP1->RequestChangeOwnership(); // P2 -> C2 (Forbidden) ReplicaPtr sP2onC2 = nodes[Client2].m_session.GetReplicaMgr().FindReplica(nodes[Peer2].m_sometimes->GetReplicaId()); AZ_TEST_ASSERT(sP2onC2); sP2onC2->RequestChangeOwnership(); // C2 -> Host (Forbidden) ReplicaPtr sC2onH = nodes[Host].m_session.GetReplicaMgr().FindReplica(nodes[Client2].m_sometimes->GetReplicaId()); AZ_TEST_ASSERT(sC2onH); sC2onH->RequestChangeOwnership(); } // Second round of migrations if (iTick == 400) { // C1 -> C2 -> Host (1st migration) AZ_TEST_ASSERT(nodes[Client1].m_always->m_requests == 1); AZ_TEST_ASSERT(nodes[Client1].m_always->m_accepted == 1); AZ_TEST_ASSERT(nodes[Client1].m_always->GetReplica()->IsProxy()); AZ_TEST_ASSERT(nodes[Client1].m_always->m_owner.Get() == Client2); AZ_TEST_ASSERT(nodes[Client2].m_session.GetReplicaMgr().FindReplica(nodes[Client1].m_always->GetReplicaId())->IsMaster()); // C1 -> C2 -> Host (2nd migration) ReplicaPtr aHonC1 = nodes[Host].m_session.GetReplicaMgr().FindReplica(nodes[Client1].m_always->GetReplicaId()); AZ_TEST_ASSERT(aHonC1); aHonC1->RequestChangeOwnership(); } // Send non-authoritative control RPCs if (iTick == 600) { // P1 -> P2 AlwaysMigratable::Ptr aP1onH = nodes[Host].m_session.GetChunkFromReplica(nodes[Peer1].m_always->GetReplicaId()); aP1onH->UpdateControlValue(); // P2 -> Host -> C2 (both at same time, with C2 arriving second) AlwaysMigratable::Ptr aP2onC1 = nodes[Client1].m_session.GetChunkFromReplica(nodes[Peer2].m_always->GetReplicaId()); aP2onC1->UpdateControlValue(); // Host -> C1 AlwaysMigratable::Ptr aHonP1 = nodes[Peer1].m_session.GetChunkFromReplica(nodes[Host].m_always->GetReplicaId()); aHonP1->UpdateControlValue(); // C1 -> C2 -> Host AlwaysMigratable::Ptr aC1onP2 = nodes[Peer2].m_session.GetChunkFromReplica(nodes[Client1].m_always->GetReplicaId()); aC1onP2->UpdateControlValue(); // C2 -> P1 AlwaysMigratable::Ptr aC2onP2 = nodes[Peer2].m_session.GetChunkFromReplica(nodes[Client2].m_always->GetReplicaId()); aC2onP2->UpdateControlValue(); // P1 -> C1 (Forbidden) NeverMigratable::Ptr nP1onH = nodes[Host].m_session.GetChunkFromReplica(nodes[Peer1].m_never->GetReplicaId()); nP1onH->UpdateControlValue(); // C2 -> P2 (Forbidden) NeverMigratable::Ptr nC2onP1 = nodes[Peer1].m_session.GetChunkFromReplica(nodes[Client2].m_never->GetReplicaId()); nC2onP1->UpdateControlValue(); // P1 -> Host SometimesMigratable::Ptr sP1onH = nodes[Host].m_session.GetChunkFromReplica(nodes[Peer1].m_sometimes->GetReplicaId()); sP1onH->UpdateControlValue(); // C1 -> P1 SometimesMigratable::Ptr sC1onC2 = nodes[Client2].m_session.GetChunkFromReplica(nodes[Client1].m_sometimes->GetReplicaId()); sC1onC2->UpdateControlValue(); // P2 -> C2 (Forbidden) SometimesMigratable::Ptr sP2onC2 = nodes[Client2].m_session.GetChunkFromReplica(nodes[Peer2].m_sometimes->GetReplicaId()); sP2onC2->UpdateControlValue(); // C2 -> Host (Forbidden) SometimesMigratable::Ptr sC2onP1 = nodes[Peer1].m_session.GetChunkFromReplica(nodes[Client2].m_sometimes->GetReplicaId()); sC2onP1->UpdateControlValue(); } // tick int tickNode = iTick % TotalNodes; nodes[tickNode].m_session.Update(); nodes[tickNode].m_session.GetReplicaMgr().Unmarshal(); nodes[tickNode].m_session.GetReplicaMgr().UpdateFromReplicas(); nodes[tickNode].m_session.GetReplicaMgr().UpdateReplicas(); nodes[tickNode].m_session.GetReplicaMgr().Marshal(); nodes[tickNode].m_session.GetTransport()->Update(); AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(k_frameTimePerNodeMs)); } // P1 -> P2 AZ_TEST_ASSERT(nodes[Peer1].m_always->m_requests == 1); AZ_TEST_ASSERT(nodes[Peer1].m_always->m_accepted == 1); AZ_TEST_ASSERT(nodes[Peer1].m_always->GetReplica()->IsProxy()); AZ_TEST_ASSERT(nodes[Peer1].m_always->m_owner.Get() == Peer2); AZ_TEST_ASSERT(nodes[Peer2].m_session.GetReplicaMgr().FindReplica(nodes[Peer1].m_always->GetReplicaId())->IsMaster()); AZ_TEST_ASSERT(nodes[Peer1].m_always->m_control.Get() == Peer2); // P2 -> Host -> C2 (both at same time, with C2 arriving second) AZ_TEST_ASSERT(nodes[Peer2].m_always->m_requests == 1); AZ_TEST_ASSERT(nodes[Peer2].m_always->m_accepted == 1); AZ_TEST_ASSERT(nodes[Peer2].m_always->GetReplica()->IsProxy()); AZ_TEST_ASSERT(nodes[Peer2].m_always->m_owner.Get() == Client2); AlwaysMigratable::Ptr aP2onH = nodes[Host].m_session.GetChunkFromReplica(nodes[Peer2].m_always->GetReplicaId()); AZ_TEST_ASSERT(aP2onH->m_requests == 1); AZ_TEST_ASSERT(aP2onH->m_accepted == 1); AZ_TEST_ASSERT(aP2onH->GetReplica()->IsProxy()); AZ_TEST_ASSERT(aP2onH->m_owner.Get() == Client2); AZ_TEST_ASSERT(nodes[Client2].m_session.GetReplicaMgr().FindReplica(nodes[Peer2].m_always->GetReplicaId())->IsMaster()); AZ_TEST_ASSERT(nodes[Peer2].m_always->m_control.Get() == Client2); // Host -> C1 AZ_TEST_ASSERT(nodes[Host].m_always->m_requests == 1); AZ_TEST_ASSERT(nodes[Host].m_always->m_accepted == 1); AZ_TEST_ASSERT(nodes[Host].m_always->GetReplica()->IsProxy()); AZ_TEST_ASSERT(nodes[Host].m_always->m_owner.Get() == Client1); AZ_TEST_ASSERT(nodes[Client1].m_session.GetReplicaMgr().FindReplica(nodes[Host].m_always->GetReplicaId())->IsMaster()); AZ_TEST_ASSERT(nodes[Host].m_always->m_control.Get() == Client1); // C1 -> C2 -> Host (2nd migration) AZ_TEST_ASSERT(nodes[Client1].m_always->m_requests == 1); AZ_TEST_ASSERT(nodes[Client1].m_always->m_accepted == 1); AZ_TEST_ASSERT(nodes[Client1].m_always->GetReplica()->IsProxy()); AZ_TEST_ASSERT(nodes[Client1].m_always->m_owner.Get() == Host); AlwaysMigratable::Ptr aC1onC2 = nodes[Client2].m_session.GetChunkFromReplica(nodes[Client1].m_always->GetReplicaId()); AZ_TEST_ASSERT(aC1onC2->m_requests == 1); AZ_TEST_ASSERT(aC1onC2->m_accepted == 1); AZ_TEST_ASSERT(aC1onC2->GetReplica()->IsProxy()); AZ_TEST_ASSERT(aC1onC2->m_owner.Get() == Host); AZ_TEST_ASSERT(nodes[Host].m_session.GetReplicaMgr().FindReplica(nodes[Client1].m_always->GetReplicaId())->IsMaster()); AZ_TEST_ASSERT(nodes[Client1].m_always->m_control.Get() == Host); // C2 -> P1 AZ_TEST_ASSERT(nodes[Client2].m_always->m_requests == 1); AZ_TEST_ASSERT(nodes[Client2].m_always->m_accepted == 1); AZ_TEST_ASSERT(nodes[Client2].m_always->GetReplica()->IsProxy()); AZ_TEST_ASSERT(nodes[Client2].m_always->m_owner.Get() == Peer1); AZ_TEST_ASSERT(nodes[Peer1].m_session.GetReplicaMgr().FindReplica(nodes[Client2].m_always->GetReplicaId())->IsMaster()); AZ_TEST_ASSERT(nodes[Client2].m_always->m_control.Get() == Peer1); // P1 -> C1 (Forbidden) AZ_TEST_ASSERT(nodes[Peer1].m_never->m_requests == 0); AZ_TEST_ASSERT(nodes[Peer1].m_never->m_accepted == 0); AZ_TEST_ASSERT(nodes[Peer1].m_never->GetReplica()->IsMaster()); AZ_TEST_ASSERT(nodes[Peer1].m_never->m_owner.Get() == Peer1); AZ_TEST_ASSERT(nodes[Client1].m_session.GetReplicaMgr().FindReplica(nodes[Peer1].m_never->GetReplicaId())->IsProxy()); AZ_TEST_ASSERT(nodes[Peer1].m_never->m_control.Get() == Peer1); // C2 -> P2 (Forbidden) AZ_TEST_ASSERT(nodes[Client2].m_never->m_requests == 0); AZ_TEST_ASSERT(nodes[Client2].m_never->m_accepted == 0); AZ_TEST_ASSERT(nodes[Client2].m_never->GetReplica()->IsMaster()); AZ_TEST_ASSERT(nodes[Client2].m_never->m_owner.Get() == Client2); AZ_TEST_ASSERT(nodes[Peer2].m_session.GetReplicaMgr().FindReplica(nodes[Client2].m_never->GetReplicaId())->IsProxy()); AZ_TEST_ASSERT(nodes[Client2].m_never->m_control.Get() == Client2); // P1 -> Host AZ_TEST_ASSERT(nodes[Peer1].m_sometimes->m_requests == 1); AZ_TEST_ASSERT(nodes[Peer1].m_sometimes->m_accepted == 1); AZ_TEST_ASSERT(nodes[Peer1].m_sometimes->GetReplica()->IsProxy()); AZ_TEST_ASSERT(nodes[Peer1].m_sometimes->m_owner.Get() == Host); AZ_TEST_ASSERT(nodes[Host].m_session.GetReplicaMgr().FindReplica(nodes[Peer1].m_sometimes->GetReplicaId())->IsMaster()); AZ_TEST_ASSERT(nodes[Peer1].m_sometimes->m_control.Get() == Host); // C1 -> P1 AZ_TEST_ASSERT(nodes[Client1].m_sometimes->m_requests == 1); AZ_TEST_ASSERT(nodes[Client1].m_sometimes->m_accepted == 1); AZ_TEST_ASSERT(nodes[Client1].m_sometimes->GetReplica()->IsProxy()); AZ_TEST_ASSERT(nodes[Client1].m_sometimes->m_owner.Get() == Peer1); AZ_TEST_ASSERT(nodes[Peer1].m_session.GetReplicaMgr().FindReplica(nodes[Client1].m_sometimes->GetReplicaId())->IsMaster()); AZ_TEST_ASSERT(nodes[Client1].m_sometimes->m_control.Get() == Peer1); // P2 -> C2 (Forbidden) AZ_TEST_ASSERT(nodes[Peer2].m_sometimes->m_requests == 1); AZ_TEST_ASSERT(nodes[Peer2].m_sometimes->m_accepted == 0); AZ_TEST_ASSERT(nodes[Peer2].m_sometimes->GetReplica()->IsMaster()); AZ_TEST_ASSERT(nodes[Peer2].m_sometimes->m_owner.Get() == Peer2); AZ_TEST_ASSERT(nodes[Client2].m_session.GetReplicaMgr().FindReplica(nodes[Peer2].m_never->GetReplicaId())->IsProxy()); AZ_TEST_ASSERT(nodes[Peer2].m_sometimes->m_control.Get() == Peer2); // C2 -> Host (Forbidden) AZ_TEST_ASSERT(nodes[Client2].m_sometimes->m_requests == 1); AZ_TEST_ASSERT(nodes[Client2].m_sometimes->m_accepted == 0); AZ_TEST_ASSERT(nodes[Client2].m_sometimes->GetReplica()->IsMaster()); AZ_TEST_ASSERT(nodes[Client2].m_sometimes->m_owner.Get() == Client2); AZ_TEST_ASSERT(nodes[Host].m_session.GetReplicaMgr().FindReplica(nodes[Client2].m_never->GetReplicaId())->IsProxy()); AZ_TEST_ASSERT(nodes[Client2].m_sometimes->m_control.Get() == Client2); // clean up for (int i = 0; i < TotalNodes; ++i) { nodes[i].m_always = nullptr; nodes[i].m_never = nullptr; nodes[i].m_sometimes = nullptr; nodes[i].m_session.GetReplicaMgr().Shutdown(); DefaultCarrier::Destroy(nodes[i].m_session.GetTransport()); } } const int Integ_ReplicaMigrationRequestTest::k_frameTimePerNodeMs; const int Integ_ReplicaMigrationRequestTest::k_hostSendTimeMs; class Integ_PeerRejoinTest : public UnitTest::GridMateMPTestFixture , public ReplicaMgrCallbackBus::Handler , public ::testing::Test { void OnNewHost(bool isHost, ReplicaManager* pMgr) override { (void)pMgr; if (isHost) { AZ_TracePrintf("GridMate", "Peer %d has completed host migration and is now the host.\n", (int)pMgr->GetLocalPeerId()); } else { AZ_TracePrintf("GridMate", "Peer %d has has received notification that host migration is complete.\n", (int)pMgr->GetLocalPeerId()); } } public: Integ_PeerRejoinTest() { ReplicaMgrCallbackBus::Handler::BusConnect(m_gridMate); } ~Integ_PeerRejoinTest() { ReplicaMgrCallbackBus::Handler::BusDisconnect(); } }; TEST_F(Integ_PeerRejoinTest, PeerRejoinTest) { ReplicaChunkDescriptorTable::Get().RegisterChunkType(); ReplicaChunkDescriptorTable::Get().RegisterChunkType(); int frameTime = 10; int framesToRun = 300; enum { p1, p2, nPeers }; MPSession peers[nPeers]; MigratableReplica::Ptr migrRep[nPeers]; NonMigratableReplica::Ptr nonMigrRep[nPeers]; // initialize full-mesh P2P session int basePort = 4427; for (int i = 0; i < nPeers; ++i) { TestCarrierDesc desc; desc.m_port = basePort + i; desc.m_enableDisconnectDetection = true; desc.m_threadUpdateTimeMS = frameTime / 2; // initialize replica managers peers[i].SetTransport(DefaultCarrier::Create(desc, m_gridMate)); peers[i].AcceptConn(true); peers[i].SetClient(false); peers[i].GetReplicaMgr().Init(ReplicaMgrDesc(i + 1 , peers[i].GetTransport() , 0 , i == 0 ? ReplicaMgrDesc::Role_SyncHost : 0 , frameTime / 2)); } AZ_TracePrintf("GridMate", "\n"); int frameCount = 0; while (frameCount < framesToRun) { static bool allReady = false; // establish all connections if (frameCount < nPeers) { for (int i = 0; i < frameCount; ++i) { peers[frameCount].GetTransport()->Connect("127.0.0.1", basePort + i); } } if (!allReady) { allReady = true; for (int i = 0; i < nPeers; ++i) { if (!peers[i].GetReplicaMgr().IsReady()) { allReady = false; } } if (allReady) { AZ_TracePrintf("GridMate", "All peers ready at frame %d\n", frameCount); } } // perform tests if (allReady) { // add replicas static bool addReplicas = true; if (addReplicas) { for (int i = 0; i < nPeers; ++i) { { auto rep = Replica::CreateReplica(nullptr); migrRep[i] = CreateAndAttachReplicaChunk(rep, aznew MyObj()); peers[i].GetReplicaMgr().AddMaster(rep); } { auto rep = Replica::CreateReplica(nullptr); nonMigrRep[i] = CreateAndAttachReplicaChunk(rep, aznew MyObj()); peers[i].GetReplicaMgr().AddMaster(rep); } } addReplicas = false; AZ_TracePrintf("GridMate", "Replicas added at frame %d\n", frameCount); } // disconnect p2 and trigger peer migration static bool dropP2 = true; if (frameCount > 50 && dropP2) { peers[p2].GetTransport()->Disconnect(AllConnections); peers[p2].GetReplicaMgr().Shutdown(); dropP2 = false; AZ_TracePrintf("GridMate", "Dropped P2 at frame %d\n", frameCount); } // reconnect p2 static bool reconP2 = true; if (frameCount > 100 && reconP2) { peers[p2].GetReplicaMgr().Init(ReplicaMgrDesc(p2 + 1 , peers[p2].GetTransport() , 0 , 0 , frameTime / 2)); peers[p1].GetTransport()->Connect("127.0.0.1", basePort + p2); peers[p2].GetTransport()->Connect("127.0.0.1", basePort + p1); reconP2 = false; AZ_TracePrintf("GridMate", "Reconnected P2 at frame %d\n", frameCount); } // disconnect p2 again static bool redropP2 = true; if (frameCount > 150 && redropP2) { peers[p2].GetTransport()->Disconnect(AllConnections); redropP2 = false; AZ_TracePrintf("GridMate", "Re-Dropped P2 at frame %d\n", frameCount); } } // tick int tickPeer = frameCount++ % nPeers; peers[tickPeer].Update(); if (peers[tickPeer].GetReplicaMgr().IsInitialized()) { peers[tickPeer].GetReplicaMgr().Unmarshal(); peers[tickPeer].GetReplicaMgr().UpdateReplicas(); peers[tickPeer].GetReplicaMgr().UpdateFromReplicas(); peers[tickPeer].GetReplicaMgr().Marshal(); } peers[tickPeer].GetTransport()->Update(); AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(frameTime)); } // clean up for (int i = 0; i < nPeers; ++i) { peers[i].GetReplicaMgr().Shutdown(); DefaultCarrier::Destroy(peers[i].GetTransport()); } } class Integ_ReplicationSecurityOptionsTest : public UnitTest::GridMateMPTestFixture , public ::testing::Test { public: enum { s1, s2, s3, nSessions }; class TestChunk : public ReplicaChunk { public: struct ForwardSourcePeerTrait : public RpcDefaultTraits { static const bool s_alwaysForwardSourcePeer = true; static const bool s_allowNonAuthoritativeRequestRelay = false; }; struct DisableNonAuthoritativeRequestTrait : public RpcAuthoritativeTraits { static const bool s_alwaysForwardSourcePeer = true; }; GM_CLASS_ALLOCATOR(TestChunk); static const char* GetChunkName() { return "ReplicationSecurityOptionsTest::TestChunk"; } TestChunk() : m_nForwardSourcePeerRpcCallsFromS1("m_nForwardSourcePeerRpcCallsFromS1", 0) , m_nForwardSourcePeerRpcCallsFromS2("m_nForwardSourcePeerRpcCallsFromS2", 0) , m_nForwardSourcePeerRpcCallsFromS3("m_nForwardSourcePeerRpcCallsFromS3", 0) , ForwardSourcePeerRpcFromS1("ForwardSourcePeerRpcFromS1") , ForwardSourcePeerRpcFromS2("ForwardSourcePeerRpcFromS2") , ForwardSourcePeerRpcFromS3("ForwardSourcePeerRpcFromS3") , m_nAuthoritativeOnlyRpcCallsFromS1("m_nAuthoritativeOnlyRpcCallsFromS1", 0) , m_nAuthoritativeOnlyRpcCallsFromS2("m_nAuthoritativeOnlyRpcCallsFromS2", 0) , m_nAuthoritativeOnlyRpcCallsFromS3("m_nAuthoritativeOnlyRpcCallsFromS3", 0) , m_nAuthoritativeOnlyProxyRpcCallsFromS1(0) , m_nAuthoritativeOnlyProxyRpcCallsFromS2(0) , m_nAuthoritativeOnlyProxyRpcCallsFromS3(0) , AuthoritativeOnlyRpcFromS1("AuthoritativeOnlyRpcFromS1") , AuthoritativeOnlyRpcFromS2("AuthoritativeOnlyRpcFromS2") , AuthoritativeOnlyRpcFromS3("AuthoritativeOnlyRpcFromS3") { } bool IsReplicaMigratable() override { return false; } bool OnForwardSourcePeerRpcFromS1(const RpcContext& rpcContext) { // make sure the requestor is set to s1 AZ_TEST_ASSERT(rpcContext.m_sourcePeer == s1 + 1); m_nForwardSourcePeerRpcCallsFromS1.Modify([](int& value) { ++value; return true; }); return false; } bool OnForwardSourcePeerRpcFromS2(const RpcContext& rpcContext) { // make sure the requestor is set to s2 AZ_TEST_ASSERT(rpcContext.m_sourcePeer == s2 + 1); // requests to s3 should be blocked in this test AZ_TEST_ASSERT(GetReplicaManager()->GetLocalPeerId() != s3 + 1); m_nForwardSourcePeerRpcCallsFromS2.Modify([](int& value) { ++value; return true; }); return false; } bool OnForwardSourcePeerRpcFromS3(const RpcContext& rpcContext) { // make sure the requestor is set to s3 AZ_TEST_ASSERT(rpcContext.m_sourcePeer == s3 + 1); // requests to s2 should be blocked in this test AZ_TEST_ASSERT(GetReplicaManager()->GetLocalPeerId() != s2 + 1); m_nForwardSourcePeerRpcCallsFromS3.Modify([](int& value) { ++value; return true; }); return false; } bool OnAuthoritativeOnlyRpcFromS1(const RpcContext& rpcContext) { // make sure the requestor is set to s1 AZ_TEST_ASSERT(rpcContext.m_sourcePeer == s1 + 1); if (IsMaster()) { m_nAuthoritativeOnlyRpcCallsFromS1.Modify([](int& value) { ++value; return true; }); } else { ++m_nAuthoritativeOnlyProxyRpcCallsFromS1; } return true; } bool OnAuthoritativeOnlyRpcFromS2(const RpcContext& rpcContext) { // make sure the requestor is set to s2 AZ_TEST_ASSERT(rpcContext.m_sourcePeer == s2 + 1); if (IsMaster()) { m_nAuthoritativeOnlyRpcCallsFromS2.Modify([](int& value) { ++value; return true; }); } else { ++m_nAuthoritativeOnlyProxyRpcCallsFromS2; } return true; } bool OnAuthoritativeOnlyRpcFromS3(const RpcContext& rpcContext) { // make sure the requestor is set to s3 AZ_TEST_ASSERT(rpcContext.m_sourcePeer == s3 + 1); if (IsMaster()) { m_nAuthoritativeOnlyRpcCallsFromS3.Modify([](int& value) { ++value; return true; }); } else { ++m_nAuthoritativeOnlyProxyRpcCallsFromS3; } return true; } DataSet m_nForwardSourcePeerRpcCallsFromS1; DataSet m_nForwardSourcePeerRpcCallsFromS2; DataSet m_nForwardSourcePeerRpcCallsFromS3; Rpc<>::BindInterface ForwardSourcePeerRpcFromS1; Rpc<>::BindInterface ForwardSourcePeerRpcFromS2; Rpc<>::BindInterface ForwardSourcePeerRpcFromS3; DataSet m_nAuthoritativeOnlyRpcCallsFromS1; DataSet m_nAuthoritativeOnlyRpcCallsFromS2; DataSet m_nAuthoritativeOnlyRpcCallsFromS3; int m_nAuthoritativeOnlyProxyRpcCallsFromS1; int m_nAuthoritativeOnlyProxyRpcCallsFromS2; int m_nAuthoritativeOnlyProxyRpcCallsFromS3; Rpc<>::BindInterface AuthoritativeOnlyRpcFromS1; Rpc<>::BindInterface AuthoritativeOnlyRpcFromS2; Rpc<>::BindInterface AuthoritativeOnlyRpcFromS3; }; using TestChunkPtr = AZStd::intrusive_ptr ; }; TEST_F(Integ_ReplicationSecurityOptionsTest, ReplicationSecurityOptionsTest) { AZ_TracePrintf("GridMate", "\n"); // Register test chunks ReplicaChunkDescriptorTable::Get().RegisterChunkType(); MPSession sessions[nSessions]; ReplicaPtr masters[nSessions]; // initialize transport int basePort = 4427; for (int i = 0; i < nSessions; ++i) { TestCarrierDesc desc; desc.m_port = basePort + i; // initialize replica managers // s2(c)<-->(p)s1(p)<-->(c)s3 sessions[i].SetTransport(DefaultCarrier::Create(desc, m_gridMate)); sessions[i].AcceptConn(true); sessions[i].SetClient(i != s1); sessions[i].GetReplicaMgr().Init(ReplicaMgrDesc(i + 1, sessions[i].GetTransport(), 0, i == 0 ? ReplicaMgrDesc::Role_SyncHost : 0)); ReplicationSecurityOptions options; options.m_enableStrictSourceValidation = true; sessions[i].GetReplicaMgr().SetSecurityOptions(options); } // connect s2 to s1 sessions[s2].GetTransport()->Connect("127.0.0.1", basePort); // connect s3 to s1 sessions[s3].GetTransport()->Connect("127.0.0.1", basePort); // main test loop for (int tick = 0; tick < 1000; ++tick) { if (tick == 100) { for (int i = 0; i < nSessions; ++i) { AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().IsReady()); masters[i] = Replica::CreateReplica("ReplicationSecurityOptionsTest::TestReplica"); TestChunkPtr chunk = CreateReplicaChunk(); masters[i]->AttachReplicaChunk(chunk); sessions[i].GetReplicaMgr().AddMaster(masters[i]); } } if (tick == 200) { AZ_TEST_START_TRACE_SUPPRESSION; for (int i = 0; i < nSessions; ++i) { sessions[s1].GetReplicaMgr().FindReplica(masters[i]->GetRepId())->FindReplicaChunk()->ForwardSourcePeerRpcFromS1(); sessions[s2].GetReplicaMgr().FindReplica(masters[i]->GetRepId())->FindReplicaChunk()->ForwardSourcePeerRpcFromS2(); sessions[s3].GetReplicaMgr().FindReplica(masters[i]->GetRepId())->FindReplicaChunk()->ForwardSourcePeerRpcFromS3(); } } if (tick == 300) { // The previous test should have triggered the following assert twice: // ReplicaChunk.cpp(449): AZ_Assert(false, "Discarding non-authoritative RPC <%s> because s_allowNonAuthoritativeRequestRelay trait is disabled!", GetDescriptor()->GetRpcName(this, rpc)); AZ_TEST_STOP_TRACE_SUPPRESSION(2); // All chunks should have received the call from the host AZ_TEST_ASSERT(masters[s1]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS1 == 1); AZ_TEST_ASSERT(masters[s2]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS1 == 1); AZ_TEST_ASSERT(masters[s3]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS1 == 1); // the host chunk should have received calls from both clients AZ_TEST_ASSERT(masters[s1]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS2 == 1); AZ_TEST_ASSERT(masters[s1]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS3 == 1); // the chunk on s2 should receive its own call but not from s3 AZ_TEST_ASSERT(masters[s2]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS2 == 1); AZ_TEST_ASSERT(masters[s2]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS3 == 0); // the chunk on s3 should receive its own call but not from s2 AZ_TEST_ASSERT(masters[s3]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS2 == 0); AZ_TEST_ASSERT(masters[s3]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS3 == 1); // all datasets should have propagated properly for (int i = 0; i < nSessions; ++i) { AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s1]->GetRepId())->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS1.Get() == masters[s1]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS1.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s1]->GetRepId())->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS2.Get() == masters[s1]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS2.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s1]->GetRepId())->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS3.Get() == masters[s1]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS3.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s2]->GetRepId())->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS1.Get() == masters[s2]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS1.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s2]->GetRepId())->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS2.Get() == masters[s2]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS2.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s2]->GetRepId())->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS3.Get() == masters[s2]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS3.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s3]->GetRepId())->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS1.Get() == masters[s3]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS1.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s3]->GetRepId())->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS2.Get() == masters[s3]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS2.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s3]->GetRepId())->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS3.Get() == masters[s3]->FindReplicaChunk()->m_nForwardSourcePeerRpcCallsFromS3.Get()); } } if (tick == 400) { AZ_TEST_START_TRACE_SUPPRESSION; for (int i = 0; i < nSessions; ++i) { sessions[s1].GetReplicaMgr().FindReplica(masters[i]->GetRepId())->FindReplicaChunk()->AuthoritativeOnlyRpcFromS1(); sessions[s2].GetReplicaMgr().FindReplica(masters[i]->GetRepId())->FindReplicaChunk()->AuthoritativeOnlyRpcFromS2(); sessions[s3].GetReplicaMgr().FindReplica(masters[i]->GetRepId())->FindReplicaChunk()->AuthoritativeOnlyRpcFromS3(); } } if (tick == 500) { // The previous test should have triggered the following assert six times: // ReplicaChunk.cpp(444): AZ_Assert(false, "Discarding non-authoritative RPC <%s> because s_allowNonAuthoritativeRequests trait is disabled!", GetDescriptor()->GetRpcName(this, rpc)); AZ_TEST_STOP_TRACE_SUPPRESSION(6); // Each chunk should have received their own AuthoritativeOnlyRpc once. AZ_TEST_ASSERT(masters[s1]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS1 == 1); AZ_TEST_ASSERT(masters[s2]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS2 == 1); AZ_TEST_ASSERT(masters[s3]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS3 == 1); // Calls from other nodes should have been discarded. AZ_TEST_ASSERT(masters[s1]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS2 == 0); AZ_TEST_ASSERT(masters[s1]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS3 == 0); AZ_TEST_ASSERT(masters[s2]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS1 == 0); AZ_TEST_ASSERT(masters[s2]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS3 == 0); AZ_TEST_ASSERT(masters[s3]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS1 == 0); AZ_TEST_ASSERT(masters[s3]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS2 == 0); // Calls should have successfully propagated to the other 2 proxies AZ_TEST_ASSERT(sessions[s1].GetReplicaMgr().FindReplica(masters[s2]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyProxyRpcCallsFromS2 == 1); AZ_TEST_ASSERT(sessions[s1].GetReplicaMgr().FindReplica(masters[s3]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyProxyRpcCallsFromS3 == 1); AZ_TEST_ASSERT(sessions[s2].GetReplicaMgr().FindReplica(masters[s1]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyProxyRpcCallsFromS1 == 1); AZ_TEST_ASSERT(sessions[s2].GetReplicaMgr().FindReplica(masters[s3]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyProxyRpcCallsFromS3 == 1); AZ_TEST_ASSERT(sessions[s3].GetReplicaMgr().FindReplica(masters[s1]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyProxyRpcCallsFromS1 == 1); AZ_TEST_ASSERT(sessions[s3].GetReplicaMgr().FindReplica(masters[s2]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyProxyRpcCallsFromS2 == 1); // all datasets should have propagated properly for (int i = 0; i < nSessions; ++i) { AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s1]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS1.Get() == masters[s1]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS1.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s1]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS2.Get() == masters[s1]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS2.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s1]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS3.Get() == masters[s1]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS3.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s2]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS1.Get() == masters[s2]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS1.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s2]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS2.Get() == masters[s2]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS2.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s2]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS3.Get() == masters[s2]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS3.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s3]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS1.Get() == masters[s3]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS1.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s3]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS2.Get() == masters[s3]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS2.Get()); AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().FindReplica(masters[s3]->GetRepId())->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS3.Get() == masters[s3]->FindReplicaChunk()->m_nAuthoritativeOnlyRpcCallsFromS3.Get()); } } // tick everything for (int i = 0; i < nSessions; ++i) { sessions[i].Update(); sessions[i].GetReplicaMgr().Unmarshal(); sessions[i].GetReplicaMgr().UpdateReplicas(); sessions[i].GetReplicaMgr().UpdateFromReplicas(); sessions[i].GetReplicaMgr().Marshal(); sessions[i].GetTransport()->Update(); } AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10)); } for (int i = 0; i < nSessions; ++i) { sessions[i].GetReplicaMgr().Shutdown(); DefaultCarrier::Destroy(sessions[i].GetTransport()); } } /* *03.25.2015* Typical test results on Core i7 3.4 GHz desktop (with polling): Release: Replica update time (msec): avg=2.02, min=2, max=3 (peers=40, replicas=16000, freq=0%, samples=4000) Replica update time (msec): avg=4.83, min=2, max=12 (peers=40, replicas=16000, freq=10%, samples=4000) Replica update time (msec): avg=4.73, min=3, max=12 (peers=40, replicas=16000, freq=100%, samples=4000) DebugOpt: Replica update time (msec): avg=2.53, min=2, max=5 (peers=40, replicas=16000, freq=0%, samples=4000) Replica update time (msec): avg=4.21, min=2, max=8 (peers=40, replicas=16000, freq=10%, samples=4000) Replica update time (msec): avg=5.59, min=3, max=14 (peers=40, replicas=16000, freq=100%, samples=4000) Test results (task based marshaling): Release: Replica update time (msec): avg=0.03, min=0, max=1 (peers=40, replicas=16000, freq=0%, samples=4000) Replica update time (msec): avg=3.94, min=1, max=11 (peers=40, replicas=16000, freq=10%, samples=4000) Replica update time (msec): avg=5.21, min=4, max=15 (peers=40, replicas=16000, freq=100%, samples=4000) DebugOpt: Replica update time (msec): avg=1.00, min=1, max=2 (peers=40, replicas=16000, freq=0%, samples=4000) Replica update time (msec): avg=4.94, min=1, max=9 (peers=40, replicas=16000, freq=10%, samples=4000) Replica update time (msec): avg=8.05, min=6, max=15 (peers=40, replicas=16000, freq=100%, samples=4000) */ class Integ_ReplicaStressTest : public UnitTest::GridMateMPTestFixture { public: class StressTestReplica : public ReplicaChunk { public: GM_CLASS_ALLOCATOR(StressTestReplica); typedef AZStd::intrusive_ptr Ptr; static const char* GetChunkName() { return "StressTestReplica"; } StressTestReplica() : m_data("Data") { } bool IsReplicaMigratable() override { return false; } bool m_changing; DataSet m_data; }; static const size_t NUM_PEERS = 40; static const size_t NUM_REPLICAS_PER_PEER = 400; static const int FRAME_TIME = 5; static const int BASE_PORT = 44270; // TODO: Reduce the size or disable the test for platforms which can't allocate 2 GiB Integ_ReplicaStressTest() : UnitTest::GridMateMPTestFixture(2000u * 1024u * 1024u) {} void UpdateReplica(MPSession& session) { session.GetReplicaMgr().Unmarshal(); session.GetReplicaMgr().UpdateReplicas(); session.GetReplicaMgr().UpdateFromReplicas(); session.GetReplicaMgr().Marshal(); } void Wait(MPSession* sessions, vector >& replicas, int numFrames, int frameTime) { (void)replicas; while (numFrames--) { for (size_t i = 0; i < NUM_PEERS; ++i) { sessions[i].Update(); UpdateReplica(sessions[i]); } for (size_t i = 0; i < NUM_PEERS; ++i) { sessions[i].GetTransport()->Update(); } AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(frameTime)); } } // freq is 0..1, 0.0 - no dirty replicas per tick, 1.0 - all replicas are dirty every tick, 0.5 - 50% of replicas per tick void TestReplicas(MPSession* sessions, vector >& replicas, int numFrames, int frameTime, double freq) { AZStd::chrono::system_clock::duration minUpdateTime(AZStd::chrono::system_clock::duration::max()); AZStd::chrono::system_clock::duration maxUpdateTime(AZStd::chrono::system_clock::duration::min()); AZStd::chrono::system_clock::duration sumSamples(AZStd::chrono::system_clock::duration::zero()); unsigned long long numSamples = 0; int count = 0; while (numFrames--) { MarkChanging(replicas, freq); for (auto& r : replicas) { if (r.second->m_changing) { r.second->m_data.Set(count++); } } for (size_t i = 0; i < NUM_PEERS; ++i) { sessions[i].Update(); AZStd::chrono::system_clock::time_point beforeUpdateTime = AZStd::chrono::system_clock::now(); UpdateReplica(sessions[i]); auto updateTime = AZStd::chrono::system_clock::now() - beforeUpdateTime; minUpdateTime = AZStd::min(updateTime, minUpdateTime); maxUpdateTime = AZStd::max(updateTime, maxUpdateTime); sumSamples += updateTime; ++numSamples; } for (size_t i = 0; i < NUM_PEERS; ++i) { sessions[i].GetTransport()->Update(); } AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(frameTime)); } AZ_TEST_ASSERT(numSamples > 0); AZ_Printf("GridMate", "\n\n----------------\nReplica update time (msec): avg=%.2f, min=%.2f, max=%.2f (peers=%d, replicas=%d, freq=%d%%, samples=%llu)\n", static_cast(AZStd::chrono::duration_cast(sumSamples).count()) / static_cast(numSamples), AZStd::chrono::duration_cast(minUpdateTime).count() / 1000.0, AZStd::chrono::duration_cast(maxUpdateTime).count() / 1000.0, NUM_PEERS, replicas.size(), static_cast(freq * 100.0), numSamples); } bool ConnectPeers(MPSession* sessions, int frameTime) { size_t frameCount = 0; bool allReady = false; size_t maxFramesToReady = NUM_PEERS + 100; // this is to avoid infinite loop waiting for all peers to become ready in case some peer cannot connect vector > replicas; while (!allReady && frameCount < maxFramesToReady) { // establish all connections if (frameCount < NUM_PEERS) { for (size_t i = 0; i < frameCount; ++i) { sessions[frameCount].GetTransport()->Connect("127.0.0.1", BASE_PORT + static_cast(i)); } } allReady = true; for (size_t i = 0; i < NUM_PEERS; ++i) { if (!sessions[i].GetReplicaMgr().IsReady()) { allReady = false; break; } } if (allReady) { AZ_Printf("GridMate", "All peers ready at frame %d\n", frameCount); } Wait(sessions, replicas, 1, frameTime); ++frameCount; } return allReady; } virtual void RunStressTests(MPSession* sessions, vector >& replicas) { // testing 3 cases & waiting for system to settle in between TestProfiler::StartProfiling(); Wait(sessions, replicas, 50, FRAME_TIME); TestProfiler::PrintProfilingTotal("GridMate"); Wait(sessions, replicas, 20, FRAME_TIME); TestProfiler::StartProfiling(); TestReplicas(sessions, replicas, 100, FRAME_TIME, 0.0); // no replicas are dirty TestProfiler::PrintProfilingTotal("GridMate"); Wait(sessions, replicas, 20, FRAME_TIME); TestProfiler::StartProfiling(); TestReplicas(sessions, replicas, 1, FRAME_TIME, 1.0); // single burst dirty replicas Wait(sessions, replicas, 2, FRAME_TIME); TestProfiler::PrintProfilingTotal("GridMate"); Wait(sessions, replicas, 20, FRAME_TIME); TestProfiler::StartProfiling(); TestReplicas(sessions, replicas, 100, FRAME_TIME, 0.1); // 10% of replicas are marked dirty every frame TestProfiler::PrintProfilingTotal("GridMate"); Wait(sessions, replicas, 20, FRAME_TIME); TestProfiler::StartProfiling(); TestReplicas(sessions, replicas, 100, FRAME_TIME, 1.0); // every replica is marked dirty every frame TestProfiler::PrintProfilingTotal("GridMate"); TestProfiler::PrintProfilingSelf("GridMate"); TestProfiler::StopProfiling(); } virtual void MarkChanging(vector >& replicas, double freq) { AZ::Sfmt& sfmt = AZ::Sfmt::GetInstance(); for (auto& r : replicas) { r.second->m_changing = sfmt.RandR32_2() <= freq; } } void run() { ReplicaChunkDescriptorTable::Get().RegisterChunkType(); MPSession sessions[NUM_PEERS]; vector > replicas; replicas.reserve(NUM_PEERS * NUM_REPLICAS_PER_PEER); for (unsigned int i = 0; i < NUM_PEERS; ++i) { TestCarrierDesc desc; desc.m_port = BASE_PORT + i; desc.m_enableDisconnectDetection = false; // initialize replica managers sessions[i].SetTransport(DefaultCarrier::Create(desc, m_gridMate)); sessions[i].AcceptConn(true); sessions[i].SetClient(false); sessions[i].GetReplicaMgr().Init(ReplicaMgrDesc(i + 1 , sessions[i].GetTransport() , 0 , i == 0 ? ReplicaMgrDesc::Role_SyncHost : 0)); } bool allReady = ConnectPeers(sessions, FRAME_TIME); AZ_TEST_ASSERT(allReady); for (auto& session : sessions) { for (size_t j = 0; j < NUM_REPLICAS_PER_PEER; ++j) { auto rep = Replica::CreateReplica(nullptr); auto chunk = CreateAndAttachReplicaChunk(rep); replicas.push_back(AZStd::make_pair(rep, chunk)); session.GetReplicaMgr().AddMaster(rep); } } RunStressTests(sessions, replicas); // clean up for (auto& s : sessions) { s.GetReplicaMgr().Shutdown(); DefaultCarrier::Destroy(s.GetTransport()); } } }; /* This test performs updates to the same replicas every frame unlike stress test that picks random replicas every frame *03.25.2015* Typical test results on Core i7 3.4 GHz desktop (with polling): Release: Replica update time (msec): avg=2.54, min=2, max=9 (peers=40, replicas=16000, freq=10%, samples=4000) Replica update time (msec): avg=3.15, min=2, max=7 (peers=40, replicas=16000, freq=50%, samples=4000) DebugOpt: Replica update time (msec): avg=3.35, min=3, max=8 (peers=40, replicas=16000, freq=10%, samples=4000) Replica update time (msec): avg=4.45, min=3, max=10 (peers=40, replicas=16000, freq=50%, samples=4000) Test results (task based marshaling): Release: Replica update time (msec): avg=1.62, min=1, max=10 (peers=40, replicas=16000, freq=10%, samples=4000) Replica update time (msec): avg=4.38, min=2, max=15 (peers=40, replicas=16000, freq=50%, samples=4000) DebugOpt: Replica update time (msec): avg=2.01, min=1, max=5 (peers=40, replicas=16000, freq=10%, samples=4000) Replica update time (msec): avg=4.61, min=3, max=10 (peers=40, replicas=16000, freq=50%, samples=4000) */ class Integ_ReplicaStableStressTest : public Integ_ReplicaStressTest { public: void MarkChanging(vector >& replicas, double freq) override { (void)replicas; (void)freq; } void RunStressTests(MPSession* sessions, vector >& replicas) override { Integ_ReplicaStressTest::MarkChanging(replicas, 0.1); // picks 10% of replicas Wait(sessions, replicas, 20, FRAME_TIME); TestProfiler::StartProfiling(); TestReplicas(sessions, replicas, 100, FRAME_TIME, 0.1); TestProfiler::PrintProfilingTotal("GridMate"); TestProfiler::PrintProfilingSelf("GridMate"); Integ_ReplicaStressTest::MarkChanging(replicas, 0.5); // picks 50% of replicas Wait(sessions, replicas, 20, FRAME_TIME); TestProfiler::StartProfiling(); TestReplicas(sessions, replicas, 100, FRAME_TIME, 0.5); TestProfiler::PrintProfilingTotal("GridMate"); TestProfiler::PrintProfilingSelf("GridMate"); TestProfiler::StopProfiling(); } }; /* * This test verifies bandwidth limiter. The test takes ~2 minutes. It sends ~7k/s of data with a limit of 4k with the following pattern: * * * time | 10s | 10s | 20s | 20s | 10s | 20s | * +-----+-----+----------+----------+-----+----------+ * sendrate | 0k | 7k | 7k | 1.5k | 7k | 7k | * | | | | | | | * expected |none |brst | capped |under cap |brst | capped | * */ class Integ_ReplicaBandiwdthTest : public UnitTest::GridMateMPTestFixture { public: class BandwidthTestChunk : public ReplicaChunk { public: GM_CLASS_ALLOCATOR(BandwidthTestChunk); typedef AZStd::intrusive_ptr Ptr; static const char* GetChunkName() { return "BandwidthTestChunk"; } BandwidthTestChunk() : m_value("Value") { Touch(); } void Touch() { string randomStr; for (unsigned i = 0; i < k_strSize; ++i) { randomStr += 'a' + (rand() % 26); } m_value.Set(randomStr); } bool IsReplicaMigratable() override { return false; } static const unsigned k_strSize = 64; DataSet m_value; }; void run() { ReplicaChunkDescriptorTable::Get().RegisterChunkType(); MPSession sessions[nSessions]; // initialize transport int basePort = 4427; for (int i = 0; i < nSessions; ++i) { TestCarrierDesc desc; desc.m_port = basePort + i; desc.m_enableDisconnectDetection = false; sessions[i].SetClient(i != sHost); sessions[i].SetTransport(DefaultCarrier::Create(desc, m_gridMate)); sessions[i].AcceptConn(true); sessions[i].GetReplicaMgr().Init(ReplicaMgrDesc(i + 1, sessions[i].GetTransport(), 0, i == 0 ? ReplicaMgrDesc::Role_SyncHost : 0)); } // adding replicas for the host static const size_t k_numReplicas = 10; BandwidthTestChunk::Ptr chunks[k_numReplicas]; for (size_t i = 0; i < k_numReplicas; ++i) { auto rep = Replica::CreateReplica(nullptr); chunks[i] = CreateAndAttachReplicaChunk(rep); sessions[sHost].GetReplicaMgr().AddMaster(rep); } // connect to host for (size_t i = 0; i < nSessions; ++i) { if (i == sHost) { continue; } sessions[i].GetTransport()->Connect("127.0.0.1", basePort); } static const int k_delayMS = 100; // tick time bool isDone = false; size_t numReplicasToChange = 0; /* 10 replicas x ~70 bytes per replica x 10 frames per second =~ 7000 bytes per second Will try to cutoff it at 4k per sec */ static const unsigned k_sendRateLimit = 4000; unsigned tickNo = 0; AZ_Printf("GridMate", "Created %d sessions.\n", nSessions); while (!isDone) { for (size_t i = 0; i < numReplicasToChange; ++i) { chunks[i]->Touch(); } for (auto connId : sessions[sHost].m_connections) { if (connId == InvalidConnectionID) { continue; } TrafficControl::Statistics lastSecEffective; sessions[sHost].GetTransport()->QueryStatistics(connId, nullptr, nullptr, &lastSecEffective); m_measurements[connId].m_sendData.push_back(lastSecEffective.m_dataSend); if (!(tickNo % 30)) // printout every 3 seconds { AZ_Printf("GridMate", " - effective sendRate=%.2f KB\n", lastSecEffective.m_dataSend / 1000.f); } //AZ_TracePrintf("GridMate", "%d\n", lastSecEffective.m_dataSend); } // ======================================================= // Actual tests // ======================================================= if (tickNo == 100) // things should've settle at this point, session is established, initial replicas are sent { for (size_t i = 0; i < AZ_ARRAY_SIZE(sessions); ++i) { AZ_TEST_ASSERT(sessions[i].GetReplicaMgr().IsReady()); } numReplicasToChange = k_numReplicas; ResetMeasurements(); AZ_Printf("GridMate", "Starting replica data send. Unlimited.\n"); } else if (tickNo == 200) { // test if initial unlimited burst was allowed for (const auto& m : m_measurements) { AZ_TEST_ASSERT(m.second.Max() > k_sendRateLimit * 1.5f); } ResetMeasurements(); sessions[sHost].GetReplicaMgr().SetSendLimit(k_sendRateLimit); AZ_Printf("GridMate", "Limited by SendLimit=%d Bps.\n", sessions[sHost].GetReplicaMgr().GetSendLimit()); } else if (tickNo == 400) { // checking if bandwidth was rate limited for (const auto& m : m_measurements) { if (!(m.second.Mean() < k_sendRateLimit * 1.1f)) { AZ_Printf("GridMate", "rate mean: %f limit %d\n", m.second.Mean(), sessions[sHost].GetReplicaMgr().GetSendLimit()) } AZ_TEST_ASSERT(m.second.Mean() < k_sendRateLimit * 1.1f); // allowing 10% margin of error } ResetMeasurements(); numReplicasToChange = k_numReplicas / 4; // reducing outgoing traffic 1/4 AZ_Printf("GridMate", "Reduced send rate...\n"); } else if (tickNo == 600) { // checking if traffic below the limit for (const auto& m : m_measurements) { AZ_TEST_ASSERT(m.second.Mean() < k_sendRateLimit * 0.7f); } ResetMeasurements(); sessions[sHost].GetReplicaMgr().SetSendLimit(0); // returning to unlimited send rate numReplicasToChange = k_numReplicas; AZ_Printf("GridMate", "Full send rate...\n"); } else if (tickNo == 700) { // test if burst allowed for (const auto& m : m_measurements) { AZ_TEST_ASSERT(m.second.Max() > k_sendRateLimit * 1.5f); } ResetMeasurements(); sessions[sHost].GetReplicaMgr().SetSendLimit(k_sendRateLimit); AZ_Printf("GridMate", "Limited by SendLimit=%d Bps.\n", sessions[sHost].GetReplicaMgr().GetSendLimit()); } else if (tickNo == 900) { // checking if bandwidth was limited for (const auto& m : m_measurements) { AZ_TEST_ASSERT(m.second.Mean() < k_sendRateLimit * 1.1f); // allowing 10% jerking around limit } isDone = true; } // ======================================================= // Tick everything // ======================================================= for (size_t i = 0; i < nSessions; ++i) { sessions[i].Update(); sessions[i].GetReplicaMgr().Unmarshal(); } for (size_t i = 0; i < nSessions; ++i) { sessions[i].GetReplicaMgr().UpdateReplicas(); } for (size_t i = 0; i < nSessions; ++i) { sessions[i].GetReplicaMgr().UpdateFromReplicas(); sessions[i].GetReplicaMgr().Marshal(); } for (size_t i = 0; i < nSessions; ++i) { sessions[i].GetTransport()->Update(); } ++tickNo; AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(k_delayMS)); } for (int i = 0; i < nSessions; ++i) { sessions[i].GetReplicaMgr().Shutdown(); DefaultCarrier::Destroy(sessions[i].GetTransport()); } } void ResetMeasurements() { for (auto& m : m_measurements) { m.second.Reset(); } } struct Measurement { unsigned int Mean() const { AZ_Assert(!m_sendData.empty(), "No data!"); unsigned int sum = 0; for (auto val : m_sendData) { sum += val; } return sum / static_cast(m_sendData.size()); } unsigned int Max() const { unsigned int curMax = 0; for (auto val : m_sendData) { curMax = AZStd::GetMax(curMax, val); } return curMax; } void Reset() { m_sendData.clear(); } vector m_sendData; // data sent per second }; enum { sHost, sClient1, nSessions }; unordered_map m_measurements; }; } // namespace UnitTest GM_TEST_SUITE(ReplicaSuite) GM_TEST(InterpolatorTest) #if !defined(AZ_DEBUG_BUILD) // these tests are a little slow for debug GM_TEST(Integ_ReplicaBandiwdthTest) GM_TEST(Integ_ReplicaStressTest) GM_TEST(Integ_ReplicaStableStressTest) #endif GM_TEST_SUITE_END()