@couch-kit/runtime 0.2.0 → 0.3.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 +33 -0
- package/dist/index.cjs +17 -3
- package/dist/index.js +17 -3
- package/lib/runtime.d.ts +16 -0
- package/lib/runtime.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/runtime.ts +36 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# @couch-kit/runtime
|
|
2
2
|
|
|
3
|
+
## 0.3.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)]:
|
|
34
|
+
- @couch-kit/core@0.10.0
|
|
35
|
+
|
|
3
36
|
## 0.2.0
|
|
4
37
|
|
|
5
38
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -565,20 +565,34 @@ class GameHostRuntime {
|
|
|
565
565
|
return project ? project(this.state, playerId) : this.state;
|
|
566
566
|
}
|
|
567
567
|
broadcastState = () => {
|
|
568
|
-
|
|
568
|
+
const transport = this.transport;
|
|
569
|
+
if (!transport)
|
|
569
570
|
return;
|
|
570
571
|
const actions = this.actionQueue;
|
|
571
572
|
this.actionQueue = [];
|
|
572
573
|
this.stateDirty = false;
|
|
573
574
|
if (!this.config.project) {
|
|
574
|
-
|
|
575
|
+
transport.broadcast(createStateUpdateMessage(this.state, actions));
|
|
575
576
|
return;
|
|
576
577
|
}
|
|
578
|
+
const entries = [];
|
|
577
579
|
for (const connectionId of this.joinedConnections) {
|
|
578
580
|
const playerId = this.sessionManager.getPlayerIdForSocket(connectionId);
|
|
579
581
|
if (!playerId)
|
|
580
582
|
continue;
|
|
581
|
-
|
|
583
|
+
entries.push({
|
|
584
|
+
connectionId,
|
|
585
|
+
message: createStateUpdateMessage(this.viewFor(playerId), actions)
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
if (entries.length === 0)
|
|
589
|
+
return;
|
|
590
|
+
if (transport.sendMany) {
|
|
591
|
+
transport.sendMany(entries);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
for (const { connectionId, message } of entries) {
|
|
595
|
+
this.send(connectionId, message);
|
|
582
596
|
}
|
|
583
597
|
};
|
|
584
598
|
send(connectionId, message) {
|
package/dist/index.js
CHANGED
|
@@ -520,20 +520,34 @@ class GameHostRuntime {
|
|
|
520
520
|
return project ? project(this.state, playerId) : this.state;
|
|
521
521
|
}
|
|
522
522
|
broadcastState = () => {
|
|
523
|
-
|
|
523
|
+
const transport = this.transport;
|
|
524
|
+
if (!transport)
|
|
524
525
|
return;
|
|
525
526
|
const actions = this.actionQueue;
|
|
526
527
|
this.actionQueue = [];
|
|
527
528
|
this.stateDirty = false;
|
|
528
529
|
if (!this.config.project) {
|
|
529
|
-
|
|
530
|
+
transport.broadcast(createStateUpdateMessage(this.state, actions));
|
|
530
531
|
return;
|
|
531
532
|
}
|
|
533
|
+
const entries = [];
|
|
532
534
|
for (const connectionId of this.joinedConnections) {
|
|
533
535
|
const playerId = this.sessionManager.getPlayerIdForSocket(connectionId);
|
|
534
536
|
if (!playerId)
|
|
535
537
|
continue;
|
|
536
|
-
|
|
538
|
+
entries.push({
|
|
539
|
+
connectionId,
|
|
540
|
+
message: createStateUpdateMessage(this.viewFor(playerId), actions)
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
if (entries.length === 0)
|
|
544
|
+
return;
|
|
545
|
+
if (transport.sendMany) {
|
|
546
|
+
transport.sendMany(entries);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
for (const { connectionId, message } of entries) {
|
|
550
|
+
this.send(connectionId, message);
|
|
537
551
|
}
|
|
538
552
|
};
|
|
539
553
|
send(connectionId, message) {
|
package/lib/runtime.d.ts
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import { type GameReducer, type HostMessage, type IAction, type IGameState } from "@couch-kit/core";
|
|
2
|
+
/** One connection's share of a {@link GameRuntimeTransport.sendMany} delivery. */
|
|
3
|
+
export interface AddressedMessage {
|
|
4
|
+
connectionId: string;
|
|
5
|
+
message: HostMessage;
|
|
6
|
+
}
|
|
2
7
|
/** Minimal message-delivery surface required by the authoritative runtime. */
|
|
3
8
|
export interface GameRuntimeTransport {
|
|
4
9
|
send(connectionId: string, message: HostMessage): void;
|
|
5
10
|
broadcast(message: HostMessage): void;
|
|
11
|
+
/**
|
|
12
|
+
* Delivers a batch of per-connection messages, for transports that can carry
|
|
13
|
+
* them in one frame.
|
|
14
|
+
*
|
|
15
|
+
* Optional: the runtime falls back to a {@link GameRuntimeTransport.send} per
|
|
16
|
+
* entry, which is what a plain LAN WebSocket transport wants anyway. It earns
|
|
17
|
+
* its keep on the relay, where each frame the display sends is separately
|
|
18
|
+
* billed and rate-limited, so a projected state update costs one message
|
|
19
|
+
* rather than one per player.
|
|
20
|
+
*/
|
|
21
|
+
sendMany?(entries: readonly AddressedMessage[]): void;
|
|
6
22
|
}
|
|
7
23
|
/** Configuration shared by every authoritative Couch Kit host transport. */
|
|
8
24
|
export interface GameHostRuntimeConfig<S extends IGameState, A extends IAction> {
|
package/lib/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,UAAU,EAEhB,MAAM,iBAAiB,CAAC;AAczB,8EAA8E;AAC9E,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IACvD,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@couch-kit/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"provenance": true
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"clean": "rm -rf dist lib"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@couch-kit/core": "0.
|
|
59
|
+
"@couch-kit/core": "0.10.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"typescript": "^7.0.0"
|
package/src/runtime.ts
CHANGED
|
@@ -23,10 +23,27 @@ import {
|
|
|
23
23
|
type JoinSessionPayload,
|
|
24
24
|
} from "./session-manager.js";
|
|
25
25
|
|
|
26
|
+
/** One connection's share of a {@link GameRuntimeTransport.sendMany} delivery. */
|
|
27
|
+
export interface AddressedMessage {
|
|
28
|
+
connectionId: string;
|
|
29
|
+
message: HostMessage;
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
/** Minimal message-delivery surface required by the authoritative runtime. */
|
|
27
33
|
export interface GameRuntimeTransport {
|
|
28
34
|
send(connectionId: string, message: HostMessage): void;
|
|
29
35
|
broadcast(message: HostMessage): void;
|
|
36
|
+
/**
|
|
37
|
+
* Delivers a batch of per-connection messages, for transports that can carry
|
|
38
|
+
* them in one frame.
|
|
39
|
+
*
|
|
40
|
+
* Optional: the runtime falls back to a {@link GameRuntimeTransport.send} per
|
|
41
|
+
* entry, which is what a plain LAN WebSocket transport wants anyway. It earns
|
|
42
|
+
* its keep on the relay, where each frame the display sends is separately
|
|
43
|
+
* billed and rate-limited, so a projected state update costs one message
|
|
44
|
+
* rather than one per player.
|
|
45
|
+
*/
|
|
46
|
+
sendMany?(entries: readonly AddressedMessage[]): void;
|
|
30
47
|
}
|
|
31
48
|
|
|
32
49
|
/** Configuration shared by every authoritative Couch Kit host transport. */
|
|
@@ -404,27 +421,39 @@ export class GameHostRuntime<S extends IGameState, A extends IAction> {
|
|
|
404
421
|
}
|
|
405
422
|
|
|
406
423
|
private readonly broadcastState = (): void => {
|
|
407
|
-
|
|
424
|
+
const transport = this.transport;
|
|
425
|
+
if (!transport) return;
|
|
408
426
|
|
|
409
427
|
const actions = this.actionQueue;
|
|
410
428
|
this.actionQueue = [];
|
|
411
429
|
this.stateDirty = false;
|
|
412
430
|
|
|
413
431
|
if (!this.config.project) {
|
|
414
|
-
|
|
432
|
+
transport.broadcast(createStateUpdateMessage(this.state, actions));
|
|
415
433
|
return;
|
|
416
434
|
}
|
|
417
435
|
|
|
418
436
|
// Projected games get one message per connection rather than a broadcast:
|
|
419
|
-
// a shared frame cannot carry different views.
|
|
420
|
-
//
|
|
437
|
+
// a shared frame cannot carry different views. Transports that can batch
|
|
438
|
+
// (see GameRuntimeTransport.sendMany) still put them on the wire as a
|
|
439
|
+
// single frame; the rest fall back to N sends, bounded by players-per-room.
|
|
440
|
+
const entries: AddressedMessage[] = [];
|
|
421
441
|
for (const connectionId of this.joinedConnections) {
|
|
422
442
|
const playerId = this.sessionManager.getPlayerIdForSocket(connectionId);
|
|
423
443
|
if (!playerId) continue;
|
|
424
|
-
|
|
444
|
+
entries.push({
|
|
425
445
|
connectionId,
|
|
426
|
-
createStateUpdateMessage(this.viewFor(playerId), actions),
|
|
427
|
-
);
|
|
446
|
+
message: createStateUpdateMessage(this.viewFor(playerId), actions),
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
if (entries.length === 0) return;
|
|
450
|
+
|
|
451
|
+
if (transport.sendMany) {
|
|
452
|
+
transport.sendMany(entries);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
for (const { connectionId, message } of entries) {
|
|
456
|
+
this.send(connectionId, message);
|
|
428
457
|
}
|
|
429
458
|
};
|
|
430
459
|
|