Enhance map update handling and connection stability
- Introduced mechanisms to detect stale connections in the map updates, allowing for automatic reconnection if no messages are received within a specified timeframe. - Updated the `refresh` method in `SmartTileLayer` to return a boolean indicating whether the tile was refreshed, improving the handling of tile updates. - Enhanced the `Send` method in the `Topic` struct to drop messages for full subscribers while keeping them subscribed, ensuring continuous delivery of future updates. - Added a keepalive mechanism in the `WatchGridUpdates` handler to maintain the connection and prevent timeouts.
This commit is contained in:
@@ -38,6 +38,9 @@ export interface UseMapUpdatesReturn {
|
||||
|
||||
const RECONNECT_INITIAL_MS = 1000
|
||||
const RECONNECT_MAX_MS = 30000
|
||||
/** If no SSE message received for this long, treat connection as stale and reconnect. */
|
||||
const STALE_CONNECTION_MS = 65000
|
||||
const STALE_CHECK_INTERVAL_MS = 30000
|
||||
|
||||
export function startMapUpdates(options: UseMapUpdatesOptions): UseMapUpdatesReturn {
|
||||
const { backendBase, layer, overlayLayer, map, getCurrentMapId, onMerge, connectionStateRef } = options
|
||||
@@ -50,6 +53,8 @@ export function startMapUpdates(options: UseMapUpdatesOptions): UseMapUpdatesRet
|
||||
let batchScheduled = false
|
||||
let source: EventSource | null = null
|
||||
let reconnectTimeoutId: ReturnType<typeof setTimeout> | null = null
|
||||
let staleCheckIntervalId: ReturnType<typeof setInterval> | null = null
|
||||
let lastMessageTime = 0
|
||||
let reconnectDelayMs = RECONNECT_INITIAL_MS
|
||||
let destroyed = false
|
||||
|
||||
@@ -79,15 +84,22 @@ export function startMapUpdates(options: UseMapUpdatesOptions): UseMapUpdatesRet
|
||||
overlayLayer.cache[key] = u.T
|
||||
}
|
||||
const visible = getVisibleTileBounds()
|
||||
// u.Z is backend storage zoom (0..5); visible.zoom is map zoom (1..6). With zoomReverse, current backend Z = maxZoom - mapZoom.
|
||||
const currentBackendZ = visible ? layer.options.maxZoom - visible.zoom : null
|
||||
let needRedraw = false
|
||||
for (const u of updates) {
|
||||
if (visible && u.Z !== visible.zoom) continue
|
||||
if (visible && currentBackendZ != null && u.Z !== currentBackendZ) continue
|
||||
if (
|
||||
visible &&
|
||||
(u.X < visible.minX || u.X > visible.maxX || u.Y < visible.minY || u.Y > visible.maxY)
|
||||
)
|
||||
continue
|
||||
if (layer.map === u.M) layer.refresh(u.X, u.Y, u.Z)
|
||||
if (overlayLayer.map === u.M) overlayLayer.refresh(u.X, u.Y, u.Z)
|
||||
if (layer.map === u.M && !layer.refresh(u.X, u.Y, u.Z)) needRedraw = true
|
||||
if (overlayLayer.map === u.M && !overlayLayer.refresh(u.X, u.Y, u.Z)) needRedraw = true
|
||||
}
|
||||
if (needRedraw) {
|
||||
layer.redraw()
|
||||
overlayLayer.redraw()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,12 +111,30 @@ export function startMapUpdates(options: UseMapUpdatesOptions): UseMapUpdatesRet
|
||||
|
||||
function connect() {
|
||||
if (destroyed || !import.meta.client) return
|
||||
if (staleCheckIntervalId != null) {
|
||||
clearInterval(staleCheckIntervalId)
|
||||
staleCheckIntervalId = null
|
||||
}
|
||||
source = new EventSource(updatesUrl)
|
||||
if (connectionStateRef) connectionStateRef.value = 'connecting'
|
||||
|
||||
source.onopen = () => {
|
||||
if (connectionStateRef) connectionStateRef.value = 'open'
|
||||
lastMessageTime = Date.now()
|
||||
reconnectDelayMs = RECONNECT_INITIAL_MS
|
||||
staleCheckIntervalId = setInterval(() => {
|
||||
if (destroyed || !source) return
|
||||
if (Date.now() - lastMessageTime > STALE_CONNECTION_MS) {
|
||||
if (staleCheckIntervalId != null) {
|
||||
clearInterval(staleCheckIntervalId)
|
||||
staleCheckIntervalId = null
|
||||
}
|
||||
source.close()
|
||||
source = null
|
||||
if (connectionStateRef) connectionStateRef.value = 'error'
|
||||
connect()
|
||||
}
|
||||
}, STALE_CHECK_INTERVAL_MS)
|
||||
}
|
||||
|
||||
source.onerror = () => {
|
||||
@@ -121,6 +151,7 @@ export function startMapUpdates(options: UseMapUpdatesOptions): UseMapUpdatesRet
|
||||
}
|
||||
|
||||
source.onmessage = (event: MessageEvent) => {
|
||||
lastMessageTime = Date.now()
|
||||
if (connectionStateRef) connectionStateRef.value = 'open'
|
||||
try {
|
||||
const raw: unknown = event?.data
|
||||
@@ -157,6 +188,10 @@ export function startMapUpdates(options: UseMapUpdatesOptions): UseMapUpdatesRet
|
||||
|
||||
function cleanup() {
|
||||
destroyed = true
|
||||
if (staleCheckIntervalId != null) {
|
||||
clearInterval(staleCheckIntervalId)
|
||||
staleCheckIntervalId = null
|
||||
}
|
||||
if (reconnectTimeoutId != null) {
|
||||
clearTimeout(reconnectTimeoutId)
|
||||
reconnectTimeoutId = null
|
||||
|
||||
@@ -58,7 +58,7 @@ export const SmartTileLayer = L.TileLayer.extend({
|
||||
return Util.template(this._url, Util.extend(data, this.options))
|
||||
},
|
||||
|
||||
refresh(x: number, y: number, z: number) {
|
||||
refresh(x: number, y: number, z: number): boolean {
|
||||
let zoom = z
|
||||
const maxZoom = this.options.maxZoom
|
||||
const zoomReverse = this.options.zoomReverse
|
||||
@@ -71,19 +71,20 @@ export const SmartTileLayer = L.TileLayer.extend({
|
||||
|
||||
const key = `${x}:${y}:${zoom}`
|
||||
const tile = this._tiles[key]
|
||||
if (!tile?.el) return
|
||||
if (!tile?.el) return false
|
||||
const newUrl = this.getTrueTileUrl({ x, y }, z)
|
||||
if (tile.el.dataset.tileUrl === newUrl) return
|
||||
if (tile.el.dataset.tileUrl === newUrl) return true
|
||||
tile.el.dataset.tileUrl = newUrl
|
||||
tile.el.src = newUrl
|
||||
tile.el.classList.add('tile-fresh')
|
||||
const el = tile.el
|
||||
setTimeout(() => el.classList.remove('tile-fresh'), 400)
|
||||
return true
|
||||
},
|
||||
}) as unknown as new (urlTemplate: string, options?: L.TileLayerOptions) => L.TileLayer & {
|
||||
cache: SmartTileLayerCache
|
||||
invalidTile: string
|
||||
map: number
|
||||
getTrueTileUrl: (coords: { x: number; y: number }, zoom: number) => string
|
||||
refresh: (x: number, y: number, z: number) => void
|
||||
refresh: (x: number, y: number, z: number) => boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user