perf(traffic): reuse canvas pixels while scrolling

This commit is contained in:
Tunglies
2026-10-03 06:58:54 +08:00
parent ea509b8236
commit 3607e66bb3
7 changed files with 89 additions and 99 deletions
Generated
+1 -1
View File
@@ -1079,7 +1079,7 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "clash-verge"
version = "2.5.7"
version = "2.5.8"
dependencies = [
"aes-gcm",
"anyhow",
+5 -20
View File
@@ -1,35 +1,20 @@
## v2.5.7
## v2.5.8
<details>
<summary><strong> 🐞 修复问题 </strong></summary>
- 修复读屏软件将普通模式下的侧边栏导航项播报为不可用的可拖拽控件的问题
- 修复订阅包含自定义 DNS 时,确认开启的 DNS 覆写在重启应用后被自动关闭的问题
**🖥️ Windows**
</details>
- 修复 Windows 系统盘根目录的删除权限被误判,导致服务模式和 TUN 无法使用的问题
- 修复 Windows 部分系统安装服务失败,提示「program not found」的问题
<details>
<summary><strong> ✨ 新增功能 </strong></summary>
**🍎 macOS**
- macOS 修复启动或重启应用时偶发内核启动失败,并误提示「需要更新系统服务」的问题
- macOS 修复 VPN 接管网络或开机网络未就绪时的问题:服务模式内核无法启动、TUN 不可用、系统代理状态读取报错
- macOS 修复用 `ipconfig set` 手动配置网卡后无法设置系统代理的问题
**🍎/🐧 macOS/Linux**
- 修复 macOS/Linux 服务模式停止或重启内核时直接强制结束的问题:内核来不及正常退出、最后的日志丢失
</details>
<details>
<summary><strong> 🚀 优化改进 </strong></summary>
- 优化服务模式的问题排查:服务自身的运行日志会保存到文件,便于追查内核被停止的原因
**🖥️ Windows**
- 优化 Windows 服务安全检查未通过时的启动提示:不再只显示「服务无法启动内核」,直接说明原因并提供修复文档链接
- 优化侧边栏流量图表的 CPU 占用
</details>
+37
View File
@@ -1,3 +1,40 @@
## v2.5.7
<details>
<summary><strong> 🐞 修复问题 </strong></summary>
- 修复读屏软件将普通模式下的侧边栏导航项播报为不可用的可拖拽控件的问题
- 修复订阅包含自定义 DNS 时,确认开启的 DNS 覆写在重启应用后被自动关闭的问题
**🖥️ Windows**
- 修复 Windows 系统盘根目录的删除权限被误判,导致服务模式和 TUN 无法使用的问题
- 修复 Windows 部分系统安装服务失败,提示「program not found」的问题
**🍎 macOS**
- macOS 修复启动或重启应用时偶发内核启动失败,并误提示「需要更新系统服务」的问题
- macOS 修复 VPN 接管网络或开机网络未就绪时的问题:服务模式内核无法启动、TUN 不可用、系统代理状态读取报错
- macOS 修复用 `ipconfig set` 手动配置网卡后无法设置系统代理的问题
**🍎/🐧 macOS/Linux**
- 修复 macOS/Linux 服务模式停止或重启内核时直接强制结束的问题:内核来不及正常退出、最后的日志丢失
</details>
<details>
<summary><strong> 🚀 优化改进 </strong></summary>
- 优化服务模式的问题排查:服务自身的运行日志会保存到文件,便于追查内核被停止的原因
**🖥️ Windows**
- 优化 Windows 服务安全检查未通过时的启动提示:不再只显示「服务无法启动内核」,直接说明原因并提供修复文档链接
</details>
## v2.5.6
<details>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "2.5.7",
"version": "2.5.8",
"license": "GPL-3.0-only",
"scripts": {
"prepare": "husky || true",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "clash-verge"
version = "2.5.7"
version = "2.5.8"
description = "clash verge"
authors = ["zzzgydi", "Tunglies", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
+1 -1
View File
@@ -1,5 +1,5 @@
{
"version": "2.5.7",
"version": "2.5.8",
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"bundle": {
"active": true,
+43 -75
View File
@@ -13,7 +13,6 @@ const upLineWidth = 4
const downLineAlpha = 1
const downLineWidth = 4
const sampleIntervalMs = 1000
const frameIntervalMs = 1000 / 15
const animationDurationMs = sampleIntervalMs
const zeroTraffic: Traffic = { up: 0, down: 0, upTotal: 0, downTotal: 0 }
@@ -36,7 +35,7 @@ type TrafficValueKey = 'up' | 'down'
* draw the traffic graph
*/
export function TrafficGraph({ ref }: { ref?: Ref<TrafficRef> }) {
const countRef = useRef(0)
const animationStartRef = useRef<number | null>(null)
const styleRef = useRef(true)
const listRef = useRef<Traffic[]>(createDefaultList())
const canvasRef = useRef<HTMLCanvasElement>(null!)
@@ -69,7 +68,6 @@ export function TrafficGraph({ ref }: { ref?: Ref<TrafficRef> }) {
if (shouldAppend) {
if (list.length > maxPoint + 2) list.shift()
list.push(data)
countRef.current = 0
requestDrawRef.current(true)
}
@@ -84,11 +82,8 @@ export function TrafficGraph({ ref }: { ref?: Ref<TrafficRef> }) {
}, [])
useEffect(() => {
let raf = 0
let frameTimer: ReturnType<typeof setTimeout> | null = null
let animation: Animation | null = null
let resizeObserver: ResizeObserver | null = null
let animationStart = 0
let lastFrameTime = 0
const canvas = canvasRef.current!
if (!canvas) return
@@ -102,25 +97,13 @@ export function TrafficGraph({ ref }: { ref?: Ref<TrafficRef> }) {
const upLineColor = secondary.main || '#9c27b0'
const downLineColor = primary.main || '#5b5c9d'
const cancelPendingDraw = () => {
if (frameTimer !== null) {
clearTimeout(frameTimer)
frameTimer = null
}
if (raf) {
cancelAnimationFrame(raf)
raf = 0
}
}
const drawGraph = (offset = countRef.current) => {
const drawGraph = () => {
const list = listRef.current
const lineStyle = styleRef.current
const width = canvas.width
const height = canvas.height
const dx = width / maxPoint
const dx = width / (maxPoint + 1)
const dy = height / 7
const l1 = dy
const l2 = dy * 4
@@ -142,17 +125,17 @@ export function TrafficGraph({ ref }: { ref?: Ref<TrafficRef> }) {
const drawBezier = (list: Traffic[], valueKey: TrafficValueKey) => {
if (list.length === 0) return
const firstX = (dx * -1 - offset + 3) | 0
const firstX = (dx * -1 + 3) | 0
const firstY = countY(list[0]?.[valueKey] ?? 0)
context.moveTo(firstX, firstY)
for (let i = 1; i < list.length; i++) {
const p1x = (dx * (i - 1) - offset + 3) | 0
const p1x = (dx * (i - 1) + 3) | 0
const p1y = countY(list[i]?.[valueKey] ?? 0)
const hasNext = i + 1 < list.length
const p2x = hasNext ? (dx * i - offset + 3) | 0 : p1x
const p2x = hasNext ? (dx * i + 3) | 0 : p1x
const p2y = hasNext ? countY(list[i + 1]?.[valueKey] ?? 0) : p1y
context.quadraticCurveTo(p1x, p1y, (p1x + p2x) / 2, (p1y + p2y) / 2)
@@ -162,13 +145,10 @@ export function TrafficGraph({ ref }: { ref?: Ref<TrafficRef> }) {
const drawLine = (list: Traffic[], valueKey: TrafficValueKey) => {
if (list.length === 0) return
context.moveTo((dx * -1 - offset) | 0, countY(list[0]?.[valueKey] ?? 0))
context.moveTo((dx * -1) | 0, countY(list[0]?.[valueKey] ?? 0))
for (let i = 1; i < list.length; i++) {
context.lineTo(
(dx * (i - 1) - offset) | 0,
countY(list[i]?.[valueKey] ?? 0),
)
context.lineTo((dx * (i - 1)) | 0, countY(list[i]?.[valueKey] ?? 0))
}
}
@@ -211,51 +191,28 @@ export function TrafficGraph({ ref }: { ref?: Ref<TrafficRef> }) {
context.closePath()
}
const drawAnimatedFrame = (timestamp: number) => {
raf = 0
const timeSinceLastFrame = timestamp - lastFrameTime
if (timeSinceLastFrame < frameIntervalMs) {
frameTimer = setTimeout(() => {
frameTimer = null
raf = requestAnimationFrame(drawAnimatedFrame)
}, frameIntervalMs - timeSinceLastFrame)
return
}
lastFrameTime = timestamp
const dx = canvas.width / maxPoint
const progress = Math.min(
(timestamp - animationStart) / animationDurationMs,
1,
)
const offset = progress * dx
countRef.current = offset
drawGraph(offset)
if (progress < 1) {
raf = requestAnimationFrame(drawAnimatedFrame)
return
}
countRef.current = dx
}
const requestDraw = (animate = false) => {
cancelPendingDraw()
if (animate) animationStartRef.current = performance.now()
animation?.cancel()
drawGraph()
if (!animate) {
raf = requestAnimationFrame(() => {
raf = 0
drawGraph()
})
return
}
animationStart = performance.now()
lastFrameTime = animationStart - frameIntervalMs
raf = requestAnimationFrame(drawAnimatedFrame)
const elapsed =
animationStartRef.current === null
? 0
: Math.min(
performance.now() - animationStartRef.current,
animationDurationMs,
)
const step = canvas.getBoundingClientRect().width / (maxPoint + 1)
animation = canvas.animate(
[
{ transform: 'translateX(0)' },
{ transform: `translateX(${-step}px)` },
],
{ duration: animationDurationMs, easing: 'linear', fill: 'forwards' },
)
animation.currentTime = elapsed
if (animationStartRef.current === null) animation.pause()
}
requestDrawRef.current = requestDraw
@@ -263,7 +220,7 @@ export function TrafficGraph({ ref }: { ref?: Ref<TrafficRef> }) {
if (typeof ResizeObserver !== 'undefined') {
resizeObserver = new ResizeObserver(() => requestDraw(false))
resizeObserver.observe(canvas)
resizeObserver.observe(canvas.parentElement!)
}
return () => {
@@ -271,9 +228,20 @@ export function TrafficGraph({ ref }: { ref?: Ref<TrafficRef> }) {
requestDrawRef.current = () => {}
}
resizeObserver?.disconnect()
cancelPendingDraw()
animation?.cancel()
}
}, [palette])
return <canvas ref={canvasRef} style={{ width: '100%', height: '100%' }} />
return (
<div style={{ width: '100%', height: '100%', overflow: 'hidden' }}>
<canvas
ref={canvasRef}
width={310}
style={{
width: `${((maxPoint + 1) / maxPoint) * 100}%`,
height: '100%',
}}
/>
</div>
)
}