@bloopjs/web 0.0.44 → 0.0.45
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 +30 -1
- package/dist/App.d.ts.map +1 -1
- package/dist/mod.d.ts +1 -0
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +762 -3
- package/dist/mod.js.map +9 -4
- package/dist/netcode/broker.d.ts +12 -0
- package/dist/netcode/broker.d.ts.map +1 -0
- package/dist/netcode/logs.d.ts +37 -0
- package/dist/netcode/logs.d.ts.map +1 -0
- package/dist/netcode/mod.d.ts +7 -0
- package/dist/netcode/mod.d.ts.map +1 -0
- package/dist/netcode/protocol.d.ts +42 -0
- package/dist/netcode/protocol.d.ts.map +1 -0
- package/dist/netcode/transport.d.ts +16 -0
- package/dist/netcode/transport.d.ts.map +1 -0
- package/package.json +4 -3
- package/src/App.ts +48 -3
- package/src/mod.ts +1 -0
- package/src/netcode/broker.ts +136 -0
- package/src/netcode/logs.ts +81 -0
- package/src/netcode/mod.ts +14 -0
- package/src/netcode/protocol.ts +56 -0
- package/src/netcode/transport.ts +237 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BrokerMessage } from "./protocol.ts";
|
|
2
|
+
export type RoomEvents = {
|
|
3
|
+
onBrokerMessage: (message: BrokerMessage) => void;
|
|
4
|
+
onPeerIdAssign: (peerId: string) => void;
|
|
5
|
+
onPeerConnected: (peerId: string) => void;
|
|
6
|
+
onPeerDisconnected: (peerId: string) => void;
|
|
7
|
+
onDataChannelOpen: (peerId: string, reliable: boolean, channel: RTCDataChannel) => void;
|
|
8
|
+
onMessage: (peerId: string, data: Uint8Array, reliable: boolean) => void;
|
|
9
|
+
onDataChannelClose: (peerId: string, reliable: boolean) => void;
|
|
10
|
+
};
|
|
11
|
+
export declare function joinRoom(brokerUrl: string, _roomId: string, cbs: RoomEvents): void;
|
|
12
|
+
//# sourceMappingURL=broker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker.d.ts","sourceRoot":"","sources":["../../src/netcode/broker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AASnD,MAAM,MAAM,UAAU,GAAG;IACvB,eAAe,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAClD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7C,iBAAiB,EAAE,CACjB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,cAAc,KACpB,IAAI,CAAC;IACV,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACzE,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;CACjE,CAAC;AAEF,wBAAgB,QAAQ,CACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,UAAU,QA0GhB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { PeerId } from "./protocol.ts";
|
|
2
|
+
export type Log = {
|
|
3
|
+
source: "webrtc" | "ws" | "local" | "rollback";
|
|
4
|
+
/** absolute frame number since the start of the sim */
|
|
5
|
+
frame_number: number;
|
|
6
|
+
/** relative frame number since the start of the current session */
|
|
7
|
+
match_frame: number | null;
|
|
8
|
+
/** unix timestamp */
|
|
9
|
+
timestamp: number;
|
|
10
|
+
severity: LogSeverity;
|
|
11
|
+
label?: string;
|
|
12
|
+
json?: any;
|
|
13
|
+
direction?: LogDirection;
|
|
14
|
+
from?: PeerId;
|
|
15
|
+
to?: PeerId;
|
|
16
|
+
reliable?: boolean;
|
|
17
|
+
packet?: {
|
|
18
|
+
size: number;
|
|
19
|
+
bytes: Uint8Array;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type LogOpts = Partial<Log> & {
|
|
23
|
+
source: Log["source"];
|
|
24
|
+
};
|
|
25
|
+
export type LogDirection = "inbound" | "outbound";
|
|
26
|
+
export type LogSeverity = "debug" | "log" | "warn" | "error";
|
|
27
|
+
export type OnLogCallback = (log: Log) => void;
|
|
28
|
+
export type Logger = {
|
|
29
|
+
onLog: OnLogCallback | null;
|
|
30
|
+
matchFrame: number;
|
|
31
|
+
frameNumber: number;
|
|
32
|
+
log(opts: LogOpts): void;
|
|
33
|
+
warn(opts: LogOpts): void;
|
|
34
|
+
error(opts: LogOpts): void;
|
|
35
|
+
};
|
|
36
|
+
export declare const logger: Logger;
|
|
37
|
+
//# sourceMappingURL=logs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/netcode/logs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,MAAM,GAAG,GAAG;IAChB,MAAM,EAAE,QAAQ,GAAG,IAAI,GAAG,OAAO,GAAG,UAAU,CAAC;IAC/C,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,qBAAqB;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,GAAG,CAAC;IAEX,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,UAAU,CAAC;KACnB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG;IACnC,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,CAAC;AAElD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;AAE/C,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAE5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IAEpB,GAAG,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CAC5B,CAAA;AAED,eAAO,MAAM,MAAM,EAAE,MAmCpB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { PeerId, BrokerMessage, PeerMessage } from "./protocol.ts";
|
|
2
|
+
export type { Log, LogOpts, LogDirection, LogSeverity, OnLogCallback, } from "./logs.ts";
|
|
3
|
+
export type { WebRtcPipe } from "./transport.ts";
|
|
4
|
+
export type { RoomEvents } from "./broker.ts";
|
|
5
|
+
export { PacketType } from "./protocol.ts";
|
|
6
|
+
export { logger } from "./logs.ts";
|
|
7
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/netcode/mod.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACxE,YAAY,EACV,GAAG,EACH,OAAO,EACP,YAAY,EACZ,WAAW,EACX,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type PeerId = string;
|
|
2
|
+
export declare enum PacketType {
|
|
3
|
+
None = 0,
|
|
4
|
+
Inputs = 1
|
|
5
|
+
}
|
|
6
|
+
export type BrokerMessage = {
|
|
7
|
+
type: "welcome";
|
|
8
|
+
yourId: PeerId;
|
|
9
|
+
peerIds: PeerId[];
|
|
10
|
+
serverId: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: "message:json";
|
|
13
|
+
peerId: PeerId;
|
|
14
|
+
message: PeerMessage;
|
|
15
|
+
} | {
|
|
16
|
+
type: "message:string";
|
|
17
|
+
peerId: PeerId;
|
|
18
|
+
message: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: "message:buffer";
|
|
21
|
+
peerId: PeerId;
|
|
22
|
+
message: ArrayBuffer;
|
|
23
|
+
} | {
|
|
24
|
+
type: "peer:connect";
|
|
25
|
+
peerId: PeerId;
|
|
26
|
+
} | {
|
|
27
|
+
type: "peer:disconnect";
|
|
28
|
+
peerId: PeerId;
|
|
29
|
+
};
|
|
30
|
+
export type PeerMessage = {
|
|
31
|
+
type: "offer";
|
|
32
|
+
target: string;
|
|
33
|
+
payload: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: "answer";
|
|
36
|
+
target: string;
|
|
37
|
+
payload: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: "message";
|
|
40
|
+
payload: any;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/netcode/protocol.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAK5B,oBAAY,UAAU;IACpB,IAAI,IAAI;IACR,MAAM,IAAI;CACX;AAED,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,WAAW,CAAC;CACtB,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,WAAW,CAAC;CACtB,GACD;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,iBAAiB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,MAAM,MAAM,WAAW,GACnB;IACE,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,GAAG,CAAC;CACd,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WebSocket } from "partysocket";
|
|
2
|
+
export type WebRtcPipe = {
|
|
3
|
+
peerConnection: RTCPeerConnection;
|
|
4
|
+
reliable: RTCDataChannel;
|
|
5
|
+
unreliable: RTCDataChannel;
|
|
6
|
+
peerId: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function connect(ws: WebSocket, peerId: string,
|
|
9
|
+
/** defaults to 10s */
|
|
10
|
+
timeoutMs?: number): Promise<WebRtcPipe>;
|
|
11
|
+
export declare function logErrors(dc: RTCDataChannel): Promise<void>;
|
|
12
|
+
export declare function logPeerConnection(pc: RTCPeerConnection, peerId: string): Promise<void>;
|
|
13
|
+
export declare function gatherIce(pc: RTCPeerConnection, timeoutMs: number): Promise<boolean | Error>;
|
|
14
|
+
export declare function waitForAnswer(ws: WebSocket, pc: RTCPeerConnection, timeoutMs: number): Promise<void>;
|
|
15
|
+
export declare function listenForOffers(ws: WebSocket, cb: (pipe: WebRtcPipe) => void): void;
|
|
16
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/netcode/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAQ7C,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,EAAE,iBAAiB,CAAC;IAClC,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,OAAO,CAC3B,EAAE,EAAE,SAAS,EACb,MAAM,EAAE,MAAM;AACd,sBAAsB;AACtB,SAAS,GAAE,MAAc,GACxB,OAAO,CAAC,UAAU,CAAC,CAiCrB;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,cAAc,iBA6BjD;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,iBAc5E;AAED,wBAAsB,SAAS,CAC7B,EAAE,EAAE,iBAAiB,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,CAkB1B;AAED,wBAAsB,aAAa,CACjC,EAAE,EAAE,SAAS,EACb,EAAE,EAAE,iBAAiB,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,QAwE5E"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bloopjs/web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.45",
|
|
4
4
|
"author": "Neil Sarkar",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +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.45",
|
|
37
|
+
"@bloopjs/engine": "0.0.45",
|
|
38
|
+
"partysocket": "^1.1.6"
|
|
38
39
|
}
|
|
39
40
|
}
|
package/src/App.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { type Bloop, type MountOpts, mount, type Sim } from "@bloopjs/bloop";
|
|
2
2
|
import type { Key } from "@bloopjs/engine";
|
|
3
3
|
import { mouseButtonCodeToMouseButton } from "@bloopjs/engine";
|
|
4
|
+
import {
|
|
5
|
+
joinRoom as joinRoomInternal,
|
|
6
|
+
type RoomEvents,
|
|
7
|
+
} from "./netcode/broker";
|
|
8
|
+
import { logger } from "./netcode/logs.ts";
|
|
4
9
|
|
|
5
10
|
export type StartOptions = {
|
|
6
11
|
/** A bloop game instance */
|
|
@@ -13,8 +18,12 @@ export type StartOptions = {
|
|
|
13
18
|
startPaused?: boolean;
|
|
14
19
|
/** Whether the sim should be recording to tape from initialization, defaults to true */
|
|
15
20
|
startRecording?: boolean;
|
|
21
|
+
/** URL for the WebRTC signaling broker (e.g. "wss://broker.example.com/ws") */
|
|
22
|
+
brokerUrl?: string;
|
|
16
23
|
};
|
|
17
24
|
|
|
25
|
+
const DEFAULT_BROKER_URL = "wss://webrtc-divine-glade-8064.fly.dev/ws";
|
|
26
|
+
|
|
18
27
|
/** Start a bloop game on the web */
|
|
19
28
|
export async function start(opts: StartOptions): Promise<App> {
|
|
20
29
|
if (!opts.sim) {
|
|
@@ -26,29 +35,46 @@ export async function start(opts: StartOptions): Promise<App> {
|
|
|
26
35
|
opts.sim = sim;
|
|
27
36
|
}
|
|
28
37
|
|
|
29
|
-
const app = new App(
|
|
38
|
+
const app = new App(
|
|
39
|
+
opts.sim,
|
|
40
|
+
opts.game,
|
|
41
|
+
opts.brokerUrl ?? DEFAULT_BROKER_URL,
|
|
42
|
+
);
|
|
30
43
|
return app;
|
|
31
44
|
}
|
|
32
45
|
|
|
46
|
+
/**
|
|
47
|
+
* The main application class for running a bloop game in the browser
|
|
48
|
+
*
|
|
49
|
+
* This class handles translating browser events and APIs to bloopjs and the wasm engine.
|
|
50
|
+
*
|
|
51
|
+
* Usually instantiated with the start() function
|
|
52
|
+
*/
|
|
33
53
|
export class App {
|
|
34
54
|
#sim: Sim;
|
|
35
55
|
game: Bloop<any>;
|
|
56
|
+
/** URL for the WebRTC signaling broker */
|
|
57
|
+
readonly brokerUrl: string;
|
|
36
58
|
/** RequestAnimationFrame handle for cancelling */
|
|
37
59
|
#rafHandle: number | null = null;
|
|
38
60
|
#unsubscribe: UnsubscribeFn | null = null;
|
|
39
61
|
#now: number = performance.now();
|
|
40
62
|
|
|
41
|
-
constructor(sim: Sim, game: Bloop<any
|
|
63
|
+
constructor(sim: Sim, game: Bloop<any>, brokerUrl: string) {
|
|
42
64
|
this.#sim = sim;
|
|
43
65
|
this.game = game;
|
|
66
|
+
this.brokerUrl = brokerUrl;
|
|
44
67
|
|
|
45
68
|
this.game.hooks.beforeFrame = (frame: number) => {
|
|
69
|
+
logger.frameNumber = this.#sim.time.frame;
|
|
70
|
+
logger.matchFrame = this.#sim.wasm.get_match_frame();
|
|
46
71
|
this.beforeFrame.notify(frame);
|
|
47
72
|
};
|
|
48
73
|
|
|
49
74
|
this.subscribe();
|
|
50
75
|
}
|
|
51
76
|
|
|
77
|
+
/** The simulation instance associated with this app */
|
|
52
78
|
get sim(): Sim {
|
|
53
79
|
return this.#sim;
|
|
54
80
|
}
|
|
@@ -57,7 +83,14 @@ export class App {
|
|
|
57
83
|
this.#sim = sim;
|
|
58
84
|
}
|
|
59
85
|
|
|
86
|
+
/** Join a multiplayer room via the broker */
|
|
87
|
+
joinRoom(roomId: string, callbacks: RoomEvents): void {
|
|
88
|
+
joinRoomInternal(this.brokerUrl, roomId, callbacks);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Event listeners for before a frame is processed */
|
|
60
92
|
beforeFrame: ReturnType<typeof createListener> = createListener<[number]>();
|
|
93
|
+
/** Event listeners for after a frame is processed */
|
|
61
94
|
afterFrame: ReturnType<typeof createListener> = createListener<[number]>();
|
|
62
95
|
|
|
63
96
|
/** Subscribe to the browser events and start the render loop */
|
|
@@ -176,11 +209,23 @@ export class App {
|
|
|
176
209
|
this.afterFrame.unsubscribeAll();
|
|
177
210
|
}
|
|
178
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Accept Hot Module Replacement when running in a vite dev server
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
*
|
|
217
|
+
* ```ts
|
|
218
|
+
* import.meta.hot?.accept("./game", async (newModule) => {
|
|
219
|
+
* await app.acceptHmr(newModule?.game, {
|
|
220
|
+
* wasmUrl: monorepoWasmUrl,
|
|
221
|
+
* });
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
179
224
|
async acceptHmr(module: any, opts?: Partial<MountOpts>): Promise<void> {
|
|
180
225
|
const game = (module.game ?? module) as Bloop<any>;
|
|
181
226
|
if (!game.hooks) {
|
|
182
227
|
throw new Error(
|
|
183
|
-
`HMR: missing game.hooks export on module: ${JSON.stringify(module)}
|
|
228
|
+
`HMR: missing game.hooks export on module: ${JSON.stringify(module)}`,
|
|
184
229
|
);
|
|
185
230
|
}
|
|
186
231
|
|
package/src/mod.ts
CHANGED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { WebSocket } from "partysocket";
|
|
2
|
+
import { logger } from "./logs.ts";
|
|
3
|
+
import type { BrokerMessage } from "./protocol.ts";
|
|
4
|
+
import {
|
|
5
|
+
connect,
|
|
6
|
+
listenForOffers,
|
|
7
|
+
logErrors,
|
|
8
|
+
logPeerConnection,
|
|
9
|
+
type WebRtcPipe,
|
|
10
|
+
} from "./transport.ts";
|
|
11
|
+
|
|
12
|
+
export type RoomEvents = {
|
|
13
|
+
onBrokerMessage: (message: BrokerMessage) => void;
|
|
14
|
+
onPeerIdAssign: (peerId: string) => void;
|
|
15
|
+
onPeerConnected: (peerId: string) => void;
|
|
16
|
+
onPeerDisconnected: (peerId: string) => void;
|
|
17
|
+
|
|
18
|
+
onDataChannelOpen: (
|
|
19
|
+
peerId: string,
|
|
20
|
+
reliable: boolean,
|
|
21
|
+
channel: RTCDataChannel
|
|
22
|
+
) => void;
|
|
23
|
+
onMessage: (peerId: string, data: Uint8Array, reliable: boolean) => void;
|
|
24
|
+
onDataChannelClose: (peerId: string, reliable: boolean) => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function joinRoom(
|
|
28
|
+
brokerUrl: string,
|
|
29
|
+
_roomId: string,
|
|
30
|
+
cbs: RoomEvents
|
|
31
|
+
) {
|
|
32
|
+
const broker = new WebSocket(brokerUrl);
|
|
33
|
+
|
|
34
|
+
broker.addEventListener("open", () => {
|
|
35
|
+
logger.log({
|
|
36
|
+
source: "ws",
|
|
37
|
+
label: "Connection opened",
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
broker.addEventListener("close", (event) => {
|
|
42
|
+
logger.warn({
|
|
43
|
+
source: "ws",
|
|
44
|
+
label: "Connection closed",
|
|
45
|
+
json: event,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
broker.addEventListener("error", (event) => {
|
|
50
|
+
logger.error({
|
|
51
|
+
source: "ws",
|
|
52
|
+
label: "Connection error",
|
|
53
|
+
json: event,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const pipes: Map<string, WebRtcPipe> = new Map();
|
|
58
|
+
|
|
59
|
+
let ourId = "";
|
|
60
|
+
|
|
61
|
+
broker.addEventListener("message", async (event) => {
|
|
62
|
+
try {
|
|
63
|
+
const envelope = JSON.parse(event.data) as BrokerMessage;
|
|
64
|
+
logger.log({
|
|
65
|
+
source: "ws",
|
|
66
|
+
direction: "inbound",
|
|
67
|
+
json: envelope,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
switch (envelope.type) {
|
|
71
|
+
case "welcome":
|
|
72
|
+
ourId = envelope.yourId;
|
|
73
|
+
cbs.onPeerIdAssign(envelope.yourId);
|
|
74
|
+
for (const peerId of envelope.peerIds) {
|
|
75
|
+
if (peerId === ourId) continue;
|
|
76
|
+
cbs.onPeerConnected(peerId);
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
case "message:json":
|
|
80
|
+
break;
|
|
81
|
+
case "peer:connect": {
|
|
82
|
+
const pipe = await connect(broker, envelope.peerId);
|
|
83
|
+
registerPipe(pipe, cbs);
|
|
84
|
+
cbs.onPeerConnected(envelope.peerId);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "peer:disconnect":
|
|
88
|
+
cbs.onPeerDisconnected(envelope.peerId);
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
logger.warn({
|
|
92
|
+
source: "ws",
|
|
93
|
+
label: `Unknown message type: ${envelope.type}`,
|
|
94
|
+
json: envelope,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
} catch (e) {
|
|
98
|
+
logger.error({
|
|
99
|
+
source: "ws",
|
|
100
|
+
label: "Failed to parse json",
|
|
101
|
+
json: {
|
|
102
|
+
data: event.data,
|
|
103
|
+
error: e,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
listenForOffers(broker, (pipe) => {
|
|
110
|
+
registerPipe(pipe, cbs);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
function registerPipe(pipe: WebRtcPipe, cbs: RoomEvents) {
|
|
114
|
+
logErrors(pipe.reliable);
|
|
115
|
+
logErrors(pipe.unreliable);
|
|
116
|
+
logPeerConnection(pipe.peerConnection, ourId);
|
|
117
|
+
|
|
118
|
+
cbs.onDataChannelOpen(pipe.peerId, true, pipe.reliable);
|
|
119
|
+
cbs.onDataChannelOpen(pipe.peerId, false, pipe.unreliable);
|
|
120
|
+
|
|
121
|
+
pipe.reliable.onmessage = (event) => {
|
|
122
|
+
cbs.onMessage(pipe.peerId, event.data, true);
|
|
123
|
+
};
|
|
124
|
+
pipe.reliable.onclose = () => {
|
|
125
|
+
cbs.onDataChannelClose(pipe.peerId, true);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
pipe.unreliable.onmessage = (event) => {
|
|
129
|
+
cbs.onMessage(pipe.peerId, event.data, false);
|
|
130
|
+
};
|
|
131
|
+
pipe.unreliable.onclose = () => {
|
|
132
|
+
cbs.onDataChannelClose(pipe.peerId, false);
|
|
133
|
+
};
|
|
134
|
+
pipes.set(pipe.peerId, pipe);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { PeerId } from "./protocol.ts";
|
|
2
|
+
|
|
3
|
+
export type Log = {
|
|
4
|
+
source: "webrtc" | "ws" | "local" | "rollback";
|
|
5
|
+
/** absolute frame number since the start of the sim */
|
|
6
|
+
frame_number: number;
|
|
7
|
+
/** relative frame number since the start of the current session */
|
|
8
|
+
match_frame: number | null;
|
|
9
|
+
/** unix timestamp */
|
|
10
|
+
timestamp: number;
|
|
11
|
+
severity: LogSeverity;
|
|
12
|
+
label?: string;
|
|
13
|
+
json?: any;
|
|
14
|
+
// webrtc stuff
|
|
15
|
+
direction?: LogDirection;
|
|
16
|
+
from?: PeerId;
|
|
17
|
+
to?: PeerId;
|
|
18
|
+
reliable?: boolean;
|
|
19
|
+
packet?: {
|
|
20
|
+
size: number;
|
|
21
|
+
bytes: Uint8Array;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type LogOpts = Partial<Log> & {
|
|
26
|
+
source: Log["source"];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type LogDirection = "inbound" | "outbound";
|
|
30
|
+
|
|
31
|
+
export type LogSeverity = "debug" | "log" | "warn" | "error";
|
|
32
|
+
|
|
33
|
+
export type OnLogCallback = (log: Log) => void;
|
|
34
|
+
|
|
35
|
+
export type Logger = {
|
|
36
|
+
onLog: OnLogCallback | null;
|
|
37
|
+
|
|
38
|
+
matchFrame: number;
|
|
39
|
+
frameNumber: number;
|
|
40
|
+
|
|
41
|
+
log(opts: LogOpts): void;
|
|
42
|
+
warn(opts: LogOpts): void;
|
|
43
|
+
error(opts: LogOpts): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const logger: Logger = {
|
|
47
|
+
onLog: null as OnLogCallback | null,
|
|
48
|
+
|
|
49
|
+
matchFrame: -1,
|
|
50
|
+
frameNumber: -1,
|
|
51
|
+
|
|
52
|
+
log(opts: LogOpts) {
|
|
53
|
+
this.onLog?.({
|
|
54
|
+
...opts,
|
|
55
|
+
frame_number: this.frameNumber,
|
|
56
|
+
match_frame: this.matchFrame >= 0 ? this.matchFrame : null,
|
|
57
|
+
timestamp: Date.now(),
|
|
58
|
+
severity: "log",
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
warn(opts: LogOpts) {
|
|
63
|
+
this.onLog?.({
|
|
64
|
+
...opts,
|
|
65
|
+
frame_number: this.frameNumber,
|
|
66
|
+
match_frame: this.matchFrame >= 0 ? this.matchFrame : null,
|
|
67
|
+
timestamp: Date.now(),
|
|
68
|
+
severity: "warn",
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
error(opts: LogOpts) {
|
|
73
|
+
this.onLog?.({
|
|
74
|
+
...opts,
|
|
75
|
+
frame_number: this.frameNumber,
|
|
76
|
+
match_frame: this.matchFrame >= 0 ? this.matchFrame : null,
|
|
77
|
+
timestamp: Date.now(),
|
|
78
|
+
severity: "error",
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// biome-ignore assist/source/organizeImports: organized by hand
|
|
2
|
+
export type { PeerId, BrokerMessage, PeerMessage } from "./protocol.ts";
|
|
3
|
+
export type {
|
|
4
|
+
Log,
|
|
5
|
+
LogOpts,
|
|
6
|
+
LogDirection,
|
|
7
|
+
LogSeverity,
|
|
8
|
+
OnLogCallback,
|
|
9
|
+
} from "./logs.ts";
|
|
10
|
+
export type { WebRtcPipe } from "./transport.ts";
|
|
11
|
+
export type { RoomEvents } from "./broker.ts";
|
|
12
|
+
|
|
13
|
+
export { PacketType } from "./protocol.ts";
|
|
14
|
+
export { logger } from "./logs.ts";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type PeerId = string;
|
|
2
|
+
|
|
3
|
+
// TODO: bring the websocket server from neilsarkar/labs into this repo
|
|
4
|
+
|
|
5
|
+
// Wire format packet types
|
|
6
|
+
export enum PacketType {
|
|
7
|
+
None = 0,
|
|
8
|
+
Inputs = 1,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type BrokerMessage =
|
|
12
|
+
| {
|
|
13
|
+
type: "welcome";
|
|
14
|
+
yourId: PeerId;
|
|
15
|
+
peerIds: PeerId[];
|
|
16
|
+
serverId: string;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
type: "message:json";
|
|
20
|
+
peerId: PeerId;
|
|
21
|
+
message: PeerMessage;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
type: "message:string";
|
|
25
|
+
peerId: PeerId;
|
|
26
|
+
message: string;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
type: "message:buffer";
|
|
30
|
+
peerId: PeerId;
|
|
31
|
+
message: ArrayBuffer;
|
|
32
|
+
}
|
|
33
|
+
| {
|
|
34
|
+
type: "peer:connect";
|
|
35
|
+
peerId: PeerId;
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
type: "peer:disconnect";
|
|
39
|
+
peerId: PeerId;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type PeerMessage =
|
|
43
|
+
| {
|
|
44
|
+
type: "offer";
|
|
45
|
+
target: string;
|
|
46
|
+
payload: string;
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
type: "answer";
|
|
50
|
+
target: string;
|
|
51
|
+
payload: string;
|
|
52
|
+
}
|
|
53
|
+
| {
|
|
54
|
+
type: "message";
|
|
55
|
+
payload: any;
|
|
56
|
+
};
|