Skip to content

Commit d87cf15

Browse files
committed
improve preformance
1 parent b0236f9 commit d87cf15

6 files changed

Lines changed: 102 additions & 121 deletions

File tree

src/addons/addons/debugger/performance.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ export default async function createPerformanceTab({ debug, addon, console, msg
124124
let lastFpsTime = now() + 3000;
125125

126126
debug.addAfterStepCallback(() => {
127+
if (!isVisible) {
128+
return;
129+
}
127130
if (isPaused()) {
128131
return;
129132
}
@@ -147,10 +150,8 @@ export default async function createPerformanceTab({ debug, addon, console, msg
147150
clonesData.shift();
148151
clonesData.push(vm.runtime._cloneCounter);
149152

150-
if (isVisible) {
151-
fpsChart.update();
152-
performanceClonesChart.update();
153-
}
153+
fpsChart.update();
154+
performanceClonesChart.update();
154155
}
155156
});
156157

src/addons/addons/debugger/threads.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,12 @@ export default async function createThreadsTab({ debug, addon, console, msg }) {
204204
logView.queueUpdateContent();
205205
};
206206

207+
let isVisible = false;
208+
207209
debug.addAfterStepCallback(() => {
210+
if (!isVisible) {
211+
return;
212+
}
208213
updateContent();
209214

210215
const runningThread = getRunningThread();
@@ -259,11 +264,14 @@ export default async function createThreadsTab({ debug, addon, console, msg }) {
259264
});
260265

261266
const show = () => {
267+
isVisible = true;
262268
logView.show();
263269
updateContent();
264270
};
265271
const hide = () => {
272+
isVisible = false;
266273
logView.hide();
274+
highlighter.setGlowingThreads([]);
267275
};
268276

269277
return {

src/addons/addons/debugger/userscript.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,10 @@ export default async function ({ addon, console, msg }) {
236236
const afterStepCallbacks = [];
237237
vm.runtime._step = function (...args) {
238238
const ret = originalStep.call(this, ...args);
239-
for (const cb of afterStepCallbacks) {
240-
cb();
239+
if (isInterfaceVisible) {
240+
for (const cb of afterStepCallbacks) {
241+
cb();
242+
}
241243
}
242244
return ret;
243245
};

src/addons/addons/debugger/variables.js

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ export default async function createVariablesTab({ debug, addon, msg }) {
44
// 存储所有变量包装器
55
let localVariables = [];
66
let globalVariables = [];
7+
let isVisible = false;
8+
let reloadDirty = true;
9+
let quickUpdateTimer = null;
10+
const QUICK_UPDATE_INTERVAL = 100;
711

812
// 创建 Tab
913
const tab = debug.createHeaderTab({
@@ -240,7 +244,18 @@ export default async function createVariablesTab({ debug, addon, msg }) {
240244
};
241245

242246
// 完全重新加载变量
247+
const clearQuickUpdateTimer = () => {
248+
if (quickUpdateTimer !== null) {
249+
clearTimeout(quickUpdateTimer);
250+
quickUpdateTimer = null;
251+
}
252+
};
253+
243254
const fullReload = () => {
255+
if (!isVisible) {
256+
reloadDirty = true;
257+
return;
258+
}
244259
// 保存现有变量的 checked 状态
245260
const checkedState = new Map();
246261
[...localVariables, ...globalVariables].forEach(v => {
@@ -289,6 +304,7 @@ export default async function createVariablesTab({ debug, addon, msg }) {
289304

290305
// 更新标题可见性
291306
updateHeadingVisibility();
307+
reloadDirty = false;
292308
};
293309

294310
const updateHeadingVisibility = () => {
@@ -298,6 +314,7 @@ export default async function createVariablesTab({ debug, addon, msg }) {
298314

299315
// 快速更新(只更新勾选的变量)
300316
const quickUpdate = () => {
317+
if (!isVisible) return;
301318
[...localVariables, ...globalVariables].forEach(v => {
302319
if (v.checked) {
303320
v.updateValue();
@@ -310,6 +327,24 @@ export default async function createVariablesTab({ debug, addon, msg }) {
310327
});
311328
};
312329

330+
const scheduleQuickUpdate = (force = false) => {
331+
if (!isVisible) {
332+
return;
333+
}
334+
if (reloadDirty) {
335+
fullReload();
336+
return;
337+
}
338+
if (!force && quickUpdateTimer !== null) {
339+
return;
340+
}
341+
clearQuickUpdateTimer();
342+
quickUpdateTimer = setTimeout(() => {
343+
quickUpdateTimer = null;
344+
quickUpdate();
345+
}, force ? 0 : QUICK_UPDATE_INTERVAL);
346+
};
347+
313348
// 搜索功能
314349
searchBox.addEventListener("input", (e) => {
315350
const search = e.target.value;
@@ -318,7 +353,12 @@ export default async function createVariablesTab({ debug, addon, msg }) {
318353
});
319354

320355
// 监听项目加载
321-
vm.runtime.on("PROJECT_LOADED", fullReload);
356+
vm.runtime.on("PROJECT_LOADED", () => {
357+
reloadDirty = true;
358+
if (isVisible) {
359+
fullReload();
360+
}
361+
});
322362

323363
// 监听角色切换 - 只在编辑目标变化时重新加载
324364
let lastEditingTargetId = null;
@@ -327,26 +367,37 @@ export default async function createVariablesTab({ debug, addon, msg }) {
327367
const currentId = currentEditingTarget ? currentEditingTarget.id : null;
328368
if (currentId !== lastEditingTargetId) {
329369
lastEditingTargetId = currentId;
330-
fullReload();
370+
reloadDirty = true;
371+
if (isVisible) {
372+
fullReload();
373+
}
331374
}
332375
});
333376

334377
// 初始加载
335-
fullReload();
336378

337379
// 在每一步后更新变量值
338-
const originalStep = vm.runtime._step;
339-
vm.runtime._step = function(...args) {
340-
const ret = originalStep.apply(this, args);
341-
quickUpdate();
342-
return ret;
343-
};
380+
lastEditingTargetId = vm.editingTarget ? vm.editingTarget.id : null;
381+
382+
debug.addAfterStepCallback(() => {
383+
scheduleQuickUpdate();
384+
});
344385

345386
return {
346387
tab,
347388
content,
348389
buttons: [],
349-
show: () => {},
350-
hide: () => {},
390+
show: () => {
391+
isVisible = true;
392+
if (reloadDirty) {
393+
fullReload();
394+
} else {
395+
scheduleQuickUpdate(true);
396+
}
397+
},
398+
hide: () => {
399+
isVisible = false;
400+
clearQuickUpdateTimer();
401+
},
351402
};
352403
}

src/containers/blocks.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,6 @@ const DroppableBlocks = DropAreaHOC([
9191
])(BlocksComponent);
9292

9393
const WORKSPACE_METRICS_DEBOUNCE_MS = 120;
94-
const LARGE_WORKSPACE_BLOCK_COUNT = 1000;
95-
9694
class Blocks extends React.Component {
9795
constructor(props) {
9896
super(props);
@@ -415,7 +413,12 @@ class Blocks extends React.Component {
415413
if (!this.workspace || !this.workspace.setOffscreenTopBlockCullingEnabled) {
416414
return;
417415
}
418-
this.workspace.setOffscreenTopBlockCullingEnabled(this.isLargeWorkspace);
416+
// The experimental offscreen top-block culling path caused very large
417+
// projects to spend more time in XML load/render bookkeeping than they
418+
// saved during interaction. Keep it disabled and use the original
419+
// TurboWarp/Scratch Blocks rendering path for stability.
420+
this.isLargeWorkspace = false;
421+
this.workspace.setOffscreenTopBlockCullingEnabled(false);
419422
}
420423
setLocale() {
421424
this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale);
@@ -659,7 +662,7 @@ class Blocks extends React.Component {
659662
try {
660663
this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, this.workspace);
661664
this.lastAppliedWorkspaceXML = data.xml;
662-
this.isLargeWorkspace = this.workspace.getAllBlocks(false).length >= LARGE_WORKSPACE_BLOCK_COUNT;
665+
this.isLargeWorkspace = false;
663666
this.syncWorkspaceCullingState();
664667
} catch (error) {
665668
// The workspace is likely incomplete. What did update should be

src/lib/blocks.js

Lines changed: 16 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -22,37 +22,13 @@ const applyScratchBlocksPerformancePatches = ScratchBlocks => {
2222
workspaceProto.pendingGridUpdateTimer_ = null;
2323
workspaceProto.deferGridUpdate_ = false;
2424

25-
workspaceProto.setOffscreenTopBlockCullingEnabled = function (enabled) {
26-
if (this.offscreenTopBlockCullingEnabled_ === enabled) {
27-
if (enabled) this.queueIntersectionCheck();
28-
return;
29-
}
30-
this.offscreenTopBlockCullingEnabled_ = enabled;
31-
32-
const topBlocks = this.getTopBlocks(false);
33-
let needsFullRender = false;
34-
for (const block of topBlocks) {
35-
if (block.setIntersects) block.setIntersects(true);
36-
if (!enabled && block.deferredRenderPending_) {
37-
needsFullRender = true;
38-
}
39-
}
40-
41-
if (!enabled && needsFullRender) {
42-
this.render();
43-
if (!this.isFlyout) {
44-
setTimeout(() => {
45-
for (const block of topBlocks) {
46-
if (block.workspace) {
47-
block.setConnectionsHidden(false);
48-
block.deferredRenderPending_ = false;
49-
}
50-
}
51-
}, 1);
52-
}
53-
}
54-
55-
this.queueIntersectionCheck();
25+
workspaceProto.setOffscreenTopBlockCullingEnabled = function () {
26+
// Experimental top-block culling caused large projects to become
27+
// much slower than upstream TurboWarp/Scratch. Keep the original
28+
// full-render path by forcing this feature off.
29+
this.offscreenTopBlockCullingEnabled_ = false;
30+
this.deferBlockRendering_ = false;
31+
this.intersectionCheckPendingAfterDrag_ = false;
5632
};
5733

5834
const originalTranslate = workspaceProto.translate;
@@ -63,46 +39,9 @@ const applyScratchBlocksPerformancePatches = ScratchBlocks => {
6339
}
6440
};
6541

66-
workspaceProto.ensureTopBlockRendered_ = function (block) {
67-
if (block.deferredRenderPending_) {
68-
block.render(false);
69-
block.deferredRenderPending_ = false;
70-
this.resizeContents();
71-
if (!this.isFlyout) {
72-
setTimeout(() => {
73-
if (block.workspace) {
74-
block.setConnectionsHidden(false);
75-
}
76-
}, 1);
77-
}
78-
return;
79-
}
80-
if (!block.rendered) {
81-
block.render(false);
82-
}
83-
};
84-
85-
workspaceProto.renderVisibleTopBlocks = function () {
86-
for (const block of this.getTopBlocks(false)) {
87-
if (this.isBlockInViewport_ && this.isBlockInViewport_(block)) {
88-
this.ensureTopBlockRendered_(block);
89-
}
90-
}
91-
};
92-
93-
workspaceProto.queueIntersectionCheck = function () {
94-
if (!this.offscreenTopBlockCullingEnabled_) return;
95-
if (this.isDragSurfaceActive_ || (this.isDragging && this.isDragging())) {
96-
this.intersectionCheckPendingAfterDrag_ = true;
97-
return;
98-
}
99-
if (this.renderVisibleTopBlocks) {
100-
this.renderVisibleTopBlocks();
101-
}
102-
if (this.intersectionObserver) {
103-
this.intersectionObserver.queueIntersectionCheck();
104-
}
105-
};
42+
workspaceProto.ensureTopBlockRendered_ = function () {};
43+
workspaceProto.renderVisibleTopBlocks = function () {};
44+
workspaceProto.queueIntersectionCheck = function () {};
10645

10746
workspaceProto.scheduleWheelScroll_ = function (deltaX, deltaY) {
10847
if (!this.pendingWheelScrollDelta_) {
@@ -216,7 +155,7 @@ const applyScratchBlocksPerformancePatches = ScratchBlocks => {
216155
if (this.flyout_) {
217156
this.flyout_.reflow();
218157
}
219-
this.queueIntersectionCheck();
158+
// Disabled experimental intersection/culling refresh.
220159
};
221160

222161
const originalWorkspaceDispose = workspaceProto.dispose;
@@ -235,38 +174,17 @@ const applyScratchBlocksPerformancePatches = ScratchBlocks => {
235174
const originalResetDragSurface = workspaceProto.resetDragSurface;
236175
workspaceProto.resetDragSurface = function () {
237176
originalResetDragSurface.call(this);
238-
if (this.intersectionCheckPendingAfterDrag_) {
239-
this.intersectionCheckPendingAfterDrag_ = false;
240-
this.queueIntersectionCheck();
241-
}
177+
this.intersectionCheckPendingAfterDrag_ = false;
242178
};
243179
}
244180

245181
if (intersectionProto) {
246-
const originalIntersectionDispose = intersectionProto.dispose;
247-
intersectionProto.dispose = function () {
248-
if (this.intersectionCheckFrame_ !== null && this.intersectionCheckFrame_ !== undefined) {
249-
cancelAnimationFrame(this.intersectionCheckFrame_);
250-
this.intersectionCheckFrame_ = null;
251-
}
252-
originalIntersectionDispose.call(this);
253-
};
254-
255-
intersectionProto.queueIntersectionCheck = function () {
256-
if (this.intersectionCheckQueued) return;
257-
this.intersectionCheckQueued = true;
258-
if (window.requestAnimationFrame) {
259-
this.intersectionCheckFrame_ = window.requestAnimationFrame(this.checkForIntersections);
260-
} else {
261-
this.intersectionCheckFrame_ = setTimeout(this.checkForIntersections, 16);
262-
}
263-
};
264-
265-
const originalIntersectionCheck = intersectionProto.checkForIntersections;
182+
intersectionProto.observe = function () {};
183+
intersectionProto.unobserve = function () {};
184+
intersectionProto.queueIntersectionCheck = function () {};
266185
intersectionProto.checkForIntersections = function () {
267186
this.intersectionCheckQueued = false;
268187
this.intersectionCheckFrame_ = null;
269-
originalIntersectionCheck.call(this);
270188
};
271189
}
272190

@@ -325,12 +243,10 @@ const applyScratchBlocksPerformancePatches = ScratchBlocks => {
325243
}
326244

327245
if (blockProto) {
246+
blockProto.updateIntersectionObserver = function () {};
328247
const originalInitSvg = blockProto.initSvg;
329248
blockProto.initSvg = function () {
330249
originalInitSvg.call(this);
331-
if (this.updateIntersectionObserver) {
332-
this.updateIntersectionObserver();
333-
}
334250
};
335251
}
336252

0 commit comments

Comments
 (0)