@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.
411 lines • 13.9 kB
JavaScript
"use strict";
// src/PerformanceOptimizations.ts
Object.defineProperty(exports, "__esModule", { value: true });
exports.PerformanceOptimizations = exports.AdaptiveConfig = exports.ThrottleUtils = exports.ClusterOptimizer = exports.RenderOptimizer = exports.FrameRateManager = exports.MemoryManager = exports.PERFORMANCE_CONSTANTS = void 0;
/**
* Configuration et optimisations de performance pour la librairie de cartes
*/
// ===========================
// CONSTANTES DE PERFORMANCE
// ===========================
exports.PERFORMANCE_CONSTANTS = {
// Limites de frame rate
TARGET_FPS: 60,
MIN_FRAME_TIME: 16.67, // 1000ms / 60fps
// Cache et mémoire
DEFAULT_TILE_CACHE_SIZE: 100, // MB
MAX_MEMORY_USAGE: 256, // MB
MEMORY_WARNING_THRESHOLD: 0.8, // 80% de la limite
// Gestes et interactions
GESTURE_THROTTLE_MS: 16, // ~60fps
GESTURE_DEBOUNCE_MS: 100,
ZOOM_THROTTLE_MS: 32, // ~30fps pour le zoom
PAN_THROTTLE_MS: 16, // ~60fps pour le panoramique
// Rendu
MAX_MARKERS_WITHOUT_CLUSTERING: 500,
CLUSTER_UPDATE_THROTTLE_MS: 200,
TILE_LOAD_THROTTLE_MS: 50,
VIEWPORT_BUFFER_RATIO: 0.2, // 20% de buffer autour du viewport
// Préchargement
MAX_CONCURRENT_TILE_DOWNLOADS: 4,
TILE_PRELOAD_RADIUS: 2, // tuiles autour du centre
PRELOAD_DELAY_MS: 100,
// Animations
DEFAULT_ANIMATION_DURATION: 300,
SMOOTH_ZOOM_STEPS: 5,
ANIMATION_EASING: 'easeOutCubic',
};
// ===========================
// GESTIONNAIRE DE MÉMOIRE
// ===========================
class MemoryManager {
constructor() {
this.memoryWarningCallbacks = [];
this.isMonitoring = false;
}
static getInstance() {
if (!MemoryManager.instance) {
MemoryManager.instance = new MemoryManager();
}
return MemoryManager.instance;
}
/**
* Démarre la surveillance de la mémoire
*/
startMonitoring() {
if (this.isMonitoring)
return;
this.isMonitoring = true;
this.monitoringInterval = setInterval(() => {
this.checkMemoryUsage();
}, 5000); // Vérifier toutes les 5 secondes
}
/**
* Arrête la surveillance de la mémoire
*/
stopMonitoring() {
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
this.monitoringInterval = undefined;
}
this.isMonitoring = false;
}
/**
* Ajoute un callback pour les alertes mémoire
*/
onMemoryWarning(callback) {
this.memoryWarningCallbacks.push(callback);
return () => {
const index = this.memoryWarningCallbacks.indexOf(callback);
if (index > -1) {
this.memoryWarningCallbacks.splice(index, 1);
}
};
}
/**
* Force le nettoyage de la mémoire
*/
forceCleanup() {
if (global.gc) {
global.gc();
}
this.triggerMemoryWarning();
}
checkMemoryUsage() {
// En React Native, nous utilisons une estimation basée sur les performances
const performance = global.performance;
if (performance?.memory) {
const used = performance.memory.usedJSHeapSize / (1024 * 1024); // MB
const limit = exports.PERFORMANCE_CONSTANTS.MAX_MEMORY_USAGE;
if (used / limit > exports.PERFORMANCE_CONSTANTS.MEMORY_WARNING_THRESHOLD) {
this.triggerMemoryWarning();
}
}
}
triggerMemoryWarning() {
this.memoryWarningCallbacks.forEach(callback => {
try {
callback();
}
catch (error) {
console.warn('Erreur dans le callback de warning mémoire:', error);
}
});
}
}
exports.MemoryManager = MemoryManager;
// ===========================
// GESTIONNAIRE DE FRAME RATE
// ===========================
class FrameRateManager {
constructor() {
this.frameTimeHistory = [];
this.lastFrameTime = 0;
this.dropThresholds = {
minor: 40, // < 40 FPS
major: 25, // < 25 FPS
severe: 15, // < 15 FPS
};
}
/**
* Enregistre le temps d'une frame
*/
recordFrame() {
const now = performance.now();
const frameTime = now - this.lastFrameTime;
if (this.lastFrameTime > 0) {
this.frameTimeHistory.push(frameTime);
// Garder seulement les 60 dernières frames (1 seconde à 60fps)
if (this.frameTimeHistory.length > 60) {
this.frameTimeHistory.shift();
}
}
this.lastFrameTime = now;
return frameTime;
}
/**
* Calcule le FPS moyen
*/
getAverageFPS() {
if (this.frameTimeHistory.length === 0)
return 0;
const averageFrameTime = this.frameTimeHistory.reduce((a, b) => a + b, 0) / this.frameTimeHistory.length;
return Math.round(1000 / averageFrameTime);
}
/**
* Détecte les baisses de performance
*/
detectPerformanceIssues() {
const fps = this.getAverageFPS();
if (fps < this.dropThresholds.severe) {
return {
severity: 'severe',
fps,
recommendation: 'Réduire drastiquement les marqueurs, désactiver le clustering, diminuer la qualité des tuiles',
};
}
else if (fps < this.dropThresholds.major) {
return {
severity: 'major',
fps,
recommendation: 'Activer le clustering, réduire le nombre de marqueurs visibles',
};
}
else if (fps < this.dropThresholds.minor) {
return {
severity: 'minor',
fps,
recommendation: 'Optimiser le rendu des marqueurs, réduire les animations',
};
}
return {
severity: 'none',
fps,
recommendation: 'Performance optimale',
};
}
/**
* Recommandations automatiques d'optimisation
*/
getOptimizationRecommendations() {
const { severity } = this.detectPerformanceIssues();
return {
reduceClustering: severity === 'severe',
reduceMarkers: severity === 'major' || severity === 'severe',
reduceTileQuality: severity === 'major' || severity === 'severe',
disableAnimations: severity === 'severe',
};
}
}
exports.FrameRateManager = FrameRateManager;
// ===========================
// OPTIMISEUR DE RENDU
// ===========================
class RenderOptimizer {
/**
* Met à jour les limites du viewport
*/
static updateViewportBounds(bounds) {
this.viewportBounds = bounds;
}
/**
* Vérifie si un élément est visible dans le viewport
*/
static isInViewport(x, y, bufferRatio = exports.PERFORMANCE_CONSTANTS.VIEWPORT_BUFFER_RATIO) {
if (!this.viewportBounds)
return true;
const { minX, maxX, minY, maxY } = this.viewportBounds;
const bufferX = (maxX - minX) * bufferRatio;
const bufferY = (maxY - minY) * bufferRatio;
return (x >= minX - bufferX &&
x <= maxX + bufferX &&
y >= minY - bufferY &&
y <= maxY + bufferY);
}
/**
* Filtre les marqueurs visibles avec optimisation
*/
static filterVisibleMarkers(markers, coordinateToScreen) {
if (!this.viewportBounds || markers.length === 0)
return markers;
return markers.filter(marker => {
const screenPos = coordinateToScreen(marker.coordinate);
return this.isInViewport(screenPos.x, screenPos.y);
});
}
/**
* Optimise la liste des tuiles à charger
*/
static optimizeTileLoading(tiles, maxConcurrent = exports.PERFORMANCE_CONSTANTS.MAX_CONCURRENT_TILE_DOWNLOADS) {
// Trier par priorité (plus haute d'abord)
const sortedTiles = [...tiles].sort((a, b) => b.priority - a.priority);
// Limiter le nombre de tuiles simultanées
return sortedTiles.slice(0, maxConcurrent);
}
}
exports.RenderOptimizer = RenderOptimizer;
RenderOptimizer.viewportBounds = null;
// ===========================
// OPTIMISEUR DE CLUSTERING
// ===========================
class ClusterOptimizer {
/**
* Détermine si le clustering doit être recalculé
*/
static shouldUpdateClusters(markersCount, zoom, forceUpdate = false) {
const now = Date.now();
if (forceUpdate)
return true;
// Réduire la fréquence de mise à jour si beaucoup de marqueurs
const throttleMs = markersCount > 1000
? exports.PERFORMANCE_CONSTANTS.CLUSTER_UPDATE_THROTTLE_MS * 2
: exports.PERFORMANCE_CONSTANTS.CLUSTER_UPDATE_THROTTLE_MS;
return now - this.lastUpdate > throttleMs;
}
/**
* Met à jour le cache des clusters
*/
static updateClustersCache(clusters) {
this.cachedClusters = clusters;
this.lastUpdate = Date.now();
}
/**
* Obtient les clusters depuis le cache
*/
static getCachedClusters() {
return this.cachedClusters;
}
/**
* Calcule la distance optimale pour le clustering basée sur le zoom
*/
static getOptimalClusterDistance(zoom, baseDistance = 50) {
// Plus le zoom est élevé, plus la distance de clustering diminue
const factor = Math.max(0.1, 1 - (zoom - 10) * 0.1);
return baseDistance * factor;
}
}
exports.ClusterOptimizer = ClusterOptimizer;
ClusterOptimizer.lastUpdate = 0;
ClusterOptimizer.cachedClusters = [];
// ===========================
// UTILITAIRES DE THROTTLING
// ===========================
class ThrottleUtils {
/**
* Throttle une fonction avec une clé unique
*/
static throttle(func, delay, key) {
return ((...args) => {
const now = Date.now();
const cached = this.throttleCache.get(key);
if (!cached || now - cached.lastCall >= delay) {
this.throttleCache.set(key, { lastCall: now });
return func(...args);
}
});
}
/**
* Debounce une fonction avec une clé unique
*/
static debounce(func, delay, key) {
return ((...args) => {
const cached = this.throttleCache.get(key);
if (cached?.timeoutId) {
clearTimeout(cached.timeoutId);
}
const timeoutId = setTimeout(() => {
func(...args);
this.throttleCache.delete(key);
}, delay);
this.throttleCache.set(key, {
lastCall: Date.now(),
timeoutId
});
});
}
/**
* Nettoie le cache de throttling
*/
static clearCache() {
for (const [key, cached] of this.throttleCache.entries()) {
if (cached.timeoutId) {
clearTimeout(cached.timeoutId);
}
}
this.throttleCache.clear();
}
}
exports.ThrottleUtils = ThrottleUtils;
ThrottleUtils.throttleCache = new Map();
// ===========================
// CONFIGURATION ADAPTATIVE
// ===========================
class AdaptiveConfig {
/**
* Met à jour la configuration basée sur les performances
*/
static updateConfigForPerformance(frameRateManager, markersCount) {
const recommendations = frameRateManager.getOptimizationRecommendations();
const newConfig = { ...this.currentConfig };
// Adapter le clustering
if (markersCount > newConfig.maxMarkersWithoutClustering) {
newConfig.enableClustering = true;
newConfig.clusterRadius = recommendations.reduceMarkers ? 80 : 50;
}
else {
newConfig.enableClustering = false;
}
// Adapter la qualité des tuiles
newConfig.enableHighDPI = !recommendations.reduceTileQuality;
// Adapter les animations
newConfig.enableAnimations = !recommendations.disableAnimations;
// Adapter le throttling
newConfig.tileLoadThrottle = recommendations.reduceTileQuality
? exports.PERFORMANCE_CONSTANTS.TILE_LOAD_THROTTLE_MS * 2
: exports.PERFORMANCE_CONSTANTS.TILE_LOAD_THROTTLE_MS;
this.currentConfig = newConfig;
return newConfig;
}
/**
* Obtient la configuration actuelle
*/
static getCurrentConfig() {
return { ...this.currentConfig };
}
/**
* Réinitialise la configuration aux valeurs par défaut
*/
static resetToDefaults() {
this.currentConfig = {
enableClustering: true,
clusterRadius: 50,
maxMarkersWithoutClustering: exports.PERFORMANCE_CONSTANTS.MAX_MARKERS_WITHOUT_CLUSTERING,
tileLoadThrottle: exports.PERFORMANCE_CONSTANTS.TILE_LOAD_THROTTLE_MS,
enableHighDPI: true,
enableAnimations: true,
};
}
}
exports.AdaptiveConfig = AdaptiveConfig;
AdaptiveConfig.currentConfig = {
enableClustering: true,
clusterRadius: 50,
maxMarkersWithoutClustering: exports.PERFORMANCE_CONSTANTS.MAX_MARKERS_WITHOUT_CLUSTERING,
tileLoadThrottle: exports.PERFORMANCE_CONSTANTS.TILE_LOAD_THROTTLE_MS,
enableHighDPI: true,
enableAnimations: true,
};
// ===========================
// EXPORTS PRINCIPAUX
// ===========================
exports.PerformanceOptimizations = {
Constants: exports.PERFORMANCE_CONSTANTS,
MemoryManager: MemoryManager.getInstance(),
FrameRateManager: new FrameRateManager(),
RenderOptimizer,
ClusterOptimizer,
ThrottleUtils,
AdaptiveConfig,
};
exports.default = exports.PerformanceOptimizations;
//# sourceMappingURL=PerformanceOptimizations.js.map