TF-495 Show FPS when load more emails

This commit is contained in:
dab246
2022-04-19 16:24:21 +07:00
committed by Dat H. Pham
parent 3aa3c38e2d
commit ead1ccd345
5 changed files with 154 additions and 2 deletions
+1
View File
@@ -27,6 +27,7 @@ export 'presentation/utils/html_transformer/dom/add_tooltip_link_transformers.da
export 'data/utils/device_manager.dart';
export 'utils/app_logger.dart';
export 'utils/benchmark.dart';
export 'utils/fps_manager.dart';
// Views
export 'presentation/views/text/slogan_builder.dart';
+128
View File
@@ -0,0 +1,128 @@
import 'dart:collection';
import 'dart:ui';
import 'package:flutter/scheduler.dart';
/// FpsManager callback.
typedef FpsCallback = void Function(FpsInfo fpsInfo);
class FpsManager {
FpsManager._();
factory FpsManager() => _instance ??= FpsManager._();
static FpsManager? _instance;
/// Threshold time consuming per frame
/// 1000/60hz ≈ 16.6ms 1000/120hz ≈ 8.3ms
Duration _thresholdPerFrame = Duration(microseconds: Duration.microsecondsPerSecond ~/ 60);
/// Refresh rate, default 60
double _refreshRate = 60;
/// Set refresh rate
set refreshRate(double rate) {
if (rate != _refreshRate && rate >= 60) {
_refreshRate = rate;
_thresholdPerFrame = Duration(microseconds: Duration.microsecondsPerSecond ~/ _refreshRate);
}
}
bool _started = false;
List<FpsCallback> _fpsCallbacks = [];
/// Temporarily save 120 frames
static const int _queue_capacity = 120;
final ListQueue framesQueue = ListQueue<FrameTiming>(_queue_capacity);
void addFpsCallback(FpsCallback callback) {
_fpsCallbacks.add(callback);
}
void removeFpsCallback(FpsCallback callback) {
assert(_fpsCallbacks.contains(callback));
_fpsCallbacks.remove(callback);
}
void start() async {
if (!_started) {
SchedulerBinding.instance?.addTimingsCallback(_onTimingsCallback);
_started = true;
}
}
void stop() {
if (_started) {
SchedulerBinding.instance?.removeTimingsCallback(_onTimingsCallback);
_started = false;
}
}
_onTimingsCallback(List<FrameTiming> timings) async {
if (_fpsCallbacks.isNotEmpty) {
for (FrameTiming timing in timings) {
framesQueue.addFirst(timing);
}
while (framesQueue.length > _queue_capacity) {
framesQueue.removeLast();
}
List<FrameTiming> drawFrames = [];
for (FrameTiming timing in framesQueue) {
if (drawFrames.isEmpty) {
drawFrames.add(timing);
} else {
int lastStart =
drawFrames.last.timestampInMicroseconds(FramePhase.vsyncStart);
int interval = lastStart -
timing.timestampInMicroseconds(FramePhase.rasterFinish);
if (interval > (_thresholdPerFrame.inMicroseconds * 2)) {
// maybe in different set
break;
}
drawFrames.add(timing);
}
}
framesQueue.clear();
// compute total frames count.
int totalCount = drawFrames.map((frame) {
// If droppedCount > 0,
int droppedCount = frame.totalSpan.inMicroseconds ~/ _thresholdPerFrame.inMicroseconds;
return droppedCount + 1;
}).fold(0, (a, b) => a + b);
int drawFramesCount = drawFrames.length;
int droppedCount = totalCount - drawFramesCount;
double fps = drawFramesCount / totalCount * _refreshRate;
FpsInfo fpsInfo = FpsInfo(fps, totalCount, droppedCount, drawFramesCount);
_fpsCallbacks.forEach((callBack) {
callBack(fpsInfo);
});
}
}
}
class FpsInfo {
final double fps;
final int totalFramesCount;
final int droppedFramesCount;
final int drawFramesCount;
FpsInfo(
this.fps,
this.totalFramesCount,
this.droppedFramesCount,
this.drawFramesCount
);
@override
String toString() {
return 'FpsInfo{'
'fps: $fps, '
'totalFramesCount: $totalFramesCount, '
'droppedFramesCount: $droppedFramesCount, '
'drawFramesCount: $drawFramesCount}';
}
}
+18
View File
@@ -8,6 +8,7 @@ import 'package:tmail_ui_user/features/base/mixin/popup_context_menu_action_mixi
abstract class BaseController extends GetxController with MessageDialogActionMixin, PopupContextMenuActionMixin {
final viewState = Rx<Either<Failure, Success>>(Right(UIState.idle));
final connectivityResult = Rxn<ConnectivityResult>();
FpsCallback? fpsCallback;
void consumeState(Stream<Either<Failure, Success>> newStateStream) async {
newStateStream.listen(
@@ -48,4 +49,21 @@ abstract class BaseController extends GetxController with MessageDialogActionMix
void onError(dynamic error);
void onDone();
void startFpsMeter() {
FpsManager().start();
fpsCallback = (fpsInfo) {
log('BaseController::startFpsMeter(): $fpsInfo');
};
if (fpsCallback != null) {
FpsManager().addFpsCallback(fpsCallback!);
}
}
void stopFpsMeter() {
FpsManager().stop();
if (fpsCallback != null) {
FpsManager().removeFpsCallback(fpsCallback!);
}
}
}
@@ -243,7 +243,9 @@ class ThreadRepositoryImpl extends ThreadRepository {
@override
Stream<EmailsResponse> loadMoreEmails(GetEmailRequest getEmailRequest) async* {
bench.start('loadMoreEmails in computed');
final response = await compute(_getAllEmailsWithoutLastEmailId, getEmailRequest);
bench.end('loadMoreEmails in computed');
await _updateEmailCache(newCreated: response.emailList);
yield response;
}
@@ -222,6 +222,8 @@ class ThreadController extends BaseController {
_markAsStarMultipleEmailFailure(failure);
} else if (failure is EmptyTrashFolderFailure) {
_emptyTrashFolderFailure(failure);
} else if (failure is LoadMoreEmailsFailure) {
stopFpsMeter();
}
},
(success) {
@@ -245,6 +247,8 @@ class ThreadController extends BaseController {
_markAsEmailReadSuccess(success);
} else if (success is MoveToMailboxSuccess) {
_moveToMailboxSuccess(success);
} else if (success is LoadMoreEmailsSuccess) {
stopFpsMeter();
}
}
);
@@ -383,7 +387,7 @@ class ThreadController extends BaseController {
void loadMoreEmails() {
if (canLoadMore && _accountId != null) {
log('ThreadController::loadMoreEmails(): latest: ${emailList.last.receivedAt}');
bench.start('loadMoreEmails');
startFpsMeter();
consumeState(_loadMoreEmailsInMailboxInteractor.execute(
GetEmailRequest(
_accountId!,
@@ -397,7 +401,6 @@ class ThreadController extends BaseController {
}
void _loadMoreEmailsSuccess(LoadMoreEmailsSuccess success) {
bench.end('loadMoreEmails');
log('ThreadController::_loadMoreEmailsSuccess(): [BEFORE] totalEmailList = ${emailList.length}');
if (success.emailList.isNotEmpty) {
log('ThreadController::_loadMoreEmailsSuccess(): add success: ${success.emailList.length}');