mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-03 13:32:16 +08:00
refactor(traffic): drop unreachable inline monitor fallback
The inline traffic monitor duplicated the worker's message loop, snapshot throttling and aging logic but only runs when Worker is undefined or its constructor throws, which no supported Tauri webview or browser hits. Keep the worker as the single implementation and remove the mode switch and the pending-message queue that could only ever buffer the init message.
This commit is contained in:
@@ -3,7 +3,6 @@ import { Traffic } from 'tauri-plugin-mihomo-api'
|
||||
|
||||
import { useVisibility } from '@/hooks/use-visibility'
|
||||
import { debugLog } from '@/utils/debug'
|
||||
import { TrafficDataSampler, isSameTrafficData } from '@/utils/traffic-sampler'
|
||||
|
||||
// 引用计数管理器
|
||||
class ReferenceCounter {
|
||||
@@ -32,119 +31,11 @@ const WORKER_CONFIG = {
|
||||
defaultRangeMinutes: 10,
|
||||
}
|
||||
|
||||
class InlineTrafficMonitor {
|
||||
private config = { ...WORKER_CONFIG }
|
||||
private sampler = new TrafficDataSampler(this.config)
|
||||
private throttleTimer: ReturnType<typeof setTimeout> | null = null
|
||||
private agingTimer: ReturnType<typeof setInterval> | null = null
|
||||
private currentRange = this.config.defaultRangeMinutes
|
||||
private lastTimestamp: number | undefined
|
||||
private lastBroadcast: ITrafficDataPoint[] = []
|
||||
|
||||
constructor(
|
||||
private emit: (snapshot: ITrafficWorkerSnapshotMessage) => void,
|
||||
) {}
|
||||
|
||||
start(rangeMinutes?: number) {
|
||||
this.currentRange = rangeMinutes ?? this.currentRange
|
||||
this.handle({
|
||||
type: 'init',
|
||||
config: {
|
||||
...this.config,
|
||||
defaultRangeMinutes: this.currentRange,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.throttleTimer !== null) {
|
||||
clearTimeout(this.throttleTimer)
|
||||
this.throttleTimer = null
|
||||
}
|
||||
if (this.agingTimer !== null) {
|
||||
clearInterval(this.agingTimer)
|
||||
this.agingTimer = null
|
||||
}
|
||||
this.sampler.clear()
|
||||
this.lastTimestamp = undefined
|
||||
}
|
||||
|
||||
handle(message: TrafficWorkerRequestMessage) {
|
||||
switch (message.type) {
|
||||
case 'init': {
|
||||
this.config = { ...message.config }
|
||||
this.sampler = new TrafficDataSampler(this.config)
|
||||
this.currentRange = message.config.defaultRangeMinutes
|
||||
// Re-broadcast when points age out of the range, not on every tick
|
||||
this.agingTimer = setInterval(() => {
|
||||
if (this.throttleTimer !== null) return
|
||||
const slice = this.sampler.getDataForTimeRange(this.currentRange)
|
||||
if (!isSameTrafficData(this.lastBroadcast, slice)) {
|
||||
this.emitSnapshot()
|
||||
}
|
||||
}, 1000)
|
||||
this.emitSnapshot()
|
||||
break
|
||||
}
|
||||
case 'append': {
|
||||
const timestamp = message.payload.timestamp ?? Date.now()
|
||||
const dataPoint: ITrafficDataPoint = {
|
||||
up: message.payload.up || 0,
|
||||
down: message.payload.down || 0,
|
||||
timestamp,
|
||||
}
|
||||
|
||||
this.lastTimestamp = timestamp
|
||||
this.sampler.addDataPoint(dataPoint)
|
||||
this.scheduleSnapshot()
|
||||
break
|
||||
}
|
||||
case 'setRange': {
|
||||
if (this.currentRange !== message.minutes) {
|
||||
this.currentRange = message.minutes
|
||||
this.emitSnapshot()
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'requestSnapshot': {
|
||||
this.emitSnapshot()
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private emitSnapshot() {
|
||||
const dataPoints = this.sampler.getDataForTimeRange(this.currentRange)
|
||||
|
||||
this.emit({
|
||||
type: 'snapshot',
|
||||
dataPoints,
|
||||
samplerStats: this.sampler.getStats(),
|
||||
lastTimestamp: this.lastTimestamp,
|
||||
})
|
||||
this.lastBroadcast = dataPoints
|
||||
}
|
||||
|
||||
private scheduleSnapshot() {
|
||||
if (this.throttleTimer !== null) return
|
||||
this.throttleTimer = setTimeout(() => {
|
||||
this.throttleTimer = null
|
||||
this.emitSnapshot()
|
||||
}, this.config.snapshotIntervalMs)
|
||||
}
|
||||
}
|
||||
|
||||
class TrafficWorkerClient {
|
||||
private worker: Worker | null = null
|
||||
private inlineMonitor: InlineTrafficMonitor | null = null
|
||||
private mode: 'worker' | 'inline' | null = null
|
||||
private listeners = new Set<
|
||||
(snapshot: ITrafficWorkerSnapshotMessage) => void
|
||||
>()
|
||||
private pendingMessages: TrafficWorkerRequestMessage[] = []
|
||||
private ready = false
|
||||
private currentRange = WORKER_CONFIG.defaultRangeMinutes
|
||||
|
||||
start(rangeMinutes?: number) {
|
||||
@@ -155,101 +46,52 @@ class TrafficWorkerClient {
|
||||
|
||||
this.currentRange = rangeMinutes ?? this.currentRange
|
||||
|
||||
if (this.ready) return
|
||||
if (this.worker) return
|
||||
|
||||
const initMessage: TrafficWorkerRequestMessage = {
|
||||
type: 'init',
|
||||
config: {
|
||||
rawDataMinutes: WORKER_CONFIG.rawDataMinutes,
|
||||
compressedDataMinutes: WORKER_CONFIG.compressedDataMinutes,
|
||||
compressionRatio: WORKER_CONFIG.compressionRatio,
|
||||
snapshotIntervalMs: WORKER_CONFIG.snapshotIntervalMs,
|
||||
defaultRangeMinutes: this.currentRange,
|
||||
},
|
||||
config: { ...WORKER_CONFIG, defaultRangeMinutes: this.currentRange },
|
||||
}
|
||||
|
||||
if (typeof Worker !== 'undefined') {
|
||||
try {
|
||||
this.worker = new Worker(
|
||||
new URL('../services/traffic-monitor-worker.ts', import.meta.url),
|
||||
{ type: 'module' },
|
||||
)
|
||||
this.mode = 'worker'
|
||||
this.worker = new Worker(
|
||||
new URL('../services/traffic-monitor-worker.ts', import.meta.url),
|
||||
{ type: 'module' },
|
||||
)
|
||||
|
||||
this.worker.onmessage = (
|
||||
event: MessageEvent<TrafficWorkerResponseMessage>,
|
||||
) => {
|
||||
const message = event.data
|
||||
if (message.type === 'snapshot') {
|
||||
if (import.meta.env.MODE === 'perf') {
|
||||
document.documentElement.dataset.perfWorker = String(
|
||||
message.lastTimestamp ?? 0,
|
||||
)
|
||||
document.documentElement.dataset.perfPoints = String(
|
||||
message.dataPoints.length,
|
||||
)
|
||||
const last = message.dataPoints.at(-1)
|
||||
document.documentElement.dataset.perfLastUp = String(
|
||||
last?.up ?? 0,
|
||||
)
|
||||
document.documentElement.dataset.perfLastDown = String(
|
||||
last?.down ?? 0,
|
||||
)
|
||||
}
|
||||
this.listeners.forEach((listener) => {
|
||||
listener(message)
|
||||
})
|
||||
}
|
||||
this.worker.onmessage = (
|
||||
event: MessageEvent<TrafficWorkerResponseMessage>,
|
||||
) => {
|
||||
const message = event.data
|
||||
if (message.type === 'snapshot') {
|
||||
if (import.meta.env.MODE === 'perf') {
|
||||
document.documentElement.dataset.perfWorker = String(
|
||||
message.lastTimestamp ?? 0,
|
||||
)
|
||||
document.documentElement.dataset.perfPoints = String(
|
||||
message.dataPoints.length,
|
||||
)
|
||||
const last = message.dataPoints.at(-1)
|
||||
document.documentElement.dataset.perfLastUp = String(last?.up ?? 0)
|
||||
document.documentElement.dataset.perfLastDown = String(
|
||||
last?.down ?? 0,
|
||||
)
|
||||
}
|
||||
|
||||
this.worker.onerror = (error) => {
|
||||
debugLog(`[TrafficWorkerClient] Worker error: ${String(error)}`)
|
||||
}
|
||||
|
||||
this.ready = true
|
||||
this.post(initMessage)
|
||||
this.flushQueue()
|
||||
return
|
||||
} catch (error) {
|
||||
debugLog(
|
||||
`[TrafficWorkerClient] Worker initialization failed, falling back to inline sampler: ${String(error)}`,
|
||||
)
|
||||
this.worker = null
|
||||
this.mode = null
|
||||
this.listeners.forEach((listener) => {
|
||||
listener(message)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
debugLog(
|
||||
'[TrafficWorkerClient] Worker not supported, using inline sampler',
|
||||
)
|
||||
}
|
||||
|
||||
this.startInline(initMessage)
|
||||
}
|
||||
this.worker.onerror = (error) => {
|
||||
debugLog(`[TrafficWorkerClient] Worker error: ${String(error)}`)
|
||||
}
|
||||
|
||||
private startInline(initMessage: TrafficWorkerRequestMessage) {
|
||||
this.inlineMonitor = new InlineTrafficMonitor((snapshot) => {
|
||||
this.listeners.forEach((listener) => {
|
||||
listener(snapshot)
|
||||
})
|
||||
})
|
||||
this.mode = 'inline'
|
||||
this.ready = true
|
||||
this.post(initMessage)
|
||||
this.flushQueue()
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.worker) {
|
||||
this.worker.terminate()
|
||||
}
|
||||
if (this.inlineMonitor) {
|
||||
this.inlineMonitor.stop()
|
||||
}
|
||||
this.worker?.terminate()
|
||||
this.worker = null
|
||||
this.inlineMonitor = null
|
||||
this.mode = null
|
||||
this.ready = false
|
||||
this.pendingMessages = []
|
||||
}
|
||||
|
||||
onSnapshot(listener: (snapshot: ITrafficWorkerSnapshotMessage) => void) {
|
||||
@@ -260,37 +102,11 @@ class TrafficWorkerClient {
|
||||
}
|
||||
|
||||
private post(message: TrafficWorkerRequestMessage) {
|
||||
if (!this.ready) {
|
||||
this.pendingMessages.push(message)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.mode === 'worker' && this.worker) {
|
||||
this.worker.postMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.mode === 'inline' && this.inlineMonitor) {
|
||||
this.inlineMonitor.handle(message)
|
||||
return
|
||||
}
|
||||
|
||||
this.pendingMessages.push(message)
|
||||
}
|
||||
|
||||
private flushQueue() {
|
||||
if (!this.ready || this.pendingMessages.length === 0) {
|
||||
return
|
||||
}
|
||||
const queued = [...this.pendingMessages]
|
||||
this.pendingMessages = []
|
||||
queued.forEach((message) => {
|
||||
this.post(message)
|
||||
})
|
||||
this.worker?.postMessage(message)
|
||||
}
|
||||
|
||||
private ensureStarted() {
|
||||
if (!this.ready) {
|
||||
if (!this.worker) {
|
||||
this.start(this.currentRange)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user