@chauffleet/expo-custom-map
Version:
Open source custom map library for Expo/React Native. Use your own tiles without Google Maps, Mapbox, or API keys. Created by ChaufFleet.
240 lines • 9.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useAdvancedTileCache = void 0;
// src/hooks/useAdvancedTileCache.ts - Cache intelligent pour tuiles ultra-fluide
const react_1 = require("react");
const useAdvancedTileCache = (config = {}) => {
const { maxSize = 100, // 100MB
maxEntries = 1000, maxAge = 30 * 60 * 1000, // 30 minutes
preloadRadius = 2, enableCompression = true, } = config;
// Cache principal
const cache = (0, react_1.useRef)(new Map()).current;
const cacheSize = (0, react_1.useRef)(0); // Taille en bytes
const requestQueue = (0, react_1.useRef)([]);
const activeRequests = (0, react_1.useRef)(new Set());
const maxConcurrentRequests = 6;
// Worker pour les téléchargements
const downloadWorker = (0, react_1.useRef)(null);
// Calculer la clé de cache
const getCacheKey = (0, react_1.useCallback)((url) => {
return url.replace(/[^a-zA-Z0-9]/g, '_');
}, []);
// Estimer la taille d'une image
const estimateImageSize = (0, react_1.useCallback)((data) => {
// Estimation basée sur la longueur de l'URL de données
return data.length * 0.75; // Base64 overhead
}, []);
// Nettoyer le cache selon la stratégie LRU améliorée
const cleanup = (0, react_1.useCallback)(() => {
const now = Date.now();
const entries = Array.from(cache.entries());
// Supprimer les entrées expirées
const expiredKeys = entries
.filter(([, entry]) => now - entry.timestamp > maxAge)
.map(([key]) => key);
expiredKeys.forEach(key => {
const entry = cache.get(key);
if (entry) {
cacheSize.current -= entry.size;
cache.delete(key);
}
});
// Si encore trop grand, appliquer LRU avec priorité
if (cache.size > maxEntries || cacheSize.current > maxSize * 1024 * 1024) {
const remainingEntries = Array.from(cache.entries())
.filter(([key]) => !expiredKeys.includes(key));
// Trier par score (fréquence d'utilisation + récence + priorité)
remainingEntries.sort(([, a], [, b]) => {
const scoreA = a.accessCount * (1 / (now - a.timestamp)) * a.priority;
const scoreB = b.accessCount * (1 / (now - b.timestamp)) * b.priority;
return scoreA - scoreB;
});
// Supprimer les moins intéressants
const toDelete = remainingEntries.slice(0, Math.floor(remainingEntries.length * 0.3));
toDelete.forEach(([key, entry]) => {
cacheSize.current -= entry.size;
cache.delete(key);
});
}
}, [cache, maxAge, maxEntries, maxSize]);
// Obtenir une tuile du cache
const getTile = (0, react_1.useCallback)((url) => {
const key = getCacheKey(url);
const entry = cache.get(key);
if (!entry)
return null;
// Vérifier l'âge
if (Date.now() - entry.timestamp > maxAge) {
cache.delete(key);
cacheSize.current -= entry.size;
return null;
}
// Mettre à jour les statistiques d'accès
entry.accessCount++;
entry.timestamp = Date.now();
return entry.data;
}, [getCacheKey, cache, maxAge]);
// Ajouter une tuile au cache
const cacheTile = (0, react_1.useCallback)((url, data, priority = 1) => {
const key = getCacheKey(url);
const size = estimateImageSize(data);
// Nettoyer si nécessaire avant d'ajouter
if (cache.size >= maxEntries || cacheSize.current + size > maxSize * 1024 * 1024) {
cleanup();
}
cache.set(key, {
data,
timestamp: Date.now(),
accessCount: 1,
size,
priority,
});
cacheSize.current += size;
}, [getCacheKey, estimateImageSize, cache, maxEntries, maxSize, cleanup]);
// Télécharger une tuile avec gestion intelligente de la file
const downloadTile = (0, react_1.useCallback)(async (url, priority = 1) => {
// Vérifier le cache d'abord
const cached = getTile(url);
if (cached)
return cached;
// Éviter les requêtes dupliquées
if (activeRequests.current.has(url)) {
return new Promise((resolve) => {
const checkCache = () => {
const result = getTile(url);
if (result) {
resolve(result);
}
else {
setTimeout(checkCache, 50);
}
};
checkCache();
});
}
activeRequests.current.add(url);
try {
// Téléchargement avec timeout et retry
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response = await fetch(url, {
signal: controller.signal,
cache: 'force-cache',
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const blob = await response.blob();
const dataUrl = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
// Mettre en cache
cacheTile(url, dataUrl, priority);
return dataUrl;
}
catch (error) {
console.warn('Failed to load tile:', url, error);
return null;
}
finally {
activeRequests.current.delete(url);
}
}, [getTile, cacheTile]);
// Worker de téléchargement en arrière-plan
(0, react_1.useEffect)(() => {
const worker = {
isRunning: false,
process: async () => {
if (worker.isRunning)
return;
worker.isRunning = true;
while (requestQueue.current.length > 0 && activeRequests.current.size < maxConcurrentRequests) {
// Trier par priorité
requestQueue.current.sort((a, b) => b.priority - a.priority);
const request = requestQueue.current.shift();
if (!request)
break;
try {
const result = await downloadTile(request.url, request.priority);
request.callback(result);
}
catch (error) {
request.callback(null, error);
}
}
worker.isRunning = false;
// Continuer si il y a encore des requêtes
if (requestQueue.current.length > 0) {
setTimeout(() => worker.process(), 100);
}
}
};
downloadWorker.current = worker;
}, [downloadTile]);
// Précharger des tuiles de manière intelligente
const preloadTiles = (0, react_1.useCallback)((urls, priority = 0.5) => {
urls.forEach(url => {
if (!getTile(url) && !activeRequests.current.has(url)) {
requestQueue.current.push({
url,
priority,
callback: () => { }, // Préchargement silencieux
});
}
});
// Démarrer le worker si nécessaire
if (downloadWorker.current && requestQueue.current.length > 0) {
downloadWorker.current.process();
}
}, [getTile]);
// Charger une tuile avec priorité
const loadTile = (0, react_1.useCallback)((url, priority = 1) => {
const cached = getTile(url);
if (cached)
return Promise.resolve(cached);
return new Promise((resolve, reject) => {
requestQueue.current.push({
url,
priority,
callback: (data, error) => {
if (error)
reject(error);
else
resolve(data);
},
});
if (downloadWorker.current) {
downloadWorker.current.process();
}
});
}, [getTile]);
// Statistiques du cache
const getCacheStats = (0, react_1.useCallback)(() => ({
entries: cache.size,
sizeBytes: cacheSize.current,
sizeMB: (cacheSize.current / (1024 * 1024)).toFixed(2),
activeRequests: activeRequests.current.size,
queueLength: requestQueue.current.length,
}), [cache.size]);
// Nettoyer périodiquement
(0, react_1.useEffect)(() => {
const interval = setInterval(cleanup, 60000); // Toutes les minutes
return () => clearInterval(interval);
}, [cleanup]);
return {
getTile,
loadTile,
preloadTiles,
getCacheStats,
clearCache: () => {
cache.clear();
cacheSize.current = 0;
},
};
};
exports.useAdvancedTileCache = useAdvancedTileCache;
//# sourceMappingURL=useAdvancedTileCache.js.map