@couch-kit/display 0.3.0 → 0.4.0
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/CHANGELOG.md +35 -0
- package/dist/index.js +20 -1
- package/lib/relay-display-host.d.ts +15 -0
- package/lib/relay-display-host.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/relay-display-host.ts +43 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# @couch-kit/display
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#164](https://github.com/faluciano/react-native-couch-kit/pull/164) [`a157126`](https://github.com/faluciano/react-native-couch-kit/commit/a157126424e4d73dcc7185118d5be0db6719792e) Thanks [@faluciano](https://github.com/faluciano)! - Send a projected state update as one relay frame instead of one per player
|
|
8
|
+
|
|
9
|
+
A game with a `project` function sends every player their own view, which meant
|
|
10
|
+
one WebSocket frame per player for every state change. Relays bill and
|
|
11
|
+
rate-limit per inbound frame, so a four-player table paid four messages for one
|
|
12
|
+
update and spent four of the display's 30-per-second budget.
|
|
13
|
+
|
|
14
|
+
`GameRuntimeTransport` gains an optional `sendMany(entries)`. When a transport
|
|
15
|
+
implements it, the runtime hands over the whole projected batch at once;
|
|
16
|
+
transports that do not — the LAN WebSocket path — keep receiving one `send` per
|
|
17
|
+
connection and are unaffected.
|
|
18
|
+
|
|
19
|
+
`RelayDisplayHost` implements it with a new `DATA_MULTI` envelope carrying a
|
|
20
|
+
peer-id-to-payload map, which the relay unpacks into ordinary `DATA` frames.
|
|
21
|
+
Phones need no update — nothing on the client side can tell a batched update
|
|
22
|
+
from a unicast one. If the combined frame would exceed the relay's 256KB
|
|
23
|
+
ceiling, the display falls back to individual frames rather than send something
|
|
24
|
+
the relay would drop.
|
|
25
|
+
|
|
26
|
+
Relays must be updated before displays: both bundled implementations
|
|
27
|
+
(`services/relay`, `services/relay-worker`) understand `DATA_MULTI`, and an
|
|
28
|
+
older relay answers it with `MALFORMED`. The type is host-only — a phone sending
|
|
29
|
+
it is rejected, so it cannot be used to reach another phone directly.
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- Updated dependencies [[`a157126`](https://github.com/faluciano/react-native-couch-kit/commit/a157126424e4d73dcc7185118d5be0db6719792e), [`a157126`](https://github.com/faluciano/react-native-couch-kit/commit/a157126424e4d73dcc7185118d5be0db6719792e)]:
|
|
34
|
+
- @couch-kit/runtime@0.3.0
|
|
35
|
+
- @couch-kit/client@0.14.0
|
|
36
|
+
- @couch-kit/core@0.10.0
|
|
37
|
+
|
|
3
38
|
## 0.3.0
|
|
4
39
|
|
|
5
40
|
### Minor Changes
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,8 @@ class RelayDisplayHost {
|
|
|
39
39
|
this.ws.onerror = (event) => this.runtime.handleError(event instanceof Error ? event : new Error("Relay socket error"));
|
|
40
40
|
const transport = {
|
|
41
41
|
send: (connectionId, message) => this.sendEnvelope(message, connectionId),
|
|
42
|
-
broadcast: (message) => this.sendEnvelope(message)
|
|
42
|
+
broadcast: (message) => this.sendEnvelope(message),
|
|
43
|
+
sendMany: (entries) => this.sendMultiEnvelope(entries)
|
|
43
44
|
};
|
|
44
45
|
this.runtime.setTransport(transport);
|
|
45
46
|
}
|
|
@@ -54,6 +55,24 @@ class RelayDisplayHost {
|
|
|
54
55
|
this.runtime.stop();
|
|
55
56
|
this.ws.close();
|
|
56
57
|
}
|
|
58
|
+
sendMultiEnvelope(entries) {
|
|
59
|
+
const payloads = {};
|
|
60
|
+
for (const { connectionId, message } of entries) {
|
|
61
|
+
payloads[connectionId] = JSON.stringify(message);
|
|
62
|
+
}
|
|
63
|
+
const frame = JSON.stringify({
|
|
64
|
+
type: RelayMessageTypes.DATA_MULTI,
|
|
65
|
+
roomId: this.assignedRoomId ?? undefined,
|
|
66
|
+
payloads
|
|
67
|
+
});
|
|
68
|
+
if (frameByteLength(frame) > DEFAULT_MAX_MESSAGE_BYTES) {
|
|
69
|
+
for (const { connectionId, message } of entries) {
|
|
70
|
+
this.sendEnvelope(message, connectionId);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.ws.send(frame);
|
|
75
|
+
}
|
|
57
76
|
sendEnvelope(message, to) {
|
|
58
77
|
const envelope = {
|
|
59
78
|
type: RelayMessageTypes.DATA,
|
|
@@ -68,6 +68,21 @@ export declare class RelayDisplayHost<S extends IGameState, A extends IAction> {
|
|
|
68
68
|
dispatch: (action: A) => void;
|
|
69
69
|
/** Tear down the runtime and relay socket. */
|
|
70
70
|
stop(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Sends per-connection messages as one `DATA_MULTI` frame, which the relay
|
|
73
|
+
* unpacks into an ordinary `DATA` frame per phone.
|
|
74
|
+
*
|
|
75
|
+
* A projected game re-sends every player's view on every state change, so on
|
|
76
|
+
* a four-player table this is the difference between one billed relay message
|
|
77
|
+
* and four — and between one and four against the relay's per-connection rate
|
|
78
|
+
* limit, which the display shares across all its fan-out.
|
|
79
|
+
*
|
|
80
|
+
* Falls back to individual sends if the combined frame would exceed the
|
|
81
|
+
* relay's message ceiling: N views in one envelope is N times the bytes, and
|
|
82
|
+
* a frame the relay rejects delivers nothing to anyone. Splitting costs
|
|
83
|
+
* messages; being dropped costs the game.
|
|
84
|
+
*/
|
|
85
|
+
private sendMultiEnvelope;
|
|
71
86
|
private sendEnvelope;
|
|
72
87
|
private handleRelayMessage;
|
|
73
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relay-display-host.d.ts","sourceRoot":"","sources":["../src/relay-display-host.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"relay-display-host.d.ts","sourceRoot":"","sources":["../src/relay-display-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,qBAAqB,EAE3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAe,MAAM,iBAAiB,CAAC;AAOxE;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB,CACtC,CAAC,SAAS,UAAU,EACpB,CAAC,SAAS,OAAO,CACjB,SAAQ,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC;IACnC,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,gBAAgB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACnE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAY;IAC/B,+EAA+E;IAC/E,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAA6B;IACzD,uDAAuD;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAE3C,YAAY,OAAO,EAAE,uBAAuB,CAAC,CAAC,EAAE,CAAC,CAAC,EAwCjD;IAED;;;;OAIG;IACH,IAAI,QAAQ,IAAI,MAAM,GAAG,IAAI,CAE5B;IAED,wCAAwC;IACxC,QAAQ,QAAO,CAAC,CAA4B;IAE5C,gFAAgF;IAChF,SAAS,aAAc,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CACb;IAEnC,8CAA8C;IAC9C,QAAQ,WAAY,CAAC,KAAG,IAAI,CAAkC;IAE9D,8CAA8C;IAC9C,IAAI,IAAI,IAAI,CAIX;IAED;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,kBAAkB;CA2C3B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@couch-kit/display",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"provenance": true
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"clean": "rm -rf dist lib"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@couch-kit/client": "0.
|
|
55
|
-
"@couch-kit/core": "0.
|
|
56
|
-
"@couch-kit/runtime": "0.
|
|
54
|
+
"@couch-kit/client": "0.14.0",
|
|
55
|
+
"@couch-kit/core": "0.10.0",
|
|
56
|
+
"@couch-kit/runtime": "0.3.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"typescript": "^7.0.0"
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
GameHostRuntime,
|
|
3
3
|
frameByteLength,
|
|
4
4
|
DEFAULT_MAX_MESSAGE_BYTES,
|
|
5
|
+
type AddressedMessage,
|
|
5
6
|
type GameHostRuntimeConfig,
|
|
6
7
|
type GameRuntimeTransport,
|
|
7
8
|
} from "@couch-kit/runtime";
|
|
@@ -19,8 +20,10 @@ import {
|
|
|
19
20
|
* object used by the RN-TV host) and the shared relay coordinates; the display
|
|
20
21
|
* host owns the authoritative runtime and the relay socket.
|
|
21
22
|
*/
|
|
22
|
-
export interface RelayDisplayHostOptions<
|
|
23
|
-
extends
|
|
23
|
+
export interface RelayDisplayHostOptions<
|
|
24
|
+
S extends IGameState,
|
|
25
|
+
A extends IAction,
|
|
26
|
+
> extends GameHostRuntimeConfig<S, A> {
|
|
24
27
|
/** WebSocket URL of the shared relay server. */
|
|
25
28
|
url: string;
|
|
26
29
|
/**
|
|
@@ -103,9 +106,9 @@ export class RelayDisplayHost<S extends IGameState, A extends IAction> {
|
|
|
103
106
|
);
|
|
104
107
|
|
|
105
108
|
const transport: GameRuntimeTransport = {
|
|
106
|
-
send: (connectionId, message) =>
|
|
107
|
-
this.sendEnvelope(message, connectionId),
|
|
109
|
+
send: (connectionId, message) => this.sendEnvelope(message, connectionId),
|
|
108
110
|
broadcast: (message) => this.sendEnvelope(message),
|
|
111
|
+
sendMany: (entries) => this.sendMultiEnvelope(entries),
|
|
109
112
|
};
|
|
110
113
|
this.runtime.setTransport(transport);
|
|
111
114
|
}
|
|
@@ -136,6 +139,42 @@ export class RelayDisplayHost<S extends IGameState, A extends IAction> {
|
|
|
136
139
|
this.ws.close();
|
|
137
140
|
}
|
|
138
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Sends per-connection messages as one `DATA_MULTI` frame, which the relay
|
|
144
|
+
* unpacks into an ordinary `DATA` frame per phone.
|
|
145
|
+
*
|
|
146
|
+
* A projected game re-sends every player's view on every state change, so on
|
|
147
|
+
* a four-player table this is the difference between one billed relay message
|
|
148
|
+
* and four — and between one and four against the relay's per-connection rate
|
|
149
|
+
* limit, which the display shares across all its fan-out.
|
|
150
|
+
*
|
|
151
|
+
* Falls back to individual sends if the combined frame would exceed the
|
|
152
|
+
* relay's message ceiling: N views in one envelope is N times the bytes, and
|
|
153
|
+
* a frame the relay rejects delivers nothing to anyone. Splitting costs
|
|
154
|
+
* messages; being dropped costs the game.
|
|
155
|
+
*/
|
|
156
|
+
private sendMultiEnvelope(entries: readonly AddressedMessage[]): void {
|
|
157
|
+
const payloads: Record<string, string> = {};
|
|
158
|
+
for (const { connectionId, message } of entries) {
|
|
159
|
+
payloads[connectionId] = JSON.stringify(message);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const frame = JSON.stringify({
|
|
163
|
+
type: RelayMessageTypes.DATA_MULTI,
|
|
164
|
+
roomId: this.assignedRoomId ?? undefined,
|
|
165
|
+
payloads,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (frameByteLength(frame) > DEFAULT_MAX_MESSAGE_BYTES) {
|
|
169
|
+
for (const { connectionId, message } of entries) {
|
|
170
|
+
this.sendEnvelope(message, connectionId);
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.ws.send(frame);
|
|
176
|
+
}
|
|
177
|
+
|
|
139
178
|
private sendEnvelope(message: HostMessage, to?: string): void {
|
|
140
179
|
const envelope: Record<string, unknown> = {
|
|
141
180
|
type: RelayMessageTypes.DATA,
|