Skip to content

Electron Main Process

electron/main.ts を中心とした Electron メインプロセスの起動シーケンスと、electron/main/ 配下の helper の構成。

3 プロセス構成(Manager / Recorder / Viewer)

このアプリは同じ electron/main.ts3 つの実行ファイルとして配布し、exe 名 / 起動フラグでモードを切り替える(detectRecorderMode / detectViewerMode)。役割を分離することで「記録は裏で軽く常駐、確認や重い分析は必要なときだけ」という運用ができる。

exeモード役割UI
WingStats Manager.exe通常オーバーレイ設定・戦績分析のメイン画面フル UI
WingStats Recorder.exe--recorder-mode画面を持たない記録専用サービス。ログ監視 → DB へ自動保存トレイのみ
WingStats Viewer.exe--viewer-mode記録状況をすばやく確認する軽量ウィンドウ(DB は read-only 接続)軽量 UI

単一 writer と委譲

DB / 設定への書き込みは常に 1 プロセスだけが行う(single-writer)。userData/process.lockelectron/core/processLock.ts)に { role, pid, startedAt, appVersion } を排他作成し、Recorder が常駐している間は Manager が起動時に lock を検出して 委譲モードelectron/core/writerDelegation.ts)に入り、自前の log watcher / auto-fetch / startup-import を起動しない。

ライブ連携(オーバーレイのリアルタイム反映)

Recorder が writer のときも、オーバーレイのリアルタイム表示は維持される。Recorder は LiveEventWsServerws://127.0.0.1:47831/live)でライブイベントを配信し、Manager は LiveEventWsClient で受信して自身の LiveEventBus へ再注入する。renderer 側は writer が誰かを意識せず、従来どおり IPC チャンネル(bp-opponent:update / log-watcher-phase 等)で受け取るため、既存のオーバーレイ実装は変更不要。

常駐設定

記録サービスの常駐(Windows ログイン時起動)は AutoLaunchServicesetRecorderEnabled → Run キー登録)で管理する。UI からは初回セットアップの「アプリ構成」ステップ、または設定タブの「自動起動」で切り替えられる。

全体構成

electron/
├── main.ts                     # エントリ(telemetry phase 追加で 754 行)
├── main/                       # main.ts から抽出した helper
│   ├── dbInit.ts               # DB 初期化失敗時のダイアログ + リトライ
│   ├── bpOpponentVisibility.ts # BP 対戦相手ウィンドウの表示判定
│   └── perfRelay.ts            # 起動計測 + perf marker IPC
├── core/                       # ライフサイクル骨格
│   ├── AppLifecycle.ts         # 単一インスタンスロック / グローバル例外 / app events
│   ├── PathResolver.ts         # userData パス解決
│   ├── ProcessManager.ts
│   └── StartupSequencer.ts     # critical-path / deferred-boot の 2 段階起動
├── windows/                    # BrowserWindow ラッパ
│   ├── ControlWindowManager.ts
│   ├── OverlayWindowManager.ts
│   ├── BPOpponentWindow.ts
│   └── WindowStateStore.ts
├── ipc/                        # IPC ハンドラ群
│   ├── IPCRegistry.ts          # core / features を分けた段階登録
│   └── *Handlers.ts            # 機能別ハンドラ
├── services/                   # アプリサービス
│   ├── AutoUpdateService.ts
│   ├── LogWatcherService.ts
│   ├── BrowserAutomationService.ts
│   ├── browserAutomation/      # browser detection / response listener
│   ├── ResponseStorageService.ts
│   └── UserSettingsService.ts
├── preload/                    # preload.ts の実装本体
│   ├── api/                    # window.electronAPI に expose する API 群
│   ├── eventChannels.ts
│   └── types.ts
├── database.ts                 # 薄い facade
├── database/                   # 6 repository(Database Design 参照)
└── logger.ts

起動シーケンス

StartupSequencercritical pathdeferred boot に分けて初期化を進める。first paint までの時間を最短化するため、UI を先に出して DB / Service / feature IPC は後で実行する。

Critical Path の責務

Step役割なぜ critical補足
loadMinimalUserSettingsbpWindowOnlyInBP 等、ウィンドウ表示判断に必要な設定だけ読むウィンドウ生成前に値が必要失敗時はデフォルト値で続行
createWindowscontrol + overlay の BrowserWindow を生成画面を出すコア処理overlay は OBS が読む URL
registerCoreIpccore IPC(ウィンドウ操作 / 設定 / アップデート等)を登録renderer の最初の IPC が成立する必要があるbpOpponentWindownull で OK(後で updateWindows で反映)

Deferred Boot の責務

Step役割失敗時の振る舞い
initDatabaseinitDatabaseWithFailureDialog() で SQLite を開くダイアログ → 削除 / DB なし続行 / 終了
initServicesBPOpponentWindow 生成 / IPCRegistry に reflectDB 不可でも UI は使えるよう続行
registerFeatureIpcDB / 統計 / log watcher / browser automation 系 IPC を登録ユーザーが終了を選んだ場合は no-op
startStartupImportPreviewrenderer 駆動なので no-op (将来の拡張点)
startLogWatcherUserSettings の opt-in を見て auto-start(現状 false)

DB 初期化失敗の対処(dbInit.ts

initDatabaseWithFailureDialog()initDatabase() を呼び、失敗時はダイアログでユーザに選ばせる。

  • ダイアログのボタンラベル: 🗑️ DBファイルを削除して再試行 / ▶️ DBなしで続行 / ❌ アプリを終了
  • 終了選択時は呼び出し側の electron/main.tsapp.quit() を行い、deferred boot の残りステップを skip
  • 終了選択時の挙動は electron/main.ts 側の deferred boot skip ロジックで検証

BP 対戦相手ウィンドウ表示制御(bpOpponentVisibility.ts

makeBPOpponentVisibility(getCtx)closure factorygetCtx で「現在の window / onlyInBP 設定 / 試合フェーズ」を毎回読み直す。

ts
const { applyVisibility } = makeBPOpponentVisibility(
  () => ({ window: bpOpponentWindow, onlyInBP: bpWindowOnlyInBP, currentPhase })
);
  • bpWindowOnlyInBP=false → 常に表示
  • bpWindowOnlyInBP=truecurrentPhase === 'BP' のときだけ表示
  • 試合フェーズの切り替えで applyVisibility() を呼ぶ責務は IPC ハンドラ側

起動計測 (perfRelay.ts)

関数役割
reportBinAndModuleLoad()PERF_RUN_ELECTRON_T0_MS 環境変数(spawn 時刻)と現在時刻の差分を [perf] electron.binAndModuleLoad=Xms として stdout に書く
registerPerfMarkerRelay()ELECTRON_PERF_MEASUREELECTRON_PERF_DEV_MEASURE'1' のときだけ、renderer からの perf-marker IPC を stdout にリレー

利用側スクリプト: scripts/perf/measure-startup.ts / measure-dev-startup.ts

IPC レジストリ(段階登録)

IPCRegistry2 段階登録 を行う。registerCore() は critical path で実行し、registerFeatures() は DB 初期化後の deferred boot で実行する。updateWindows / updateDatabaseAvailability で後から参照を差し替える。

単一インスタンスロック

AppLifecycle.setupSingleInstanceLock(onSecondInstance):

  • 二重起動時、後発プロセスは即 process.exit(0)
  • 既存プロセスでは onSecondInstance callback で control window をフォーカスする

グローバル例外ハンドラ

AppLifecycle.setupGlobalErrorHandlers():

  • process.on('uncaughtException') / unhandledRejection をキャッチして Logger 出力
  • 開発時は dev tools で詳細確認できるよう error の stack を残す

関連ドキュメント


Last verified: 2026-06-01 / commit 12bb761f