opt: легонько наоптимизибурмалдил в rlottie стикерах/эмодзи

This commit is contained in:
Jganenokk
2026-07-11 15:45:39 +07:00
parent ce929a121f
commit b8dce4fd63
4 changed files with 103 additions and 52 deletions
+15 -1
View File
@@ -29,7 +29,7 @@ class RlottieDiskCache {
static final RlottieDiskCache instance = RlottieDiskCache._();
static const _magic = 0x4b524c46;
static const _version = 1;
static const _version = 2;
static const int _maxBytes = 256 * 1024 * 1024;
Directory? _dir;
@@ -103,6 +103,13 @@ class RlottieDiskCache {
for (var i = 0; i < frameCount; i++) {
payload.setRange(i * frameBytes, (i + 1) * frameBytes, frames[i]);
}
for (var i = frameCount - 1; i >= 1; i--) {
final cur = i * frameBytes;
final prev = (i - 1) * frameBytes;
for (var b = 0; b < frameBytes; b++) {
payload[cur + b] ^= payload[prev + b];
}
}
final compressed = gzip.encode(payload);
final header = ByteData(28);
header.setUint32(0, _magic);
@@ -131,6 +138,13 @@ class RlottieDiskCache {
final frameBytes = px * px * 4;
final payload = Uint8List.fromList(gzip.decode(bytes.sublist(28)));
if (payload.length != frameBytes * frameCount) return null;
for (var i = 1; i < frameCount; i++) {
final cur = i * frameBytes;
final prev = (i - 1) * frameBytes;
for (var b = 0; b < frameBytes; b++) {
payload[cur + b] ^= payload[prev + b];
}
}
final frames = <Uint8List>[];
for (var i = 0; i < frameCount; i++) {
frames.add(Uint8List.sublistView(
+34 -20
View File
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io' show Platform;
import 'dart:isolate';
import 'dart:ui' as ui;
@@ -81,7 +82,7 @@ class RlottieEngine {
static String? debugLibraryPath;
static const int _maxBytes = 384 * 1024 * 1024;
static const int _maxBytes = 192 * 1024 * 1024;
static const int _modelCacheBytes = 20 * 1024 * 1024;
final Map<String, RlottieClip> _clips = {};
@@ -91,9 +92,10 @@ class RlottieEngine {
int _totalBytes = 0;
int _clock = 0;
int _nextJobId = 1;
int _rrIndex = 0;
bool? _available;
Future<SendPort>? _workerFuture;
Future<List<SendPort>>? _poolFuture;
int _tick() => ++_clock;
String _keyFor(String url, int px) => '$url@$px';
@@ -107,15 +109,23 @@ class RlottieEngine {
}();
}
Future<SendPort> _worker() {
return _workerFuture ??= () async {
Future<SendPort> _worker() async {
final pool = await (_poolFuture ??= _spawnPool());
return pool[_rrIndex++ % pool.length];
}
Future<List<SendPort>> _spawnPool() async {
final count = (Platform.numberOfProcessors - 1).clamp(1, 3);
final ports = <SendPort>[];
for (var i = 0; i < count; i++) {
final receive = ReceivePort();
await Isolate.spawn(rlottieWorkerMain, receive.sendPort);
final broadcast = receive.asBroadcastStream();
final port = await broadcast.first as SendPort;
broadcast.listen(_onWorkerMessage);
return port;
}();
ports.add(port);
}
return ports;
}
void _onWorkerMessage(dynamic message) {
@@ -155,7 +165,7 @@ class RlottieEngine {
bytes,
frame.px,
frame.px,
ui.PixelFormat.rgba8888,
ui.PixelFormat.bgra8888,
(image) {
job.clip._setFrame(frame.index, image);
_totalBytes += frame.px * frame.px * 4;
@@ -229,8 +239,13 @@ class RlottieEngine {
if (inlineJson == null) {
final disk = await RlottieDiskCache.instance.load(url, px);
if (disk != null) {
final clip = await _clipFromDisk(key, px, disk);
final clip = RlottieClip(key: key, px: px)
..frameCount = disk.frameCount
..frameRate = disk.frameRate
..durationMs = disk.durationMs;
clip._allocate(disk.frameCount);
_clips[key] = clip;
unawaited(_decodeDiskProgressive(clip, disk));
return clip;
}
}
@@ -255,26 +270,25 @@ class RlottieEngine {
return completer.future;
}
Future<RlottieClip> _clipFromDisk(String key, int px, DiskClip disk) async {
final clip = RlottieClip(key: key, px: px)
..frameCount = disk.frameCount
..frameRate = disk.frameRate
..durationMs = disk.durationMs
..complete = true;
clip._allocate(disk.frameCount);
Future<void> _decodeDiskProgressive(RlottieClip clip, DiskClip disk) async {
for (var i = 0; i < disk.frameCount; i++) {
final image = await _decode(disk.frames[i], px);
if (!identical(_clips[clip.key], clip)) return;
final image = await _decode(disk.frames[i], clip.px);
if (!identical(_clips[clip.key], clip)) {
image.dispose();
return;
}
clip._setFrame(i, image);
_totalBytes += px * px * 4;
_totalBytes += clip.px * clip.px * 4;
}
clip.complete = true;
_evictIfNeeded();
return clip;
}
Future<ui.Image> _decode(Uint8List rgba, int px) {
Future<ui.Image> _decode(Uint8List bgra, int px) {
final completer = Completer<ui.Image>();
ui.decodeImageFromPixels(
rgba, px, px, ui.PixelFormat.rgba8888, completer.complete);
bgra, px, px, ui.PixelFormat.bgra8888, completer.complete);
return completer.future;
}
+17 -19
View File
@@ -66,6 +66,8 @@ class CancelJob {
final int jobId;
}
const double _maxCacheFps = 30.0;
void rlottieWorkerMain(SendPort toMain) {
final port = ReceivePort();
toMain.send(port.sendPort);
@@ -104,24 +106,33 @@ void rlottieWorkerMain(SendPort toMain) {
final total = rl.totalFrame(anim);
final fps = rl.frameRate(anim);
final durationMs = fps <= 0 ? 1000 : (total / fps * 1000).round();
var outCount = total;
if (fps > _maxCacheFps && total > 1) {
outCount = (durationMs / 1000.0 * _maxCacheFps).round().clamp(2, total);
}
final outFps = durationMs <= 0 ? fps : outCount * 1000.0 / durationMs;
toMain.send(ClipMeta(
jobId: job.jobId,
totalFrame: total,
frameRate: fps,
totalFrame: outCount,
frameRate: outFps,
durationMs: durationMs,
));
final px = job.px;
final buffer = calloc<Uint32>(px * px);
final byteView = buffer.cast<Uint8>().asTypedList(px * px * 4);
try {
for (var i = 0; i < total; i++) {
for (var i = 0; i < outCount; i++) {
if (cancelled.contains(job.jobId)) break;
rl.render(anim, i, buffer, px);
final rgba = _bgraToRgba(buffer, px * px);
final src = outCount == total
? i
: (i * (total - 1) / (outCount - 1)).round().clamp(0, total - 1);
rl.render(anim, src, buffer, px);
toMain.send(RenderedFrame(
jobId: job.jobId,
index: i,
data: TransferableTypedData.fromList([rgba]),
data: TransferableTypedData.fromList([Uint8List.fromList(byteView)]),
px: px,
));
}
@@ -138,16 +149,3 @@ void rlottieWorkerMain(SendPort toMain) {
});
}
Uint8List _bgraToRgba(Pointer<Uint32> buffer, int pixels) {
final src = buffer.asTypedList(pixels);
final out = Uint8List(pixels * 4);
for (var i = 0; i < pixels; i++) {
final v = src[i];
final o = i * 4;
out[o] = (v >> 16) & 0xff;
out[o + 1] = (v >> 8) & 0xff;
out[o + 2] = v & 0xff;
out[o + 3] = (v >> 24) & 0xff;
}
return out;
}
+37 -12
View File
@@ -84,6 +84,8 @@ class LottiePlayer extends StatefulWidget {
class _LottiePlayerState extends State<LottiePlayer>
with SingleTickerProviderStateMixin {
static const int _leadFrames = 6;
static const double _slowSpeed = 0.5;
static const double _rampMs = 200.0;
final ValueNotifier<int> _frameIndex = ValueNotifier(0);
late final Ticker _ticker;
@@ -94,6 +96,11 @@ class _LottiePlayerState extends State<LottiePlayer>
bool _started = false;
bool _showedFrames = false;
double _speed = 1.0;
double _targetSpeed = 1.0;
double _playheadMs = 0.0;
double? _lastElapsedMs;
bool get _isScrolling => _scrollState?.value ?? false;
bool get _canLoad =>
!_isScrolling && !LottieLoadGovernor.instance.throttled.value;
@@ -125,6 +132,10 @@ class _LottiePlayerState extends State<LottiePlayer>
_releaseClip();
_started = false;
_showedFrames = false;
_playheadMs = 0.0;
_speed = 1.0;
_targetSpeed = _isScrolling ? _slowSpeed : 1.0;
_lastElapsedMs = null;
}
}
@@ -152,20 +163,31 @@ class _LottiePlayerState extends State<LottiePlayer>
if (clip == null || clip.frameCount <= 1) return;
final periodMs = clip.durationMs;
if (periodMs <= 0) return;
final t = (elapsed.inMilliseconds % periodMs) / periodMs;
final index = (t * (clip.frameCount - 1)).round().clamp(
0,
clip.frameCount - 1,
);
final nowMs = elapsed.inMicroseconds / 1000.0;
final last = _lastElapsedMs;
_lastElapsedMs = nowMs;
if (last == null) return;
var dt = nowMs - last;
if (dt < 0) dt = 0;
if (dt > 64) dt = 64;
if (_speed != _targetSpeed) {
final step = dt / _rampMs * (1.0 - _slowSpeed);
final diff = _targetSpeed - _speed;
_speed = diff.abs() <= step ? _targetSpeed : _speed + step * diff.sign;
}
_playheadMs = (_playheadMs + dt * _speed) % periodMs;
final t = _playheadMs / periodMs;
final index =
(t * (clip.frameCount - 1)).round().clamp(0, clip.frameCount - 1);
if (index != _frameIndex.value) _frameIndex.value = index;
}
void _onGateChanged() {
if (!mounted) return;
if (_isScrolling) {
if (_ticker.isActive) _ticker.stop();
return;
}
_targetSpeed = _isScrolling ? _slowSpeed : 1.0;
final clip = _clip;
if (clip != null) {
_maybeStartTicker(clip);
@@ -182,15 +204,18 @@ class _LottiePlayerState extends State<LottiePlayer>
}
void _maybeStartTicker(RlottieClip clip) {
if (_isScrolling || clip.frameCount <= 1) return;
if (clip.frameCount <= 1) return;
final lead = clip.frameCount < _leadFrames ? clip.frameCount : _leadFrames;
if (clip.ready.value >= lead && !_ticker.isActive) _ticker.start();
if (clip.ready.value >= lead && !_ticker.isActive) {
_lastElapsedMs = null;
_ticker.start();
}
}
void _ensure(double box) {
if (_clip != null) return;
final dpr = MediaQuery.devicePixelRatioOf(context);
final raw = (box * dpr.clamp(1.0, 2.0)).clamp(96.0, 512.0);
final raw = (box * dpr.clamp(1.0, 2.0)).clamp(96.0, 384.0);
_px = (raw / 32).ceil() * 32;
if (_started || !_canLoad) return;
_startLoad();