оптимизация: слегоньца набурмалдил

This commit is contained in:
Jganenok
2026-06-03 13:55:14 +07:00
parent 4bdb0e70cf
commit db9de4d8a2
9 changed files with 229 additions and 123 deletions
+21 -15
View File
@@ -92,25 +92,31 @@ const int _compressionThreshold = 32;
/// `0` — без сжатия, иначе `(rawLen ~/ compLen) + 1` (множитель размера, по
/// которому получатель выделяет буфер под распаковку).
Uint8List packPacket(int opcode, Map<dynamic, dynamic> payload, {int seq = 0}) {
final header = ByteData(headerSize);
final Uint8List raw = msgpack.serialize(payload);
final List<int> body;
final int flag;
if (raw.length < _compressionThreshold) {
body = raw;
flag = 0;
} else {
body = lz4Compress(raw);
flag = (raw.length ~/ body.length) + 1;
}
final out = Uint8List(headerSize + body.length);
final header = ByteData.view(out.buffer, out.offsetInBytes, headerSize);
header.setUint8(0, 10);
header.setUint8(1, CmdType.request);
header.setUint16(2, seq, Endian.big);
header.setUint16(4, opcode, Endian.big);
final raw = Uint8List.fromList(msgpack.serialize(payload));
if (raw.length < _compressionThreshold) {
header.setUint32(6, raw.length & 0xFFFFFF, Endian.big);
return Uint8List.fromList(header.buffer.asUint8List() + raw);
}
final compressed = lz4Compress(raw);
final compLen = compressed.length;
final flag = (raw.length ~/ compLen) + 1;
header.setUint32(6, ((flag & 0xFF) << 24) | (compLen & 0xFFFFFF), Endian.big);
return Uint8List.fromList(header.buffer.asUint8List() + compressed);
header.setUint32(
6,
((flag & 0xFF) << 24) | (body.length & 0xFFFFFF),
Endian.big,
);
out.setRange(headerSize, out.length, body);
return out;
}
/// Распаковка пакета от сервера
+12 -9
View File
@@ -435,17 +435,20 @@ class AppDatabase {
// Chats cache
static Future<void> saveChats(List<Map<String, dynamic>> rows) async {
if (rows.isEmpty) return;
try {
final db = await _instance;
final batch = db.batch();
for (final row in rows) {
batch.insert(
'chats_cache',
row,
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
await batch.commit(noResult: true);
await db.transaction((txn) async {
final batch = txn.batch();
for (final row in rows) {
batch.insert(
'chats_cache',
row,
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
await batch.commit(noResult: true);
});
} catch (e) {
logger.e("Ошибка при сохранении чата: $e");
}
+38 -11
View File
@@ -7,46 +7,73 @@ import '../utils/logger.dart';
/// Копит сырые байты из сокета, нарезает их на байтовые срезы целых пакетов.
class PacketReceiver {
Uint8List _buffer = Uint8List(0);
int _start = 0;
int _end = 0;
static const int _maxBufferSize = 2 * 1024 * 1024; // 2 мегабуйта
/// Добавляет байты в буфер и возвращает все собранные пакеты как сырые срезы.
/// Полностью синхронный — нарезка не блокируется на распаковке, поэтому
/// конкурентные вызовы из stream-листенера не могут пересечься на `_buffer`.
///
/// Накопление идёт без перекопирования всего буфера на каждый чанк: целые
/// пакеты отдаются как `sublistView`, а потреблённый префикс отбрасывается
/// сдвигом указателя `_start`, а не пересборкой буфера.
List<Uint8List> feed(Uint8List data) {
final newBuffer = Uint8List(_buffer.length + data.length);
newBuffer.setAll(0, _buffer);
newBuffer.setAll(_buffer.length, data);
_buffer = newBuffer;
_append(data);
if (_buffer.length > _maxBufferSize) {
if (_end - _start > _maxBufferSize) {
logger.e(
'PacketReceiver: переполнение буфера (${_buffer.length} B), сброс',
'PacketReceiver: переполнение буфера (${_end - _start} B), сброс',
);
reset();
return const [];
}
final packets = <Uint8List>[];
while (_buffer.length >= headerSize) {
while (_end - _start >= headerSize) {
final bd = ByteData.view(
_buffer.buffer,
_buffer.offsetInBytes,
_buffer.offsetInBytes + _start,
headerSize,
);
final packedLen = bd.getUint32(6, Endian.big);
final payloadLength = packedLen & 0xFFFFFF;
final totalLength = headerSize + payloadLength;
if (_buffer.length < totalLength) break;
if (_end - _start < totalLength) break;
packets.add(Uint8List.sublistView(_buffer, 0, totalLength));
_buffer = _buffer.sublist(totalLength);
packets.add(Uint8List.sublistView(_buffer, _start, _start + totalLength));
_start += totalLength;
}
if (_start == _end) {
_start = 0;
_end = 0;
}
return packets;
}
void _append(Uint8List data) {
final pending = _end - _start;
if (pending == 0) {
_buffer = Uint8List.fromList(data);
_start = 0;
_end = data.length;
return;
}
final total = pending + data.length;
final newBuffer = Uint8List(total);
newBuffer.setRange(0, pending, _buffer, _start);
newBuffer.setRange(pending, total, data);
_buffer = newBuffer;
_start = 0;
_end = total;
}
void reset() {
_buffer = Uint8List(0);
_start = 0;
_end = 0;
}
}
+33 -6
View File
@@ -15,6 +15,7 @@ class MediaCache {
static int get maxBytes => AppMediaCacheLimit.current.value;
static Directory? _dir;
static int? _cachedSize;
static Future<Directory> _cacheDir() async {
final cached = _dir;
@@ -81,6 +82,12 @@ class MediaCache {
}
await sink.close();
await part.rename(file.path);
final known = _cachedSize;
if (known != null) {
try {
_cachedSize = known + await file.length();
} catch (_) {}
}
await _enforceLimit();
return file;
} catch (_) {
@@ -96,7 +103,18 @@ class MediaCache {
}
/// Суммарный размер кэша в байтах.
///
/// Результат держится в памяти и поддерживается инкрементально при
/// загрузке/очистке/вытеснении — повторные вызовы не пересканируют каталог.
static Future<int> currentSize() async {
final cached = _cachedSize;
if (cached != null) return cached;
final total = await _scanSize();
_cachedSize = total;
return total;
}
static Future<int> _scanSize() async {
final dir = await _cacheDir();
var total = 0;
await for (final entity in dir.list()) {
@@ -121,34 +139,43 @@ class MediaCache {
} catch (_) {}
}
}
_cachedSize = 0;
return freed;
}
/// Вытесняет старые файлы (по mtime), пока размер превышает [maxBytes].
///
/// Под лимитом — ранний выход без сканирования каталога (частый случай).
/// Каталог обходится только когда лимит реально превышен.
static Future<void> _enforceLimit() async {
final limit = maxBytes;
if (limit <= 0) return;
var total = _cachedSize ?? await _scanSize();
if (total <= limit) {
_cachedSize = total;
return;
}
final dir = await _cacheDir();
final files = <File>[];
var total = 0;
await for (final entity in dir.list()) {
if (entity is File && !entity.path.endsWith('.part')) {
files.add(entity);
try {
total += await entity.length();
} catch (_) {}
}
}
if (maxBytes <= 0 || total <= maxBytes) return;
files.sort((a, b) =>
a.statSync().modified.compareTo(b.statSync().modified));
for (final file in files) {
if (total <= maxBytes) break;
if (total <= limit) break;
try {
total -= await file.length();
await file.delete();
} catch (_) {}
}
_cachedSize = total;
}
static String _sanitize(String name) {