@bloopjs/web 0.0.48 → 0.0.50

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.
Files changed (42) hide show
  1. package/dist/App.d.ts +8 -1
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/debugui/DebugUi.d.ts +22 -0
  4. package/dist/debugui/DebugUi.d.ts.map +1 -0
  5. package/dist/debugui/components/DebugToggle.d.ts +6 -0
  6. package/dist/debugui/components/DebugToggle.d.ts.map +1 -0
  7. package/dist/debugui/components/Logs.d.ts +2 -0
  8. package/dist/debugui/components/Logs.d.ts.map +1 -0
  9. package/dist/debugui/components/Root.d.ts +7 -0
  10. package/dist/debugui/components/Root.d.ts.map +1 -0
  11. package/dist/debugui/components/Stats.d.ts +2 -0
  12. package/dist/debugui/components/Stats.d.ts.map +1 -0
  13. package/dist/debugui/hooks/useAutoScroll.d.ts +6 -0
  14. package/dist/debugui/hooks/useAutoScroll.d.ts.map +1 -0
  15. package/dist/debugui/mod.d.ts +3 -0
  16. package/dist/debugui/mod.d.ts.map +1 -0
  17. package/dist/debugui/state.d.ts +33 -0
  18. package/dist/debugui/state.d.ts.map +1 -0
  19. package/dist/debugui/styles.d.ts +2 -0
  20. package/dist/debugui/styles.d.ts.map +1 -0
  21. package/dist/mod.d.ts +2 -1
  22. package/dist/mod.d.ts.map +1 -1
  23. package/dist/mod.js +1868 -13
  24. package/dist/mod.js.map +20 -6
  25. package/dist/netcode/mod.d.ts +2 -0
  26. package/dist/netcode/mod.d.ts.map +1 -1
  27. package/dist/netcode/scaffold.d.ts +13 -0
  28. package/dist/netcode/scaffold.d.ts.map +1 -0
  29. package/package.json +7 -5
  30. package/src/App.ts +41 -2
  31. package/src/debugui/DebugUi.ts +111 -0
  32. package/src/debugui/components/DebugToggle.tsx +25 -0
  33. package/src/debugui/components/Logs.tsx +57 -0
  34. package/src/debugui/components/Root.tsx +54 -0
  35. package/src/debugui/components/Stats.tsx +68 -0
  36. package/src/debugui/hooks/useAutoScroll.ts +62 -0
  37. package/src/debugui/mod.ts +2 -0
  38. package/src/debugui/state.ts +127 -0
  39. package/src/debugui/styles.ts +200 -0
  40. package/src/mod.ts +2 -1
  41. package/src/netcode/mod.ts +2 -0
  42. package/src/netcode/scaffold.ts +169 -0
@@ -0,0 +1,200 @@
1
+ export const styles = /*css*/ `
2
+ /* Reset for shadow DOM */
3
+ * {
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ /* Layout */
8
+ .fullscreen {
9
+ width: 100%;
10
+ height: 100%;
11
+ margin: 0;
12
+ padding: 0;
13
+ overflow: hidden;
14
+ }
15
+
16
+ .layout {
17
+ display: grid;
18
+ grid-template-areas:
19
+ "game stats"
20
+ "logs logs";
21
+ grid-template-columns: calc(50% - 0.5rem) calc(50% - 0.5rem);
22
+ grid-template-rows: calc(50% - 0.5rem) calc(50% - 0.5rem);
23
+ gap: 1rem;
24
+ width: 100%;
25
+ height: 100%;
26
+ padding: 1rem;
27
+ }
28
+
29
+ .game {
30
+ grid-area: game;
31
+ border-radius: 8px;
32
+ overflow: hidden;
33
+ }
34
+
35
+ .stats {
36
+ grid-area: stats;
37
+ background-color: #f0f0f0;
38
+ padding: 1rem;
39
+ border-radius: 8px;
40
+ overflow: hidden;
41
+ }
42
+
43
+ .logs {
44
+ grid-area: logs;
45
+ background-color: #f0f0f0;
46
+ padding: 1rem;
47
+ border-radius: 8px;
48
+ overflow: hidden;
49
+ }
50
+
51
+ /* Canvas container */
52
+ .canvas-container {
53
+ width: 100%;
54
+ height: 100%;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ }
59
+
60
+ .canvas-container canvas {
61
+ max-width: 100%;
62
+ max-height: 100%;
63
+ }
64
+
65
+ /* Debug toggle button */
66
+ .debug-toggle {
67
+ position: fixed;
68
+ bottom: 16px;
69
+ right: 16px;
70
+ width: 40px;
71
+ height: 40px;
72
+ border-radius: 50%;
73
+ border: none;
74
+ background-color: rgba(0, 0, 0, 0.5);
75
+ color: white;
76
+ font-size: 18px;
77
+ cursor: pointer;
78
+ z-index: 1000;
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ transition: background-color 0.2s;
83
+ }
84
+
85
+ .debug-toggle:hover {
86
+ background-color: rgba(0, 0, 0, 0.7);
87
+ }
88
+
89
+ /* Stats panel */
90
+ .stats-panel {
91
+ background: rgba(0, 0, 0, 0.7);
92
+ color: white;
93
+ padding: 12px;
94
+ border-radius: 8px;
95
+ font-family: monospace;
96
+ font-size: 14px;
97
+ max-width: 100%;
98
+ overflow: hidden;
99
+ }
100
+
101
+ .stats-panel h3 {
102
+ margin: 0 0 8px 0;
103
+ font-size: 14px;
104
+ font-weight: 600;
105
+ white-space: nowrap;
106
+ overflow: hidden;
107
+ text-overflow: ellipsis;
108
+ }
109
+
110
+ .stats-panel table {
111
+ width: 100%;
112
+ border-collapse: collapse;
113
+ table-layout: fixed;
114
+ }
115
+
116
+ .stats-panel tr {
117
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
118
+ }
119
+
120
+ .stats-panel tr:last-child {
121
+ border-bottom: none;
122
+ }
123
+
124
+ .stats-panel td {
125
+ padding: 4px 0;
126
+ overflow: hidden;
127
+ text-overflow: ellipsis;
128
+ white-space: nowrap;
129
+ }
130
+
131
+ .stats-panel td:first-child {
132
+ opacity: 0.7;
133
+ width: 60%;
134
+ }
135
+
136
+ .stats-panel td:last-child {
137
+ text-align: right;
138
+ font-weight: 600;
139
+ width: 40%;
140
+ }
141
+
142
+ .stats-panel p {
143
+ margin: 0;
144
+ opacity: 0.7;
145
+ }
146
+
147
+ /* Logs panel */
148
+ .logs-list {
149
+ width: 100%;
150
+ height: 100%;
151
+ overflow: auto;
152
+ margin: 0;
153
+ padding: 0;
154
+ }
155
+
156
+ .logs-list li {
157
+ margin: 0 0 24px 0;
158
+ list-style: none;
159
+ }
160
+
161
+ .logs-list h3 {
162
+ font-size: 16px;
163
+ font-weight: 500;
164
+ margin: 0;
165
+ }
166
+
167
+ .logs-list .ws {
168
+ color: darkolivegreen;
169
+ }
170
+
171
+ .logs-list .webrtc {
172
+ color: darkmagenta;
173
+ }
174
+
175
+ .logs-list .rollback {
176
+ color: darkblue;
177
+ }
178
+
179
+ .logs-list .local {
180
+ color: #333;
181
+ }
182
+
183
+ .logs-list .content {
184
+ font-size: 16px;
185
+ }
186
+
187
+ .logs-list p {
188
+ margin: 4px 0;
189
+ }
190
+
191
+ .logs-list pre {
192
+ margin: 0;
193
+ white-space: pre-wrap;
194
+ word-break: break-word;
195
+ background-color: oldlace;
196
+ padding: 8px;
197
+ border-radius: 4px;
198
+ border: 1px inset lavender;
199
+ }
200
+ `;
package/src/mod.ts CHANGED
@@ -1,2 +1,3 @@
1
- export { App, start } from "./App.ts";
1
+ export { App, type StartOptions, start } from "./App.ts";
2
+ export * as Debug from "./debugui/mod.ts";
2
3
  export * from "./netcode/mod.ts";
@@ -9,6 +9,8 @@ export type {
9
9
  } from "./logs.ts";
10
10
  export type { WebRtcPipe } from "./transport.ts";
11
11
  export type { RoomEvents } from "./broker.ts";
12
+ export type { JoinRollbackRoomOptions } from "./scaffold.ts";
12
13
 
13
14
  export { PacketType } from "./protocol.ts";
14
15
  export { logger } from "./logs.ts";
16
+ export { joinRollbackRoom } from "./scaffold.ts";
@@ -0,0 +1,169 @@
1
+ import type { App } from "../App.ts";
2
+ import * as Debug from "../debugui/mod.ts";
3
+ import { logger } from "./logs.ts";
4
+
5
+ export type JoinRollbackRoomOptions = {
6
+ /** Called when session becomes active */
7
+ onSessionStart?: () => void;
8
+ /** Called when session ends */
9
+ onSessionEnd?: () => void;
10
+ };
11
+
12
+ /**
13
+ * Join a rollback netcode room and wire up packet processing.
14
+ * This is a scaffold/stopgap - not the final architecture.
15
+ */
16
+ export function joinRollbackRoom(
17
+ roomId: string,
18
+ app: App,
19
+ opts?: JoinRollbackRoomOptions,
20
+ ): void {
21
+ // State
22
+ let udp: RTCDataChannel | null = null;
23
+ let sessionActive = false;
24
+ let localPeerId: number | null = null;
25
+ let remotePeerId: number | null = null;
26
+ let localStringPeerId: string | null = null;
27
+ let remoteStringPeerId: string | null = null;
28
+ const incomingPackets: Uint8Array[] = [];
29
+
30
+ function assignPeerIds(
31
+ localId: string,
32
+ remoteId: string,
33
+ ): { local: number; remote: number } {
34
+ if (localId < remoteId) {
35
+ return { local: 0, remote: 1 };
36
+ } else {
37
+ return { local: 1, remote: 0 };
38
+ }
39
+ }
40
+
41
+ function receivePackets() {
42
+ for (const packetData of incomingPackets) {
43
+ app.sim.net.receivePacket(packetData);
44
+
45
+ if (remotePeerId == null) {
46
+ return;
47
+ }
48
+
49
+ const peerState = app.sim.net.getPeerState(remotePeerId);
50
+ Debug.updatePeer(remoteStringPeerId!, {
51
+ ack: peerState.ack,
52
+ seq: peerState.seq,
53
+ lastPacketTime: performance.now(),
54
+ });
55
+ }
56
+ incomingPackets.length = 0;
57
+ }
58
+
59
+ function sendPacket() {
60
+ if (!udp || remotePeerId === null) {
61
+ console.warn("[netcode] Cannot send packet, udp or remotePeerId is null");
62
+ return;
63
+ }
64
+
65
+ if (udp.readyState !== "open") {
66
+ console.warn(
67
+ "[netcode] Data channel not open, cannot send packet. readyState=",
68
+ udp.readyState,
69
+ );
70
+ return;
71
+ }
72
+
73
+ const packet = app.sim.net.getOutboundPacket(remotePeerId);
74
+
75
+ if (!packet) {
76
+ console.warn("[netcode] No packet to send");
77
+ return;
78
+ }
79
+
80
+ udp.send(packet);
81
+ }
82
+
83
+ // Wire up logger to debug state
84
+ logger.onLog = (log) => {
85
+ Debug.addLog(log);
86
+ };
87
+
88
+ app.joinRoom(roomId, {
89
+ onPeerIdAssign: (peerId) => {
90
+ console.log(`Assigned peer ID: ${peerId}`);
91
+ localStringPeerId = peerId;
92
+ },
93
+ onBrokerMessage: (_message) => {},
94
+ onMessage(_peerId, data, _reliable) {
95
+ incomingPackets.push(new Uint8Array(data));
96
+ },
97
+ onDataChannelClose(peerId, reliable) {
98
+ console.log(`Data channel closed: ${peerId} (reliable: ${reliable})`);
99
+ if (!reliable && remotePeerId !== null) {
100
+ app.sim.net.disconnectPeer(remotePeerId);
101
+ sessionActive = false;
102
+ opts?.onSessionEnd?.();
103
+ }
104
+ },
105
+ onDataChannelOpen(peerId, reliable, channel) {
106
+ console.log(`Data channel opened: ${peerId} (reliable: ${reliable})`);
107
+ if (!reliable) {
108
+ udp = channel;
109
+
110
+ if (localStringPeerId === null) {
111
+ console.error("[netcode] Local peer ID not assigned yet!");
112
+ return;
113
+ }
114
+
115
+ const ids = assignPeerIds(localStringPeerId, peerId);
116
+ localPeerId = ids.local;
117
+ Debug.setLocalId(localPeerId);
118
+ remotePeerId = ids.remote;
119
+ remoteStringPeerId = peerId;
120
+ Debug.setRemoteId(remotePeerId);
121
+
122
+ // Initialize the session in the engine (2 players)
123
+ app.sim.sessionInit(2);
124
+
125
+ // Set up local and remote peers in net state
126
+ app.sim.net.setLocalPeer(localPeerId);
127
+ app.sim.net.connectPeer(remotePeerId);
128
+
129
+ sessionActive = true;
130
+ console.log(`[netcode] Session started at frame ${app.sim.time.frame}`);
131
+ opts?.onSessionStart?.();
132
+ }
133
+ },
134
+ onPeerConnected(peerId) {
135
+ Debug.addPeer({
136
+ id: peerId,
137
+ nickname: peerId.substring(0, 6),
138
+ ack: -1,
139
+ seq: -1,
140
+ lastPacketTime: performance.now(),
141
+ });
142
+ console.log(
143
+ `[netcode] Peer connected: ${peerId}. Total peers: ${Debug.debugState.netStatus.value.peers.length}`,
144
+ );
145
+ },
146
+ onPeerDisconnected(peerId) {
147
+ Debug.removePeer(peerId);
148
+ if (remotePeerId !== null && peerId === remoteStringPeerId) {
149
+ app.sim.net.disconnectPeer(remotePeerId);
150
+ sessionActive = false;
151
+ opts?.onSessionEnd?.();
152
+ }
153
+ },
154
+ });
155
+
156
+ // Process packets and send our state each frame
157
+ app.beforeFrame.subscribe((_frame) => {
158
+ if (!sessionActive || !udp || remotePeerId === null) {
159
+ return;
160
+ }
161
+
162
+ try {
163
+ receivePackets();
164
+ sendPacket();
165
+ } catch (e) {
166
+ console.error("Error in beforeFrame:", e);
167
+ }
168
+ });
169
+ }