From 7ff04851f49d77130b24f44d10e1d6880a89fb5a Mon Sep 17 00:00:00 2001 From: stopflock Date: Sat, 22 Nov 2025 13:22:17 -0600 Subject: [PATCH] Fix tile loading finally --- DEVELOPER.md | 2 +- README.md | 1 - assets/changelog.json | 6 ++++ lib/dev_config.dart | 11 ++++--- .../tiles_from_remote.dart | 32 ++++++++++--------- pubspec.yaml | 2 +- 6 files changed, 31 insertions(+), 23 deletions(-) diff --git a/DEVELOPER.md b/DEVELOPER.md index 7faed70..fd12fdf 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -123,7 +123,7 @@ The welcome popup explains that the app: **Key methods:** - `getNodes()`: Returns cache immediately, triggers pre-fetch if needed (spatial or temporal) -- `getTile()`: Tile fetching with enhanced retry strategy (6 attempts, 1-8s delays) +- `getTile()`: Tile fetching with unlimited retry strategy (retries until success) - `_fetchRemoteNodes()`: Handles Overpass → OSM API fallback **Smart caching flow:** diff --git a/README.md b/README.md index 514c414..f2b1eaf 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,6 @@ cp lib/keys.dart.example lib/keys.dart ### Needed Bugfixes - Update node cache to reflect cleared queue entries -- Improve/retune tile fetching backoff/retry - Are offline areas preferred for fast loading even when online? Check working. - Fix network indicator - only done when fetch queue is empty! diff --git a/assets/changelog.json b/assets/changelog.json index 96a9454..7623581 100644 --- a/assets/changelog.json +++ b/assets/changelog.json @@ -1,4 +1,10 @@ { + "1.4.6": { + "content": [ + "• IMPROVED: Tile fetching reliability - removed retry limits so visible tiles always load eventually", + "• FIXED: Queue management - cancel requests for off-screen tiles, ongoing requests continue normally" + ] + }, "1.4.5": { "content": [ "• NEW: Minimum zoom level (Z15) enforced for adding and editing surveillance nodes to ensure precise positioning", diff --git a/lib/dev_config.dart b/lib/dev_config.dart index 962d67d..8ddfc6a 100644 --- a/lib/dev_config.dart +++ b/lib/dev_config.dart @@ -124,12 +124,13 @@ const double kPinchZoomThreshold = 0.2; // How much pinch required to start zoom const double kPinchMoveThreshold = 30.0; // How much drag required for two-finger pan (default 40.0) const double kRotationThreshold = 6.0; // Degrees of rotation required before map actually rotates (Google Maps style) -// Tile fetch retry parameters (configurable backoff system) -const int kTileFetchMaxAttempts = 16; // Number of retry attempts before giving up -const int kTileFetchInitialDelayMs = 500; // Base delay for first retry (1 second) +// Tile fetch configuration (brutalist approach: simple, configurable, unlimited retries) +const int kTileFetchConcurrentThreads = 10; // Number of simultaneous tile downloads +const int kTileFetchInitialDelayMs = 200; // Base delay for first retry (500ms) const double kTileFetchBackoffMultiplier = 1.5; // Multiply delay by this each attempt -const int kTileFetchMaxDelayMs = 10000; // Cap delays at this value (8 seconds max) -const int kTileFetchRandomJitterMs = 250; // Random fuzz to add (0 to 500ms) +const int kTileFetchMaxDelayMs = 5000; // Cap delays at this value (10 seconds max) +const int kTileFetchRandomJitterMs = 100; // Random fuzz to add (0 to 250ms) +// Note: Removed max attempts - tiles retry indefinitely until they succeed or are canceled // User download max zoom span (user can download up to kMaxUserDownloadZoomSpan zooms above min) const int kMaxUserDownloadZoomSpan = 7; diff --git a/lib/services/map_data_submodules/tiles_from_remote.dart b/lib/services/map_data_submodules/tiles_from_remote.dart index a435749..df905db 100644 --- a/lib/services/map_data_submodules/tiles_from_remote.dart +++ b/lib/services/map_data_submodules/tiles_from_remote.dart @@ -9,7 +9,7 @@ import 'package:deflockapp/dev_config.dart'; import '../network_status.dart'; /// Global semaphore to limit simultaneous tile fetches -final _tileFetchSemaphore = _SimpleSemaphore(4); // Max 4 concurrent +final _tileFetchSemaphore = _SimpleSemaphore(kTileFetchConcurrentThreads); /// Clear queued tile requests when map view changes significantly void clearRemoteTileQueue() { @@ -93,15 +93,15 @@ bool _isTileVisible(int z, int x, int y, LatLngBounds viewBounds) { -/// Fetches a tile from any remote provider, with in-memory retries/backoff, and global concurrency limit. -/// Returns tile image bytes, or throws on persistent failure. +/// Fetches a tile from any remote provider with unlimited retries. +/// Returns tile image bytes. Retries forever until success. +/// Brutalist approach: Keep trying until it works - no arbitrary retry limits. Future> fetchRemoteTile({ required int z, required int x, required int y, required String url, }) async { - const int maxAttempts = kTileFetchMaxAttempts; int attempt = 0; final random = Random(); final hostInfo = Uri.parse(url).host; // For logging @@ -109,20 +109,23 @@ Future> fetchRemoteTile({ while (true) { await _tileFetchSemaphore.acquire(z: z, x: x, y: y); try { - // Only log on first attempt or errors - if (attempt == 1) { + // Only log on first attempt + if (attempt == 0) { debugPrint('[fetchRemoteTile] Fetching $z/$x/$y from $hostInfo'); } attempt++; final resp = await http.get(Uri.parse(url)); if (resp.statusCode == 200 && resp.bodyBytes.isNotEmpty) { - // Success - no logging for normal operation - NetworkStatus.instance.reportOsmTileSuccess(); // Generic tile server reporting + // Success! + if (attempt > 1) { + debugPrint('[fetchRemoteTile] SUCCESS $z/$x/$y from $hostInfo after $attempt attempts'); + } + NetworkStatus.instance.reportOsmTileSuccess(); return resp.bodyBytes; } else { debugPrint('[fetchRemoteTile] FAIL $z/$x/$y from $hostInfo: code=${resp.statusCode}, bytes=${resp.bodyBytes.length}'); - NetworkStatus.instance.reportOsmTileIssue(); // Generic tile server reporting + NetworkStatus.instance.reportOsmTileIssue(); throw HttpException('Failed to fetch tile $z/$x/$y from $hostInfo: status ${resp.statusCode}'); } } catch (e) { @@ -130,17 +133,16 @@ Future> fetchRemoteTile({ if (e.toString().contains('Connection refused') || e.toString().contains('Connection timed out') || e.toString().contains('Connection reset')) { - NetworkStatus.instance.reportOsmTileIssue(); // Generic tile server reporting - } - - if (attempt >= maxAttempts) { - debugPrint("[fetchRemoteTile] Failed for $z/$x/$y from $hostInfo after $attempt attempts: $e"); - rethrow; + NetworkStatus.instance.reportOsmTileIssue(); } + // Calculate delay and retry (no attempt limit - keep trying forever) final delay = _calculateRetryDelay(attempt, random); if (attempt == 1) { debugPrint("[fetchRemoteTile] Attempt $attempt for $z/$x/$y from $hostInfo failed: $e. Retrying in ${delay}ms."); + } else if (attempt % 10 == 0) { + // Log every 10th attempt to show we're still working + debugPrint("[fetchRemoteTile] Still trying $z/$x/$y from $hostInfo (attempt $attempt). Retrying in ${delay}ms."); } await Future.delayed(Duration(milliseconds: delay)); } finally { diff --git a/pubspec.yaml b/pubspec.yaml index e9a1658..24e8b74 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: deflockapp description: Map public surveillance infrastructure with OpenStreetMap publish_to: "none" -version: 1.4.5+16 # The thing after the + is the version code, incremented with each release +version: 1.4.6+17 # The thing after the + is the version code, incremented with each release environment: sdk: ">=3.5.0 <4.0.0" # oauth2_client 4.x needs Dart 3.5+