improve: performance, added a fps monitor panel
This commit is contained in:
parent
74a21e721d
commit
7aef8e873d
@ -4,7 +4,7 @@
|
||||
"private": false,
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"workspaces": ["packages/web", "packages/core", "packages/electron"],
|
||||
"workspaces": ["packages/web", "packages/core"],
|
||||
"scripts": {
|
||||
"electron:dev": "bun --filter 'electron' dev",
|
||||
"web:dev": "bun --filter 'web' dev",
|
||||
@ -37,7 +37,6 @@
|
||||
"concurrently": "^9.0.1",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"dependencies": {},
|
||||
"trustedDependencies": [
|
||||
"svelte-preprocess"
|
||||
]
|
||||
|
135
packages/core/components/displayFPS.svelte
Normal file
135
packages/core/components/displayFPS.svelte
Normal file
@ -0,0 +1,135 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Chart from 'chart.js/auto';
|
||||
|
||||
// 可调整的更新频率(毫秒)
|
||||
const UPDATE_INTERVAL = 300;
|
||||
const TIME_WINDOW = 1000 * 15;
|
||||
|
||||
let frameCount = 0;
|
||||
let lastTime = 0;
|
||||
let fps = 0;
|
||||
let frameTimes: number[] = [];
|
||||
let frameTime = 0;
|
||||
let onePercentLow = 0;
|
||||
|
||||
let fpsChart: Chart;
|
||||
let frameTimeChart: Chart;
|
||||
|
||||
let lastFrameTime = 0;
|
||||
|
||||
function updateFPS() {
|
||||
const currentTime = performance.now();
|
||||
const deltaTime = currentTime - lastTime;
|
||||
|
||||
frameTime = currentTime - lastFrameTime;
|
||||
// 计算1% Low FPS
|
||||
const sortedFrameTimes = frameTimes.sort((a, b) => b - a);
|
||||
const onePercentIndex = Math.floor(sortedFrameTimes.length * 0.01);
|
||||
onePercentLow = Math.round(1000 / sortedFrameTimes[onePercentIndex]);
|
||||
if (frameTimeChart) {
|
||||
if ((frameTimeChart.data.labels![0] as number) < Date.now() - 5000) {
|
||||
frameTimeChart.data.labels!.shift();
|
||||
frameTimeChart.data.datasets[0].data.shift();
|
||||
}
|
||||
frameTimeChart.data.labels!.push(Date.now());
|
||||
frameTimeChart.data.datasets[0].data.push(frameTime);
|
||||
}
|
||||
|
||||
if (deltaTime > UPDATE_INTERVAL) {
|
||||
fps = Math.round((frameCount * 1000) / deltaTime);
|
||||
// 更新图表数据
|
||||
if (fpsChart) {
|
||||
if ((fpsChart.data.labels![0] as number) < Date.now() - TIME_WINDOW) {
|
||||
fpsChart.data.labels!.shift();
|
||||
fpsChart.data.datasets[0].data.shift();
|
||||
}
|
||||
fpsChart.data.labels!.push(Date.now());
|
||||
fpsChart.data.datasets[0].data.push(fps);
|
||||
fpsChart.update();
|
||||
}
|
||||
if (frameTimeChart) {
|
||||
frameTimeChart.update();
|
||||
}
|
||||
|
||||
frameCount = 0;
|
||||
lastTime = currentTime;
|
||||
}
|
||||
|
||||
frameCount++;
|
||||
frameTimes.push(frameTime);
|
||||
lastFrameTime = performance.now();
|
||||
|
||||
requestAnimationFrame(updateFPS);
|
||||
}
|
||||
|
||||
const createChart = (ctx: CanvasRenderingContext2D, label: string, color: string) => {
|
||||
return new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: label,
|
||||
borderColor: color,
|
||||
data: [],
|
||||
pointRadius: 0,
|
||||
borderWidth: label == 'FPS' ? undefined : 2,
|
||||
tension: label == 'FPS' ? 0.1 : 0,
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: {
|
||||
display: false
|
||||
},
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
ticks: {
|
||||
padding: 0
|
||||
},
|
||||
border: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
interaction: {
|
||||
mode: 'x'
|
||||
},
|
||||
animations: {
|
||||
y: {
|
||||
duration: 0
|
||||
},
|
||||
x: {
|
||||
duration: label == 'FPS' ? undefined : 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
const ctx1 = (document.getElementById('fpsChart')! as HTMLCanvasElement).getContext('2d')!;
|
||||
const ctx2 = (document.getElementById('frameTimeChart')! as HTMLCanvasElement).getContext('2d')!;
|
||||
|
||||
fpsChart = createChart(ctx1, 'FPS', '#4CAF50');
|
||||
frameTimeChart = createChart(ctx2, 'Frame Time', '#2196F3');
|
||||
updateFPS();
|
||||
});
|
||||
</script>
|
||||
|
||||
<p>
|
||||
<span>{fps} fps</span><br />
|
||||
<span>Frame Time: {frameTime.toFixed(2)} ms</span><br />
|
||||
<span>1% Low: {onePercentLow} fps</span>
|
||||
</p>
|
||||
<span class="fixed right-2 text-white/50">fps</span>
|
||||
<canvas id="fpsChart" width="400" height="100" class="mt-2"></canvas>
|
||||
<span class="fixed right-2 text-white/50">frametime</span>
|
||||
<canvas id="frameTimeChart" width="400" height="100" class="mt-2"></canvas>
|
@ -38,6 +38,7 @@
|
||||
let isCurrentLyric = false;
|
||||
|
||||
function updateY(timestamp: number) {
|
||||
if (stopped) return;
|
||||
if (lastUpdateY === undefined) {
|
||||
lastUpdateY = new Date().getTime();
|
||||
}
|
||||
@ -52,6 +53,7 @@
|
||||
}
|
||||
|
||||
function updateX(timestamp: number) {
|
||||
if (stopped) return;
|
||||
if (lastUpdateX === undefined) {
|
||||
lastUpdateX = timestamp;
|
||||
}
|
||||
@ -70,6 +72,7 @@
|
||||
* @param {number} pos - X offset, in pixels
|
||||
*/
|
||||
export const setX = (pos: number) => {
|
||||
stopped = true;
|
||||
positionX = pos;
|
||||
};
|
||||
|
||||
@ -78,6 +81,7 @@
|
||||
* @param {number} pos - Y offset, in pixels
|
||||
*/
|
||||
export const setY = (pos: number) => {
|
||||
stopped = true;
|
||||
positionY = pos;
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
import LyricLine from './lyricLine.svelte';
|
||||
import createLyricsSearcher from '@core/lyrics/lyricSearcher';
|
||||
import userAdjustingProgress from '@core/state/userAdjustingProgress';
|
||||
import DisplayFps from '../displayFPS.svelte';
|
||||
|
||||
// constants
|
||||
const viewportHeight = document.documentElement.clientHeight;
|
||||
@ -16,11 +17,16 @@
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// Props
|
||||
let { originalLyrics, progress, player, showInteractiveBox } : {
|
||||
originalLyrics: LyricData,
|
||||
progress: number,
|
||||
player: HTMLAudioElement | null,
|
||||
showInteractiveBox: boolean
|
||||
let {
|
||||
originalLyrics,
|
||||
progress,
|
||||
player,
|
||||
showInteractiveBox
|
||||
}: {
|
||||
originalLyrics: LyricData;
|
||||
progress: number;
|
||||
player: HTMLAudioElement | null;
|
||||
showInteractiveBox: boolean;
|
||||
} = $props();
|
||||
|
||||
// States
|
||||
@ -76,15 +82,28 @@
|
||||
for (let i = 0; i < lyricElements.length; i++) {
|
||||
const currentLyricComponent = lyricComponents[i];
|
||||
const lyric = originalLyrics.scripts[i];
|
||||
const lyricBeforeProgress = lyric.end < progress;
|
||||
const lyricInProgress = lyric.start <= progress && progress <= lyric.end;
|
||||
const lyricWillHigherThanViewport = lyricTopList[i + 3] - relativeOrigin < 0;
|
||||
const lyricWillLowerThanViewport = lyricTopList[i - 3] - relativeOrigin > lyricsContainer?.getBoundingClientRect().height!;
|
||||
|
||||
let delay = 0;
|
||||
if (progress > lyric.end) {
|
||||
if (lyricBeforeProgress) {
|
||||
delay = 0;
|
||||
} else if (lyric.start <= progress && progress <= lyric.end) {
|
||||
delay = 0.042;
|
||||
} else if (lyricInProgress) {
|
||||
delay = 0.03;
|
||||
} else {
|
||||
delay = Math.min(Math.min(currentLyricDuration, 0.6), 0.067 * (i - currentLyricIndex + 1.2));
|
||||
}
|
||||
currentLyricComponent.update({ x: 0, y: lyricTopList[i] - relativeOrigin }, delay);
|
||||
|
||||
// if it's not in the viewport, we need to use animations
|
||||
if (lyricWillHigherThanViewport || lyricWillLowerThanViewport) {
|
||||
currentLyricComponent.update({ x: 0, y: lyricTopList[i] - relativeOrigin }, delay);
|
||||
}
|
||||
// if it's still in the viewport, we need to use spring animation
|
||||
else {
|
||||
currentLyricComponent.update({ x: 0, y: lyricTopList[i] - relativeOrigin }, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,11 +130,16 @@
|
||||
});
|
||||
|
||||
function handleScroll(deltaY: number) {
|
||||
if (lyricComponents[0].getInfo().y > 0 && deltaY < 0) {
|
||||
deltaY = 0;
|
||||
}
|
||||
if (lyricComponents[lyricComponents.length - 1].getInfo().y < 100 && deltaY > 0) {
|
||||
deltaY = 0;
|
||||
}
|
||||
for (let i = 0; i < lyricElements.length; i++) {
|
||||
const currentLyricComponent = lyricComponents[i];
|
||||
const currentY = currentLyricComponent.getInfo().y;
|
||||
scrolling = true;
|
||||
currentLyricComponent.stop();
|
||||
currentLyricComponent.setY(currentY - deltaY);
|
||||
currentLyricComponent.syncSpringWithDelta(deltaY);
|
||||
}
|
||||
@ -200,21 +224,19 @@
|
||||
lastEventProgress = progress;
|
||||
if (!lyricChanged || scrolling) return;
|
||||
if (!lyricIndexDeltaTooBig && deltaInRange) {
|
||||
console.log("Event: regular move");
|
||||
console.log('Event: regular move');
|
||||
console.log(new Date().getTime(), lastSeekForward);
|
||||
computeLayout();
|
||||
}
|
||||
else if ($userAdjustingProgress) {
|
||||
} else if ($userAdjustingProgress) {
|
||||
if (deltaTooBig && lyricChanged) {
|
||||
console.log("Event: seek forward");
|
||||
console.log('Event: seek forward');
|
||||
seekForward();
|
||||
} else if (deltaIsNegative && lyricChanged) {
|
||||
console.log("Event: seek backward");
|
||||
console.log('Event: seek backward');
|
||||
seekForward();
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Event: regular move");
|
||||
} else {
|
||||
console.log('Event: regular move');
|
||||
computeLayout();
|
||||
}
|
||||
});
|
||||
@ -253,16 +275,23 @@
|
||||
{#if debugMode}
|
||||
<span
|
||||
class="text-white text-lg absolute z-50 px-2 py-0.5 m-2 rounded-3xl bg-white bg-opacity-20 backdrop-blur-lg
|
||||
right-0 font-mono">
|
||||
right-0 font-mono"
|
||||
>
|
||||
progress: {progress.toFixed(2)}, nextUpdate: {nextUpdate}, scrolling: {scrolling}, current: {currentLyricIndex},
|
||||
uap: {$userAdjustingProgress}
|
||||
</span>
|
||||
<div
|
||||
class="text-black/80 text-sm absolute z-50 px-3 py-2 m-2 rounded-lg bg-white/30 backdrop-blur-xl
|
||||
left-0 font-mono"
|
||||
>
|
||||
<DisplayFps />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if originalLyrics && originalLyrics.scripts}
|
||||
<div
|
||||
class={`absolute top-[6.5rem] md:top-36 xl:top-0 w-screen xl:w-[52vw] px-6 md:px-12 duration-500
|
||||
${showInteractiveBox ? "h-[calc(100vh-21rem)]" : "h-[calc(100vh-7rem)]"}
|
||||
${showInteractiveBox ? 'h-[calc(100vh-21rem)]' : 'h-[calc(100vh-7rem)]'}
|
||||
lg:px-[7.5rem] xl:left-[46vw] xl:px-[3vw] xl:h-screen font-sans
|
||||
text-left no-scrollbar z-[1] pt-16 overflow-hidden`}
|
||||
style={`mask: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 7%, rgba(0, 0, 0, 1) 95%,
|
||||
@ -270,8 +299,16 @@
|
||||
bind:this={lyricsContainer}
|
||||
>
|
||||
{#each lyricLines as lyric, i}
|
||||
<LyricLine line={lyric} index={i} bind:this={lyricComponents[i]} {debugMode} {lyricClick} {progress}
|
||||
{currentLyricIndex} {scrolling}/>
|
||||
<LyricLine
|
||||
line={lyric}
|
||||
index={i}
|
||||
bind:this={lyricComponents[i]}
|
||||
{debugMode}
|
||||
{lyricClick}
|
||||
{progress}
|
||||
{currentLyricIndex}
|
||||
{scrolling}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -27,13 +27,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@alikia/aqualyrics": "npm:@jsr/alikia__aqualyrics",
|
||||
"@applemusic-like-lyrics/core": "^0.1.3",
|
||||
"@applemusic-like-lyrics/lyric": "^0.2.2",
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@types/bun": "^1.1.6",
|
||||
"bezier-easing": "^2.1.0",
|
||||
"chart.js": "^4.4.7",
|
||||
"jotai": "^2.8.0",
|
||||
"jotai-svelte": "^0.0.2",
|
||||
"jss": "^10.10.0",
|
||||
|
@ -37,8 +37,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@alikia/aqualyrics": "npm:@jsr/alikia__aqualyrics",
|
||||
"@applemusic-like-lyrics/core": "^0.1.3",
|
||||
"@applemusic-like-lyrics/lyric": "^0.2.4",
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
|
@ -209,7 +209,7 @@
|
||||
if (audioPlayer === null) return;
|
||||
if ($userAdjustingProgress === false) currentProgress = audioPlayer.currentTime;
|
||||
progressBarRaw.set(audioPlayer.currentTime);
|
||||
}, 50);
|
||||
}, 20);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
@ -13,7 +13,8 @@
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"types": ["bun"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "build", "dist", "tmp"]
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
|
Loading…
Reference in New Issue
Block a user