Skip to content

Commit 4c63a23

Browse files
committed
perf: add unified PageTurnPerf logs for page-turn jank
1 parent 457252c commit 4c63a23

8 files changed

Lines changed: 346 additions & 36 deletions

lib/animation/simulation_turn_page_animation.dart

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:math' as math;
22

33
import 'package:book/animation/base_animation_page.dart';
4+
import 'package:book/common/page_turn_perf.dart';
45
import 'package:book/common/read_setting.dart';
56
import 'package:book/view/page_turn/touch_event.dart';
67
import 'package:flutter/material.dart';
@@ -282,10 +283,12 @@ class SimulationTurnPageAnimation extends BaseAnimationPage {
282283

283284
@override
284285
void onDraw(Canvas canvas) {
286+
final sw = PageTurnPerf.enabled ? (Stopwatch()..start()) : null;
285287
// Draw curl while user is dragging or confirm/cancel animation is running.
286288
// Do NOT require mTouch.dy != 0 — top-corner curls end with y≈0.
287289
final animating = isConfirmAnimation || isStartAnimation;
288-
if (animating && mTouch != Offset.zero) {
290+
final layered = animating && mTouch != Offset.zero;
291+
if (layered) {
289292
// Order: bottom (revealed) → top (remaining) → back of flipped flap.
290293
drawBottomPageCanvas(canvas);
291294
drawTopPageCanvas(canvas);
@@ -296,6 +299,18 @@ class SimulationTurnPageAnimation extends BaseAnimationPage {
296299
canvas.drawPicture(targetPicture);
297300
}
298301
}
302+
if (sw != null) {
303+
sw.stop();
304+
PageTurnPerf.frameDraw(
305+
'simulation',
306+
us: sw.elapsedMicroseconds,
307+
animating: animating,
308+
dragging: isStartAnimation && !isConfirmAnimation,
309+
extra: layered
310+
? 'layers=3 dir=${isTurnToNext ? "next" : "pre"}'
311+
: 'layers=1',
312+
);
313+
}
299314
}
300315

301316
/// 画在最顶上的那页(剩余未翻起区域,需 clip)

lib/animation/static_page_turn.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:book/animation/base_animation_page.dart';
2+
import 'package:book/common/page_turn_perf.dart';
23
import 'package:book/view/page_turn/touch_event.dart';
34
import 'package:flutter/material.dart';
45

@@ -13,10 +14,21 @@ class StaticPageTurn extends BaseAnimationPage {
1314

1415
@override
1516
void onDraw(Canvas canvas) {
17+
final sw = PageTurnPerf.enabled ? (Stopwatch()..start()) : null;
1618
final pic = readerViewModel.paintCurrentPicture();
1719
if (pic != null) {
1820
canvas.drawPicture(pic);
1921
}
22+
if (sw != null) {
23+
sw.stop();
24+
PageTurnPerf.frameDraw(
25+
'static',
26+
us: sw.elapsedMicroseconds,
27+
animating: false,
28+
dragging: _moved,
29+
extra: pic == null ? 'pic=null' : 'pic=ok',
30+
);
31+
}
2032
}
2133

2234
@override

lib/animation/turn_page_animation.dart

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'package:book/animation/base_animation_page.dart';
2+
import 'package:book/common/page_turn_perf.dart';
23
import 'package:book/view/page_turn/touch_event.dart';
34
import 'package:bot_toast/bot_toast.dart';
4-
import 'package:flutter/foundation.dart';
55
import 'package:flutter/material.dart';
66

77
/// 覆盖翻页:拖动时顶页平移露出底页,松手确认/取消。
@@ -27,8 +27,6 @@ class CoverPageAnimation extends BaseAnimationPage {
2727
colors: [Colors.black54, Colors.transparent],
2828
);
2929

30-
int _drawSample = 0;
31-
3230
void _ensureAnimation(AnimationController controller) {
3331
if (currentAnimation != null) return;
3432
currentAnimationTween = Tween(begin: Offset.zero, end: Offset.zero);
@@ -80,10 +78,12 @@ class CoverPageAnimation extends BaseAnimationPage {
8078

8179
@override
8280
void onDraw(Canvas canvas) {
83-
final sw = kDebugMode ? (Stopwatch()..start()) : null;
81+
final sw = PageTurnPerf.enabled ? (Stopwatch()..start()) : null;
8482
// Draw animated layers while dragging OR while confirm/cancel runs.
8583
final animating = animationType != null;
86-
if ((isDragging || animating) && (mTouch.dx != 0 || mTouch.dy != 0)) {
84+
final layered =
85+
(isDragging || animating) && (mTouch.dx != 0 || mTouch.dy != 0);
86+
if (layered) {
8787
drawBottomPage(canvas);
8888
drawCurrentShadow(canvas);
8989
drawTopPage(canvas);
@@ -92,13 +92,15 @@ class CoverPageAnimation extends BaseAnimationPage {
9292
}
9393
if (sw != null) {
9494
sw.stop();
95-
// Sample every 12th frame to keep logs quiet during continuous drag.
96-
if ((_drawSample++ % 12) == 0 && sw.elapsedMicroseconds > 500) {
97-
debugPrint(
98-
'[CoverDraw] us=${sw.elapsedMicroseconds} '
99-
'drag=$isDragging anim=$animating',
100-
);
101-
}
95+
PageTurnPerf.frameDraw(
96+
'cover',
97+
us: sw.elapsedMicroseconds,
98+
animating: animating,
99+
dragging: isDragging,
100+
extra: layered
101+
? 'layers=2 dir=${isTurnNext ? "next" : "pre"}'
102+
: 'layers=1',
103+
);
102104
}
103105
}
104106

lib/common/page_turn_perf.dart

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import 'package:flutter/foundation.dart';
2+
3+
/// Unified page-turn performance probe.
4+
///
5+
/// Filter logcat / console with: **`PageTurnPerf`**
6+
///
7+
/// Covers cover / simulation / static animation draws, picture cache
8+
/// hit/miss, commit, warm, and manager lifecycle so rapid-tap jank can
9+
/// be attributed without grepping multiple prefixes.
10+
///
11+
/// Enabled in **debug and profile** builds only (never release).
12+
class PageTurnPerf {
13+
PageTurnPerf._();
14+
15+
static const String prefix = 'PageTurnPerf';
16+
17+
/// Hard switch — false disables every probe immediately.
18+
static bool enabled = !kReleaseMode;
19+
20+
/// Log every cache hit (noisy at 60fps — off by default).
21+
static bool verboseHits = false;
22+
23+
/// Only emit sampled frame draws slower than this (µs).
24+
static const int slowDrawUs = 800;
25+
26+
/// Sample every Nth draw frame when under [slowDrawUs].
27+
static const int drawSampleEvery = 12;
28+
29+
/// Always log picture record slower than this (ms).
30+
static const int slowPaintMs = 4;
31+
32+
static int _seq = 0;
33+
static int _drawSample = 0;
34+
35+
/// Monotonic-ish turn id for correlating start → commit → warm.
36+
static int nextTurnId() => ++_seq;
37+
38+
static void log(String event, [String detail = '']) {
39+
if (!enabled) return;
40+
if (detail.isEmpty) {
41+
debugPrint('[$prefix] $event');
42+
} else {
43+
debugPrint('[$prefix] $event $detail');
44+
}
45+
}
46+
47+
/// Log only when [ms] / [us] looks expensive.
48+
static void logSlow(
49+
String event, {
50+
int? ms,
51+
int? us,
52+
String detail = '',
53+
int slowMs = slowPaintMs,
54+
int slowUs = slowDrawUs,
55+
}) {
56+
if (!enabled) return;
57+
final expensive = (ms != null && ms >= slowMs) ||
58+
(us != null && us >= slowUs);
59+
if (!expensive) return;
60+
final timing = ms != null
61+
? 'ms=$ms'
62+
: us != null
63+
? 'us=$us'
64+
: '';
65+
log(event, [timing, detail].where((s) => s.isNotEmpty).join(' '));
66+
}
67+
68+
/// Time a sync block; always logs if [always], else only when slow.
69+
static T timeSync<T>(
70+
String event,
71+
T Function() body, {
72+
String detail = '',
73+
bool always = false,
74+
int slowMs = slowPaintMs,
75+
}) {
76+
if (!enabled) return body();
77+
final sw = Stopwatch()..start();
78+
try {
79+
return body();
80+
} finally {
81+
sw.stop();
82+
final ms = sw.elapsedMilliseconds;
83+
final us = sw.elapsedMicroseconds;
84+
if (always || ms >= slowMs || us >= slowDrawUs) {
85+
final timing = ms > 0 ? 'ms=$ms' : 'us=$us';
86+
log(event, [timing, detail].where((s) => s.isNotEmpty).join(' '));
87+
}
88+
}
89+
}
90+
91+
/// Sampled frame draw timing (cover / sim / static).
92+
///
93+
/// Always logs when slower than [slowDrawUs]; otherwise every
94+
/// [drawSampleEvery] frames so continuous drag stays readable.
95+
static void frameDraw(
96+
String mode, {
97+
required int us,
98+
required bool animating,
99+
required bool dragging,
100+
String extra = '',
101+
}) {
102+
if (!enabled) return;
103+
final n = ++_drawSample;
104+
final slow = us >= slowDrawUs;
105+
if (!slow && (n % drawSampleEvery) != 0) return;
106+
final tag = slow ? 'draw.$mode.SLOW' : 'draw.$mode';
107+
log(
108+
tag,
109+
'us=$us anim=$animating drag=$dragging'
110+
'${extra.isEmpty ? '' : ' $extra'}',
111+
);
112+
}
113+
114+
static String modeName(int type) {
115+
switch (type) {
116+
case 0:
117+
return 'static';
118+
case 1:
119+
return 'simulation';
120+
case 2:
121+
return 'cover';
122+
case 3:
123+
return 'scroll';
124+
default:
125+
return 'mode$type';
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)