mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-03-31 00:59:38 +02:00
chasing excess tile fetching and lack of correct cache clearing - NOT WORKING
This commit is contained in:
@@ -9,6 +9,17 @@ import '../network_status.dart';
|
||||
/// Global semaphore to limit simultaneous tile fetches
|
||||
final _tileFetchSemaphore = _SimpleSemaphore(4); // Max 4 concurrent
|
||||
|
||||
/// Cancellation token to invalidate all pending requests
|
||||
int _globalCancelToken = 0;
|
||||
|
||||
/// Clear queued tile requests and cancel all retries
|
||||
void clearOSMTileQueue() {
|
||||
final oldToken = _globalCancelToken;
|
||||
_globalCancelToken++; // Invalidate all pending requests and retries
|
||||
final clearedCount = _tileFetchSemaphore.clearQueue();
|
||||
debugPrint('[OSMTiles] Cancel token: $oldToken -> $_globalCancelToken, cleared $clearedCount queued');
|
||||
}
|
||||
|
||||
/// Fetches a tile from OSM, with in-memory retries/backoff, and global concurrency limit.
|
||||
/// Returns tile image bytes, or throws on persistent failure.
|
||||
Future<List<int>> fetchOSMTile({
|
||||
@@ -25,13 +36,31 @@ Future<List<int>> fetchOSMTile({
|
||||
kTileFetchSecondDelayMs + random.nextInt(kTileFetchJitter2Ms),
|
||||
kTileFetchThirdDelayMs + random.nextInt(kTileFetchJitter3Ms),
|
||||
];
|
||||
|
||||
// Remember the cancel token when we start this request
|
||||
final requestCancelToken = _globalCancelToken;
|
||||
print('[fetchOSMTile] START $z/$x/$y with token $requestCancelToken (global: $_globalCancelToken)');
|
||||
|
||||
while (true) {
|
||||
// Check if this request was cancelled
|
||||
if (requestCancelToken != _globalCancelToken) {
|
||||
print('[fetchOSMTile] CANCELLED $z/$x/$y (token: $requestCancelToken vs $_globalCancelToken)');
|
||||
throw Exception('Tile request cancelled');
|
||||
}
|
||||
|
||||
await _tileFetchSemaphore.acquire();
|
||||
try {
|
||||
print('[fetchOSMTile] FETCH $z/$x/$y');
|
||||
attempt++;
|
||||
final resp = await http.get(Uri.parse(url));
|
||||
print('[fetchOSMTile] HTTP ${resp.statusCode} for $z/$x/$y, length=${resp.bodyBytes.length}');
|
||||
|
||||
// Check cancellation after HTTP request completes - this is the key check!
|
||||
if (requestCancelToken != _globalCancelToken) {
|
||||
print('[fetchOSMTile] CANCELLED $z/$x/$y after HTTP (token: $requestCancelToken vs $_globalCancelToken)');
|
||||
throw Exception('Tile request cancelled');
|
||||
}
|
||||
|
||||
if (resp.statusCode == 200 && resp.bodyBytes.isNotEmpty) {
|
||||
print('[fetchOSMTile] SUCCESS $z/$x/$y');
|
||||
NetworkStatus.instance.reportOsmTileSuccess();
|
||||
@@ -42,6 +71,11 @@ Future<List<int>> fetchOSMTile({
|
||||
throw HttpException('Failed to fetch tile $z/$x/$y: status ${resp.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
// Don't retry cancelled requests
|
||||
if (e.toString().contains('cancelled')) {
|
||||
rethrow;
|
||||
}
|
||||
|
||||
print('[fetchOSMTile] Exception $z/$x/$y: $e');
|
||||
|
||||
// Report network issues on connection errors
|
||||
@@ -55,9 +89,22 @@ Future<List<int>> fetchOSMTile({
|
||||
print("[fetchOSMTile] Failed for $z/$x/$y after $attempt attempts: $e");
|
||||
rethrow;
|
||||
}
|
||||
|
||||
final delay = delays[attempt - 1].clamp(0, 60000);
|
||||
print("[fetchOSMTile] Attempt $attempt for $z/$x/$y failed: $e. Retrying in ${delay}ms.");
|
||||
|
||||
// Check cancellation before and after delay
|
||||
if (requestCancelToken != _globalCancelToken) {
|
||||
print('[fetchOSMTile] CANCELLED $z/$x/$y before retry');
|
||||
throw Exception('Tile request cancelled');
|
||||
}
|
||||
|
||||
await Future.delayed(Duration(milliseconds: delay));
|
||||
|
||||
if (requestCancelToken != _globalCancelToken) {
|
||||
print('[fetchOSMTile] CANCELLED $z/$x/$y after retry delay');
|
||||
throw Exception('Tile request cancelled');
|
||||
}
|
||||
} finally {
|
||||
_tileFetchSemaphore.release();
|
||||
}
|
||||
@@ -90,4 +137,11 @@ class _SimpleSemaphore {
|
||||
_current--;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all queued requests (call when view changes significantly)
|
||||
int clearQueue() {
|
||||
final clearedCount = _queue.length;
|
||||
_queue.clear();
|
||||
return clearedCount;
|
||||
}
|
||||
}
|
||||
@@ -60,25 +60,21 @@ class NetworkStatus extends ChangeNotifier {
|
||||
|
||||
/// Report successful operations to potentially clear issues faster
|
||||
void reportOsmTileSuccess() {
|
||||
// Don't immediately clear on single success, but reduce recovery time
|
||||
// Clear issues immediately on success (they were likely temporary)
|
||||
if (_osmTilesHaveIssues) {
|
||||
debugPrint('[NetworkStatus] OSM tile server issues cleared after success');
|
||||
_osmTilesHaveIssues = false;
|
||||
_osmRecoveryTimer?.cancel();
|
||||
_osmRecoveryTimer = Timer(const Duration(seconds: 30), () {
|
||||
_osmTilesHaveIssues = false;
|
||||
notifyListeners();
|
||||
debugPrint('[NetworkStatus] OSM tile server issues cleared after success');
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void reportOverpassSuccess() {
|
||||
if (_overpassHasIssues) {
|
||||
debugPrint('[NetworkStatus] Overpass API issues cleared after success');
|
||||
_overpassHasIssues = false;
|
||||
_overpassRecoveryTimer?.cancel();
|
||||
_overpassRecoveryTimer = Timer(const Duration(seconds: 30), () {
|
||||
_overpassHasIssues = false;
|
||||
notifyListeners();
|
||||
debugPrint('[NetworkStatus] Overpass API issues cleared after success');
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import 'camera_provider_with_cache.dart';
|
||||
import 'map/camera_markers.dart';
|
||||
import 'map/direction_cones.dart';
|
||||
import 'map/map_overlays.dart';
|
||||
import 'network_status_indicator.dart';
|
||||
import '../dev_config.dart';
|
||||
|
||||
class MapView extends StatefulWidget {
|
||||
@@ -95,6 +96,7 @@ class _MapViewState extends State<MapView> {
|
||||
void _onTilesCached() {
|
||||
// When new tiles are cached, just trigger a widget rebuild
|
||||
// This should cause the TileLayer to re-render with cached tiles
|
||||
debugPrint('[MapView] Tile cached callback triggered, calling setState');
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
@@ -176,12 +178,6 @@ class _MapViewState extends State<MapView> {
|
||||
return ids1.length == ids2.length && ids1.containsAll(ids2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appState = context.watch<AppState>();
|
||||
@@ -270,6 +266,11 @@ class _MapViewState extends State<MapView> {
|
||||
if (session != null) {
|
||||
appState.updateSession(target: pos.center);
|
||||
}
|
||||
|
||||
// Simple approach: cancel tiles on ANY significant view change
|
||||
final tileProvider = Provider.of<TileProviderWithCache>(context, listen: false);
|
||||
tileProvider.cancelAllTileRequests();
|
||||
|
||||
// Request more cameras on any map movement/zoom at valid zoom level
|
||||
// This ensures cameras load even when zooming without panning (like with zoom buttons)
|
||||
if (pos.zoom >= 10) {
|
||||
@@ -323,6 +324,9 @@ class _MapViewState extends State<MapView> {
|
||||
uploadMode: appState.uploadMode,
|
||||
session: session,
|
||||
),
|
||||
|
||||
// Network status indicator (top-left)
|
||||
const NetworkStatusIndicator(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import '../services/map_data_provider.dart';
|
||||
import '../services/map_data_submodules/tiles_from_osm.dart';
|
||||
|
||||
/// In-memory tile cache and async provider for custom tiles.
|
||||
class TileProviderWithCache extends TileProvider with ChangeNotifier {
|
||||
@@ -11,7 +12,7 @@ class TileProviderWithCache extends TileProvider with ChangeNotifier {
|
||||
bool _disposed = false;
|
||||
int _disposeCount = 0;
|
||||
VoidCallback? _onTilesCachedCallback;
|
||||
|
||||
|
||||
TileProviderWithCache();
|
||||
|
||||
/// Set a callback to be called when tiles are cached (used by MapView for refresh)
|
||||
@@ -19,6 +20,12 @@ class TileProviderWithCache extends TileProvider with ChangeNotifier {
|
||||
_onTilesCachedCallback = callback;
|
||||
}
|
||||
|
||||
/// Cancel ALL pending tile requests - delegates to OSM tile fetcher
|
||||
void cancelAllTileRequests() {
|
||||
clearOSMTileQueue(); // This handles all the cancellation logic
|
||||
debugPrint('[TileProviderWithCache] Cancelled all tile requests');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposeCount++;
|
||||
@@ -73,12 +80,17 @@ class TileProviderWithCache extends TileProvider with ChangeNotifier {
|
||||
if (!_disposed && hasListeners) {
|
||||
notifyListeners(); // This updates any listening widgets
|
||||
}
|
||||
// Trigger map refresh callback to force tile re-rendering
|
||||
// Trigger map refresh callback to force tile re-rendering
|
||||
debugPrint('[TileProviderWithCache] Tile cached: $key, calling refresh callback');
|
||||
_onTilesCachedCallback?.call();
|
||||
}
|
||||
// If bytes were empty, don't cache (will re-attempt next time)
|
||||
} catch (e) {
|
||||
// Do NOT cache a failed or empty tile! Placeholder tiles will be evicted on online transition.
|
||||
// Cancelled requests will throw exceptions from fetchOSMTile(), just ignore them
|
||||
if (e.toString().contains('cancelled')) {
|
||||
debugPrint('[TileProviderWithCache] Tile request was cancelled: $key');
|
||||
}
|
||||
// Don't cache failed tiles regardless of reason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user