Skip to content

Commit 74774c6

Browse files
yuyutaotaozhoushaw
andauthored
fix: memory leak in report file (#275)
* fix: memory leak in report file * chore: remove unused console.log * chore: set default value of TRACKING_ACTIVE_TAB_KEY into true --------- Co-authored-by: zhouxiao.shaw <zhouxiao.shaw@bytedance.com>
1 parent cb52da2 commit 74774c6

7 files changed

Lines changed: 133 additions & 77 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'pixi.js/unsafe-eval';
2+
import * as PIXI from 'pixi.js';
3+
4+
const globalTextureMap = new Map<string, PIXI.Texture>();
5+
6+
export const loadTexture = async (img: string) => {
7+
if (globalTextureMap.has(img)) return;
8+
return PIXI.Assets.load(img).then((texture) => {
9+
globalTextureMap.set(img, texture);
10+
});
11+
};
12+
13+
export const getTextureFromCache = (name: string) => {
14+
return globalTextureMap.get(name);
15+
};
16+
17+
export const getTexture = async (name: string) => {
18+
if (globalTextureMap.has(name)) {
19+
return globalTextureMap.get(name);
20+
}
21+
22+
await loadTexture(name);
23+
return globalTextureMap.get(name);
24+
};

‎packages/visualizer/src/component/player.tsx‎

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import type { BaseElement } from '@midscene/core';
1313
import { Button, ConfigProvider, Spin } from 'antd';
1414
import { rectMarkForItem } from './blackboard';
15+
import { getTextureFromCache, loadTexture } from './pixi-loader';
1516
import type {
1617
AnimationScript,
1718
CameraState,
@@ -180,25 +181,21 @@ export default function Player(props?: {
180181

181182
const cameraState = useRef<CameraState>({ ...basicCameraState });
182183

183-
const preloadImage = async (img: string): Promise<void> => {
184-
if (imgSpriteMap.current.has(img)) return;
185-
return PIXI.Assets.load(img).then((texture) => {
186-
const sprite = PIXI.Sprite.from(texture);
187-
imgSpriteMap.current.set(img, sprite);
188-
});
189-
};
190-
191184
const repaintImage = async (): Promise<void> => {
192185
const imgToUpdate = currentImg.current;
193186
if (!imgToUpdate) {
194187
console.warn('no image to update');
195188
return;
196189
}
197-
if (!imgSpriteMap.current.has(imgToUpdate)) {
190+
if (!getTextureFromCache(imgToUpdate)) {
198191
console.warn('image not loaded', imgToUpdate);
199-
await preloadImage(imgToUpdate!);
192+
await loadTexture(imgToUpdate!);
200193
}
201-
const sprite = imgSpriteMap.current.get(imgToUpdate);
194+
const texture = getTextureFromCache(imgToUpdate);
195+
if (!texture) {
196+
throw new Error('texture not found');
197+
}
198+
const sprite = PIXI.Sprite.from(texture);
202199
if (!sprite) {
203200
throw new Error('sprite not found');
204201
}
@@ -265,14 +262,15 @@ export default function Player(props?: {
265262
x?: number,
266263
y?: number,
267264
): Promise<void> => {
268-
if (!imgSpriteMap.current.has(img)) {
265+
if (!getTextureFromCache(img)) {
269266
console.warn('image not loaded', img);
270-
await preloadImage(img);
267+
await loadTexture(img);
271268
}
272-
const sprite = imgSpriteMap.current.get(img);
273-
if (!sprite) {
274-
throw new Error('sprite not found');
269+
const texture = getTextureFromCache(img);
270+
if (!texture) {
271+
throw new Error('texture not found');
275272
}
273+
const sprite = PIXI.Sprite.from(texture);
276274

277275
let targetX = pointerSprite.current?.x;
278276
let targetY = pointerSprite.current?.y;
@@ -570,7 +568,7 @@ export default function Player(props?: {
570568
.map((item) => item.img!);
571569

572570
// Load and display the image
573-
await Promise.all([...allImages, mouseLoading].map(preloadImage));
571+
await Promise.all([...allImages, mouseLoading].map(loadTexture));
574572

575573
// pointer on top
576574
insightMarkContainer.removeChildren();

‎packages/visualizer/src/component/playground-component.less‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,24 +74,34 @@ body {
7474
padding-bottom: 40px;
7575
}
7676

77-
.ant-btn {
77+
.form-controller-wrapper {
7878
position: absolute;
7979
bottom: 8px;
80-
right: 8px;
80+
padding: 0 12px;
81+
left: 0px;
82+
display: flex;
83+
flex-direction: row;
84+
justify-content: space-between;
85+
width: 100%;
86+
box-sizing: border-box;
87+
align-items: flex-end;
88+
gap: 8px;
8189
}
8290

8391
.settings-wrapper {
8492
&.settings-wrapper-hover {
8593
color: @main-text;
8694
}
8795

88-
position: absolute;
89-
bottom: 8px;
90-
left: 12px;
9196
display: flex;
9297
flex-direction: row;
93-
gap: 16px;
98+
gap: 2px;
9499
color: @weak-text;
100+
flex-wrap: wrap;
101+
}
102+
103+
.history-selector {
104+
margin-right: 8px;
95105
}
96106
}
97107

‎packages/visualizer/src/component/playground-component.tsx‎

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export function Playground({
194194

195195
const [verticalMode, setVerticalMode] = useState(false);
196196

197-
const { tabId, tabUrl } = useChromeTabInfo();
197+
const { tabId } = useChromeTabInfo();
198198

199199
// if the screen is narrow, we use vertical mode
200200
useEffect(() => {
@@ -239,7 +239,7 @@ export function Playground({
239239

240240
const addHistory = useEnvConfig((state) => state.addHistory);
241241

242-
const trackingTip = 'Track newly-opened tabs';
242+
const trackingTip = 'track newly-opened tabs';
243243
const configItems = [
244244
{
245245
label: (
@@ -260,7 +260,7 @@ export function Playground({
260260
<Dropdown menu={{ items: configItems }}>
261261
<Space>
262262
<SettingOutlined />
263-
{trackingActiveTab ? trackingTip : 'Focus on current tab'}
263+
{trackingActiveTab ? trackingTip : 'focus on current tab'}
264264
</Space>
265265
</Dropdown>
266266
</div>
@@ -600,18 +600,20 @@ export function Playground({
600600
/>
601601
</Form.Item>
602602

603-
{actionBtn}
604-
<div
605-
className={
606-
hoveringSettings
607-
? 'settings-wrapper settings-wrapper-hover'
608-
: 'settings-wrapper'
609-
}
610-
onMouseEnter={() => setHoveringSettings(true)}
611-
onMouseLeave={() => setHoveringSettings(false)}
612-
>
613-
{historySelector}
614-
{configSelector}
603+
<div className="form-controller-wrapper">
604+
<div
605+
className={
606+
hoveringSettings
607+
? 'settings-wrapper settings-wrapper-hover'
608+
: 'settings-wrapper'
609+
}
610+
onMouseEnter={() => setHoveringSettings(true)}
611+
onMouseLeave={() => setHoveringSettings(false)}
612+
>
613+
{historySelector}
614+
{configSelector}
615+
</div>
616+
{actionBtn}
615617
</div>
616618
</div>
617619
</div>

‎packages/visualizer/src/component/replay-scripts.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ export const generateAnimationScripts = (
265265
scripts.push({
266266
type: 'img',
267267
img: planningTask.recorder?.[0]?.screenshot,
268-
camera: fullPageCameraState,
268+
camera: index === 0 ? fullPageCameraState : undefined,
269269
duration: stillDuration,
270270
title: typeStr(task),
271271
subTitle: paramStr(task),

‎packages/visualizer/src/component/store.tsx‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const useBlackboardPreference = create<{
3232
const CONFIG_KEY = 'midscene-env-config';
3333
const SERVICE_MODE_KEY = 'midscene-service-mode';
3434
const HISTORY_KEY = 'midscene-prompt-history';
35+
const TRACKING_ACTIVE_TAB_KEY = 'midscene-tracking-active-tab';
3536
const getConfigStringFromLocalStorage = () => {
3637
const configString = localStorage.getItem(CONFIG_KEY);
3738
return configString || '';
@@ -145,6 +146,8 @@ export const useEnvConfig = create<{
145146
const savedServiceMode = localStorage.getItem(
146147
SERVICE_MODE_KEY,
147148
) as ServiceModeType | null;
149+
const savedTrackingActiveTab =
150+
localStorage.getItem(TRACKING_ACTIVE_TAB_KEY) !== 'false';
148151
return {
149152
serviceMode: ifInExtension
150153
? 'In-Browser-Extension'
@@ -163,9 +166,13 @@ export const useEnvConfig = create<{
163166
set({ config, configString });
164167
localStorage.setItem(CONFIG_KEY, configString);
165168
},
166-
trackingActiveTab: false,
169+
trackingActiveTab: savedTrackingActiveTab,
167170
setTrackingActiveTab: (trackingActiveTab: boolean) => {
168171
set({ trackingActiveTab });
172+
localStorage.setItem(
173+
TRACKING_ACTIVE_TAB_KEY,
174+
trackingActiveTab.toString(),
175+
);
169176
},
170177
history: getHistoryFromLocalStorage(),
171178
clearHistory: () => {

‎packages/visualizer/src/component/timeline.tsx‎

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useEffect, useMemo, useRef } from 'react';
44

55
import './timeline.less';
66
import type { ExecutionRecorderItem, ExecutionTask } from '@midscene/core';
7+
import { getTextureFromCache, loadTexture } from './pixi-loader';
78
import { useAllCurrentTasks, useExecutionDump } from './store';
89

910
interface TimelineItem {
@@ -120,6 +121,7 @@ const TimelineWidget = (props: {
120121
]);
121122

122123
useEffect(() => {
124+
let freeFn = () => {};
123125
Promise.resolve(
124126
(async () => {
125127
if (!domRef.current) {
@@ -161,6 +163,9 @@ const TimelineWidget = (props: {
161163
height: canvasHeight,
162164
backgroundColor: sideBg,
163165
});
166+
freeFn = () => {
167+
app.destroy();
168+
};
164169
if (!domRef.current) {
165170
app.destroy();
166171
return;
@@ -226,44 +231,50 @@ const TimelineWidget = (props: {
226231
const container = new PIXI.Container();
227232
shotContainers.push(container);
228233
app.stage.addChild(container);
229-
const img = new Image();
230-
img.src = screenshot.img;
231-
img.onload = () => {
232-
const screenshotTexture = PIXI.Texture.from(img);
233-
const screenshotSprite = new PIXI.Sprite(screenshotTexture);
234-
235-
// get width / height of img
236-
const originalWidth = img.width;
237-
const originalHeight = img.height;
238-
239-
const screenshotHeight = screenshotMaxHeight;
240-
const screenshotWidth = Math.floor(
241-
(screenshotHeight / originalHeight) * originalWidth,
242-
);
234+
Promise.resolve(
235+
(async () => {
236+
await loadTexture(screenshot.img);
237+
const texture = getTextureFromCache(screenshot.img);
238+
if (!texture) {
239+
return;
240+
}
241+
242+
// clone the sprite
243+
const screenshotSprite = PIXI.Sprite.from(texture);
244+
245+
// get width / height of img
246+
const originalWidth = screenshotSprite.width;
247+
const originalHeight = screenshotSprite.height;
248+
249+
const screenshotHeight = screenshotMaxHeight;
250+
const screenshotWidth = Math.floor(
251+
(screenshotHeight / originalHeight) * originalWidth,
252+
);
243253

244-
const screenshotX = leftForTimeOffset(screenshot.timeOffset);
245-
allScreenshots[index].x = screenshotX;
246-
allScreenshots[index].y = screenshotTop;
247-
allScreenshots[index].width = screenshotWidth;
248-
allScreenshots[index].height = screenshotMaxHeight;
249-
250-
const border = new PIXI.Graphics();
251-
border.lineStyle(sizeRatio, shotBorderColor, 1);
252-
border.drawRect(
253-
screenshotX,
254-
screenshotTop,
255-
screenshotWidth,
256-
screenshotMaxHeight,
257-
);
258-
border.endFill();
259-
container.addChild(border);
260-
261-
screenshotSprite.x = screenshotX;
262-
screenshotSprite.y = screenshotTop;
263-
screenshotSprite.width = screenshotWidth;
264-
screenshotSprite.height = screenshotMaxHeight;
265-
container.addChild(screenshotSprite);
266-
};
254+
const screenshotX = leftForTimeOffset(screenshot.timeOffset);
255+
allScreenshots[index].x = screenshotX;
256+
allScreenshots[index].y = screenshotTop;
257+
allScreenshots[index].width = screenshotWidth;
258+
allScreenshots[index].height = screenshotMaxHeight;
259+
260+
const border = new PIXI.Graphics();
261+
border.lineStyle(sizeRatio, shotBorderColor, 1);
262+
border.drawRect(
263+
screenshotX,
264+
screenshotTop,
265+
screenshotWidth,
266+
screenshotMaxHeight,
267+
);
268+
border.endFill();
269+
container.addChild(border);
270+
271+
screenshotSprite.x = screenshotX;
272+
screenshotSprite.y = screenshotTop;
273+
screenshotSprite.width = screenshotWidth;
274+
screenshotSprite.height = screenshotMaxHeight;
275+
container.addChild(screenshotSprite);
276+
})(),
277+
);
267278
});
268279

269280
const highlightMaskUpdater = (
@@ -422,6 +433,10 @@ const TimelineWidget = (props: {
422433
canvas.addEventListener('pointerdown', onPointerTap);
423434
})(),
424435
);
436+
437+
return () => {
438+
freeFn();
439+
};
425440
}, []);
426441

427442
return <div className="timeline-canvas-wrapper" ref={domRef} />;

0 commit comments

Comments
 (0)