From df3d0a5cda5a350881d59bdc51bcc92e2b8a9996 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Wed, 13 May 2026 00:24:33 +0900 Subject: [PATCH] feat(installer-rp): stagger music download starts (2.5s gap) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 동시 N개를 모두 t=0 에 띄우면 카드들이 0% 에서 같이 멈춰있다가 한꺼번에 100% 가 되는 "정지된 듯한" 구간이 보였음. 이제 새 다운로드 시작 사이에 최소 2.5초 간격을 두어, 어떤 카드는 70%, 어떤 카드는 30% 식으로 항상 진행 흐름이 이어지게 만든다. - musicStartChain 으로 acquire 직렬화 → race-free - nextMusicStartAt 으로 마지막 시작 시점 추적 - 동시성(코어 수 기반) 자체는 그대로 유지, 시작 시점만 분산 Co-Authored-By: Claude Opus 4.7 --- src/installer-rp/main.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/installer-rp/main.ts b/src/installer-rp/main.ts index a2767e0..17d1123 100644 --- a/src/installer-rp/main.ts +++ b/src/installer-rp/main.ts @@ -46,6 +46,28 @@ function pickMusicConcurrency(): number { return 5 } +/** + * 새 다운로드 시작 사이의 최소 간격(ms). + * - 동시 N개를 모두 t=0 에 시작하면 카드들이 0% 에서 같이 정지된 듯 보임. + * - 시차를 두고 시작하면 "1번 끝남 → 4번 시작 → 2번 끝남 → 5번 시작" 식으로 + * 유저 입장에서 항상 뭔가 새로 시작/완료되는 흐름이 보임. + * - 너무 길면 동시성 이득을 깎아먹음. 2.5s 가 체감/속도 균형점. + */ +const MUSIC_START_STAGGER_MS = 2500 + +/** start-gate. 여러 worker 가 동시에 acquire 해도 직렬화되어 순차 통과. */ +let musicStartChain: Promise = Promise.resolve() +let nextMusicStartAt = 0 +function acquireMusicStartSlot(): Promise { + const slot = musicStartChain.then(async () => { + const wait = Math.max(0, nextMusicStartAt - Date.now()) + if (wait > 0) await new Promise((r) => setTimeout(r, wait)) + nextMusicStartAt = Date.now() + MUSIC_START_STAGGER_MS + }) + musicStartChain = slot.catch(() => {}) + return slot +} + const DEFAULT_MANIFEST_URL = process.env.MANIFEST_URL ?? 'http://127.0.0.1:3000/manifest.json' const state: RpInstallerState = { @@ -215,13 +237,15 @@ ipcMain.handle('rp:install:start', async (): Promise<{ resourcepackPath: string sendProgress({ phase: 'prep', message: '준비 완료', done: true }) throwIfCancelled() - // 2-2. 음악 다운로드 (CPU 코어 수 기반 자동 동시 다운로드, ogg 변환) + // 2-2. 음악 다운로드 (CPU 코어 수 기반 자동 동시 다운로드, 시차 출발, ogg 변환) const musicDir = path.join(tempRoot, 'music') await fsp.mkdir(musicDir, { recursive: true }) const concurrency = pickMusicConcurrency() const cpuCount = os.cpus()?.length ?? 0 + // 첫 음악은 즉시 시작 가능하도록 base 를 현재 시각으로. + nextMusicStartAt = Date.now() sendLog(`CPU 코어 ${cpuCount}개 감지 → 동시 다운로드 ${concurrency}개`) - sendLog(`음악 다운로드 시작 (${musicTotal}곡, 동시 ${concurrency}개)`) + sendLog(`음악 다운로드 시작 (${musicTotal}곡, 동시 ${concurrency}개, 시차 ${MUSIC_START_STAGGER_MS}ms)`) // 클로저 안에서 narrowing 이 풀리지 않도록 로컬 alias. const musicList = pack.list.music @@ -231,6 +255,9 @@ ipcMain.handle('rp:install:start', async (): Promise<{ resourcepackPath: string if (state.cancelRequested) return const i = nextIndex++ if (i >= musicTotal) return + // 시차 게이트: 새 다운로드 시작은 직전 시작과 최소 MUSIC_START_STAGGER_MS 간격을 둠. + await acquireMusicStartSlot() + if (state.cancelRequested) return const entry = musicList[i] const idx = i + 1 sendLog(`${idx}번 노래 다운로드 시작`)