11/**
2- * 性能监控 — FPS追踪与动态降级
2+ * 性能监控 — FPS追踪与动态降级(基于 drei 成熟方案)
3+ *
4+ * 重构说明(docs/standards/refactoring.md,外部行为不变):
5+ * - FPS 测量与防抖委托给 drei <PerformanceMonitor>(工业级实现):
6+ * 内置滑动均值(iterations×ms 采样)、bounds 滞回、threshold 比例持续判定
7+ * 与 flip-flop 保护,替代原自研单窗口测量 + 手写滞回/持续逻辑;
8+ * - tier 迁移策略抽取至纯函数模块 tierPolicy(已单测);
9+ * - 本组件仅做装配:把 drei 回调映射为 tier 逐级迁移,并在窗口隐藏时
10+ * 卸载监控以重置测量基线(后台 rAF 节流期 FPS 不可信,卸载重挂即自然
11+ * 清零,避免回到前台被误判降级)。
312 *
413 * @ai -context: 3D 场景核心(R3F):PerformanceMonitor。
514 */
6- import { useFrame } from '@ react-three/fiber ' ;
7- import { useRef } from 'react' ;
15+ import { useEffect , useState } from 'react' ;
16+ import { PerformanceMonitor as DreiPerformanceMonitor } from '@ react-three/drei ' ;
817import { create } from 'zustand' ;
18+ import { stepTier , type PerformanceTier } from './tierPolicy' ;
919
10- type PerformanceTier = 'high' | 'medium' | 'low' ;
20+ /** FPS 下界:持续低于此值触发降级(滞回缓冲区下限) */
21+ export const FPS_LOWER_BOUND = 25 ;
22+ /** FPS 上界:持续高于此值触发升级(滞回缓冲区上限) */
23+ export const FPS_UPPER_BOUND = 50 ;
24+ /** 升降震荡(flip-flop)允许次数上限,超过则回落稳定档 */
25+ const MAX_FLIPFLOPS = 4 ;
1126
1227interface PerformanceState {
1328 tier : PerformanceTier ;
@@ -24,27 +39,38 @@ export const usePerformanceStore = create<PerformanceState>((set) => ({
2439} ) ) ;
2540
2641export function PerformanceMonitor ( ) {
27- const frameCount = useRef ( 0 ) ;
28- const lastTime = useRef ( performance . now ( ) ) ;
29- const { setTier, setFps } = usePerformanceStore ( ) ;
30-
31- useFrame ( ( ) => {
32- frameCount . current ++ ;
33- const now = performance . now ( ) ;
34- const elapsed = now - lastTime . current ;
35-
36- if ( elapsed >= 2000 ) {
37- const fps = Math . round ( frameCount . current * ( 1000 / elapsed ) ) ;
38- setFps ( fps ) ;
39-
40- if ( fps < 25 ) setTier ( 'low' ) ;
41- else if ( fps < 45 ) setTier ( 'medium' ) ;
42- else setTier ( 'high' ) ;
42+ // 窗口隐藏时卸载监控:后台期间浏览器节流 rAF,FPS 读数不可信;
43+ // 返回前台重挂即自然重置采样基线,避免误判降级。
44+ // 附带收益:同时重置 drei 内部 flipped/fallback 计数,防止 fallback 永久锁定。
45+ const [ visible , setVisible ] = useState ( ( ) => document . visibilityState === 'visible' ) ;
46+ useEffect ( ( ) => {
47+ const onVisibility = ( ) => setVisible ( document . visibilityState === 'visible' ) ;
48+ document . addEventListener ( 'visibilitychange' , onVisibility ) ;
49+ return ( ) => document . removeEventListener ( 'visibilitychange' , onVisibility ) ;
50+ } , [ ] ) ;
4351
44- frameCount . current = 0 ;
45- lastTime . current = now ;
46- }
47- } ) ;
52+ if ( ! visible ) return null ;
4853
49- return null ;
54+ return (
55+ < DreiPerformanceMonitor
56+ bounds = { ( ) => [ FPS_LOWER_BOUND , FPS_UPPER_BOUND ] }
57+ flipflops = { MAX_FLIPFLOPS }
58+ onIncline = { ( api ) => {
59+ // 持续高 FPS(75%+ 样本 ≥ 上界)→ 逐级升档
60+ const { tier, setTier, setFps } = usePerformanceStore . getState ( ) ;
61+ setFps ( Math . round ( api . fps ) ) ;
62+ setTier ( stepTier ( tier , 'up' ) ) ;
63+ } }
64+ onDecline = { ( api ) => {
65+ // 持续低 FPS(75%+ 样本 < 下界)→ 逐级降档
66+ const { tier, setTier, setFps } = usePerformanceStore . getState ( ) ;
67+ setFps ( Math . round ( api . fps ) ) ;
68+ setTier ( stepTier ( tier , 'down' ) ) ;
69+ } }
70+ onFallback = { ( ) => {
71+ // 震荡超限 → 稳定在中档:保留后处理特效、适度降低 DPR
72+ usePerformanceStore . getState ( ) . setTier ( 'medium' ) ;
73+ } }
74+ />
75+ ) ;
5076}
0 commit comments