@next2d/builder 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/steam.md CHANGED
@@ -107,6 +107,8 @@ const off = window.next2dWindow.onResume(() => { /* 状態の再送など */ });
107
107
 
108
108
  重ね表示が有効なときは、アプリは**1つだけ**起動します(2つ目の起動はすぐに終了します。macOSではDockにアイコンも出しません)。開発中に複数起動したいときは `--next2d-allow-multiple` を付けて起動します。
109
109
 
110
+ スリープ(ノートPCのふたを閉じる等)から復帰すると、GPUがリセットされて描画が止まることがあるため、画面(renderer)を読み込み直します(ゲームは自動保存を前提にしてください)。電源・ディスプレイ・GPUのイベントは `userData/diagnostics.log`(Windowsは `%APPDATA%\<appId>\diagnostics.log`)に記録されます。
111
+
110
112
  Linuxの透明表示はコンポジタが必要です。
111
113
 
112
114
  ### STEP4:認証を用意する
@@ -327,6 +329,8 @@ const off = window.next2dWindow.onResume(() => { /* resend state, etc. */ });
327
329
 
328
330
  With the overlay enabled the app runs **once**: a second launch quits at once (and, on macOS, never shows a Dock icon). Start with `--next2d-allow-multiple` to run several copies while developing.
329
331
 
332
+ After sleep (e.g. closing a laptop lid) the GPU may be reset and drawing stops, so the page (renderer) is loaded again; the game should save automatically. Power, display and GPU events are logged to `userData/diagnostics.log` (Windows: `%APPDATA%\<appId>\diagnostics.log`).
333
+
330
334
  Transparency on Linux requires a compositor.
331
335
 
332
336
  ### STEP4: Prepare credentials
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next2d/builder",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Multi-platform builder for Next2d Framework, supporting export to various platforms such as macOS, Windows, iOS, Android and Web(HTML)",
5
5
  "author": "Toshiyuki Ienaga <ienaga@next2d.app>",
6
6
  "license": "MIT",
@@ -66,6 +66,50 @@ const refreshForwarding = () => {
66
66
  mainWindow.setIgnoreMouseEvents(false);
67
67
  mainWindow.setIgnoreMouseEvents(true, { forward: true });
68
68
  };
69
+ // A small log of power/display/GPU events (userData/diagnostics.log) to find out what happened after sleep.
70
+ const diagnosticsFile = () => path.join(app.getPath("userData"), "diagnostics.log");
71
+ const diag = (message) => {
72
+ if (!overlay) return;
73
+ try {
74
+ const file = diagnosticsFile();
75
+ if (fs.existsSync(file) && fs.statSync(file).size > 200000) fs.writeFileSync(file, "");
76
+ fs.appendFileSync(file, `${new Date().toISOString()} ${message}\n`);
77
+ } catch { /* diagnostics must never break the game */ }
78
+ };
79
+ // After sleep (closing a laptop lid) the GPU is often reset. The renderer then keeps showing its last frame but can
80
+ // no longer draw (the web page is not told), so the pet looks alive and menus never appear. Load the page again to
81
+ // get a fresh GPU device. The game saves automatically, so progress is kept.
82
+ // Counts of mouse events the page really receives (moves are forwarded while click-through; down/up only arrive
83
+ // while the window accepts clicks). Logged when they change, to tell "clicks never reach the page" from other causes.
84
+ const DIAG_SCRIPT = `(() => {
85
+ if (window.__n2dDiag) return;
86
+ const d = window.__n2dDiag = { moves: 0, downs: 0, ups: 0, keys: 0, last: null };
87
+ const mark = (name) => (event) => { d[name]++; d.last = [event.clientX, event.clientY]; };
88
+ addEventListener("mousemove", mark("moves"), true);
89
+ addEventListener("pointerdown", mark("downs"), true);
90
+ addEventListener("pointerup", mark("ups"), true);
91
+ addEventListener("keydown", () => { d.keys++; }, true);
92
+ })()`;
93
+ let lastSnapshot = "";
94
+ const snapshotRenderer = async () => {
95
+ if (!mainWindow || mainWindow.isDestroyed()) return;
96
+ try {
97
+ const raw = await mainWindow.webContents.executeJavaScript("JSON.stringify(window.__n2dDiag || null)");
98
+ if (raw !== lastSnapshot) {
99
+ lastSnapshot = raw;
100
+ diag(`renderer ${raw} ignore=${overlayIgnore} onTop=${mainWindow.isAlwaysOnTop()} bounds=${JSON.stringify(mainWindow.getBounds())}`);
101
+ }
102
+ } catch (error) {
103
+ diag(`renderer snapshot failed: ${error?.message ?? error}`);
104
+ }
105
+ };
106
+ const reloadRenderer = () => {
107
+ if (!mainWindow || mainWindow.isDestroyed()) return;
108
+ diag("reload renderer (resume)");
109
+ overlayIgnore = Boolean(overlay?.clickThrough);
110
+ mainWindow.setIgnoreMouseEvents(overlayIgnore, overlayIgnore ? { forward: true } : undefined);
111
+ mainWindow.webContents.reloadIgnoringCache();
112
+ };
69
113
  const createWindow = async () => {
70
114
  const { width, height } = screen.getPrimaryDisplay().workAreaSize;
71
115
  // The preload reads these flags; each API is exposed only when its feature is configured.
@@ -103,8 +147,25 @@ const createWindow = async () => {
103
147
  if (overlay.clickThrough) {
104
148
  mainWindow.setIgnoreMouseEvents(true, { forward: true });
105
149
  }
150
+ // Errors the game prints (a failed window request, for example) are otherwise invisible in a packaged app.
151
+ let consoleLogged = 0;
152
+ mainWindow.webContents.on("console-message", (...args) => {
153
+ const first = args[0];
154
+ const detail = first && typeof first === "object" && "message" in first ? first : { level: args[1], message: args[2] };
155
+ if (!["error", "warning", 2, 3].includes(detail.level) || ++consoleLogged > 200) return;
156
+ diag(`console ${detail.level}: ${String(detail.message).slice(0, 300)}`);
157
+ });
158
+ mainWindow.webContents.on("did-finish-load", () => {
159
+ diag("renderer loaded");
160
+ mainWindow.webContents.executeJavaScript(DIAG_SCRIPT).catch(() => {});
161
+ });
162
+ const snapshotTimer = setInterval(snapshotRenderer, 5000);
163
+ snapshotTimer.unref?.();
164
+ mainWindow.once("closed", () => clearInterval(snapshotTimer));
165
+ mainWindow.webContents.on("unresponsive", () => diag("renderer unresponsive"));
166
+ mainWindow.webContents.on("responsive", () => diag("renderer responsive"));
106
167
  const events = ["display-metrics-changed", "display-added", "display-removed"];
107
- const onDisplayChange = () => { placeOverlay(); keepOnTop(); };
168
+ const onDisplayChange = () => { diag("display changed"); placeOverlay(); keepOnTop(); };
108
169
  for (const name of events) screen.on(name, onDisplayChange);
109
170
  mainWindow.once("closed", () => {
110
171
  for (const name of events) screen.removeListener(name, onDisplayChange);
@@ -187,7 +248,10 @@ app.whenReady().then(async () => {
187
248
  }
188
249
  if (overlay) {
189
250
  const trusted = (event) => {
190
- if (!isTrustedSender(event, mainWindow)) throw new Error("Window API access denied");
251
+ if (!isTrustedSender(event, mainWindow)) {
252
+ diag(`window API access denied (frame url: ${event?.senderFrame?.url})`);
253
+ throw new Error("Window API access denied");
254
+ }
191
255
  };
192
256
  ipcMain.handle("next2d:window:set-ignore-mouse-events", (event, ignore) => {
193
257
  trusted(event);
@@ -210,7 +274,18 @@ app.whenReady().then(async () => {
210
274
  mainWindow.setIgnoreMouseEvents(overlayIgnore, overlayIgnore ? { forward: true } : undefined);
211
275
  mainWindow.webContents.send("next2d:window:resume");
212
276
  };
213
- for (const name of ["resume", "unlock-screen"]) powerMonitor.on(name, () => setTimeout(recoverOverlay, 1000));
277
+ for (const name of ["suspend", "lock-screen"]) powerMonitor.on(name, () => diag(name));
278
+ for (const name of ["resume", "unlock-screen"]) {
279
+ powerMonitor.on(name, () => {
280
+ diag(name);
281
+ setTimeout(() => {
282
+ recoverOverlay();
283
+ // Only a resume from sleep can reset the GPU (unlocking the screen cannot).
284
+ if (name === "resume") reloadRenderer();
285
+ }, 1000);
286
+ });
287
+ }
288
+ app.on("child-process-gone", (_event, details) => diag(`child-process-gone ${details.type} ${details.reason}`));
214
289
  ipcMain.handle("next2d:window:quit", (event) => {
215
290
  trusted(event);
216
291
  // Reply before the window is torn down.