оптимизация: слегоньца набурмалдил
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/// Распаковка пакета от сервера
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user