diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /widget/tests/gtest/TestTouchResampler.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream/124.0.1.tar.xz firefox-upstream/124.0.1.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/tests/gtest/TestTouchResampler.cpp')
-rw-r--r-- | widget/tests/gtest/TestTouchResampler.cpp | 941 |
1 files changed, 941 insertions, 0 deletions
diff --git a/widget/tests/gtest/TestTouchResampler.cpp b/widget/tests/gtest/TestTouchResampler.cpp new file mode 100644 index 0000000000..1a5b8e2430 --- /dev/null +++ b/widget/tests/gtest/TestTouchResampler.cpp @@ -0,0 +1,941 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <initializer_list> +#include "InputData.h" +#include "Units.h" +#include "gtest/gtest.h" +#include "mozilla/Maybe.h" +#include "mozilla/TimeStamp.h" +#include "TouchResampler.h" + +using namespace mozilla; +using widget::TouchResampler; + +class TouchResamplerTest : public ::testing::Test { + protected: + virtual void SetUp() { baseTimeStamp = TimeStamp::Now(); } + + TimeStamp Time(double aMilliseconds) { + return baseTimeStamp + TimeDuration::FromMilliseconds(aMilliseconds); + } + + uint64_t ProcessEvent( + MultiTouchInput::MultiTouchType aType, + std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>> + aHistoricalData, + const TimeStamp& aTimeStamp, const ScreenIntPoint& aPosition) { + MultiTouchInput input(aType, 0, aTimeStamp, 0); + input.mTouches.AppendElement(SingleTouchData(1, aPosition, {}, 0.0f, 0.0f)); + for (const auto& histData : aHistoricalData) { + input.mTouches[0].mHistoricalData.AppendElement( + SingleTouchData::HistoricalTouchData{ + histData.first, histData.second, {}, {}, 0.0f, 0.0f}); + } + return resampler.ProcessEvent(std::move(input)); + } + + void CheckTime(const TimeStamp& aTimeStamp, + const TimeStamp& aExpectedTimeStamp) { + EXPECT_EQ((aTimeStamp - baseTimeStamp).ToMilliseconds(), + (aExpectedTimeStamp - baseTimeStamp).ToMilliseconds()); + } + + void CheckEvent(const MultiTouchInput& aEvent, + MultiTouchInput::MultiTouchType aExpectedType, + std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>> + aExpectedHistoricalData, + const TimeStamp& aExpectedTimeStamp, + const ScreenIntPoint& aExpectedPosition) { + EXPECT_EQ(aEvent.mType, aExpectedType); + EXPECT_EQ(aEvent.mTouches.Length(), size_t(1)); + EXPECT_EQ(aEvent.mTouches[0].mHistoricalData.Length(), + aExpectedHistoricalData.size()); + for (size_t i = 0; i < aExpectedHistoricalData.size(); i++) { + CheckTime(aEvent.mTouches[0].mHistoricalData[i].mTimeStamp, + aExpectedHistoricalData.begin()[i].first); + EXPECT_EQ(aEvent.mTouches[0].mHistoricalData[i].mScreenPoint, + aExpectedHistoricalData.begin()[i].second); + } + CheckTime(aEvent.mTimeStamp, aExpectedTimeStamp); + EXPECT_EQ(aEvent.mTouches[0].mScreenPoint, aExpectedPosition); + } + + struct ExpectedOutgoingEvent { + Maybe<uint64_t> mEventId; + MultiTouchInput::MultiTouchType mType = MultiTouchInput::MULTITOUCH_START; + std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>> mHistoricalData; + TimeStamp mTimeStamp; + ScreenIntPoint mPosition; + }; + + void CheckOutgoingEvents( + std::initializer_list<ExpectedOutgoingEvent> aExpectedEvents) { + auto outgoing = resampler.ConsumeOutgoingEvents(); + EXPECT_EQ(outgoing.size(), aExpectedEvents.size()); + for (const auto& expectedEvent : aExpectedEvents) { + auto outgoingEvent = std::move(outgoing.front()); + outgoing.pop(); + + EXPECT_EQ(outgoingEvent.mEventId, expectedEvent.mEventId); + CheckEvent(outgoingEvent.mEvent, expectedEvent.mType, + expectedEvent.mHistoricalData, expectedEvent.mTimeStamp, + expectedEvent.mPosition); + } + } + + TimeStamp baseTimeStamp; + TouchResampler resampler; +}; + +TEST_F(TouchResamplerTest, BasicExtrapolation) { + // Execute the following sequence: + // + // 0----------10-------16-----20---------------32------------ + // * touchstart at (10, 10) + // * touchmove at (20, 20) + // * frame + // * touchend at (20, 20) + // * frame + // + // And expect the following output: + // + // 0----------10-------16-----20---------------32------------ + // * touchstart at (10, 10) + // * touchmove at (26, 26) + // * touchmove at (20, 20) + // * touchend at (20, 20) + // + // The first frame should emit an extrapolated touchmove from the position + // data in the touchstart and touchmove events. + // The touchend should force a synthesized touchmove that returns back to a + // non-resampled position. + + EXPECT_FALSE(resampler.InTouchingState()); + + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(10, 10)); + EXPECT_TRUE(resampler.InTouchingState()); + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0), + ScreenIntPoint(20, 20)); + + resampler.NotifyFrame(Time(16.0)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(10, 10)}, + + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(20, 20)}}, + Time(16.0), + ScreenIntPoint(26, 26)}, + }); + + auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0), + ScreenIntPoint(20, 20)); + + EXPECT_FALSE(resampler.InTouchingState()); + + CheckOutgoingEvents({ + {Nothing(), + MultiTouchInput::MULTITOUCH_MOVE, + {}, + Time(16.0), + ScreenIntPoint(20, 20)}, + + {Some(idEnd2), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(20.0), + ScreenIntPoint(20, 20)}, + }); + + // No more events should be produced from here on out. + resampler.NotifyFrame(Time(32.0)); + auto outgoing = resampler.ConsumeOutgoingEvents(); + EXPECT_TRUE(outgoing.empty()); +} + +TEST_F(TouchResamplerTest, BasicInterpolation) { + // Same test as BasicExtrapolation, but with a frame time that's 10ms earlier. + // + // Execute the following sequence: + // + // 0------6---10-----------20--22------------30------------- + // * touchstart at (10, 10) + // * touchmove at (20, 20) + // * frame + // * touchend at (20, 20) + // * frame + // + // And expect the following output: + // + // 0------6---10-----------20--22------------30------------- + // * touchstart at (10, 10) + // * touchmove (16, 16) + // * touchmove (20, 20) + // * touchend at (20, 20) + // + // The first frame should emit an interpolated touchmove from the position + // data in the touchstart and touchmove events. + // The touchend should create a touchmove that returns back to a non-resampled + // position. + + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(10, 10)); + EXPECT_TRUE(resampler.InTouchingState()); + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0), + ScreenIntPoint(20, 20)); + + resampler.NotifyFrame(Time(6.0)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(10, 10)}, + + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {}, + Time(6.0), + ScreenIntPoint(16, 16)}, + }); + + auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0), + ScreenIntPoint(20, 20)); + EXPECT_FALSE(resampler.InTouchingState()); + + CheckOutgoingEvents({ + {Nothing(), + MultiTouchInput::MULTITOUCH_MOVE, + {}, + Time(10.0), + ScreenIntPoint(20, 20)}, + + {Some(idEnd2), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(20.0), + ScreenIntPoint(20, 20)}, + }); + + // No more events should be produced from here on out. + resampler.NotifyFrame(Time(22.0)); + auto outgoing = resampler.ConsumeOutgoingEvents(); + EXPECT_TRUE(outgoing.empty()); +} + +TEST_F(TouchResamplerTest, InterpolationFromHistoricalData) { + // Interpolate from the historical data in a touch move event. + // + // Execute the following sequence: + // + // 0----------10-------16-----20-----------30--32------------ + // * touchstart at (10, 10) + // * [hist] at (20, 25) for + // `---------------* touchmove at (30, 30) + // * frame + // * touchend at (30, 30) + // * frame + // + // And expect the following output: + // + // 0----------10-------16-----20-----------30--32------------ + // * touchstart at (10, 10) + // * [hist] at (20, 25) for + // `--------* touchmove at (26, 28) + // * touchmove at (30, 30) + // * touchend at (30, 30) + // + // The first frame should emit an interpolated touchmove from the position + // data in the touchmove event, and integrate the historical data point into + // the resampled event. + // The touchend should force a synthesized touchmove that returns back to a + // non-resampled position. + + // This also tests that interpolation works for both x and y, by giving the + // historical datapoint different values for x and y. + // (26, 28) is 60% of the way from (20, 25) to (30, 30). + + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(10, 10)); + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(20, 25)}}, + Time(20.0), ScreenIntPoint(30, 30)); + resampler.NotifyFrame(Time(16.0)); + auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(30.0), + ScreenIntPoint(30, 30)); + resampler.NotifyFrame(Time(32.0)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(10, 10)}, + + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(20, 25)}}, + Time(16.0), + ScreenIntPoint(26, 28)}, + + {Nothing(), + MultiTouchInput::MULTITOUCH_MOVE, + {}, + Time(20.0), + ScreenIntPoint(30, 30)}, + + {Some(idEnd2), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(30.0), + ScreenIntPoint(30, 30)}, + }); +} + +TEST_F(TouchResamplerTest, MultipleTouches) { + EXPECT_FALSE(resampler.InTouchingState()); + + // Touch start + MultiTouchInput inputStart0(MultiTouchInput::MULTITOUCH_START, 0, Time(0.0), + 0); + inputStart0.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(10, 10), {}, 0.0f, 0.0f)); + auto idStart0 = resampler.ProcessEvent(std::move(inputStart0)); + EXPECT_TRUE(resampler.InTouchingState()); + + // Touch move + MultiTouchInput inputMove1(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(20.0), + 0); + inputMove1.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(30, 30), {}, 0.0f, 0.0f)); + inputMove1.mTouches[0].mHistoricalData.AppendElement( + SingleTouchData::HistoricalTouchData{ + Time(10.0), ScreenIntPoint(20, 25), {}, {}, 0.0f, 0.0f}); + auto idMove1 = resampler.ProcessEvent(std::move(inputMove1)); + EXPECT_TRUE(resampler.InTouchingState()); + + // Frame + resampler.NotifyFrame(Time(16.0)); + + // Touch move + MultiTouchInput inputMove2(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(30.0), + 0); + inputMove2.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f)); + auto idMove2 = resampler.ProcessEvent(std::move(inputMove2)); + EXPECT_TRUE(resampler.InTouchingState()); + + // Touch start + MultiTouchInput inputStart3(MultiTouchInput::MULTITOUCH_START, 0, Time(30.0), + 0); + inputStart3.mTouches.AppendElement( + SingleTouchData(2, ScreenIntPoint(100, 10), {}, 0.0f, 0.0f)); + inputStart3.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f)); + auto idStart3 = resampler.ProcessEvent(std::move(inputStart3)); + EXPECT_TRUE(resampler.InTouchingState()); + + // Touch move + MultiTouchInput inputMove4(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(40.0), + 0); + inputMove4.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(30, 50), {}, 0.0f, 0.0f)); + inputMove4.mTouches.AppendElement( + SingleTouchData(2, ScreenIntPoint(100, 30), {}, 0.0f, 0.0f)); + auto idMove4 = resampler.ProcessEvent(std::move(inputMove4)); + EXPECT_TRUE(resampler.InTouchingState()); + + // Frame + resampler.NotifyFrame(Time(32.0)); + + // Touch move + MultiTouchInput inputMove5(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(50.0), + 0); + inputMove5.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f)); + inputMove5.mTouches.AppendElement( + SingleTouchData(2, ScreenIntPoint(100, 40), {}, 0.0f, 0.0f)); + auto idMove5 = resampler.ProcessEvent(std::move(inputMove5)); + EXPECT_TRUE(resampler.InTouchingState()); + + // Touch end + MultiTouchInput inputEnd6(MultiTouchInput::MULTITOUCH_END, 0, Time(50.0), 0); + // Touch point with identifier 1 is lifted + inputEnd6.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f)); + auto idEnd6 = resampler.ProcessEvent(std::move(inputEnd6)); + EXPECT_TRUE(resampler.InTouchingState()); + + // Frame + resampler.NotifyFrame(Time(48.0)); + + // Touch move + MultiTouchInput inputMove7(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(60.0), + 0); + inputMove7.mTouches.AppendElement( + SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f)); + auto idMove7 = resampler.ProcessEvent(std::move(inputMove7)); + EXPECT_TRUE(resampler.InTouchingState()); + + // Frame + resampler.NotifyFrame(Time(64.0)); + + // Touch end + MultiTouchInput inputEnd8(MultiTouchInput::MULTITOUCH_END, 0, Time(70.0), 0); + // Touch point with identifier 2 is lifted + inputEnd8.mTouches.AppendElement( + SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f)); + auto idEnd8 = resampler.ProcessEvent(std::move(inputEnd8)); + EXPECT_FALSE(resampler.InTouchingState()); + + // Check outgoing events + auto outgoing = resampler.ConsumeOutgoingEvents(); + EXPECT_EQ(outgoing.size(), size_t(9)); + + auto outgoingStart0 = std::move(outgoing.front()); + outgoing.pop(); + EXPECT_EQ(outgoingStart0.mEventId, Some(idStart0)); + CheckEvent(outgoingStart0.mEvent, MultiTouchInput::MULTITOUCH_START, {}, + Time(0.0), ScreenIntPoint(10, 10)); + + auto outgoingMove1 = std::move(outgoing.front()); + outgoing.pop(); + EXPECT_EQ(outgoingMove1.mEventId, Some(idMove1)); + // (26, 28) is 60% of the way from (20, 25) to (30, 30). + CheckEvent(outgoingMove1.mEvent, MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(20, 25)}}, Time(16.0), + ScreenIntPoint(26, 28)); + + auto outgoingMove2 = std::move(outgoing.front()); + outgoing.pop(); + EXPECT_EQ(outgoingMove2.mEventId, Some(idMove2)); + CheckEvent(outgoingMove2.mEvent, MultiTouchInput::MULTITOUCH_MOVE, + {{Time(20.0), ScreenIntPoint(30, 30)}}, Time(30.0), + ScreenIntPoint(30, 40)); + + auto outgoingStart3 = std::move(outgoing.front()); + outgoing.pop(); + EXPECT_EQ(outgoingStart3.mEventId, Some(idStart3)); + EXPECT_EQ(outgoingStart3.mEvent.mType, MultiTouchInput::MULTITOUCH_START); + CheckTime(outgoingStart3.mEvent.mTimeStamp, Time(30.0)); + EXPECT_EQ(outgoingStart3.mEvent.mTouches.Length(), size_t(2)); + // touch order should be taken from the original touch start event + EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mIdentifier, 2); + EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mScreenPoint, + ScreenIntPoint(100, 10)); + EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mIdentifier, 1); + EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mScreenPoint, + ScreenIntPoint(30, 40)); + + auto outgoingMove4 = std::move(outgoing.front()); + outgoing.pop(); + EXPECT_EQ(outgoingMove4.mEventId, Some(idMove4)); + EXPECT_EQ(outgoingMove4.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE); + CheckTime(outgoingMove4.mEvent.mTimeStamp, Time(32.0)); + EXPECT_EQ(outgoingMove4.mEvent.mTouches.Length(), size_t(2)); + // Touch order should be taken from the original touch move event. + // Both touches should be resampled. + EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mIdentifier, 1); + EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mScreenPoint, + ScreenIntPoint(30, 42)); + EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mIdentifier, 2); + EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mScreenPoint, + ScreenIntPoint(100, 14)); + + auto outgoingMove5 = std::move(outgoing.front()); + outgoing.pop(); + EXPECT_EQ(outgoingMove5.mEventId, Some(idMove5)); + EXPECT_EQ(outgoingMove5.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE); + CheckTime(outgoingMove5.mEvent.mTimeStamp, Time(50.0)); + EXPECT_EQ(outgoingMove5.mEvent.mTouches.Length(), size_t(2)); + // touch order should be taken from the original touch move event + EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mIdentifier, 1); + EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mScreenPoint, + ScreenIntPoint(30, 60)); + EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData.Length(), + size_t(1)); + CheckTime(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mTimeStamp, + Time(40.0)); + EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mScreenPoint, + ScreenIntPoint(30, 50)); + EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mIdentifier, 2); + EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mScreenPoint, + ScreenIntPoint(100, 40)); + EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData.Length(), + size_t(1)); + CheckTime(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mTimeStamp, + Time(40.0)); + EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mScreenPoint, + ScreenIntPoint(100, 30)); + + auto outgoingEnd6 = std::move(outgoing.front()); + outgoing.pop(); + EXPECT_EQ(outgoingEnd6.mEventId, Some(idEnd6)); + CheckEvent(outgoingEnd6.mEvent, MultiTouchInput::MULTITOUCH_END, {}, + Time(50.0), ScreenIntPoint(30, 60)); + + auto outgoingMove7 = std::move(outgoing.front()); + outgoing.pop(); + EXPECT_EQ(outgoingMove7.mEventId, Some(idMove7)); + // No extrapolation because the frame at 64.0 cleared the data points because + // there was no pending touch move event at that point + CheckEvent(outgoingMove7.mEvent, MultiTouchInput::MULTITOUCH_MOVE, {}, + Time(60.0), ScreenIntPoint(100, 60)); + EXPECT_EQ(outgoingMove7.mEvent.mTouches[0].mIdentifier, 2); + + auto outgoingEnd8 = std::move(outgoing.front()); + outgoing.pop(); + EXPECT_EQ(outgoingEnd8.mEventId, Some(idEnd8)); + CheckEvent(outgoingEnd8.mEvent, MultiTouchInput::MULTITOUCH_END, {}, + Time(70.0), ScreenIntPoint(100, 60)); +} + +TEST_F(TouchResamplerTest, MovingPauses) { + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(10, 10)); + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0), + ScreenIntPoint(20, 20)); + resampler.NotifyFrame(Time(16.0)); + auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(30.0), + ScreenIntPoint(40, 40)); + resampler.NotifyFrame(Time(32.0)); + auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0), + ScreenIntPoint(50, 40)); + resampler.NotifyFrame(Time(48.0)); + resampler.NotifyFrame(Time(64.0)); + auto idEnd4 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), + ScreenIntPoint(50, 40)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(10, 10)}, + + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(20, 20)}}, + Time(16.0), + ScreenIntPoint(26, 26)}, + + {Some(idMove2), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(30.0), ScreenIntPoint(40, 40)}}, + Time(32.0), + ScreenIntPoint(42, 42)}, + + {Some(idMove3), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(40.0), ScreenIntPoint(50, 40)}}, + Time(48.0), + ScreenIntPoint(58, 40)}, + + // There was no event between two frames here, so we expect a reset event, + // so that we pause at a non-resampled position. + {Nothing(), + MultiTouchInput::MULTITOUCH_MOVE, + {}, + Time(48.0), + ScreenIntPoint(50, 40)}, + + {Some(idEnd4), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(70.0), + ScreenIntPoint(50, 40)}, + }); +} + +TEST_F(TouchResamplerTest, MixedInterAndExtrapolation) { + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(0, 0)); + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0), + ScreenIntPoint(0, 10)); + resampler.NotifyFrame(Time(11.0)); // 16 - 5 + auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0), + ScreenIntPoint(0, 30)); + resampler.NotifyFrame(Time(27.0)); // 32 - 5 + auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0), + ScreenIntPoint(0, 40)); + resampler.NotifyFrame(Time(43.0)); // 48 - 5 + auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(50.0), ScreenIntPoint(0, 50)}}, Time(60.0), + ScreenIntPoint(0, 60)); + resampler.NotifyFrame(Time(59.0)); // 64 - 5 + auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), + ScreenIntPoint(0, 60)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(0, 0)}, + + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(0, 10)}}, + Time(11.0), + ScreenIntPoint(0, 11)}, + + {Some(idMove2), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(20.0), ScreenIntPoint(0, 20)}}, + Time(27.0), + ScreenIntPoint(0, 27)}, + + {Some(idMove3), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(30.0), ScreenIntPoint(0, 30)}, + {Time(40.0), ScreenIntPoint(0, 40)}}, + Time(43.0), + ScreenIntPoint(0, 43)}, + + {Some(idMove4), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(50.0), ScreenIntPoint(0, 50)}}, + Time(59.0), + ScreenIntPoint(0, 59)}, + + {Nothing(), + MultiTouchInput::MULTITOUCH_MOVE, + {}, + Time(60.0), + ScreenIntPoint(0, 60)}, + + {Some(idEnd5), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(70.0), + ScreenIntPoint(0, 60)}, + }); +} + +TEST_F(TouchResamplerTest, MultipleMoveEvents) { + // Test what happens if multiple touch move events appear between two frames. + // This scenario shouldn't occur on Android but we should be able to deal with + // it anyway. Check that we don't discard any event IDs. + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(0, 0)); + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0), + ScreenIntPoint(0, 10)); + resampler.NotifyFrame(Time(11.0)); // 16 - 5 + auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0), + ScreenIntPoint(0, 30)); + auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0), + ScreenIntPoint(0, 40)); + auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(45.0), ScreenIntPoint(0, 45)}}, Time(50.0), + ScreenIntPoint(0, 50)); + auto idMove5 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(55.0), + ScreenIntPoint(0, 55)); + resampler.NotifyFrame(Time(43.0)); // 48 - 5 + resampler.NotifyFrame(Time(59.0)); // 64 - 5 + auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), + ScreenIntPoint(0, 60)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(0, 0)}, + + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(0, 10)}}, + Time(11.0), + ScreenIntPoint(0, 11)}, + + {Some(idMove2), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(20.0), ScreenIntPoint(0, 20)}}, + Time(30.0), + ScreenIntPoint(0, 30)}, + + {Some(idMove3), + MultiTouchInput::MULTITOUCH_MOVE, + {}, + Time(40.0), + ScreenIntPoint(0, 40)}, + + {Some(idMove4), + MultiTouchInput::MULTITOUCH_MOVE, + {}, + Time(43.0), + ScreenIntPoint(0, 43)}, + + {Some(idMove5), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(45.0), ScreenIntPoint(0, 45)}, + {Time(50.0), ScreenIntPoint(0, 50)}, + {Time(55.0), ScreenIntPoint(0, 55)}}, + Time(59.0), + ScreenIntPoint(0, 59)}, + + {Nothing(), + MultiTouchInput::MULTITOUCH_MOVE, + {}, + Time(59.0), + ScreenIntPoint(0, 55)}, + + {Some(idEnd5), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(70.0), + ScreenIntPoint(0, 60)}, + }); +} + +TEST_F(TouchResamplerTest, LimitFuturePrediction) { + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(0, 0)); + // Fingers move until time 44, then pause. UI thread is occupied until 64. + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(20.0), ScreenIntPoint(0, 20)}, + {Time(32.0), ScreenIntPoint(0, 32)}}, + Time(44.0), ScreenIntPoint(0, 44)); + resampler.NotifyFrame(Time(59.0)); // 64 - 5 + auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), + ScreenIntPoint(0, 44)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(0, 0)}, + + // kTouchResampleMaxPredictMs == 8 + // Refuse to predict more than 8ms into the future, the fingers might have + // paused. Make an event for time 52 (= 44 + 8) instead of 59. + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(20.0), ScreenIntPoint(0, 20)}, + {Time(32.0), ScreenIntPoint(0, 32)}, + {Time(44.0), ScreenIntPoint(0, 44)}}, + Time(52.0), + ScreenIntPoint(0, 52)}, + + {Nothing(), + MultiTouchInput::MULTITOUCH_MOVE, + {}, + Time(52.0), + ScreenIntPoint(0, 44)}, + + {Some(idEnd2), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(70.0), + ScreenIntPoint(0, 44)}, + }); +} + +TEST_F(TouchResamplerTest, LimitBacksampling) { + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(0, 0)); + // Fingers move until time 44, then pause. UI thread is occupied until 64. + // Then we get a frame callback with a very outdated frametime. + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(20.0), ScreenIntPoint(0, 20)}, + {Time(32.0), ScreenIntPoint(0, 32)}}, + Time(44.0), ScreenIntPoint(0, 44)); + resampler.NotifyFrame(Time(11.0)); // 16 - 5 + auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), + ScreenIntPoint(0, 44)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(0, 0)}, + + // kTouchResampleMaxBacksampleMs == 20 + // Refuse to sample further back than 20ms before the last data point. + // Make an event for time 24 (= 44 - 20) instead of time 11. + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(20.0), ScreenIntPoint(0, 20)}}, + Time(24.0), + ScreenIntPoint(0, 24)}, + + {Nothing(), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(32.0), ScreenIntPoint(0, 32)}}, + Time(44.0), + ScreenIntPoint(0, 44)}, + + {Some(idEnd2), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(70.0), + ScreenIntPoint(0, 44)}, + }); +} + +TEST_F(TouchResamplerTest, DontExtrapolateFromOldTouch) { + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(0, 0)); + // Fingers move until time 40, then pause. UI thread is occupied until 64. + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(20.0), ScreenIntPoint(0, 20)}, + {Time(30.0), ScreenIntPoint(0, 30)}}, + Time(40.0), ScreenIntPoint(0, 40)); + resampler.NotifyFrame(Time(59.0)); // 64 - 5 + auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), + ScreenIntPoint(0, 44)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(0, 0)}, + + // kTouchResampleOldTouchThresholdMs == 17 + // Refuse to extrapolate from a data point that's more than 17ms older + // than the frame time. + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(20.0), ScreenIntPoint(0, 20)}, + {Time(30.0), ScreenIntPoint(0, 30)}}, + Time(40.0), + ScreenIntPoint(0, 40)}, + + {Some(idEnd2), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(70.0), + ScreenIntPoint(0, 44)}, + }); +} + +TEST_F(TouchResamplerTest, DontExtrapolateIfTooOld) { + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(0, 0)); + // Fingers move until time 10, pause, and move again at 55. + // UI thread is occupied until 64. + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(55.0), + ScreenIntPoint(0, 55)); + resampler.NotifyFrame(Time(59.0)); // 64 - 5 + auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), + ScreenIntPoint(0, 60)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(0, 0)}, + + // kTouchResampleWindowSize == 40 + // Refuse to resample between two data points that are more than 40ms + // apart. + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(0, 10)}}, + Time(55.0), + ScreenIntPoint(0, 55)}, + + {Some(idEnd2), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(70.0), + ScreenIntPoint(0, 60)}, + }); +} + +TEST_F(TouchResamplerTest, DontInterpolateIfTooOld) { + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(0, 0)); + // Fingers move until time 10, pause, and move again at 60. + // UI thread is occupied until 64. + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(60.0), + ScreenIntPoint(0, 60)); + resampler.NotifyFrame(Time(59.0)); // 64 - 5 + auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), + ScreenIntPoint(0, 60)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(0, 0)}, + + // kTouchResampleWindowSize == 40 + // Refuse to resample between two data points that are more than 40ms + // apart. + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(0, 10)}}, + Time(60.0), + ScreenIntPoint(0, 60)}, + + {Some(idEnd2), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(70.0), + ScreenIntPoint(0, 60)}, + }); +} + +TEST_F(TouchResamplerTest, DiscardOutdatedHistoricalData) { + auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), + ScreenIntPoint(0, 0)); + auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(16.0), + ScreenIntPoint(0, 16)); + resampler.NotifyFrame(Time(20.0)); + auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, + {{Time(18.0), ScreenIntPoint(0, 18)}}, Time(25.0), + ScreenIntPoint(0, 25)); + auto idEnd3 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(35.0), + ScreenIntPoint(0, 25)); + + CheckOutgoingEvents({ + {Some(idStart0), + MultiTouchInput::MULTITOUCH_START, + {}, + Time(0.0), + ScreenIntPoint(0, 0)}, + + {Some(idMove1), + MultiTouchInput::MULTITOUCH_MOVE, + {{Time(10.0), ScreenIntPoint(0, 10)}, + {Time(16.0), ScreenIntPoint(0, 16)}}, + Time(20.0), + ScreenIntPoint(0, 20)}, + + // Discard the historical data point from time 18, because we've already + // sent out an event with time 20 and don't want to go back before that. + {Some(idMove2), + MultiTouchInput::MULTITOUCH_MOVE, + {}, + Time(25.0), + ScreenIntPoint(0, 25)}, + + {Some(idEnd3), + MultiTouchInput::MULTITOUCH_END, + {}, + Time(35.0), + ScreenIntPoint(0, 25)}, + }); +} |