From 4ffb8b415f38b6e32d71afca40427b95af120d56 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Wed, 24 Dec 2025 13:40:03 +0800 Subject: [PATCH] perf(traffic): optimize traffic data handling and improve performance --- Changelog.md | 1 + .../connection/connection-table.tsx | 165 ++++++++++++------ .../home/enhanced-canvas-traffic-graph.tsx | 29 ++- .../home/enhanced-traffic-stats.tsx | 11 +- src/components/layout/layout-traffic.tsx | 2 +- src/components/proxy/use-filter-sort.ts | 41 ++++- src/hooks/use-shared-swr-poller.ts | 8 + src/hooks/use-system-state.ts | 12 +- src/hooks/use-traffic-data.ts | 6 +- src/hooks/use-traffic-monitor.ts | 40 +++-- src/utils/traffic-sampler.ts | 69 ++++++-- 11 files changed, 289 insertions(+), 95 deletions(-) diff --git a/Changelog.md b/Changelog.md index 9b36edfd9..924602514 100644 --- a/Changelog.md +++ b/Changelog.md @@ -23,5 +23,6 @@ 🚀 优化改进 - 应用内更新日志支持解析并渲染 HTML 标签 +- 性能优化前后端在渲染流量图时的资源 diff --git a/src/components/connection/connection-table.tsx b/src/components/connection/connection-table.tsx index 7309e2fac..d99d01d9d 100644 --- a/src/components/connection/connection-table.tsx +++ b/src/components/connection/connection-table.tsx @@ -3,13 +3,13 @@ import { Box, IconButton, Tooltip } from "@mui/material"; import { ColumnDef, ColumnSizingState, - SortingState, - VisibilityState, flexRender, getCoreRowModel, getSortedRowModel, + SortingState, Updater, useReactTable, + VisibilityState, } from "@tanstack/react-table"; import { useVirtualizer } from "@tanstack/react-virtual"; import dayjs from "dayjs"; @@ -43,6 +43,50 @@ const reconcileColumnOrder = ( return [...filtered, ...missing]; }; +const createConnectionRow = (each: IConnectionsItem) => { + const { metadata, rulePayload } = each; + const chains = [...each.chains].reverse().join(" / "); + const rule = rulePayload ? `${each.rule}(${rulePayload})` : each.rule; + const destination = metadata.destinationIP + ? `${metadata.destinationIP}:${metadata.destinationPort}` + : `${metadata.remoteDestination}:${metadata.destinationPort}`; + + return { + id: each.id, + host: metadata.host + ? `${metadata.host}:${metadata.destinationPort}` + : `${metadata.remoteDestination}:${metadata.destinationPort}`, + download: each.download, + upload: each.upload, + dlSpeed: each.curDownload, + ulSpeed: each.curUpload, + chains, + rule, + process: truncateStr(metadata.process || metadata.processPath), + time: each.start, + source: `${metadata.sourceIP}:${metadata.sourcePort}`, + remoteDestination: destination, + type: `${metadata.type}(${metadata.network})`, + connectionData: each, + }; +}; + +type ConnectionRow = ReturnType; + +const areRowsEqual = (a: ConnectionRow, b: ConnectionRow) => + a.host === b.host && + a.download === b.download && + a.upload === b.upload && + a.dlSpeed === b.dlSpeed && + a.ulSpeed === b.ulSpeed && + a.chains === b.chains && + a.rule === b.rule && + a.process === b.process && + a.time === b.time && + a.source === b.source && + a.remoteDestination === b.remoteDestination && + a.type === b.type; + interface Props { connections: IConnectionsItem[]; onShowDetail: (data: IConnectionsItem) => void; @@ -105,35 +149,6 @@ export const ConnectionTable = (props: Props) => { }, ); - const createConnectionRow = (each: IConnectionsItem) => { - const { metadata, rulePayload } = each; - const chains = [...each.chains].reverse().join(" / "); - const rule = rulePayload ? `${each.rule}(${rulePayload})` : each.rule; - const Destination = metadata.destinationIP - ? `${metadata.destinationIP}:${metadata.destinationPort}` - : `${metadata.remoteDestination}:${metadata.destinationPort}`; - return { - id: each.id, - host: metadata.host - ? `${metadata.host}:${metadata.destinationPort}` - : `${metadata.remoteDestination}:${metadata.destinationPort}`, - download: each.download, - upload: each.upload, - dlSpeed: each.curDownload, - ulSpeed: each.curUpload, - chains, - rule, - process: truncateStr(metadata.process || metadata.processPath), - time: each.start, - source: `${metadata.sourceIP}:${metadata.sourcePort}`, - remoteDestination: Destination, - type: `${metadata.type}(${metadata.network})`, - connectionData: each, - }; - }; - - type ConnectionRow = ReturnType; - type ColumnField = Exclude; interface BaseColumn { @@ -209,7 +224,7 @@ export const ConnectionTable = (props: Props) => { width: 100, minWidth: 80, align: "right", - cell: (row) => dayjs(row.time).fromNow(), + // cell filled later with shared relativeNow ticker }, { field: "source", @@ -366,30 +381,68 @@ export const ConnectionTable = (props: Props) => { })); }, [columns, columnVisibilityModel]); - const connRows = useMemo( - () => connections.map((each) => createConnectionRow(each)), - [connections], - ); + const prevRowsRef = useRef>(new Map()); - const columnDefs = useMemo[]>(() => { - return columns.map((column) => ({ - id: column.field, - accessorKey: column.field, - header: column.headerName, - size: column.width, - minSize: column.minWidth ?? 80, - enableResizing: true, - meta: { - align: column.align ?? "left", - field: column.field, - }, - cell: column.cell - ? ({ row }) => column.cell?.(row.original) - : (info) => info.getValue(), - })); - }, [columns]); + const connRows = useMemo(() => { + const prevMap = prevRowsRef.current; + const nextMap = new Map(); + + const nextRows = connections.map((each) => { + const nextRow = createConnectionRow(each); + const prevRow = prevMap.get(each.id); + + if (prevRow && areRowsEqual(prevRow, nextRow)) { + nextMap.set(each.id, prevRow); + return prevRow; + } + + nextMap.set(each.id, nextRow); + return nextRow; + }); + + prevRowsRef.current = nextMap; + return nextRows; + }, [connections]); const [sorting, setSorting] = useState([]); + const [relativeNow, setRelativeNow] = useState(() => Date.now()); + + const columnDefs = useMemo[]>(() => { + return columns.map((column) => { + const baseCell: ColumnDef["cell"] = column.cell + ? (ctx) => column.cell?.(ctx.row.original) + : (ctx) => ctx.getValue() as ReactNode; + + const cell: ColumnDef["cell"] = + column.field === "time" + ? (ctx) => dayjs(ctx.row.original.time).from(relativeNow) + : baseCell; + + return { + id: column.field, + accessorKey: column.field, + header: column.headerName, + size: column.width, + minSize: column.minWidth ?? 80, + enableResizing: true, + meta: { + align: column.align ?? "left", + field: column.field, + }, + cell, + } satisfies ColumnDef; + }); + }, [columns, relativeNow]); + + useEffect(() => { + if (typeof window === "undefined") return undefined; + + const timer = window.setInterval(() => { + setRelativeNow(Date.now()); + }, 5000); + + return () => window.clearInterval(timer); + }, []); const handleColumnSizingChange = useCallback( (updater: Updater) => { @@ -411,7 +464,6 @@ export const ConnectionTable = (props: Props) => { const table = useReactTable({ data: connRows, - columns: columnDefs, state: { columnVisibility: columnVisibilityState, columnSizing: columnWidths, @@ -420,10 +472,11 @@ export const ConnectionTable = (props: Props) => { columnResizeMode: "onChange", enableSortingRemoval: true, getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), + getSortedRowModel: sorting.length ? getSortedRowModel() : undefined, onSortingChange: setSorting, onColumnSizingChange: handleColumnSizingChange, onColumnVisibilityChange: handleColumnVisibilityChange, + columns: columnDefs, }); const rows = table.getRowModel().rows; @@ -432,7 +485,7 @@ export const ConnectionTable = (props: Props) => { count: rows.length, getScrollElement: () => tableContainerRef.current, estimateSize: () => ROW_HEIGHT, - overscan: 8, + overscan: 4, }); const virtualRows = rowVirtualizer.getVirtualItems(); diff --git a/src/components/home/enhanced-canvas-traffic-graph.tsx b/src/components/home/enhanced-canvas-traffic-graph.tsx index e8aa255e1..d557a7622 100644 --- a/src/components/home/enhanced-canvas-traffic-graph.tsx +++ b/src/components/home/enhanced-canvas-traffic-graph.tsx @@ -147,6 +147,7 @@ export const EnhancedCanvasTrafficGraph = memo( }); const lastDataTimestampRef = useRef(0); const resumeCooldownRef = useRef(0); + const dataStaleRef = useRef(false); // 当前显示的数据缓存 const [displayData, dispatchDisplayData] = useReducer( @@ -197,6 +198,7 @@ export const EnhancedCanvasTrafficGraph = memo( useEffect(() => { if (displayData.length === 0) { lastDataTimestampRef.current = 0; + dataStaleRef.current = false; fpsControllerRef.current.target = GRAPH_CONFIG.targetFPS; fpsControllerRef.current.samples = []; fpsControllerRef.current.lastAdjustTime = 0; @@ -209,6 +211,11 @@ export const EnhancedCanvasTrafficGraph = memo( displayData[displayData.length - 1]?.timestamp ?? null; if (latestTimestamp) { lastDataTimestampRef.current = latestTimestamp; + const age = Date.now() - latestTimestamp; + const stale = age > STALE_DATA_THRESHOLD; + dataStaleRef.current = stale; + } else { + dataStaleRef.current = false; } }, [displayData]); @@ -986,7 +993,11 @@ export const EnhancedCanvasTrafficGraph = memo( // 受控的动画循环 useEffect(() => { - if (!isWindowFocused || displayData.length === 0) { + if ( + !isWindowFocused || + displayData.length === 0 || + dataStaleRef.current + ) { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); animationFrameRef.current = undefined; @@ -1002,9 +1013,25 @@ export const EnhancedCanvasTrafficGraph = memo( return; } + const lastDataAge = + lastDataTimestampRef.current > 0 + ? Date.now() - lastDataTimestampRef.current + : null; const targetFPS = fpsControllerRef.current.target; const frameBudget = 1000 / targetFPS; + if ( + typeof lastDataAge === "number" && + lastDataAge > STALE_DATA_THRESHOLD + ) { + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + animationFrameRef.current = undefined; + } + dataStaleRef.current = true; + return; + } + if ( currentTime - lastRenderTimeRef.current >= frameBudget || !isInitializedRef.current diff --git a/src/components/home/enhanced-traffic-stats.tsx b/src/components/home/enhanced-traffic-stats.tsx index a725b542a..69d8e4e3f 100644 --- a/src/components/home/enhanced-traffic-stats.tsx +++ b/src/components/home/enhanced-traffic-stats.tsx @@ -14,8 +14,7 @@ import { alpha, useTheme, } from "@mui/material"; -import { useRef, memo, useMemo } from "react"; -import { ReactNode } from "react"; +import { ReactNode, memo, useMemo, useRef } from "react"; import { useTranslation } from "react-i18next"; import { TrafficErrorBoundary } from "@/components/common/traffic-error-boundary"; @@ -147,9 +146,12 @@ export const EnhancedTrafficStats = () => { const trafficRef = useRef(null); const pageVisible = useVisibility(); + // 是否显示流量图表 + const trafficGraph = verge?.traffic_graph ?? true; + const { response: { data: traffic }, - } = useTrafficData(); + } = useTrafficData({ enabled: trafficGraph && pageVisible }); const { response: { data: memory }, @@ -159,9 +161,6 @@ export const EnhancedTrafficStats = () => { response: { data: connections }, } = useConnectionData(); - // 是否显示流量图表 - const trafficGraph = verge?.traffic_graph ?? true; - // Canvas组件现在直接从全局Hook获取数据,无需手动添加数据点 // 使用useMemo计算解析后的流量数据 diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx index 36c35ac2e..8e26da04d 100644 --- a/src/components/layout/layout-traffic.tsx +++ b/src/components/layout/layout-traffic.tsx @@ -29,7 +29,7 @@ export const LayoutTraffic = () => { const { response: { data: traffic }, - } = useTrafficData(); + } = useTrafficData({ enabled: trafficGraph && pageVisible }); const { response: { data: memory }, } = useMemoryData(); diff --git a/src/components/proxy/use-filter-sort.ts b/src/components/proxy/use-filter-sort.ts index a4145978a..11e20bb65 100644 --- a/src/components/proxy/use-filter-sort.ts +++ b/src/components/proxy/use-filter-sort.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useReducer } from "react"; +import { useEffect, useMemo, useReducer, useRef } from "react"; import { useVerge } from "@/hooks/use-verge"; import delayManager from "@/services/delay"; @@ -22,6 +22,10 @@ export default function useFilterSort( ) { const { verge } = useVerge(); const [_, bumpRefresh] = useReducer((count: number) => count + 1, 0); + const lastInputRef = useRef<{ text: string; sort: ProxySortType } | null>( + null, + ); + const debounceTimer = useRef(null); useEffect(() => { let last = 0; @@ -40,7 +44,7 @@ export default function useFilterSort( }; }, [groupName]); - return useMemo(() => { + const compute = useMemo(() => { const fp = filterProxies(proxies, groupName, filterText, searchState); const sp = sortProxies( fp, @@ -57,6 +61,39 @@ export default function useFilterSort( searchState, verge?.default_latency_timeout, ]); + + const [result, setResult] = useReducer( + (_prev: IProxyItem[], next: IProxyItem[]) => next, + compute, + ); + + useEffect(() => { + if (debounceTimer.current !== null) { + window.clearTimeout(debounceTimer.current); + debounceTimer.current = null; + } + + const prev = lastInputRef.current; + const stableInputs = + prev && prev.text === filterText && prev.sort === sortType; + + lastInputRef.current = { text: filterText, sort: sortType }; + + const delay = stableInputs ? 0 : 150; + debounceTimer.current = window.setTimeout(() => { + setResult(compute); + debounceTimer.current = null; + }, delay); + + return () => { + if (debounceTimer.current !== null) { + window.clearTimeout(debounceTimer.current); + debounceTimer.current = null; + } + }; + }, [compute, filterText, sortType]); + + return result; } export function filterSort( diff --git a/src/hooks/use-shared-swr-poller.ts b/src/hooks/use-shared-swr-poller.ts index d5399ff37..8829bde18 100644 --- a/src/hooks/use-shared-swr-poller.ts +++ b/src/hooks/use-shared-swr-poller.ts @@ -6,6 +6,7 @@ type SharedPollerEntry = { timer: number | null; interval: number; callback: (() => void) | null; + lastFired: number; refreshWhenHidden: boolean; refreshWhenOffline: boolean; }; @@ -32,6 +33,12 @@ const ensureTimer = (key: string, entry: SharedPollerEntry) => { entry.timer = window.setInterval(() => { if (!entry.refreshWhenHidden && isDocumentHidden()) return; if (!entry.refreshWhenOffline && isOffline()) return; + const now = Date.now(); + if (entry.lastFired && now - entry.lastFired < entry.interval / 2) { + // Skip duplicate fire within half interval to coalesce concurrent consumers + return; + } + entry.lastFired = now; entry.callback?.(); }, entry.interval); }; @@ -50,6 +57,7 @@ const registerSharedPoller = ( timer: null, interval, callback, + lastFired: 0, refreshWhenHidden: options.refreshWhenHidden, refreshWhenOffline: options.refreshWhenOffline, }; diff --git a/src/hooks/use-system-state.ts b/src/hooks/use-system-state.ts index 58f14d55a..37dc4000e 100644 --- a/src/hooks/use-system-state.ts +++ b/src/hooks/use-system-state.ts @@ -4,6 +4,7 @@ import useSWR from "swr"; import { getRunningMode, isAdmin, isServiceAvailable } from "@/services/cmds"; import { showNotice } from "@/services/notice-service"; +import { useSharedSWRPoller } from "./use-shared-swr-poller"; import { useVerge } from "./use-verge"; export interface SystemState { @@ -43,11 +44,20 @@ export function useSystemState() { }, { suspense: true, - refreshInterval: 30000, + refreshInterval: 0, fallback: defaultSystemState, }, ); + useSharedSWRPoller( + "getSystemState", + 30000, + () => { + void mutateSystemState(); + }, + { refreshWhenHidden: false, refreshWhenOffline: false }, + ); + const isSidecarMode = systemState.runningMode === "Sidecar"; const isServiceMode = systemState.runningMode === "Service"; const isTunModeAvailable = systemState.isAdminMode || systemState.isServiceOk; diff --git a/src/hooks/use-traffic-data.ts b/src/hooks/use-traffic-data.ts index 4739927b1..aa897b955 100644 --- a/src/hooks/use-traffic-data.ts +++ b/src/hooks/use-traffic-data.ts @@ -5,10 +5,12 @@ import { useTrafficMonitorEnhanced } from "./use-traffic-monitor"; const FALLBACK_TRAFFIC: Traffic = { up: 0, down: 0 }; -export const useTrafficData = () => { +export const useTrafficData = (options?: { enabled?: boolean }) => { + const enabled = options?.enabled ?? true; + const { graphData: { appendData }, - } = useTrafficMonitorEnhanced({ subscribe: false }); + } = useTrafficMonitorEnhanced({ subscribe: false, enabled }); const { response, refresh } = useMihomoWsSubscription({ storageKey: "mihomo_traffic_date", buildSubscriptKey: (date) => `getClashTraffic-${date}`, diff --git a/src/hooks/use-traffic-monitor.ts b/src/hooks/use-traffic-monitor.ts index 852b53bb5..28625f0e0 100644 --- a/src/hooks/use-traffic-monitor.ts +++ b/src/hooks/use-traffic-monitor.ts @@ -343,8 +343,10 @@ const EMPTY_STATS: ISamplerStats = { */ export const useTrafficMonitorEnhanced = (options?: { subscribe?: boolean; + enabled?: boolean; }) => { const subscribeToSnapshots = options?.subscribe ?? true; + const enabled = options?.enabled ?? true; const [latestSnapshot, setLatestSnapshot] = useState<{ availableDataPoints: ITrafficDataPoint[]; samplerStats: ISamplerStats; @@ -365,6 +367,8 @@ export const useTrafficMonitorEnhanced = (options?: { // 注册引用计数与Worker生命周期 useEffect(() => { + if (!enabled) return; + const client = getWorkerClient(); clientRef.current = client; @@ -396,43 +400,53 @@ export const useTrafficMonitorEnhanced = (options?: { client.stop(); } }; - }, [subscribeToSnapshots]); + }, [enabled, subscribeToSnapshots]); // Periodically refresh "now" so idle streams age out of the selected window when subscribed useEffect(() => { - if (!subscribeToSnapshots) return; + if (!enabled || !subscribeToSnapshots) return; const timer = window.setInterval(() => { setNow(Date.now()); }, 1000); return () => window.clearInterval(timer); - }, [subscribeToSnapshots]); + }, [enabled, subscribeToSnapshots]); // 添加流量数据 - const appendData = useCallback((traffic: Traffic) => { - clientRef.current?.appendData(traffic); - }, []); + const appendData = useCallback( + (traffic: Traffic) => { + if (!enabled) return; + clientRef.current?.appendData(traffic); + }, + [enabled], + ); // 请求不同时间范围的数据 - const requestRange = useCallback((minutes: number) => { - currentRangeRef.current = minutes; - setRangeMinutes(minutes); - clientRef.current?.setRange(minutes); - }, []); + const requestRange = useCallback( + (minutes: number) => { + if (!enabled) return; + currentRangeRef.current = minutes; + setRangeMinutes(minutes); + clientRef.current?.setRange(minutes); + }, + [enabled], + ); // 清空数据 const clearData = useCallback(() => { + if (!enabled) return; clientRef.current?.clearData(); - }, []); + }, [enabled]); const filteredDataPoints = useMemo(() => { + if (!enabled) return []; const sourceData = latestSnapshot.availableDataPoints; if (sourceData.length === 0) return []; const cutoff = now - rangeMinutes * 60 * 1000; return sourceData.filter((point) => point.timestamp > cutoff); - }, [latestSnapshot.availableDataPoints, rangeMinutes, now]); + }, [enabled, latestSnapshot.availableDataPoints, rangeMinutes, now]); return { graphData: { diff --git a/src/utils/traffic-sampler.ts b/src/utils/traffic-sampler.ts index 9b6ba90e8..2d44c34d5 100644 --- a/src/utils/traffic-sampler.ts +++ b/src/utils/traffic-sampler.ts @@ -15,7 +15,9 @@ export const formatTrafficName = (timestamp: number) => export class TrafficDataSampler { private rawBuffer: ITrafficDataPoint[] = []; + private rawHead = 0; private compressedBuffer: ICompressedDataPoint[] = []; + private compressedHead = 0; private compressionQueue: ITrafficDataPoint[] = []; constructor(private config: ISamplingConfig) {} @@ -24,7 +26,17 @@ export class TrafficDataSampler { this.rawBuffer.push(point); const rawCutoff = Date.now() - this.config.rawDataMinutes * 60 * 1000; - this.rawBuffer = this.rawBuffer.filter((p) => p.timestamp > rawCutoff); + // O(1) amortized trimming using moving head; compact occasionally + while ( + this.rawHead < this.rawBuffer.length && + this.rawBuffer[this.rawHead]?.timestamp <= rawCutoff + ) { + this.rawHead++; + } + if (this.rawHead > 512 && this.rawHead > this.rawBuffer.length / 2) { + this.rawBuffer = this.rawBuffer.slice(this.rawHead); + this.rawHead = 0; + } this.compressionQueue.push(point); if (this.compressionQueue.length >= this.config.compressionRatio) { @@ -33,9 +45,19 @@ export class TrafficDataSampler { const compressedCutoff = Date.now() - this.config.compressedDataMinutes * 60 * 1000; - this.compressedBuffer = this.compressedBuffer.filter( - (p) => p.timestamp > compressedCutoff, - ); + while ( + this.compressedHead < this.compressedBuffer.length && + this.compressedBuffer[this.compressedHead]?.timestamp <= compressedCutoff + ) { + this.compressedHead++; + } + if ( + this.compressedHead > 256 && + this.compressedHead > this.compressedBuffer.length / 2 + ) { + this.compressedBuffer = this.compressedBuffer.slice(this.compressedHead); + this.compressedHead = 0; + } } private compressData() { @@ -60,18 +82,34 @@ export class TrafficDataSampler { getDataForTimeRange(minutes: number): ITrafficDataPoint[] { const cutoff = Date.now() - minutes * 60 * 1000; - const rawData = this.rawBuffer.filter((p) => p.timestamp > cutoff); + + let rawStart = this.rawHead; + while ( + rawStart < this.rawBuffer.length && + this.rawBuffer[rawStart]?.timestamp <= cutoff + ) { + rawStart++; + } + const rawData = this.rawBuffer.slice(rawStart); if (minutes <= this.config.rawDataMinutes) { return rawData; } + const compressedCutoffUpper = + Date.now() - this.config.rawDataMinutes * 60 * 1000; + + let compressedStart = this.compressedHead; + while ( + compressedStart < this.compressedBuffer.length && + this.compressedBuffer[compressedStart]?.timestamp <= cutoff + ) { + compressedStart++; + } + const compressedData = this.compressedBuffer - .filter( - (p) => - p.timestamp > cutoff && - p.timestamp <= Date.now() - this.config.rawDataMinutes * 60 * 1000, - ) + .slice(compressedStart) + .filter((p) => p.timestamp <= compressedCutoffUpper) .map((p) => ({ up: p.up, down: p.down, @@ -86,16 +124,21 @@ export class TrafficDataSampler { getStats(): ISamplerStats { return { - rawBufferSize: this.rawBuffer.length, - compressedBufferSize: this.compressedBuffer.length, + rawBufferSize: this.rawBuffer.length - this.rawHead, + compressedBufferSize: this.compressedBuffer.length - this.compressedHead, compressionQueueSize: this.compressionQueue.length, - totalMemoryPoints: this.rawBuffer.length + this.compressedBuffer.length, + totalMemoryPoints: + this.rawBuffer.length - + this.rawHead + + (this.compressedBuffer.length - this.compressedHead), }; } clear() { this.rawBuffer = []; + this.rawHead = 0; this.compressedBuffer = []; + this.compressedHead = 0; this.compressionQueue = []; } }