diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index a4527eda6..d0c7bc11c 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -3617,8 +3617,12 @@ class CursorModel with ChangeNotifier { static int _nextKeyScope = 0; final int _keyScope = _nextKeyScope++; - String nativeKey(CursorData cache, double scale) => - '${_keyScope}_${cache.updateGetKey(scale)}'; + String nativeKey(CursorData cache, double scale) { + final key = '${_keyScope}_${cache.updateGetKey(scale)}'; + // A native cursor at another raster does not hold the pixels this one is made from. + if (!_cacheKeys.contains(key)) _nativeIds.remove(cache.id); + return key; + } // Native cursors stay for the session, one per raster of each shape, and only [clear] // deletes them, as before; unlike the painted images they have no LRU, for now. A delete diff --git a/flutter/test/cursor_replay_test.dart b/flutter/test/cursor_replay_test.dart index 552b16cbb..1bccddd7d 100644 --- a/flutter/test/cursor_replay_test.dart +++ b/flutter/test/cursor_replay_test.dart @@ -239,6 +239,34 @@ void main() { expect(registered.where((key) => key.contains('_B_')), isNotEmpty); }); + test('a zoom still gets native cursors for shapes that keep decoding late', + () async { + final cursor = ffi.cursorModel; + // An animation whose frames have native cursors at 1.0 only. + for (final (id, seed) in [('A', 1), ('B', 2)]) { + await _feed(ffi, id, size: 32, seed: seed); + buildCursorOfCache(cursor, 1.0, cursor.cache); + await _settle(); + } + // At 0.5 each frame is fetched again, and each fetch answers after the next switch. + ffi.cursor.holdFetches = true; + for (var i = 0; i < 6; i++) { + _select(ffi, i.isEven ? 'A' : 'B'); + ffi.cursor.answerFetches(); + await _settle(); + buildCursorOfCache(cursor, 0.5, cursor.cache); + await _settle(); + } + expect( + registered + .where((key) => key.contains('_A_') && key.endsWith('_16_16')), + isNotEmpty); + expect( + registered + .where((key) => key.contains('_B_') && key.endsWith('_16_16')), + isNotEmpty); + }); + test('shapes waiting for a native cursor keep their pixels, within the limit', () async { const max = CursorModel.kRecentShapes;