Improve reader performance

This commit is contained in:
2025-02-14 11:35:03 +08:00
parent 350bcf4ffc
commit 22c01b4fd0
6 changed files with 68 additions and 51 deletions

View File

@@ -138,7 +138,7 @@ class ComicTile extends StatelessWidget {
: false; : false;
var history = appdata.settings['showHistoryStatusOnTile'] var history = appdata.settings['showHistoryStatusOnTile']
? HistoryManager() ? HistoryManager()
.findSync(comic.id, ComicType(comic.sourceKey.hashCode)) .find(comic.id, ComicType(comic.sourceKey.hashCode))
: null; : null;
if (history?.page == 0) { if (history?.page == 0) {
history!.page = 1; history!.page = 1;

View File

@@ -2,10 +2,12 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:isolate'; import 'dart:isolate';
import 'dart:math'; import 'dart:math';
import 'dart:ffi' as ffi;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart' show ChangeNotifier; import 'package:flutter/widgets.dart' show ChangeNotifier;
import 'package:sqlite3/common.dart';
import 'package:sqlite3/sqlite3.dart'; import 'package:sqlite3/sqlite3.dart';
import 'package:venera/foundation/comic_source/comic_source.dart'; import 'package:venera/foundation/comic_source/comic_source.dart';
import 'package:venera/foundation/comic_type.dart'; import 'package:venera/foundation/comic_type.dart';
@@ -124,30 +126,6 @@ class History implements Comic {
.map((e) => int.parse(e))), .map((e) => int.parse(e))),
maxPage = row["max_page"]; maxPage = row["max_page"];
static Future<History> 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<History> 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 @override
bool operator ==(Object other) { bool operator ==(Object other) {
return other is History && type == other.type && id == other.id; 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; int get length => _db.select("select count(*) from history;").first[0] as int;
Map<String, bool>? _cachedHistory; /// Cache of history ids. Improve the performance of find operation.
Map<String, bool>? _cachedHistoryIds;
/// Cache records recently modified by the app. Improve the performance of listeners.
final cachedHistories = <String, History>{};
bool isInitialized = false; bool isInitialized = false;
@@ -240,14 +222,38 @@ class HistoryManager with ChangeNotifier {
isInitialized = true; isInitialized = true;
} }
static const _insertHistorySql = """
insert or replace into history (id, title, subtitle, cover, time, type, ep, page, readEpisode, max_page)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
""";
static Future<void> _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<void> addHistoryAsync(History newItem) async {
_addHistoryAsync(_db.handle.address, newItem);
}
/// add history. if exists, update time. /// add history. if exists, update time.
/// ///
/// This function would be called when user start reading. /// This function would be called when user start reading.
Future<void> addHistory(History newItem) async { void addHistory(History newItem) {
_db.execute(""" _db.execute(_insertHistorySql, [
insert or replace into history (id, title, subtitle, cover, time, type, ep, page, readEpisode, max_page)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
""", [
newItem.id, newItem.id,
newItem.title, newItem.title,
newItem.subtitle, newItem.subtitle,
@@ -259,7 +265,15 @@ class HistoryManager with ChangeNotifier {
newItem.readEpisode.join(','), newItem.readEpisode.join(','),
newItem.maxPage 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(); notifyListeners();
} }
@@ -278,27 +292,31 @@ class HistoryManager with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<History?> find(String id, ComicType type) async {
return findSync(id, type);
}
void updateCache() { void updateCache() {
_cachedHistory = {}; _cachedHistoryIds = {};
var res = _db.select(""" var res = _db.select("""
select * from history; select id from history;
"""); """);
for (var element in res) { 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) { History? find(String id, ComicType type) {
if (_cachedHistory == null) { if (_cachedHistoryIds == null) {
updateCache(); updateCache();
} }
if (!_cachedHistory!.containsKey(id)) { if (!_cachedHistoryIds!.containsKey(id)) {
return null; return null;
} }
if (cachedHistories.containsKey(id)) {
return cachedHistories[id];
}
var res = _db.select(""" var res = _db.select("""
select * from history select * from history

View File

@@ -105,8 +105,8 @@ class LocalComic with HistoryMixin implements Comic {
@override @override
int? get maxPage => null; int? get maxPage => null;
void read() async { void read() {
var history = await HistoryManager().find(id, comicType); var history = HistoryManager().find(id, comicType);
App.rootContext.to( App.rootContext.to(
() => Reader( () => Reader(
type: comicType, 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. // Deleting a local comic means that it's nolonger available, thus both favorite and history should be deleted.
if (c.comicType == ComicType.local) { 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); HistoryManager().remove(c.id, c.comicType);
} }
var folders = LocalFavoritesManager().find(c.id, c.comicType); var folders = LocalFavoritesManager().find(c.id, c.comicType);

View File

@@ -61,7 +61,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
@override @override
void onReadEnd() { void onReadEnd() {
history ??= HistoryManager() history ??= HistoryManager()
.findSync(widget.id, ComicType(widget.sourceKey.hashCode)); .find(widget.id, ComicType(widget.sourceKey.hashCode));
update(); update();
} }
@@ -138,7 +138,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
if (localComic == null) { if (localComic == null) {
return const Res.error('Local comic not found'); 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) { if (isFirst) {
Future.microtask(() { Future.microtask(() {
App.rootContext.to(() { App.rootContext.to(() {
@@ -172,7 +172,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
widget.id, widget.id,
ComicType(widget.sourceKey.hashCode), ComicType(widget.sourceKey.hashCode),
); );
history = await HistoryManager() history = HistoryManager()
.find(widget.id, ComicType(widget.sourceKey.hashCode)); .find(widget.id, ComicType(widget.sourceKey.hashCode));
return comicSource.loadComicInfo!(widget.id); return comicSource.loadComicInfo!(widget.id);
} }

View File

@@ -41,7 +41,7 @@ class _ReaderWithLoadingState
@override @override
Future<Res<ReaderProps>> loadData() async { Future<Res<ReaderProps>> loadData() async {
var comicSource = ComicSource.find(widget.sourceKey); var comicSource = ComicSource.find(widget.sourceKey);
var history = HistoryManager().findSync( var history = HistoryManager().find(
widget.id, widget.id,
ComicType.fromKey(widget.sourceKey), ComicType.fromKey(widget.sourceKey),
); );

View File

@@ -238,9 +238,8 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
history!.maxPage = maxPage; history!.maxPage = maxPage;
} }
history!.readEpisode.add(chapter); history!.readEpisode.add(chapter);
print(history!.readEpisode);
history!.time = DateTime.now(); history!.time = DateTime.now();
HistoryManager().addHistory(history!); HistoryManager().addHistoryAsync(history!);
} }
} }