@bloopjs/web 0.0.57 → 0.0.59
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/dist/App.d.ts +9 -1
- package/dist/App.d.ts.map +1 -1
- package/dist/debugui/state.d.ts +3 -0
- package/dist/debugui/state.d.ts.map +1 -1
- package/dist/debugui/styles.d.ts +1 -1
- package/dist/debugui/styles.d.ts.map +1 -1
- package/dist/mod.js +56 -3
- package/dist/mod.js.map +6 -6
- package/package.json +3 -3
- package/src/App.ts +17 -2
- package/src/debugui/components/Root.tsx +4 -1
- package/src/debugui/state.ts +40 -0
- package/src/debugui/styles.ts +17 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bloopjs/web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.59",
|
|
4
4
|
"author": "Neil Sarkar",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"typescript": "^5"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@bloopjs/bloop": "0.0.
|
|
37
|
-
"@bloopjs/engine": "0.0.
|
|
36
|
+
"@bloopjs/bloop": "0.0.59",
|
|
37
|
+
"@bloopjs/engine": "0.0.59",
|
|
38
38
|
"@preact/signals": "^1.3.1",
|
|
39
39
|
"partysocket": "^1.1.6",
|
|
40
40
|
"preact": "^10.25.4"
|
package/src/App.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "./netcode/broker";
|
|
8
8
|
import { logger } from "./netcode/logs.ts";
|
|
9
9
|
import { DebugUi, type DebugUiOptions } from "./debugui/mod.ts";
|
|
10
|
-
import { debugState } from "./debugui/state.ts";
|
|
10
|
+
import { debugState, triggerHmrFlash } from "./debugui/state.ts";
|
|
11
11
|
|
|
12
12
|
export type StartOptions = {
|
|
13
13
|
/** A bloop game instance */
|
|
@@ -131,6 +131,8 @@ export class App {
|
|
|
131
131
|
beforeFrame: ReturnType<typeof createListener> = createListener<[number]>();
|
|
132
132
|
/** Event listeners for after a frame is processed */
|
|
133
133
|
afterFrame: ReturnType<typeof createListener> = createListener<[number]>();
|
|
134
|
+
/** Event listeners for HMR events */
|
|
135
|
+
onHmr: ReturnType<typeof createListener> = createListener<[HmrEvent]>();
|
|
134
136
|
|
|
135
137
|
/** Subscribe to the browser events and start the render loop */
|
|
136
138
|
subscribe(): void {
|
|
@@ -276,6 +278,7 @@ export class App {
|
|
|
276
278
|
this.sim.unmount();
|
|
277
279
|
this.beforeFrame.unsubscribeAll();
|
|
278
280
|
this.afterFrame.unsubscribeAll();
|
|
281
|
+
this.onHmr.unsubscribeAll();
|
|
279
282
|
this.#debugUi?.unmount();
|
|
280
283
|
}
|
|
281
284
|
|
|
@@ -288,10 +291,14 @@ export class App {
|
|
|
288
291
|
* import.meta.hot?.accept("./game", async (newModule) => {
|
|
289
292
|
* await app.acceptHmr(newModule?.game, {
|
|
290
293
|
* wasmUrl: monorepoWasmUrl,
|
|
294
|
+
* files: ["./game"],
|
|
291
295
|
* });
|
|
292
296
|
* ```
|
|
293
297
|
*/
|
|
294
|
-
async acceptHmr(
|
|
298
|
+
async acceptHmr(
|
|
299
|
+
module: any,
|
|
300
|
+
opts?: Partial<MountOpts> & { files?: string[] },
|
|
301
|
+
): Promise<void> {
|
|
295
302
|
const game = (module.game ?? module) as Bloop<any>;
|
|
296
303
|
if (!game.hooks) {
|
|
297
304
|
throw new Error(
|
|
@@ -310,6 +317,10 @@ export class App {
|
|
|
310
317
|
this.sim.unmount();
|
|
311
318
|
this.sim = sim;
|
|
312
319
|
this.game = game;
|
|
320
|
+
|
|
321
|
+
// Trigger HMR flash and notify listeners
|
|
322
|
+
triggerHmrFlash();
|
|
323
|
+
this.onHmr.notify({ files: opts?.files ?? [] });
|
|
313
324
|
}
|
|
314
325
|
}
|
|
315
326
|
|
|
@@ -343,3 +354,7 @@ function createListener<T extends any[]>(): {
|
|
|
343
354
|
}
|
|
344
355
|
|
|
345
356
|
export type UnsubscribeFn = () => void;
|
|
357
|
+
|
|
358
|
+
export type HmrEvent = {
|
|
359
|
+
files: string[];
|
|
360
|
+
};
|
|
@@ -59,6 +59,7 @@ function LetterboxedLayout({ canvas }: { canvas: HTMLCanvasElement }) {
|
|
|
59
59
|
const advantage = debugState.advantage.value ?? 0;
|
|
60
60
|
const frameTime = debugState.frameTime.value;
|
|
61
61
|
const snapshotSize = debugState.snapshotSize.value;
|
|
62
|
+
const hmrFlash = debugState.hmrFlash.value;
|
|
62
63
|
|
|
63
64
|
// Left bar: frame advantage (online) or frame time % (offline)
|
|
64
65
|
const leftValue = isOnline ? Math.abs(advantage) : frameTime;
|
|
@@ -78,6 +79,8 @@ function LetterboxedLayout({ canvas }: { canvas: HTMLCanvasElement }) {
|
|
|
78
79
|
const rightMax = isOnline ? 10 : 10000; // 10 frames rollback or 10KB
|
|
79
80
|
const rightLabel = isOnline ? "RB" : "KB";
|
|
80
81
|
|
|
82
|
+
const gameClassName = hmrFlash ? "letterboxed-game hmr-flash" : "letterboxed-game";
|
|
83
|
+
|
|
81
84
|
return (
|
|
82
85
|
<main className="layout-letterboxed">
|
|
83
86
|
<TopBar leftLabel={leftLabel} rightLabel={rightLabel} />
|
|
@@ -87,7 +90,7 @@ function LetterboxedLayout({ canvas }: { canvas: HTMLCanvasElement }) {
|
|
|
87
90
|
side="left"
|
|
88
91
|
color={leftColor}
|
|
89
92
|
/>
|
|
90
|
-
<div className=
|
|
93
|
+
<div className={gameClassName}>
|
|
91
94
|
<GameCanvas canvas={canvas} />
|
|
92
95
|
</div>
|
|
93
96
|
<VerticalBar
|
package/src/debugui/state.ts
CHANGED
|
@@ -37,6 +37,8 @@ export type DebugState = {
|
|
|
37
37
|
frameTime: Signal<number>; // ms per frame
|
|
38
38
|
snapshotSize: Signal<number>; // bytes
|
|
39
39
|
frameNumber: Signal<number>;
|
|
40
|
+
// HMR flash indicator
|
|
41
|
+
hmrFlash: Signal<boolean>;
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
const layoutMode = signal<LayoutMode>("off");
|
|
@@ -51,6 +53,7 @@ const fps = signal(0);
|
|
|
51
53
|
const frameTime = signal(0);
|
|
52
54
|
const snapshotSize = signal(0);
|
|
53
55
|
const frameNumber = signal(0);
|
|
56
|
+
const hmrFlash = signal(false);
|
|
54
57
|
|
|
55
58
|
export const debugState: DebugState = {
|
|
56
59
|
/** Layout mode: off, letterboxed, or full */
|
|
@@ -79,6 +82,9 @@ export const debugState: DebugState = {
|
|
|
79
82
|
frameTime,
|
|
80
83
|
snapshotSize,
|
|
81
84
|
frameNumber,
|
|
85
|
+
|
|
86
|
+
/** HMR flash indicator */
|
|
87
|
+
hmrFlash,
|
|
82
88
|
};
|
|
83
89
|
|
|
84
90
|
/** Cycle through layout modes: off -> letterboxed -> full -> off */
|
|
@@ -93,6 +99,39 @@ export function cycleLayout(): void {
|
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
|
|
102
|
+
let hmrFlashQueued = false;
|
|
103
|
+
|
|
104
|
+
/** Trigger HMR flash (only when debug UI is visible) */
|
|
105
|
+
export function triggerHmrFlash(): void {
|
|
106
|
+
if (!debugState.isVisible.value) return;
|
|
107
|
+
|
|
108
|
+
// If window doesn't have focus, queue the flash for when focus returns
|
|
109
|
+
if (!document.hasFocus()) {
|
|
110
|
+
if (!hmrFlashQueued) {
|
|
111
|
+
hmrFlashQueued = true;
|
|
112
|
+
window.addEventListener("focus", onWindowFocus);
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
doFlash();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function onWindowFocus(): void {
|
|
121
|
+
if (hmrFlashQueued) {
|
|
122
|
+
hmrFlashQueued = false;
|
|
123
|
+
window.removeEventListener("focus", onWindowFocus);
|
|
124
|
+
doFlash();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function doFlash(): void {
|
|
129
|
+
debugState.hmrFlash.value = true;
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
debugState.hmrFlash.value = false;
|
|
132
|
+
}, 300);
|
|
133
|
+
}
|
|
134
|
+
|
|
96
135
|
export function addLog(log: Log): void {
|
|
97
136
|
debugState.logs.value = [...debugState.logs.value, log];
|
|
98
137
|
}
|
|
@@ -161,4 +200,5 @@ export function resetState(): void {
|
|
|
161
200
|
debugState.frameTime.value = 0;
|
|
162
201
|
debugState.snapshotSize.value = 0;
|
|
163
202
|
debugState.frameNumber.value = 0;
|
|
203
|
+
debugState.hmrFlash.value = false;
|
|
164
204
|
}
|
package/src/debugui/styles.ts
CHANGED
|
@@ -256,6 +256,23 @@ export const styles = /*css*/ `
|
|
|
256
256
|
display: block;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
.letterboxed-game {
|
|
260
|
+
position: relative;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.letterboxed-game.hmr-flash::after {
|
|
264
|
+
content: "";
|
|
265
|
+
position: absolute;
|
|
266
|
+
inset: 0;
|
|
267
|
+
pointer-events: none;
|
|
268
|
+
animation: hmr-pulse 0.3s ease-out forwards;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@keyframes hmr-pulse {
|
|
272
|
+
0% { box-shadow: inset 0 0 0 36px #7b3fa0; }
|
|
273
|
+
100% { box-shadow: inset 0 0 0 0 #7b3fa0; }
|
|
274
|
+
}
|
|
275
|
+
|
|
259
276
|
.game {
|
|
260
277
|
grid-area: game;
|
|
261
278
|
border-radius: 8px;
|