Skip to content

Commit 041cb7f

Browse files
committed
[New] en version
1 parent c9d6604 commit 041cb7f

File tree

2 files changed

+171
-1
lines changed

2 files changed

+171
-1
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
---
2+
layout: post
3+
title: "VSCode:Centralized Animation Frame Scheduling"
4+
categories: [VSCode, UI]
5+
---
6+
7+
## 简介
8+
今天介绍一个VSCode源码中一个简单且小巧的功能。在此之前我先简单介绍一下`requestAnimationFrame`这个原生API。
9+
10+
## `requestAnimationFrame` API
11+
12+
### 工作原理
13+
`requestAnimationFrame` 会将提供的回调函数放入队列,并在下一次浏览器重绘前调用该函数。与传统的 `setTimeout` 不同,`requestAnimationFrame` 的优势在于:
14+
- **与屏幕刷新同步**:浏览器会在适当的时间调用回调函数,通常是每秒 60 帧(即 16.67ms 间隔)。
15+
- **节能效果**:当页面处于后台或不可见状态时,浏览器会暂停 `requestAnimationFrame` 的调用,从而节省资源。
16+
- **平滑的动画**:由于与浏览器刷新周期一致,动画会显得更加流畅。
17+
18+
### 基本用法
19+
```javascript
20+
function animate() {
21+
console.log('Animating...');
22+
}
23+
requestAnimationFrame(animate);
24+
```
25+
### 问题点
26+
当应用中的多个模块独立调用 `requestAnimationFrame` 时,可能出现以下问题:
27+
1. **缺乏全局优先级控制**:浏览器无法直接区分任务的优先级,导致关键任务与次要任务并行执行,影响性能。
28+
2. **复杂任务调度**:对于需要动态更新任务优先级或取消任务的场景,原生 API 支持不足。
29+
30+
为了解决这些问题,VSCode 设计了一套中央式动画帧调度器来管理动画和 UI 渲染任务。
31+
32+
## 中央调度器
33+
> 相关文件:`src\vs\base\browser\dom.ts`
34+
35+
`VSCode`整个软件中避免直接使用`window.requestsAnimationFrame`,而是提供了以下两个APIs:
36+
```ts
37+
/**
38+
* Schedule a callback to be run at the next animation frame.
39+
* This allows multiple parties to register callbacks that should run at the next animation frame.
40+
* If currently in an animation frame, `runner` will be executed immediately.
41+
* @return token that can be used to cancel the scheduled runner (only if `runner` was not executed immediately).
42+
*/
43+
export let runAtThisOrScheduleAtNextAnimationFrame: (targetWindow: Window, runner: () => void, priority?: number) => IDisposable;
44+
/**
45+
* Schedule a callback to be run at the next animation frame.
46+
* This allows multiple parties to register callbacks that should run at the next animation frame.
47+
* If currently in an animation frame, `runner` will be executed at the next animation frame.
48+
* @return token that can be used to cancel the scheduled runner.
49+
*/
50+
export let scheduleAtNextAnimationFrame: (targetWindow: Window, runner: () => void, priority?: number) => IDisposable;
51+
```
52+
53+
VSCode把函数的定义写在了一个 immediate call function 里面。因为代码量很少,我会直接把大部分代码复制过来。首先是在 function body 中定义了一些map,用来全局储存数据:
54+
```ts
55+
(function () {
56+
// The runners scheduled at the next animation frame
57+
const NEXT_QUEUE = new Map<number /* window ID */, AnimationFrameQueueItem[]>();
58+
// The runners scheduled at the current animation frame
59+
const CURRENT_QUEUE = new Map<number /* window ID */, AnimationFrameQueueItem[]>();
60+
// A flag to keep track if the native requestAnimationFrame was already called
61+
const animFrameRequested = new Map<number /* window ID */, boolean>();
62+
// A flag to indicate if currently handling a native requestAnimationFrame callback
63+
const inAnimationFrameRunner = new Map<number /* window ID */, boolean>();
64+
65+
// ...
66+
})();
67+
```
68+
69+
`runAtThisOrScheduleAtNextAnimationFrame``scheduleAtNextAnimationFrame`的定义如下:
70+
```ts
71+
(function () {
72+
// ...
73+
scheduleAtNextAnimationFrame = (targetWindow: Window, runner: () => void, priority: number = 0) => {
74+
const targetWindowId = getWindowId(targetWindow);
75+
const item = new AnimationFrameQueueItem(runner, priority);
76+
let nextQueue = NEXT_QUEUE.get(targetWindowId);
77+
if (!nextQueue) {
78+
nextQueue = [];
79+
NEXT_QUEUE.set(targetWindowId, nextQueue);
80+
}
81+
nextQueue.push(item);
82+
if (!animFrameRequested.get(targetWindowId)) {
83+
animFrameRequested.set(targetWindowId, true);
84+
targetWindow.requestAnimationFrame(() => animationFrameRunner(targetWindowId));
85+
}
86+
return item;
87+
};
88+
89+
runAtThisOrScheduleAtNextAnimationFrame = (targetWindow: Window, runner: () => void, priority?: number) => {
90+
const targetWindowId = getWindowId(targetWindow);
91+
if (inAnimationFrameRunner.get(targetWindowId)) {
92+
const item = new AnimationFrameQueueItem(runner, priority);
93+
let currentQueue = CURRENT_QUEUE.get(targetWindowId);
94+
if (!currentQueue) {
95+
currentQueue = [];
96+
CURRENT_QUEUE.set(targetWindowId, currentQueue);
97+
}
98+
currentQueue.push(item);
99+
return item;
100+
} else {
101+
return scheduleAtNextAnimationFrame(targetWindow, runner, priority);
102+
}
103+
};
104+
})();
105+
```
106+
* `AnimationFrameQueueItem` 是任务的基本单位。每个任务都被封装成一个实例,包含了以下信息:
107+
* **任务逻辑**`runner`):需要执行的具体函数。
108+
* **优先级**`priority`):用于控制任务执行的顺序。
109+
* **取消标志**`_canceled`):支持任务的动态取消。
110+
```typescript
111+
class AnimationFrameQueueItem {
112+
private _runner: () => void;
113+
public priority: number;
114+
private _canceled: boolean;
115+
116+
constructor(runner: () => void, priority: number = 0) {
117+
this._runner = runner;
118+
this.priority = priority;
119+
this._canceled = false;
120+
}
121+
122+
dispose(): void {
123+
this._canceled = true;
124+
}
125+
126+
execute(): void {
127+
if (this._canceled) return;
128+
this._runner();
129+
}
130+
131+
static sort(a: AnimationFrameQueueItem, b: AnimationFrameQueueItem): number {
132+
return b.priority - a.priority;
133+
}
134+
}
135+
```
136+
调度的核心逻辑是 `animationFrameRunner` 函数,它通过 `requestAnimationFrame` 在每帧执行任务:
137+
1.`NEXT_QUEUE` 中提取任务到 `CURRENT_QUEUE`
138+
2.`CURRENT_QUEUE` 按优先级排序。
139+
3. 按顺序依次执行任务。
140+
```typescript
141+
(function () {
142+
// ...
143+
const animationFrameRunner = (targetWindowId: number) => {
144+
animFrameRequested.set(targetWindowId, false);
145+
146+
const currentQueue = NEXT_QUEUE.get(targetWindowId) ?? [];
147+
CURRENT_QUEUE.set(targetWindowId, currentQueue);
148+
NEXT_QUEUE.set(targetWindowId, []);
149+
150+
while (currentQueue.length > 0) {
151+
currentQueue.sort(AnimationFrameQueueItem.sort);
152+
const top = currentQueue.shift()!;
153+
top.execute(); // actual animation execution
154+
}
155+
// ...
156+
};
157+
})();
158+
```
159+
160+
## 额外内容
161+
VSCode还提供了一些简单的helper function方便做测试或者补丁等等:
162+
```ts
163+
export function measure(targetWindow: Window, callback: () => void): IDisposable {
164+
return scheduleAtNextAnimationFrame(targetWindow, callback, 10000 /* must be early */);
165+
}
166+
167+
export function modify(targetWindow: Window, callback: () => void): IDisposable {
168+
return scheduleAtNextAnimationFrame(targetWindow, callback, -10000 /* must be late */);
169+
}
170+
```

4. Renderer process - 渲染进程/2024-11-18-VSCode:Centralized Animation Frame Scheduling.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
layout: post
3-
title: "VSCode:Centralized Animation Frame Scheduling."
3+
title: "VSCode系列「UI篇」:中央式动画帧调度"
44
categories: [VSCode, UI]
55
---
66

0 commit comments

Comments
 (0)