From 22c01b4fd09f6704cc763bc3090d26750222c8e6 Mon Sep 17 00:00:00 2001 From: nyne Date: Fri, 14 Feb 2025 11:35:03 +0800 Subject: [PATCH] Improve reader performance --- lib/components/comic.dart | 2 +- lib/foundation/history.dart | 100 ++++++++++++++++++++-------------- lib/foundation/local.dart | 6 +- lib/pages/comic_page.dart | 6 +- lib/pages/reader/loading.dart | 2 +- lib/pages/reader/reader.dart | 3 +- 6 files changed, 68 insertions(+), 51 deletions(-) diff --git a/lib/components/comic.dart b/lib/components/comic.dart index deb7b1e..86045a4 100644 --- a/lib/components/comic.dart +++ b/lib/components/comic.dart @@ -138,7 +138,7 @@ class ComicTile extends StatelessWidget { : false; var history = appdata.settings['showHistoryStatusOnTile'] ? HistoryManager() - .findSync(comic.id, ComicType(comic.sourceKey.hashCode)) + .find(comic.id, ComicType(comic.sourceKey.hashCode)) : null; if (history?.page == 0) { history!.page = 1; diff --git a/lib/foundation/history.dart b/lib/foundation/history.dart index 062055f..00d96d1 100644 --- a/lib/foundation/history.dart +++ b/lib/foundation/history.dart @@ -2,10 +2,12 @@ import 'dart:async'; import 'dart:convert'; import 'dart:isolate'; import 'dart:math'; +import 'dart:ffi' as ffi; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart' show ChangeNotifier; +import 'package:sqlite3/common.dart'; import 'package:sqlite3/sqlite3.dart'; import 'package:venera/foundation/comic_source/comic_source.dart'; import 'package:venera/foundation/comic_type.dart'; @@ -124,30 +126,6 @@ class History implements Comic { .map((e) => int.parse(e))), maxPage = row["max_page"]; - static Future findOrCreate( - HistoryMixin model, { - int ep = 0, - int page = 0, - }) async { - var history = await HistoryManager().find(model.id, model.historyType); - if (history != null) { - return history; - } - history = History.fromModel(model: model, ep: ep, page: page); - HistoryManager().addHistory(history); - return history; - } - - static Future createIfNull( - History? history, HistoryMixin model) async { - if (history != null) { - return history; - } - history = History.fromModel(model: model, ep: 0, page: 0); - HistoryManager().addHistory(history); - return history; - } - @override bool operator ==(Object other) { return other is History && type == other.type && id == other.id; @@ -210,7 +188,11 @@ class HistoryManager with ChangeNotifier { int get length => _db.select("select count(*) from history;").first[0] as int; - Map? _cachedHistory; + /// Cache of history ids. Improve the performance of find operation. + Map? _cachedHistoryIds; + + /// Cache records recently modified by the app. Improve the performance of listeners. + final cachedHistories = {}; bool isInitialized = false; @@ -240,14 +222,38 @@ class HistoryManager with ChangeNotifier { isInitialized = true; } + static const _insertHistorySql = """ + insert or replace into history (id, title, subtitle, cover, time, type, ep, page, readEpisode, max_page) + values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + """; + + static Future _addHistoryAsync(int dbAddr, History newItem) { + return Isolate.run(() { + var db = sqlite3.fromPointer(ffi.Pointer.fromAddress(dbAddr)); + db.execute(_insertHistorySql, [ + newItem.id, + newItem.title, + newItem.subtitle, + newItem.cover, + newItem.time.millisecondsSinceEpoch, + newItem.type.value, + newItem.ep, + newItem.page, + newItem.readEpisode.join(','), + newItem.maxPage + ]); + }); + } + + Future addHistoryAsync(History newItem) async { + _addHistoryAsync(_db.handle.address, newItem); + } + /// add history. if exists, update time. /// /// This function would be called when user start reading. - Future addHistory(History newItem) async { - _db.execute(""" - insert or replace into history (id, title, subtitle, cover, time, type, ep, page, readEpisode, max_page) - values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); - """, [ + void addHistory(History newItem) { + _db.execute(_insertHistorySql, [ newItem.id, newItem.title, newItem.subtitle, @@ -259,7 +265,15 @@ class HistoryManager with ChangeNotifier { newItem.readEpisode.join(','), newItem.maxPage ]); - updateCache(); + if (_cachedHistoryIds == null) { + updateCache(); + } else { + _cachedHistoryIds![newItem.id] = true; + } + cachedHistories[newItem.id] = newItem; + if (cachedHistories.length > 10) { + cachedHistories.remove(cachedHistories.keys.first); + } notifyListeners(); } @@ -278,27 +292,31 @@ class HistoryManager with ChangeNotifier { notifyListeners(); } - Future find(String id, ComicType type) async { - return findSync(id, type); - } - void updateCache() { - _cachedHistory = {}; + _cachedHistoryIds = {}; var res = _db.select(""" - select * from history; + select id from history; """); for (var element in res) { - _cachedHistory![element["id"] as String] = true; + _cachedHistoryIds![element["id"] as String] = true; + } + for (var key in cachedHistories.keys) { + if (!_cachedHistoryIds!.containsKey(key)) { + cachedHistories.remove(key); + } } } - History? findSync(String id, ComicType type) { - if (_cachedHistory == null) { + History? find(String id, ComicType type) { + if (_cachedHistoryIds == null) { updateCache(); } - if (!_cachedHistory!.containsKey(id)) { + if (!_cachedHistoryIds!.containsKey(id)) { return null; } + if (cachedHistories.containsKey(id)) { + return cachedHistories[id]; + } var res = _db.select(""" select * from history diff --git a/lib/foundation/local.dart b/lib/foundation/local.dart index 2eabae4..8cafdfc 100644 --- a/lib/foundation/local.dart +++ b/lib/foundation/local.dart @@ -105,8 +105,8 @@ class LocalComic with HistoryMixin implements Comic { @override int? get maxPage => null; - void read() async { - var history = await HistoryManager().find(id, comicType); + void read() { + var history = HistoryManager().find(id, comicType); App.rootContext.to( () => Reader( type: comicType, @@ -511,7 +511,7 @@ class LocalManager with ChangeNotifier { } // Deleting a local comic means that it's nolonger available, thus both favorite and history should be deleted. if (c.comicType == ComicType.local) { - if (HistoryManager().findSync(c.id, c.comicType) != null) { + if (HistoryManager().find(c.id, c.comicType) != null) { HistoryManager().remove(c.id, c.comicType); } var folders = LocalFavoritesManager().find(c.id, c.comicType); diff --git a/lib/pages/comic_page.dart b/lib/pages/comic_page.dart index 2425f28..615a85e 100644 --- a/lib/pages/comic_page.dart +++ b/lib/pages/comic_page.dart @@ -61,7 +61,7 @@ class _ComicPageState extends LoadingState @override void onReadEnd() { history ??= HistoryManager() - .findSync(widget.id, ComicType(widget.sourceKey.hashCode)); + .find(widget.id, ComicType(widget.sourceKey.hashCode)); update(); } @@ -138,7 +138,7 @@ class _ComicPageState extends LoadingState if (localComic == null) { return const Res.error('Local comic not found'); } - var history = await HistoryManager().find(widget.id, ComicType.local); + var history = HistoryManager().find(widget.id, ComicType.local); if (isFirst) { Future.microtask(() { App.rootContext.to(() { @@ -172,7 +172,7 @@ class _ComicPageState extends LoadingState widget.id, ComicType(widget.sourceKey.hashCode), ); - history = await HistoryManager() + history = HistoryManager() .find(widget.id, ComicType(widget.sourceKey.hashCode)); return comicSource.loadComicInfo!(widget.id); } diff --git a/lib/pages/reader/loading.dart b/lib/pages/reader/loading.dart index 37cdd8e..38bfd57 100644 --- a/lib/pages/reader/loading.dart +++ b/lib/pages/reader/loading.dart @@ -41,7 +41,7 @@ class _ReaderWithLoadingState @override Future> loadData() async { var comicSource = ComicSource.find(widget.sourceKey); - var history = HistoryManager().findSync( + var history = HistoryManager().find( widget.id, ComicType.fromKey(widget.sourceKey), ); diff --git a/lib/pages/reader/reader.dart b/lib/pages/reader/reader.dart index a533508..11fcf89 100644 --- a/lib/pages/reader/reader.dart +++ b/lib/pages/reader/reader.dart @@ -238,9 +238,8 @@ class _ReaderState extends State with _ReaderLocation, _ReaderWindow { history!.maxPage = maxPage; } history!.readEpisode.add(chapter); - print(history!.readEpisode); history!.time = DateTime.now(); - HistoryManager().addHistory(history!); + HistoryManager().addHistoryAsync(history!); } }