This reverts commit 9133deb9a714b0853faeed4892b220ae7a77e5a5.
This commit is contained in:
parent
9133deb9a7
commit
50897c98fc
@ -21,24 +21,13 @@ import 'resampler.dart';
|
|||||||
|
|
||||||
typedef _HandleSampleTimeChangedCallback = void Function();
|
typedef _HandleSampleTimeChangedCallback = void Function();
|
||||||
|
|
||||||
/// Class that implements clock used for sampling.
|
|
||||||
class SamplingClock {
|
|
||||||
/// Returns current time.
|
|
||||||
DateTime now() => DateTime.now();
|
|
||||||
|
|
||||||
/// Returns a new stopwatch that uses the current time as reported by `this`.
|
|
||||||
Stopwatch stopwatch() => Stopwatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Class that handles resampling of touch events for multiple pointer
|
// Class that handles resampling of touch events for multiple pointer
|
||||||
// devices.
|
// devices.
|
||||||
//
|
//
|
||||||
// The `samplingInterval` is used to determine the approximate next
|
|
||||||
// time for resampling.
|
|
||||||
// SchedulerBinding's `currentSystemFrameTimeStamp` is used to determine
|
// SchedulerBinding's `currentSystemFrameTimeStamp` is used to determine
|
||||||
// sample time.
|
// sample time.
|
||||||
class _Resampler {
|
class _Resampler {
|
||||||
_Resampler(this._handlePointerEvent, this._handleSampleTimeChanged, this._samplingInterval);
|
_Resampler(this._handlePointerEvent, this._handleSampleTimeChanged);
|
||||||
|
|
||||||
// Resamplers used to filter incoming pointer events.
|
// Resamplers used to filter incoming pointer events.
|
||||||
final Map<int, PointerEventResampler> _resamplers = <int, PointerEventResampler>{};
|
final Map<int, PointerEventResampler> _resamplers = <int, PointerEventResampler>{};
|
||||||
@ -46,12 +35,9 @@ class _Resampler {
|
|||||||
// Flag to track if a frame callback has been scheduled.
|
// Flag to track if a frame callback has been scheduled.
|
||||||
bool _frameCallbackScheduled = false;
|
bool _frameCallbackScheduled = false;
|
||||||
|
|
||||||
// Last frame time for resampling.
|
// Current frame time for resampling.
|
||||||
Duration _frameTime = Duration.zero;
|
Duration _frameTime = Duration.zero;
|
||||||
|
|
||||||
// Time since `_frameTime` was updated.
|
|
||||||
Stopwatch _frameTimeAge = Stopwatch();
|
|
||||||
|
|
||||||
// Last sample time and time stamp of last event.
|
// Last sample time and time stamp of last event.
|
||||||
//
|
//
|
||||||
// Only used for debugPrint of resampling margin.
|
// Only used for debugPrint of resampling margin.
|
||||||
@ -64,12 +50,6 @@ class _Resampler {
|
|||||||
// Callback used to handle sample time changes.
|
// Callback used to handle sample time changes.
|
||||||
final _HandleSampleTimeChangedCallback _handleSampleTimeChanged;
|
final _HandleSampleTimeChangedCallback _handleSampleTimeChanged;
|
||||||
|
|
||||||
// Interval used for sampling.
|
|
||||||
final Duration _samplingInterval;
|
|
||||||
|
|
||||||
// Timer used to schedule resampling.
|
|
||||||
Timer? _timer;
|
|
||||||
|
|
||||||
// Add `event` for resampling or dispatch it directly if
|
// Add `event` for resampling or dispatch it directly if
|
||||||
// not a touch event.
|
// not a touch event.
|
||||||
void addOrDispatch(PointerEvent event) {
|
void addOrDispatch(PointerEvent event) {
|
||||||
@ -92,43 +72,25 @@ class _Resampler {
|
|||||||
|
|
||||||
// Sample and dispatch events.
|
// Sample and dispatch events.
|
||||||
//
|
//
|
||||||
// The `samplingOffset` is relative to the current frame time, which
|
// `samplingOffset` is relative to the current frame time, which
|
||||||
// can be in the past when we're not actively resampling.
|
// can be in the past when we're not actively resampling.
|
||||||
// The `samplingClock` is the clock used to determine frame time age.
|
// `samplingInterval` is used to determine the approximate next
|
||||||
void sample(Duration samplingOffset, SamplingClock clock) {
|
// time for resampling.
|
||||||
|
// `currentSystemFrameTimeStamp` is used to determine the current
|
||||||
|
// frame time.
|
||||||
|
void sample(Duration samplingOffset, Duration samplingInterval) {
|
||||||
final SchedulerBinding? scheduler = SchedulerBinding.instance;
|
final SchedulerBinding? scheduler = SchedulerBinding.instance;
|
||||||
assert(scheduler != null);
|
assert(scheduler != null);
|
||||||
|
|
||||||
// Initialize `_frameTime` if needed. This will be used for periodic
|
|
||||||
// sampling when frame callbacks are not received.
|
|
||||||
if (_frameTime == Duration.zero) {
|
|
||||||
_frameTime = Duration(milliseconds: clock.now().millisecondsSinceEpoch);
|
|
||||||
_frameTimeAge = clock.stopwatch()..start();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule periodic resampling if `_timer` is not already active.
|
|
||||||
if (_timer?.isActive == false) {
|
|
||||||
_timer = Timer.periodic(_samplingInterval, (_) => _onSampleTimeChanged());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the effective frame time by taking the number
|
|
||||||
// of sampling intervals since last time `_frameTime` was
|
|
||||||
// updated into account. This allows us to advance sample
|
|
||||||
// time without having to receive frame callbacks.
|
|
||||||
final int samplingIntervalUs = _samplingInterval.inMicroseconds;
|
|
||||||
final int elapsedIntervals = _frameTimeAge.elapsedMicroseconds ~/ samplingIntervalUs;
|
|
||||||
final int elapsedUs = elapsedIntervals * samplingIntervalUs;
|
|
||||||
final Duration frameTime = _frameTime + Duration(microseconds: elapsedUs);
|
|
||||||
|
|
||||||
// Determine sample time by adding the offset to the current
|
// Determine sample time by adding the offset to the current
|
||||||
// frame time. This is expected to be in the past and not
|
// frame time. This is expected to be in the past and not
|
||||||
// result in any dispatched events unless we're actively
|
// result in any dispatched events unless we're actively
|
||||||
// resampling events.
|
// resampling events.
|
||||||
final Duration sampleTime = frameTime + samplingOffset;
|
final Duration sampleTime = _frameTime + samplingOffset;
|
||||||
|
|
||||||
// Determine next sample time by adding the sampling interval
|
// Determine next sample time by adding the sampling interval
|
||||||
// to the current sample time.
|
// to the current sample time.
|
||||||
final Duration nextSampleTime = sampleTime + _samplingInterval;
|
final Duration nextSampleTime = sampleTime + samplingInterval;
|
||||||
|
|
||||||
// Iterate over active resamplers and sample pointer events for
|
// Iterate over active resamplers and sample pointer events for
|
||||||
// current sample time.
|
// current sample time.
|
||||||
@ -144,30 +106,23 @@ class _Resampler {
|
|||||||
// Save last sample time for debugPrint of resampling margin.
|
// Save last sample time for debugPrint of resampling margin.
|
||||||
_lastSampleTime = sampleTime;
|
_lastSampleTime = sampleTime;
|
||||||
|
|
||||||
// Early out if another call to `sample` isn't needed.
|
|
||||||
if (_resamplers.isEmpty) {
|
|
||||||
_timer!.cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule a frame callback if another call to `sample` is needed.
|
// Schedule a frame callback if another call to `sample` is needed.
|
||||||
if (!_frameCallbackScheduled) {
|
if (!_frameCallbackScheduled && _resamplers.isNotEmpty) {
|
||||||
_frameCallbackScheduled = true;
|
_frameCallbackScheduled = true;
|
||||||
// Add a post frame callback as this avoids producing unnecessary
|
scheduler?.scheduleFrameCallback((_) {
|
||||||
// frames but ensures that sampling phase is adjusted to frame
|
|
||||||
// time when frames are produced.
|
|
||||||
scheduler?.addPostFrameCallback((_) {
|
|
||||||
_frameCallbackScheduled = false;
|
_frameCallbackScheduled = false;
|
||||||
// We use `currentSystemFrameTimeStamp` here as it's critical that
|
// We use `currentSystemFrameTimeStamp` here as it's critical that
|
||||||
// sample time is in the same clock as the event time stamps, and
|
// sample time is in the same clock as the event time stamps, and
|
||||||
// never adjusted or scaled like `currentFrameTimeStamp`.
|
// never adjusted or scaled like `currentFrameTimeStamp`.
|
||||||
_frameTime = scheduler.currentSystemFrameTimeStamp;
|
_frameTime = scheduler.currentSystemFrameTimeStamp;
|
||||||
_frameTimeAge.reset();
|
assert(() {
|
||||||
// Reset timer to match phase of latest frame callback.
|
if (debugPrintResamplingMargin) {
|
||||||
_timer?.cancel();
|
final Duration resamplingMargin = _lastEventTime - _lastSampleTime;
|
||||||
_timer = Timer.periodic(_samplingInterval, (_) => _onSampleTimeChanged());
|
debugPrint('$resamplingMargin');
|
||||||
// Trigger an immediate sample time change.
|
}
|
||||||
_onSampleTimeChanged();
|
return true;
|
||||||
|
}());
|
||||||
|
_handleSampleTimeChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,18 +133,6 @@ class _Resampler {
|
|||||||
resampler.stop(_handlePointerEvent);
|
resampler.stop(_handlePointerEvent);
|
||||||
}
|
}
|
||||||
_resamplers.clear();
|
_resamplers.clear();
|
||||||
_frameTime = Duration.zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onSampleTimeChanged() {
|
|
||||||
assert(() {
|
|
||||||
if (debugPrintResamplingMargin) {
|
|
||||||
final Duration resamplingMargin = _lastEventTime - _lastSampleTime;
|
|
||||||
debugPrint('$resamplingMargin');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
_handleSampleTimeChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,8 +147,7 @@ const Duration _defaultSamplingOffset = Duration(milliseconds: -38);
|
|||||||
// The sampling interval.
|
// The sampling interval.
|
||||||
//
|
//
|
||||||
// Sampling interval is used to determine the approximate time for subsequent
|
// Sampling interval is used to determine the approximate time for subsequent
|
||||||
// sampling. This is used to sample events when frame callbacks are not
|
// sampling. This is used to decide if early processing of up and removed events
|
||||||
// being received and decide if early processing of up and removed events
|
|
||||||
// is appropriate. 16667 us for 60hz sampling interval.
|
// is appropriate. 16667 us for 60hz sampling interval.
|
||||||
const Duration _samplingInterval = Duration(microseconds: 16667);
|
const Duration _samplingInterval = Duration(microseconds: 16667);
|
||||||
|
|
||||||
@ -328,7 +270,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
|
|||||||
|
|
||||||
if (resamplingEnabled) {
|
if (resamplingEnabled) {
|
||||||
_resampler.addOrDispatch(event);
|
_resampler.addOrDispatch(event);
|
||||||
_resampler.sample(samplingOffset, _samplingClock);
|
_resampler.sample(samplingOffset, _samplingInterval);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,16 +398,10 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
|
|||||||
_hitTests.clear();
|
_hitTests.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Overrides the sampling clock for debugging and testing.
|
|
||||||
///
|
|
||||||
/// This value is ignored in non-debug builds.
|
|
||||||
@protected
|
|
||||||
SamplingClock? get debugSamplingClock => null;
|
|
||||||
|
|
||||||
void _handleSampleTimeChanged() {
|
void _handleSampleTimeChanged() {
|
||||||
if (!locked) {
|
if (!locked) {
|
||||||
if (resamplingEnabled) {
|
if (resamplingEnabled) {
|
||||||
_resampler.sample(samplingOffset, _samplingClock);
|
_resampler.sample(samplingOffset, _samplingInterval);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
_resampler.stop();
|
_resampler.stop();
|
||||||
@ -473,23 +409,11 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SamplingClock get _samplingClock {
|
|
||||||
SamplingClock value = SamplingClock();
|
|
||||||
assert(() {
|
|
||||||
final SamplingClock? debugValue = debugSamplingClock;
|
|
||||||
if (debugValue != null)
|
|
||||||
value = debugValue;
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resampler used to filter incoming pointer events when resampling
|
// Resampler used to filter incoming pointer events when resampling
|
||||||
// is enabled.
|
// is enabled.
|
||||||
late final _Resampler _resampler = _Resampler(
|
late final _Resampler _resampler = _Resampler(
|
||||||
_handlePointerEventImmediately,
|
_handlePointerEventImmediately,
|
||||||
_handleSampleTimeChanged,
|
_handleSampleTimeChanged,
|
||||||
_samplingInterval,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Enable pointer event resampling for touch devices by setting
|
/// Enable pointer event resampling for touch devices by setting
|
||||||
|
@ -10,35 +10,15 @@
|
|||||||
|
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
class TestResampleEventFlutterBinding extends AutomatedTestWidgetsFlutterBinding {
|
|
||||||
@override
|
|
||||||
SamplingClock? get debugSamplingClock => TestSamplingClock(this.clock);
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestSamplingClock implements SamplingClock {
|
|
||||||
TestSamplingClock(this._clock);
|
|
||||||
|
|
||||||
@override
|
|
||||||
DateTime now() => _clock.now();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Stopwatch stopwatch() => _clock.stopwatch();
|
|
||||||
|
|
||||||
final Clock _clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
final TestWidgetsFlutterBinding binding = TestResampleEventFlutterBinding();
|
final TestWidgetsFlutterBinding binding = AutomatedTestWidgetsFlutterBinding();
|
||||||
testWidgets('PointerEvent resampling on a widget', (WidgetTester tester) async {
|
testWidgets('PointerEvent resampling on a widget', (WidgetTester tester) async {
|
||||||
assert(WidgetsBinding.instance == binding);
|
assert(WidgetsBinding.instance == binding);
|
||||||
Duration currentTestFrameTime() => Duration(milliseconds: binding.clock.now().millisecondsSinceEpoch);
|
Duration currentTestFrameTime() => Duration(milliseconds: binding.clock.now().millisecondsSinceEpoch);
|
||||||
void requestFrame() => SchedulerBinding.instance!.scheduleFrameCallback((_) {});
|
|
||||||
final Duration epoch = currentTestFrameTime();
|
final Duration epoch = currentTestFrameTime();
|
||||||
final ui.PointerDataPacket packet = ui.PointerDataPacket(
|
final ui.PointerDataPacket packet = ui.PointerDataPacket(
|
||||||
data: <ui.PointerData>[
|
data: <ui.PointerData>[
|
||||||
@ -50,37 +30,37 @@ void main() {
|
|||||||
ui.PointerData(
|
ui.PointerData(
|
||||||
change: ui.PointerChange.down,
|
change: ui.PointerChange.down,
|
||||||
physicalX: 0.0,
|
physicalX: 0.0,
|
||||||
timeStamp: epoch + const Duration(milliseconds: 0),
|
|
||||||
),
|
|
||||||
ui.PointerData(
|
|
||||||
change: ui.PointerChange.move,
|
|
||||||
physicalX: 15.0,
|
|
||||||
timeStamp: epoch + const Duration(milliseconds: 10),
|
timeStamp: epoch + const Duration(milliseconds: 10),
|
||||||
),
|
),
|
||||||
ui.PointerData(
|
ui.PointerData(
|
||||||
change: ui.PointerChange.move,
|
change: ui.PointerChange.move,
|
||||||
physicalX: 30.0,
|
physicalX: 10.0,
|
||||||
timeStamp: epoch + const Duration(milliseconds: 20),
|
timeStamp: epoch + const Duration(milliseconds: 20),
|
||||||
),
|
),
|
||||||
ui.PointerData(
|
ui.PointerData(
|
||||||
change: ui.PointerChange.move,
|
change: ui.PointerChange.move,
|
||||||
physicalX: 45.0,
|
physicalX: 20.0,
|
||||||
timeStamp: epoch + const Duration(milliseconds: 30),
|
timeStamp: epoch + const Duration(milliseconds: 30),
|
||||||
),
|
),
|
||||||
ui.PointerData(
|
ui.PointerData(
|
||||||
change: ui.PointerChange.move,
|
change: ui.PointerChange.move,
|
||||||
physicalX: 50.0,
|
physicalX: 30.0,
|
||||||
timeStamp: epoch + const Duration(milliseconds: 40),
|
timeStamp: epoch + const Duration(milliseconds: 40),
|
||||||
),
|
),
|
||||||
|
ui.PointerData(
|
||||||
|
change: ui.PointerChange.move,
|
||||||
|
physicalX: 40.0,
|
||||||
|
timeStamp: epoch + const Duration(milliseconds: 50),
|
||||||
|
),
|
||||||
ui.PointerData(
|
ui.PointerData(
|
||||||
change: ui.PointerChange.up,
|
change: ui.PointerChange.up,
|
||||||
physicalX: 60.0,
|
physicalX: 40.0,
|
||||||
timeStamp: epoch + const Duration(milliseconds: 40),
|
timeStamp: epoch + const Duration(milliseconds: 60),
|
||||||
),
|
),
|
||||||
ui.PointerData(
|
ui.PointerData(
|
||||||
change: ui.PointerChange.remove,
|
change: ui.PointerChange.remove,
|
||||||
physicalX: 60.0,
|
physicalX: 40.0,
|
||||||
timeStamp: epoch + const Duration(milliseconds: 40),
|
timeStamp: epoch + const Duration(milliseconds: 70),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -104,31 +84,29 @@ void main() {
|
|||||||
ui.window.onPointerDataPacket!(packet);
|
ui.window.onPointerDataPacket!(packet);
|
||||||
expect(events.length, 0);
|
expect(events.length, 0);
|
||||||
|
|
||||||
requestFrame();
|
await tester.pump(const Duration(milliseconds: 20));
|
||||||
await tester.pump(const Duration(milliseconds: 10));
|
|
||||||
expect(events.length, 1);
|
expect(events.length, 1);
|
||||||
expect(events[0], isA<PointerDownEvent>());
|
expect(events[0], isA<PointerDownEvent>());
|
||||||
expect(events[0].timeStamp, currentTestFrameTime() + kSamplingOffset);
|
expect(events[0].timeStamp, currentTestFrameTime() + kSamplingOffset);
|
||||||
expect(events[0].position, Offset(7.5 / ui.window.devicePixelRatio, 0.0));
|
expect(events[0].position, Offset(5.0 / ui.window.devicePixelRatio, 0.0));
|
||||||
|
|
||||||
// Now the system time is epoch + 20ms
|
// Now the system time is epoch + 40ms
|
||||||
requestFrame();
|
await tester.pump(const Duration(milliseconds: 20));
|
||||||
await tester.pump(const Duration(milliseconds: 10));
|
|
||||||
expect(events.length, 2);
|
expect(events.length, 2);
|
||||||
expect(events[1].timeStamp, currentTestFrameTime() + kSamplingOffset);
|
expect(events[1].timeStamp, currentTestFrameTime() + kSamplingOffset);
|
||||||
expect(events[1], isA<PointerMoveEvent>());
|
expect(events[1], isA<PointerMoveEvent>());
|
||||||
expect(events[1].position, Offset(22.5 / ui.window.devicePixelRatio, 0.0));
|
expect(events[1].position, Offset(25.0 / ui.window.devicePixelRatio, 0.0));
|
||||||
expect(events[1].delta, Offset(15.0 / ui.window.devicePixelRatio, 0.0));
|
expect(events[1].delta, Offset(20.0 / ui.window.devicePixelRatio, 0.0));
|
||||||
|
|
||||||
// Now the system time is epoch + 30ms
|
// Now the system time is epoch + 60ms
|
||||||
requestFrame();
|
await tester.pump(const Duration(milliseconds: 20));
|
||||||
await tester.pump(const Duration(milliseconds: 10));
|
|
||||||
expect(events.length, 4);
|
expect(events.length, 4);
|
||||||
expect(events[2].timeStamp, currentTestFrameTime() + kSamplingOffset);
|
expect(events[2].timeStamp, currentTestFrameTime() + kSamplingOffset);
|
||||||
expect(events[2], isA<PointerMoveEvent>());
|
expect(events[2], isA<PointerMoveEvent>());
|
||||||
expect(events[2].position, Offset(37.5 / ui.window.devicePixelRatio, 0.0));
|
expect(events[2].position, Offset(40.0 / ui.window.devicePixelRatio, 0.0));
|
||||||
expect(events[2].delta, Offset(15.0 / ui.window.devicePixelRatio, 0.0));
|
expect(events[2].delta, Offset(15.0 / ui.window.devicePixelRatio, 0.0));
|
||||||
expect(events[3].timeStamp, currentTestFrameTime() + kSamplingOffset);
|
expect(events[3].timeStamp, currentTestFrameTime() + kSamplingOffset);
|
||||||
expect(events[3], isA<PointerUpEvent>());
|
expect(events[3], isA<PointerUpEvent>());
|
||||||
|
expect(events[3].position, Offset(40.0 / ui.window.devicePixelRatio, 0.0));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,186 +0,0 @@
|
|||||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
|
||||||
import 'package:fake_async/fake_async.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/scheduler.dart';
|
|
||||||
|
|
||||||
import '../flutter_test_alternative.dart';
|
|
||||||
|
|
||||||
typedef HandleEventCallback = void Function(PointerEvent event);
|
|
||||||
|
|
||||||
class TestResampleEventFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding {
|
|
||||||
HandleEventCallback? callback;
|
|
||||||
FrameCallback? postFrameCallback;
|
|
||||||
Duration? frameTime;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void handleEvent(PointerEvent event, HitTestEntry entry) {
|
|
||||||
super.handleEvent(event, entry);
|
|
||||||
if (callback != null)
|
|
||||||
callback?.call(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Duration get currentSystemFrameTimeStamp {
|
|
||||||
assert(frameTime != null);
|
|
||||||
return frameTime!;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int addPostFrameCallback(FrameCallback callback) {
|
|
||||||
postFrameCallback = callback;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
SamplingClock? get debugSamplingClock => TestSamplingClock();
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestSamplingClock implements SamplingClock {
|
|
||||||
@override
|
|
||||||
DateTime now() => clock.now();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Stopwatch stopwatch() => clock.stopwatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef ResampleEventTest = void Function(FakeAsync async);
|
|
||||||
|
|
||||||
void testResampleEvent(String description, ResampleEventTest callback) {
|
|
||||||
test(description, () {
|
|
||||||
fakeAsync((FakeAsync async) {
|
|
||||||
callback(async);
|
|
||||||
}, initialTime: DateTime.utc(2015, 1, 1));
|
|
||||||
}, skip: isBrowser); // Fake clock is not working with the web platform.
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
final TestResampleEventFlutterBinding binding = TestResampleEventFlutterBinding();
|
|
||||||
testResampleEvent('Pointer event resampling', (FakeAsync async) {
|
|
||||||
Duration currentTime() => Duration(milliseconds: clock.now().millisecondsSinceEpoch);
|
|
||||||
final Duration epoch = currentTime();
|
|
||||||
final ui.PointerDataPacket packet = ui.PointerDataPacket(
|
|
||||||
data: <ui.PointerData>[
|
|
||||||
ui.PointerData(
|
|
||||||
change: ui.PointerChange.add,
|
|
||||||
physicalX: 0.0,
|
|
||||||
timeStamp: epoch + const Duration(milliseconds: 0),
|
|
||||||
),
|
|
||||||
ui.PointerData(
|
|
||||||
change: ui.PointerChange.down,
|
|
||||||
physicalX: 0.0,
|
|
||||||
timeStamp: epoch + const Duration(milliseconds: 10),
|
|
||||||
),
|
|
||||||
ui.PointerData(
|
|
||||||
change: ui.PointerChange.move,
|
|
||||||
physicalX: 10.0,
|
|
||||||
timeStamp: epoch + const Duration(milliseconds: 20),
|
|
||||||
),
|
|
||||||
ui.PointerData(
|
|
||||||
change: ui.PointerChange.move,
|
|
||||||
physicalX: 20.0,
|
|
||||||
timeStamp: epoch + const Duration(milliseconds: 30),
|
|
||||||
),
|
|
||||||
ui.PointerData(
|
|
||||||
change: ui.PointerChange.move,
|
|
||||||
physicalX: 30.0,
|
|
||||||
timeStamp: epoch + const Duration(milliseconds: 40),
|
|
||||||
),
|
|
||||||
ui.PointerData(
|
|
||||||
change: ui.PointerChange.move,
|
|
||||||
physicalX: 40.0,
|
|
||||||
timeStamp: epoch + const Duration(milliseconds: 50),
|
|
||||||
),
|
|
||||||
ui.PointerData(
|
|
||||||
change: ui.PointerChange.move,
|
|
||||||
physicalX: 50.0,
|
|
||||||
timeStamp: epoch + const Duration(milliseconds: 60),
|
|
||||||
),
|
|
||||||
ui.PointerData(
|
|
||||||
change: ui.PointerChange.up,
|
|
||||||
physicalX: 50.0,
|
|
||||||
timeStamp: epoch + const Duration(milliseconds: 70),
|
|
||||||
),
|
|
||||||
ui.PointerData(
|
|
||||||
change: ui.PointerChange.remove,
|
|
||||||
physicalX: 50.0,
|
|
||||||
timeStamp: epoch + const Duration(milliseconds: 70),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const Duration samplingOffset = Duration(milliseconds: -5);
|
|
||||||
const Duration frameInterval = Duration(microseconds: 16667);
|
|
||||||
|
|
||||||
GestureBinding.instance!.resamplingEnabled = true;
|
|
||||||
GestureBinding.instance!.samplingOffset = samplingOffset;
|
|
||||||
|
|
||||||
final List<PointerEvent> events = <PointerEvent>[];
|
|
||||||
binding.callback = events.add;
|
|
||||||
|
|
||||||
ui.window.onPointerDataPacket?.call(packet);
|
|
||||||
|
|
||||||
// No pointer events should have been dispatched yet.
|
|
||||||
expect(events.length, 0);
|
|
||||||
|
|
||||||
// Frame callback should have been requested.
|
|
||||||
FrameCallback? callback = binding.postFrameCallback;
|
|
||||||
binding.postFrameCallback = null;
|
|
||||||
expect(callback, isNotNull);
|
|
||||||
|
|
||||||
binding.frameTime = epoch + const Duration(milliseconds: 15);
|
|
||||||
callback!(Duration.zero);
|
|
||||||
|
|
||||||
// One pointer event should have been dispatched.
|
|
||||||
expect(events.length, 1);
|
|
||||||
expect(events[0], isA<PointerDownEvent>());
|
|
||||||
expect(events[0].timeStamp, binding.frameTime! + samplingOffset);
|
|
||||||
expect(events[0].position, Offset(0.0 / ui.window.devicePixelRatio, 0.0));
|
|
||||||
|
|
||||||
// Second frame callback should have been requested.
|
|
||||||
callback = binding.postFrameCallback;
|
|
||||||
binding.postFrameCallback = null;
|
|
||||||
expect(callback, isNotNull);
|
|
||||||
|
|
||||||
final Duration frameTime = epoch + const Duration(milliseconds: 25);
|
|
||||||
binding.frameTime = frameTime;
|
|
||||||
callback!(Duration.zero);
|
|
||||||
|
|
||||||
// Second pointer event should have been dispatched.
|
|
||||||
expect(events.length, 2);
|
|
||||||
expect(events[1], isA<PointerMoveEvent>());
|
|
||||||
expect(events[1].timeStamp, binding.frameTime! + samplingOffset);
|
|
||||||
expect(events[1].position, Offset(10.0 / ui.window.devicePixelRatio, 0.0));
|
|
||||||
expect(events[1].delta, Offset(10.0 / ui.window.devicePixelRatio, 0.0));
|
|
||||||
|
|
||||||
// Verify that resampling continues without a frame callback.
|
|
||||||
async.elapse(frameInterval * 1.5);
|
|
||||||
|
|
||||||
// Third pointer event should have been dispatched.
|
|
||||||
expect(events.length, 3);
|
|
||||||
expect(events[2], isA<PointerMoveEvent>());
|
|
||||||
expect(events[2].timeStamp, frameTime + frameInterval + samplingOffset);
|
|
||||||
|
|
||||||
async.elapse(frameInterval);
|
|
||||||
|
|
||||||
// Remaining pointer events should have been dispatched.
|
|
||||||
expect(events.length, 5);
|
|
||||||
expect(events[3], isA<PointerMoveEvent>());
|
|
||||||
expect(events[3].timeStamp, frameTime + frameInterval * 2 + samplingOffset);
|
|
||||||
expect(events[4], isA<PointerUpEvent>());
|
|
||||||
expect(events[4].timeStamp, frameTime + frameInterval * 2 + samplingOffset);
|
|
||||||
|
|
||||||
async.elapse(frameInterval);
|
|
||||||
|
|
||||||
// No more pointer events should have been dispatched.
|
|
||||||
expect(events.length, 5);
|
|
||||||
|
|
||||||
GestureBinding.instance!.resamplingEnabled = false;
|
|
||||||
});
|
|
||||||
}
|
|
@ -14,6 +14,8 @@ typedef HandleEventCallback = void Function(PointerEvent event);
|
|||||||
|
|
||||||
class TestGestureFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding {
|
class TestGestureFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding {
|
||||||
HandleEventCallback? callback;
|
HandleEventCallback? callback;
|
||||||
|
FrameCallback? frameCallback;
|
||||||
|
Duration? frameTime;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleEvent(PointerEvent event, HitTestEntry entry) {
|
void handleEvent(PointerEvent event, HitTestEntry entry) {
|
||||||
@ -21,6 +23,18 @@ class TestGestureFlutterBinding extends BindingBase with GestureBinding, Schedul
|
|||||||
if (callback != null)
|
if (callback != null)
|
||||||
callback?.call(event);
|
callback?.call(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration get currentSystemFrameTimeStamp {
|
||||||
|
assert(frameTime != null);
|
||||||
|
return frameTime!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int scheduleFrameCallback(FrameCallback callback, {bool rescheduling = false}) {
|
||||||
|
frameCallback = callback;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TestGestureFlutterBinding? _binding;
|
TestGestureFlutterBinding? _binding;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user