From 62ed89be9fbbb94369e4d67a31014a92e2bef8f6 Mon Sep 17 00:00:00 2001 From: Dario Accornero Date: Wed, 15 Jul 2026 15:50:19 +0200 Subject: [PATCH 01/11] [flame_svg] Added support in class Svg to switch between memory/map caching. --- packages/flame_svg/lib/svg.dart | 44 ++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/packages/flame_svg/lib/svg.dart b/packages/flame_svg/lib/svg.dart index e53b3e02769..f9c70201b1d 100644 --- a/packages/flame_svg/lib/svg.dart +++ b/packages/flame_svg/lib/svg.dart @@ -28,6 +28,20 @@ class Svg { /// The pixel ratio that this [Svg] is rendered based on. final double pixelRatio; + // TESTING. + var _useMap = false; + + /// Whether we use a memory cache or a map to store pre-rendered images. + bool get useMap => _useMap; + set useMap(bool use) { + _useMap = use; + emptyCache(); + } + + /// The current cache size. + int get cacheSize => !useMap ? _imageCache.size : _imageMap.length; + + final Map _imageMap = {}; final MemoryCache _imageCache = MemoryCache(); final _paint = Paint()..filterQuality = FilterQuality.medium; @@ -101,7 +115,9 @@ class Svg { Image _getImage(Size size, double widthRatio, double heightRatio) { final cacheKey = Size(size.width * widthRatio, size.height * heightRatio); - final image = _imageCache.getValue(cacheKey); + final image = !useMap + ? _imageCache.getValue(cacheKey) + : _imageMap[cacheKey]; if (image == null) { final recorder = PictureRecorder(); @@ -115,7 +131,11 @@ class Svg { ); picture.dispose(); - _imageCache.setValue(cacheKey, image); + if (!useMap) { + _imageCache.setValue(cacheKey, image); + } else { + _imageMap[cacheKey] = image; + } return image; } @@ -139,10 +159,22 @@ class Svg { /// this method once the instance is no longer needed to avoid /// memory leaks void dispose() { - _imageCache.keys.forEach((key) { - _imageCache.getValue(key)?.dispose(); - }); - _imageCache.clearCache(); + emptyCache(); + } + + /// Get rid of all cached images. + void emptyCache() { + if (!useMap) { + _imageCache.keys.forEach((key) { + _imageCache.getValue(key)?.dispose(); + }); + _imageCache.clearCache(); + } else { + _imageMap.keys.forEach((key) { + _imageMap[key]?.dispose(); + }); + _imageMap.clear(); + } } } From 4b4d5f283fa1123959aa65d45d0bd55c4b977796 Mon Sep 17 00:00:00 2001 From: Dario Accornero Date: Wed, 15 Jul 2026 16:11:04 +0200 Subject: [PATCH 02/11] Fixed erroneous cache emptying toggling between modes. --- packages/flame_svg/lib/svg.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flame_svg/lib/svg.dart b/packages/flame_svg/lib/svg.dart index f9c70201b1d..a8a82e0b4f8 100644 --- a/packages/flame_svg/lib/svg.dart +++ b/packages/flame_svg/lib/svg.dart @@ -34,8 +34,8 @@ class Svg { /// Whether we use a memory cache or a map to store pre-rendered images. bool get useMap => _useMap; set useMap(bool use) { - _useMap = use; emptyCache(); + _useMap = use; } /// The current cache size. From 10e1351b3027af545274d347d1aff3196b73a9b7 Mon Sep 17 00:00:00 2001 From: Dario Accornero Date: Fri, 17 Jul 2026 01:40:40 +0200 Subject: [PATCH 03/11] [flame_svg] Added Svg.integralSize, .fixedRatio and .useMap properties. These are available at construction/loading, as well as at runtime; the latter is probably overkill. --- packages/flame_svg/lib/svg.dart | 107 +++++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 22 deletions(-) diff --git a/packages/flame_svg/lib/svg.dart b/packages/flame_svg/lib/svg.dart index a8a82e0b4f8..9fbd14325e0 100644 --- a/packages/flame_svg/lib/svg.dart +++ b/packages/flame_svg/lib/svg.dart @@ -12,15 +12,23 @@ import 'package:flutter_svg/flutter_svg.dart'; class Svg { /// Creates an [Svg] with the received [pictureInfo]. /// Default [pixelRatio] is the device pixel ratio. - Svg(this.pictureInfo, {double? pixelRatio}) - : pixelRatio = - pixelRatio ?? - WidgetsBinding - .instance - .platformDispatcher - .views - .first - .devicePixelRatio; + Svg( + this.pictureInfo, { + double? pixelRatio, + bool integralSize = false, + bool fixedRatio = false, + bool useMap = false, + }) : _integralSize = integralSize, + _fixedRatio = fixedRatio, + _useMap = useMap, + pixelRatio = + pixelRatio ?? + WidgetsBinding + .instance + .platformDispatcher + .views + .first + .devicePixelRatio; /// The [PictureInfo] that this [Svg] represents. final PictureInfo pictureInfo; @@ -28,16 +36,35 @@ class Svg { /// The pixel ratio that this [Svg] is rendered based on. final double pixelRatio; - // TESTING. - var _useMap = false; + /// Whether we're using integral sizes for the cache keys (default: false). + bool get integralSize => _integralSize; + set integralSize(bool integral) { + emptyCache(); + _integralSize = integral; + } + + late bool _integralSize; + + /// Whether we're using a fixed ratio for the cache keys (default: false). + bool get fixedRatio => _fixedRatio; + set fixedRatio(bool fixed) { + emptyCache(); + _fixedRatio = fixed; + } + + late bool _fixedRatio; - /// Whether we use a memory cache or a map to store pre-rendered images. + // TODO(adario): Alternatively, set the memory cache size to e.g. max int... + /// Whether we use a MemoryCache or a Map to store pre-rendered images + /// (default: false). bool get useMap => _useMap; set useMap(bool use) { emptyCache(); _useMap = use; } + var _useMap = false; + /// The current cache size. int get cacheSize => !useMap ? _imageCache.size : _imageMap.length; @@ -52,22 +79,37 @@ class Svg { String fileName, { AssetsCache? cache, double? pixelRatio, + bool integralSize = false, + bool fixedRatio = false, + bool useMap = false, String? package, }) async { cache ??= Flame.assets; final svgString = await cache.readFile(fileName, package: package); - return Svg.loadFromString(svgString, pixelRatio: pixelRatio); + return Svg.loadFromString( + svgString, + pixelRatio: pixelRatio, + integralSize: integralSize, + fixedRatio: fixedRatio, + useMap: useMap, + ); } /// Loads an [Svg] from a string. static Future loadFromString( String svgString, { double? pixelRatio, + bool integralSize = false, + bool fixedRatio = false, + bool useMap = false, }) async { final pictureInfo = await vg.loadPicture(SvgStringLoader(svgString), null); return Svg( pictureInfo, pixelRatio: pixelRatio, + integralSize: integralSize, + fixedRatio: fixedRatio, + useMap: useMap, ); } @@ -82,10 +124,12 @@ class Svg { // camera.viewfinder.zoom larger than 1.0 final destinationClipBounds = canvas.getDestinationClipBounds(); final localClipBounds = canvas.getLocalClipBounds(); - final widthRatio = - destinationClipBounds.size.width / localClipBounds.size.width; - final heightRatio = - destinationClipBounds.size.height / localClipBounds.size.height; + final widthRatio = fixedRatio + ? 1.0 + : destinationClipBounds.size.width / localClipBounds.size.width; + final heightRatio = fixedRatio + ? 1.0 + : destinationClipBounds.size.height / localClipBounds.size.height; final localSize = Size(size.x, size.y); final image = _getImage(localSize, widthRatio, heightRatio); @@ -114,7 +158,14 @@ class Svg { } Image _getImage(Size size, double widthRatio, double heightRatio) { - final cacheKey = Size(size.width * widthRatio, size.height * heightRatio); + final width = size.width * widthRatio; + final height = size.height * heightRatio; + Size cacheKey; + if (fixedRatio || integralSize) { + cacheKey = Size(width.roundToDouble(), height.roundToDouble()); + } else { + cacheKey = Size(width, height); + } final image = !useMap ? _imageCache.getValue(cacheKey) : _imageMap[cacheKey]; @@ -126,8 +177,8 @@ class Svg { _render(canvas, size); final picture = recorder.endRecording(); final image = picture.toImageSync( - (size.width * pixelRatio * widthRatio).ceil(), - (size.height * pixelRatio * heightRatio).ceil(), + (width * pixelRatio).ceil(), + (height * pixelRatio).ceil(), ); picture.dispose(); @@ -181,6 +232,18 @@ class Svg { /// Provides a loading method for [Svg] on the [Game] class. extension SvgLoader on Game { /// Loads an [Svg] using the [Game]'s own asset loader. - Future loadSvg(String fileName, {String? package}) => - Svg.load(fileName, cache: assets, package: package); + Future loadSvg( + String fileName, { + String? package, + bool integralSize = false, + bool fixedRatio = false, + bool useMap = false, + }) => Svg.load( + fileName, + cache: assets, + package: package, + integralSize: integralSize, + fixedRatio: fixedRatio, + useMap: useMap, + ); } From 21ab3d737fdc8b9a59e7132b7d33e52d331ec8c4 Mon Sep 17 00:00:00 2001 From: Dario Accornero Date: Sat, 18 Jul 2026 16:22:19 +0200 Subject: [PATCH 04/11] [flame_svg] Refactored unlimited caching in Svg to use MemoryCache. --- packages/flame_svg/lib/svg.dart | 79 +++++++++++++++++---------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/packages/flame_svg/lib/svg.dart b/packages/flame_svg/lib/svg.dart index 9fbd14325e0..09b33d7a3e0 100644 --- a/packages/flame_svg/lib/svg.dart +++ b/packages/flame_svg/lib/svg.dart @@ -12,15 +12,20 @@ import 'package:flutter_svg/flutter_svg.dart'; class Svg { /// Creates an [Svg] with the received [pictureInfo]. /// Default [pixelRatio] is the device pixel ratio. + /// Setting [integralSize] to `true` uses integer dimensions for cache keys. + /// Setting [fixedRatio] to `true` ensures the cache uses a single entry. + /// Default [cacheSize] is [defaultCacheSize], which is 10 as previously; + /// specifying [unlimitedCacheSize] is the same as using a [Map] instead + /// of a [MemoryCache]. Svg( this.pictureInfo, { double? pixelRatio, bool integralSize = false, bool fixedRatio = false, - bool useMap = false, + int cacheSize = defaultCacheSize, }) : _integralSize = integralSize, _fixedRatio = fixedRatio, - _useMap = useMap, + _cacheSize = cacheSize, pixelRatio = pixelRatio ?? WidgetsBinding @@ -54,22 +59,25 @@ class Svg { late bool _fixedRatio; - // TODO(adario): Alternatively, set the memory cache size to e.g. max int... - /// Whether we use a MemoryCache or a Map to store pre-rendered images - /// (default: false). - bool get useMap => _useMap; - set useMap(bool use) { - emptyCache(); - _useMap = use; + /// The current cache size (default 10): changing the size also empties it. + int get cacheSize => _cacheSize; + set cacheSize(int size) { + assert(size >= 1); + if (size >= 1 && size != cacheSize) { + emptyCache(); + _cacheSize = size; + _imageCache = MemoryCache(cacheSize: _cacheSize); + } } - var _useMap = false; + late int _cacheSize; - /// The current cache size. - int get cacheSize => !useMap ? _imageCache.size : _imageMap.length; + /// The number of images currently used by the cache. + int get cacheUsage => _imageCache.size; - final Map _imageMap = {}; - final MemoryCache _imageCache = MemoryCache(); + late MemoryCache _imageCache = MemoryCache( + cacheSize: _cacheSize, + ); final _paint = Paint()..filterQuality = FilterQuality.medium; @@ -81,7 +89,7 @@ class Svg { double? pixelRatio, bool integralSize = false, bool fixedRatio = false, - bool useMap = false, + int cacheSize = defaultCacheSize, String? package, }) async { cache ??= Flame.assets; @@ -91,7 +99,7 @@ class Svg { pixelRatio: pixelRatio, integralSize: integralSize, fixedRatio: fixedRatio, - useMap: useMap, + cacheSize: cacheSize, ); } @@ -101,7 +109,7 @@ class Svg { double? pixelRatio, bool integralSize = false, bool fixedRatio = false, - bool useMap = false, + int cacheSize = defaultCacheSize, }) async { final pictureInfo = await vg.loadPicture(SvgStringLoader(svgString), null); return Svg( @@ -109,10 +117,16 @@ class Svg { pixelRatio: pixelRatio, integralSize: integralSize, fixedRatio: fixedRatio, - useMap: useMap, + cacheSize: cacheSize, ); } + /// Default memory cache size. + static const int defaultCacheSize = 10; + + /// Unlimited memory cache size. + static final int unlimitedCacheSize = double.maxFinite.toInt(); + /// Renders the svg on the [canvas] using the dimensions provided by [size]. void render( Canvas canvas, @@ -166,9 +180,7 @@ class Svg { } else { cacheKey = Size(width, height); } - final image = !useMap - ? _imageCache.getValue(cacheKey) - : _imageMap[cacheKey]; + final image = _imageCache.getValue(cacheKey); if (image == null) { final recorder = PictureRecorder(); @@ -182,11 +194,7 @@ class Svg { ); picture.dispose(); - if (!useMap) { - _imageCache.setValue(cacheKey, image); - } else { - _imageMap[cacheKey] = image; - } + _imageCache.setValue(cacheKey, image); return image; } @@ -215,17 +223,10 @@ class Svg { /// Get rid of all cached images. void emptyCache() { - if (!useMap) { - _imageCache.keys.forEach((key) { - _imageCache.getValue(key)?.dispose(); - }); - _imageCache.clearCache(); - } else { - _imageMap.keys.forEach((key) { - _imageMap[key]?.dispose(); - }); - _imageMap.clear(); - } + _imageCache.keys.forEach((key) { + _imageCache.getValue(key)?.dispose(); + }); + _imageCache.clearCache(); } } @@ -237,13 +238,13 @@ extension SvgLoader on Game { String? package, bool integralSize = false, bool fixedRatio = false, - bool useMap = false, + int cacheSize = Svg.defaultCacheSize, }) => Svg.load( fileName, cache: assets, package: package, integralSize: integralSize, fixedRatio: fixedRatio, - useMap: useMap, + cacheSize: cacheSize, ); } From 3194bb9754a71349b333edb1e162f08666d9a49a Mon Sep 17 00:00:00 2001 From: Dario Accornero Date: Mon, 20 Jul 2026 23:40:29 +0200 Subject: [PATCH 05/11] [flame_svg] Updated 'flutter_svg' package to latest release. --- packages/flame_svg/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flame_svg/pubspec.yaml b/packages/flame_svg/pubspec.yaml index 16ab76f086f..f017d1f69b3 100644 --- a/packages/flame_svg/pubspec.yaml +++ b/packages/flame_svg/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: flame: ^1.38.0 flutter: sdk: flutter - flutter_svg: ^2.0.5 + flutter_svg: ^2.3.0 dev_dependencies: dartdoc: ^9.0.0 From a9d5952185dd396cc270860689f1afbc6a0a5c86 Mon Sep 17 00:00:00 2001 From: Dario Accornero Date: Tue, 21 Jul 2026 14:54:06 +0200 Subject: [PATCH 06/11] [flame_svg] Added missing assertion message in class Svg. --- packages/flame_svg/lib/svg.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flame_svg/lib/svg.dart b/packages/flame_svg/lib/svg.dart index 09b33d7a3e0..1c06133f290 100644 --- a/packages/flame_svg/lib/svg.dart +++ b/packages/flame_svg/lib/svg.dart @@ -62,7 +62,7 @@ class Svg { /// The current cache size (default 10): changing the size also empties it. int get cacheSize => _cacheSize; set cacheSize(int size) { - assert(size >= 1); + assert(size >= 1, 'The cache size must contain at least one slot.'); if (size >= 1 && size != cacheSize) { emptyCache(); _cacheSize = size; From 924e2769a1994c0ffa0ee5beb7e01a2a9125e4cd Mon Sep 17 00:00:00 2001 From: Dario Accornero Date: Tue, 21 Jul 2026 15:32:40 +0200 Subject: [PATCH 07/11] [flame_svg] Refactored class Svg constructor for melos compliance. --- packages/flame_svg/lib/svg.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/flame_svg/lib/svg.dart b/packages/flame_svg/lib/svg.dart index 1c06133f290..5fda0277044 100644 --- a/packages/flame_svg/lib/svg.dart +++ b/packages/flame_svg/lib/svg.dart @@ -23,17 +23,19 @@ class Svg { bool integralSize = false, bool fixedRatio = false, int cacheSize = defaultCacheSize, - }) : _integralSize = integralSize, - _fixedRatio = fixedRatio, - _cacheSize = cacheSize, - pixelRatio = + }) : pixelRatio = pixelRatio ?? WidgetsBinding .instance .platformDispatcher .views .first - .devicePixelRatio; + .devicePixelRatio { + _integralSize = integralSize; + _fixedRatio = fixedRatio; + _cacheSize = cacheSize; + _imageCache = MemoryCache(cacheSize: _cacheSize); + } /// The [PictureInfo] that this [Svg] represents. final PictureInfo pictureInfo; @@ -75,9 +77,7 @@ class Svg { /// The number of images currently used by the cache. int get cacheUsage => _imageCache.size; - late MemoryCache _imageCache = MemoryCache( - cacheSize: _cacheSize, - ); + late MemoryCache _imageCache; final _paint = Paint()..filterQuality = FilterQuality.medium; From d419623407cff9c4277eed2af795d783c0f16c2c Mon Sep 17 00:00:00 2001 From: Dario Accornero Date: Tue, 21 Jul 2026 15:57:30 +0200 Subject: [PATCH 08/11] [flame_svg] Bumped up package version. --- packages/flame_svg/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flame_svg/pubspec.yaml b/packages/flame_svg/pubspec.yaml index f017d1f69b3..cbb89a20ffe 100644 --- a/packages/flame_svg/pubspec.yaml +++ b/packages/flame_svg/pubspec.yaml @@ -1,7 +1,7 @@ name: flame_svg resolution: workspace description: Package to add SVG rendering support for the Flame game engine -version: 1.12.2 +version: 1.13.0 homepage: https://github.com/flame-engine/flame/tree/main/packages/flame_svg funding: - https://opencollective.com/blue-fire From fbb0f2b4860556b9313cd249ee3c63b5b2aa6476 Mon Sep 17 00:00:00 2001 From: Dario Accornero Date: Tue, 21 Jul 2026 16:54:44 +0200 Subject: [PATCH 09/11] [flame_svg] Made cache emptying method private. --- packages/flame_svg/lib/svg.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/flame_svg/lib/svg.dart b/packages/flame_svg/lib/svg.dart index 5fda0277044..010b8765a66 100644 --- a/packages/flame_svg/lib/svg.dart +++ b/packages/flame_svg/lib/svg.dart @@ -46,7 +46,7 @@ class Svg { /// Whether we're using integral sizes for the cache keys (default: false). bool get integralSize => _integralSize; set integralSize(bool integral) { - emptyCache(); + _emptyCache(); _integralSize = integral; } @@ -55,7 +55,7 @@ class Svg { /// Whether we're using a fixed ratio for the cache keys (default: false). bool get fixedRatio => _fixedRatio; set fixedRatio(bool fixed) { - emptyCache(); + _emptyCache(); _fixedRatio = fixed; } @@ -66,7 +66,7 @@ class Svg { set cacheSize(int size) { assert(size >= 1, 'The cache size must contain at least one slot.'); if (size >= 1 && size != cacheSize) { - emptyCache(); + _emptyCache(); _cacheSize = size; _imageCache = MemoryCache(cacheSize: _cacheSize); } @@ -218,11 +218,11 @@ class Svg { /// this method once the instance is no longer needed to avoid /// memory leaks void dispose() { - emptyCache(); + _emptyCache(); } /// Get rid of all cached images. - void emptyCache() { + void _emptyCache() { _imageCache.keys.forEach((key) { _imageCache.getValue(key)?.dispose(); }); From 907fc804de4f6d4c5c7939f12dafc7367b8e554b Mon Sep 17 00:00:00 2001 From: Dario Accornero Date: Tue, 21 Jul 2026 23:53:19 +0200 Subject: [PATCH 10/11] Revert "[flame_svg] Bumped up package version." This reverts commit d419623407cff9c4277eed2af795d783c0f16c2c. --- packages/flame_svg/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flame_svg/pubspec.yaml b/packages/flame_svg/pubspec.yaml index cbb89a20ffe..f017d1f69b3 100644 --- a/packages/flame_svg/pubspec.yaml +++ b/packages/flame_svg/pubspec.yaml @@ -1,7 +1,7 @@ name: flame_svg resolution: workspace description: Package to add SVG rendering support for the Flame game engine -version: 1.13.0 +version: 1.12.2 homepage: https://github.com/flame-engine/flame/tree/main/packages/flame_svg funding: - https://opencollective.com/blue-fire From 48958843c103047e8db93e43d54fb5172a0d81fc Mon Sep 17 00:00:00 2001 From: Dario Accornero Date: Wed, 22 Jul 2026 01:35:29 +0200 Subject: [PATCH 11/11] [flame_svg] Fixed issues raised by Copilot in the PR #3956 review. --- packages/flame_svg/lib/svg.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/flame_svg/lib/svg.dart b/packages/flame_svg/lib/svg.dart index 010b8765a66..f6eb4829d24 100644 --- a/packages/flame_svg/lib/svg.dart +++ b/packages/flame_svg/lib/svg.dart @@ -34,7 +34,10 @@ class Svg { _integralSize = integralSize; _fixedRatio = fixedRatio; _cacheSize = cacheSize; - _imageCache = MemoryCache(cacheSize: _cacheSize); + assert(_cacheSize >= 1, 'The cache size must support at least one slot.'); + _imageCache = MemoryCache( + cacheSize: _cacheSize >= 1 ? _cacheSize : defaultCacheSize, + ); } /// The [PictureInfo] that this [Svg] represents. @@ -64,7 +67,7 @@ class Svg { /// The current cache size (default 10): changing the size also empties it. int get cacheSize => _cacheSize; set cacheSize(int size) { - assert(size >= 1, 'The cache size must contain at least one slot.'); + assert(size >= 1, 'The cache size must support at least one slot.'); if (size >= 1 && size != cacheSize) { _emptyCache(); _cacheSize = size; @@ -176,7 +179,7 @@ class Svg { final height = size.height * heightRatio; Size cacheKey; if (fixedRatio || integralSize) { - cacheKey = Size(width.roundToDouble(), height.roundToDouble()); + cacheKey = Size(width.ceilToDouble(), height.ceilToDouble()); } else { cacheKey = Size(width, height); }