mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-22 19:52:14 +08:00
perf(profile): optimize profile batch update and worker config
This commit is contained in:
@@ -103,6 +103,20 @@ export const ProfileItem = (props: Props) => {
|
||||
const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
||||
undefined,
|
||||
)
|
||||
const setLoading = useCallback(
|
||||
(loading: boolean) => {
|
||||
setLoadingCache((cache) => {
|
||||
const next = new Set(cache)
|
||||
if (loading) {
|
||||
next.add(itemData.uid)
|
||||
} else {
|
||||
next.delete(itemData.uid)
|
||||
}
|
||||
return next
|
||||
})
|
||||
},
|
||||
[itemData.uid, setLoadingCache],
|
||||
)
|
||||
|
||||
const { uid, name = 'Profile', extra, updated = 0, option } = itemData
|
||||
|
||||
@@ -251,7 +265,7 @@ export const ProfileItem = (props: Props) => {
|
||||
100,
|
||||
)
|
||||
|
||||
const loading = loadingCache[itemData.uid] ?? false
|
||||
const loading = loadingCache.has(itemData.uid)
|
||||
|
||||
// interval update fromNow field
|
||||
const [, forceRefresh] = useReducer((value: number) => value + 1, 0)
|
||||
@@ -380,7 +394,7 @@ export const ProfileItem = (props: Props) => {
|
||||
/// 2 至少使用一个代理,根据订阅,如果没订阅,默认使用系统代理
|
||||
const onUpdate = useLockFn(async (type: 0 | 1 | 2): Promise<void> => {
|
||||
setAnchorEl(null)
|
||||
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: true }))
|
||||
setLoading(true)
|
||||
|
||||
// 根据类型设置初始更新选项
|
||||
const option: Partial<IProfileOption> = {}
|
||||
@@ -408,7 +422,7 @@ export const ProfileItem = (props: Props) => {
|
||||
// 更新完全失败(包括后端的回退尝试)
|
||||
// 不需要做处理,后端会通过事件通知系统发送错误
|
||||
} finally {
|
||||
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: false }))
|
||||
setLoading(false)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -599,7 +613,7 @@ export const ProfileItem = (props: Props) => {
|
||||
Promise.allSettled([
|
||||
listen<{ uid?: string }>('profile-update-started', ({ payload }) => {
|
||||
if (payload.uid === itemData.uid) {
|
||||
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: true }))
|
||||
setLoading(true)
|
||||
}
|
||||
}),
|
||||
listen<{ uid?: string }>('profile-update-completed', ({ payload }) => {
|
||||
@@ -607,7 +621,7 @@ export const ProfileItem = (props: Props) => {
|
||||
return
|
||||
}
|
||||
|
||||
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: false }))
|
||||
setLoading(false)
|
||||
// 刷新 profile 数据以获取最新的 updated 时间戳
|
||||
void mutateProfiles()
|
||||
// 更新完成后刷新显示
|
||||
@@ -635,7 +649,7 @@ export const ProfileItem = (props: Props) => {
|
||||
disposed = true
|
||||
unlisteners.forEach((unlisten) => unlisten())
|
||||
}
|
||||
}, [fetchNextUpdateTime, itemData.uid, mutateProfiles, setLoadingCache])
|
||||
}, [fetchNextUpdateTime, itemData.uid, mutateProfiles, setLoading])
|
||||
|
||||
const handleSaveProfileDocument = useLockFn(async () => {
|
||||
const currentValue = profileDocument.value
|
||||
|
||||
+67
-26
@@ -66,9 +66,16 @@ import {
|
||||
} from '@/services/cmds'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import { queryClient } from '@/services/query-client'
|
||||
import { useSetLoadingCache, useThemeMode } from '@/services/states'
|
||||
import {
|
||||
useLoadingCache,
|
||||
useSetLoadingCache,
|
||||
useThemeMode,
|
||||
} from '@/services/states'
|
||||
import { debugLog } from '@/utils/debug'
|
||||
|
||||
// 与 src-tauri/src/main.rs 的 worker_limit 上限(8)保持一致,避免前后端更新风暴不对齐
|
||||
const PROFILE_UPDATE_WORKER_LIMIT = 8
|
||||
|
||||
// 记录profile切换状态
|
||||
const debugProfileSwitch = (action: string, profile: string, extra?: any) => {
|
||||
const timestamp = new Date().toISOString().substring(11, 23)
|
||||
@@ -602,34 +609,68 @@ const ProfilePage = () => {
|
||||
})
|
||||
|
||||
// 更新所有订阅
|
||||
const loadingCache = useLoadingCache()
|
||||
const setLoadingCache = useSetLoadingCache()
|
||||
const onUpdateAll = useLockFn(async () => {
|
||||
const throttleMutate = throttle(mutateProfiles, 2000, {
|
||||
trailing: true,
|
||||
})
|
||||
const updateOne = async (uid: string) => {
|
||||
try {
|
||||
await updateProfile(uid)
|
||||
throttleMutate()
|
||||
} catch (err: any) {
|
||||
console.error(`更新订阅 ${uid} 失败:`, err)
|
||||
} finally {
|
||||
setLoadingCache((cache) => ({ ...cache, [uid]: false }))
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const setLoadingProfiles = useCallback(
|
||||
(uids: string[], loading: boolean) => {
|
||||
setLoadingCache((cache) => {
|
||||
// 获取没有正在更新的订阅
|
||||
const items = profileItems.filter(
|
||||
(e) => e.type === 'remote' && !cache[e.uid],
|
||||
)
|
||||
const change = Object.fromEntries(items.map((e) => [e.uid, true]))
|
||||
|
||||
Promise.allSettled(items.map((e) => updateOne(e.uid))).then(resolve)
|
||||
return { ...cache, ...change }
|
||||
const next = new Set(cache)
|
||||
for (const uid of uids) {
|
||||
if (loading) {
|
||||
next.add(uid)
|
||||
} else {
|
||||
next.delete(uid)
|
||||
}
|
||||
}
|
||||
return next
|
||||
})
|
||||
})
|
||||
},
|
||||
[setLoadingCache],
|
||||
)
|
||||
const runProfileUpdates = useCallback(
|
||||
async (uids: string[]) => {
|
||||
if (uids.length === 0) return
|
||||
|
||||
const throttleMutate = throttle(mutateProfiles, 2000, {
|
||||
trailing: true,
|
||||
})
|
||||
let cursor = 0
|
||||
|
||||
const updateOne = async (uid: string) => {
|
||||
try {
|
||||
await updateProfile(uid)
|
||||
throttleMutate()
|
||||
} catch (err: any) {
|
||||
console.error(`更新订阅 ${uid} 失败:`, err)
|
||||
}
|
||||
}
|
||||
|
||||
const worker = async () => {
|
||||
while (cursor < uids.length) {
|
||||
const uid = uids[cursor++]
|
||||
await updateOne(uid)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const active = Math.min(PROFILE_UPDATE_WORKER_LIMIT, uids.length)
|
||||
await Promise.allSettled(Array.from({ length: active }, worker))
|
||||
} finally {
|
||||
setLoadingProfiles(uids, false)
|
||||
// 避免长时间批量更新后列表数据过晚刷新
|
||||
void mutateProfiles()
|
||||
}
|
||||
},
|
||||
[mutateProfiles, setLoadingProfiles],
|
||||
)
|
||||
const onUpdateAll = useLockFn(async () => {
|
||||
const items = profileItems.filter((e) => e.type === 'remote')
|
||||
const target = items
|
||||
.map((item) => item.uid)
|
||||
.filter((uid) => !loadingCache.has(uid))
|
||||
|
||||
setLoadingProfiles(target, true)
|
||||
await runProfileUpdates(target)
|
||||
})
|
||||
|
||||
const onCopyLink = async () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ const [ThemeModeProvider, useThemeMode, useSetThemeMode] = createContextState<
|
||||
|
||||
// save the state of each profile item loading
|
||||
const [LoadingCacheProvider, useLoadingCache, useSetLoadingCache] =
|
||||
createContextState<Record<string, boolean>>({})
|
||||
createContextState<Set<string>>(new Set())
|
||||
|
||||
// save update state
|
||||
const [UpdateStateProvider, useUpdateState, useSetUpdateState] =
|
||||
|
||||
Reference in New Issue
Block a user