part of 'game_resource_manager.dart'; extension _GameResourceManagerLoading on GameResourceManager { Future _loadImage( String? keyOrPath, { required bool failOnError, bool retain = false, }) { if (keyOrPath == null || keyOrPath.isEmpty) { return Future.value(null); } final requestToken = _asyncGate.token; final requestGeneration = requestToken.generation; final path = _tryResolve(keyOrPath); if (path == null) { return Future.value(null); } final existing = _images[path]; if (existing != null) { final image = existing.image; if (existing.generation == requestGeneration && existing.state == GameResourceState.ready && image != null) { if (retain) { existing.refCount++; } _touch(existing); return Future.value(image); } final inflight = existing.inflight; if (existing.generation == requestGeneration && inflight != null) { return failOnError ? _throwIfNull(inflight, keyOrPath) : inflight.catchError((_) => null); } } final record = _ImageResourceRecord(generation: requestGeneration) ..state = GameResourceState.loading; _images[path] = record; final future = _decodeImage(path, record, requestToken, retain: retain); record.inflight = future; return failOnError ? _throwIfNull(future, keyOrPath) : future; } Future _throwIfNull( Future future, String keyOrPath, ) async { final image = await future; if (image == null) { throw ResourceLoadException('Required image resource failed: $keyOrPath'); } return image; } Future _decodeImage( String path, _ImageResourceRecord record, RuntimeAsyncToken requestToken, { required bool retain, }) async { try { final activePackage = _package; if (activePackage == null) { throw StateError('GameResourceManager has no active package'); } final frame = await _loadLimiter.run(() async { final bytes = await activePackage.readBytes(path); final codec = await ui.instantiateImageCodec( bytes.buffer.asUint8List(), ); return codec.getNextFrame(); }); record.inflight = null; if (!_asyncGate.accepts(requestToken) || _images[path] != record) { frame.image.dispose(); record.state = GameResourceState.disposed; return null; } record ..image = frame.image ..estimatedBytes = frame.image.width * frame.image.height * 4 ..state = GameResourceState.ready ..lastError = null; if (retain) { record.refCount++; } _cacheBytes += record.estimatedBytes; _touch(record); _enforceImageBudget(); return frame.image; } catch (error) { record.inflight = null; if (!_asyncGate.accepts(requestToken) || _images[path] != record) { record.state = GameResourceState.disposed; return null; } record ..state = GameResourceState.failed ..lastError = error; _diagnostics?.record( type: RuntimeDiagnosticType.resourceLoadError, message: 'Image resource failed to load', error: error, context: {'path': path, 'generation': requestToken.generation}, ); return null; } } Future _preloadSpine( String keyOrPath, { required bool failOnError, }) async { final spine = await _createSpineComponent(keyOrPath); spine?.dispose(); if (failOnError && spine == null) { throw ResourceLoadException('Required spine resource failed: $keyOrPath'); } } Future _createSpineComponent(String? keyOrPath) async { if (keyOrPath == null || keyOrPath.isEmpty) { return null; } final requestToken = _asyncGate.token; final activePackage = _package; if (activePackage == null) { return null; } final resource = activePackage.manifest.resources[keyOrPath]; if (resource == null || resource.type != GameResourceType.spine) { return null; } try { await initSpineFlutter(); final atlasPath = activePackage.resolveResourcePath(resource.atlas!); final skeletonPath = activePackage.resolveResourcePath( resource.skeleton!, ); final drawable = await _loadLimiter.run(() { return SkeletonDrawableFlutter.fromMemory(atlasPath, skeletonPath, ( name, ) async { final bytes = await activePackage.readBytes(name); return bytes.buffer.asUint8List( bytes.offsetInBytes, bytes.lengthInBytes, ); }); }); if (!_asyncGate.accepts(requestToken)) { drawable.dispose(); return null; } return SpineComponent(drawable); } catch (error) { if (!_asyncGate.accepts(requestToken)) { return null; } _diagnostics?.record( type: RuntimeDiagnosticType.resourceLoadError, message: 'Spine resource failed to load', error: error, context: {'key': keyOrPath, 'generation': requestToken.generation}, ); return null; } } String? _tryResolve(String keyOrPath) { try { return resolve(keyOrPath); } catch (_) { return null; } } }