@crowdedkingdoms/crowdyjs 8.2.0 → 8.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/README.md +81 -3
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +5 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/kit/blueprints/combat.d.ts +75 -0
- package/dist/kit/blueprints/combat.d.ts.map +1 -0
- package/dist/kit/blueprints/combat.js +351 -0
- package/dist/kit/blueprints/core.d.ts +222 -0
- package/dist/kit/blueprints/core.d.ts.map +1 -0
- package/dist/kit/blueprints/core.js +185 -0
- package/dist/kit/blueprints/decks.d.ts +50 -0
- package/dist/kit/blueprints/decks.d.ts.map +1 -0
- package/dist/kit/blueprints/decks.js +163 -0
- package/dist/kit/blueprints/economy.d.ts +88 -0
- package/dist/kit/blueprints/economy.d.ts.map +1 -0
- package/dist/kit/blueprints/economy.js +533 -0
- package/dist/kit/blueprints/guild.d.ts +51 -0
- package/dist/kit/blueprints/guild.d.ts.map +1 -0
- package/dist/kit/blueprints/guild.js +50 -0
- package/dist/kit/blueprints/index.d.ts +21 -0
- package/dist/kit/blueprints/index.d.ts.map +1 -0
- package/dist/kit/blueprints/index.js +20 -0
- package/dist/kit/blueprints/inventory.d.ts +38 -0
- package/dist/kit/blueprints/inventory.d.ts.map +1 -0
- package/dist/kit/blueprints/inventory.js +157 -0
- package/dist/kit/blueprints/leaderboards.d.ts +49 -0
- package/dist/kit/blueprints/leaderboards.d.ts.map +1 -0
- package/dist/kit/blueprints/leaderboards.js +126 -0
- package/dist/kit/blueprints/locks.d.ts +82 -0
- package/dist/kit/blueprints/locks.d.ts.map +1 -0
- package/dist/kit/blueprints/locks.js +155 -0
- package/dist/kit/blueprints/loot.d.ts +84 -0
- package/dist/kit/blueprints/loot.d.ts.map +1 -0
- package/dist/kit/blueprints/loot.js +228 -0
- package/dist/kit/blueprints/matches.d.ts +59 -0
- package/dist/kit/blueprints/matches.d.ts.map +1 -0
- package/dist/kit/blueprints/matches.js +246 -0
- package/dist/kit/blueprints/npcs.d.ts +71 -0
- package/dist/kit/blueprints/npcs.d.ts.map +1 -0
- package/dist/kit/blueprints/npcs.js +122 -0
- package/dist/kit/blueprints/plots.d.ts +54 -0
- package/dist/kit/blueprints/plots.d.ts.map +1 -0
- package/dist/kit/blueprints/plots.js +146 -0
- package/dist/kit/blueprints/progression.d.ts +68 -0
- package/dist/kit/blueprints/progression.d.ts.map +1 -0
- package/dist/kit/blueprints/progression.js +324 -0
- package/dist/kit/blueprints/quests.d.ts +65 -0
- package/dist/kit/blueprints/quests.d.ts.map +1 -0
- package/dist/kit/blueprints/quests.js +284 -0
- package/dist/kit/blueprints/worldsim.d.ts +84 -0
- package/dist/kit/blueprints/worldsim.d.ts.map +1 -0
- package/dist/kit/blueprints/worldsim.js +461 -0
- package/dist/kit/combat.d.ts +103 -0
- package/dist/kit/combat.d.ts.map +1 -0
- package/dist/kit/combat.js +151 -0
- package/dist/kit/decks.d.ts +123 -0
- package/dist/kit/decks.d.ts.map +1 -0
- package/dist/kit/decks.js +138 -0
- package/dist/kit/economy.d.ts +244 -0
- package/dist/kit/economy.d.ts.map +1 -0
- package/dist/kit/economy.js +365 -0
- package/dist/kit/features.d.ts +60 -0
- package/dist/kit/features.d.ts.map +1 -0
- package/dist/kit/features.js +65 -0
- package/dist/kit/index.d.ts +13 -2
- package/dist/kit/index.d.ts.map +1 -1
- package/dist/kit/index.js +12 -1
- package/dist/kit/inventory.d.ts +5 -0
- package/dist/kit/inventory.d.ts.map +1 -1
- package/dist/kit/inventory.js +14 -1
- package/dist/kit/kit.d.ts +58 -2
- package/dist/kit/kit.d.ts.map +1 -1
- package/dist/kit/kit.js +24 -2
- package/dist/kit/leaderboards.d.ts +66 -0
- package/dist/kit/leaderboards.d.ts.map +1 -0
- package/dist/kit/leaderboards.js +94 -0
- package/dist/kit/loot.d.ts +106 -0
- package/dist/kit/loot.d.ts.map +1 -0
- package/dist/kit/loot.js +120 -0
- package/dist/kit/matches.d.ts +140 -0
- package/dist/kit/matches.d.ts.map +1 -0
- package/dist/kit/matches.js +253 -0
- package/dist/kit/objects.js +1 -1
- package/dist/kit/plots.js +1 -1
- package/dist/kit/progression.d.ts +181 -0
- package/dist/kit/progression.d.ts.map +1 -0
- package/dist/kit/progression.js +265 -0
- package/dist/kit/quests.d.ts +110 -0
- package/dist/kit/quests.d.ts.map +1 -0
- package/dist/kit/quests.js +158 -0
- package/dist/kit/social.d.ts +267 -0
- package/dist/kit/social.d.ts.map +1 -0
- package/dist/kit/social.js +230 -0
- package/dist/kit/worldsim.d.ts +225 -0
- package/dist/kit/worldsim.d.ts.map +1 -0
- package/dist/kit/worldsim.js +231 -0
- package/dist/stores/actors.d.ts +316 -0
- package/dist/stores/actors.d.ts.map +1 -0
- package/dist/stores/actors.js +506 -0
- package/dist/stores/chunks.d.ts +180 -0
- package/dist/stores/chunks.d.ts.map +1 -0
- package/dist/stores/chunks.js +378 -0
- package/dist/stores/codec.d.ts +109 -0
- package/dist/stores/codec.d.ts.map +1 -0
- package/dist/stores/codec.js +186 -0
- package/dist/stores/durable.d.ts +144 -0
- package/dist/stores/durable.d.ts.map +1 -0
- package/dist/stores/durable.js +246 -0
- package/dist/stores/errors.d.ts +69 -0
- package/dist/stores/errors.d.ts.map +1 -0
- package/dist/stores/errors.js +87 -0
- package/dist/stores/inbox.d.ts +184 -0
- package/dist/stores/inbox.d.ts.map +1 -0
- package/dist/stores/inbox.js +326 -0
- package/dist/stores/index.d.ts +186 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +109 -0
- package/dist/stores/keys.d.ts +52 -0
- package/dist/stores/keys.d.ts.map +1 -0
- package/dist/stores/keys.js +76 -0
- package/dist/stores/model.d.ts +81 -0
- package/dist/stores/model.d.ts.map +1 -0
- package/dist/stores/model.js +163 -0
- package/dist/stores/session.d.ts +119 -0
- package/dist/stores/session.d.ts.map +1 -0
- package/dist/stores/session.js +116 -0
- package/dist/stores/ticker.d.ts +44 -0
- package/dist/stores/ticker.d.ts.map +1 -0
- package/dist/stores/ticker.js +127 -0
- package/package.json +6 -1
- package/dist/kit/blueprints.d.ts +0 -370
- package/dist/kit/blueprints.d.ts.map +0 -1
- package/dist/kit/blueprints.js +0 -643
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messaging stores — typed inboxes over the three message-shaped realtime
|
|
3
|
+
* surfaces: channel messages ({@link ChannelInbox}), direct actor-to-actor
|
|
4
|
+
* messages ({@link ActorInbox}), and app-defined client/server events
|
|
5
|
+
* ({@link EventRouter}). Each keeps a queryable history and dispatches typed
|
|
6
|
+
* callbacks, replacing the per-app chat rings and payload plumbing.
|
|
7
|
+
*/
|
|
8
|
+
import type { ChunkCoordinatesInput } from '../generated/graphql.js';
|
|
9
|
+
import { type StateCodec } from './codec.js';
|
|
10
|
+
import type { WorldSessionContext } from './session.js';
|
|
11
|
+
/** A decoded message in a {@link ChannelInbox} or {@link ActorInbox}. */
|
|
12
|
+
export interface InboxMessage<T> {
|
|
13
|
+
/** The channel id (channel messages) or the notification uuid (actor messages). */
|
|
14
|
+
channelId?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The uuid on the wire. For channel messages this is the SENDER's actor
|
|
17
|
+
* uuid; for single-actor messages the payload is opaque to the server, so
|
|
18
|
+
* apps conventionally embed the sender identity in the payload itself.
|
|
19
|
+
*/
|
|
20
|
+
uuid: string;
|
|
21
|
+
/** The decoded payload. */
|
|
22
|
+
payload: T;
|
|
23
|
+
epochMillis: number;
|
|
24
|
+
receivedAt: number;
|
|
25
|
+
}
|
|
26
|
+
/** Options for {@link attachChannelInbox}. */
|
|
27
|
+
export interface ChannelInboxConfig<T = string> {
|
|
28
|
+
/** Codec for message payloads. Defaults to UTF-8 text. */
|
|
29
|
+
codec?: StateCodec<T>;
|
|
30
|
+
/** Messages kept per channel (oldest dropped). Defaults to 100. */
|
|
31
|
+
capacity?: number;
|
|
32
|
+
/**
|
|
33
|
+
* The sender uuid stamped on outbound messages. Wired from the session's
|
|
34
|
+
* local actor automatically; a random uuid otherwise.
|
|
35
|
+
*/
|
|
36
|
+
senderUuid?: string | (() => string | null);
|
|
37
|
+
/** Clock override for tests. Defaults to `Date.now`. */
|
|
38
|
+
now?: () => number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The SDK-managed **channel inbox**: every `channelMessage` notification is
|
|
42
|
+
* decoded and appended to a per-channel history ring (chronological, capped),
|
|
43
|
+
* with typed send + subscribe. This is the inbound channel fan-out most
|
|
44
|
+
* games never get around to writing.
|
|
45
|
+
*/
|
|
46
|
+
export declare class ChannelInbox<T = string> {
|
|
47
|
+
private readonly ctx;
|
|
48
|
+
private readonly config;
|
|
49
|
+
private readonly byChannel;
|
|
50
|
+
private readonly listeners;
|
|
51
|
+
private readonly codec;
|
|
52
|
+
private readonly capacity;
|
|
53
|
+
private readonly now;
|
|
54
|
+
private readonly fallbackUuid;
|
|
55
|
+
private decodeFailureCount;
|
|
56
|
+
private sequence;
|
|
57
|
+
constructor(ctx: WorldSessionContext, config?: ChannelInboxConfig<T>);
|
|
58
|
+
/** One channel's history, oldest first (capped at `capacity`). */
|
|
59
|
+
messages(channelId: string): Array<InboxMessage<T>>;
|
|
60
|
+
/** Channel ids with recorded history. */
|
|
61
|
+
channels(): string[];
|
|
62
|
+
/** Payloads that failed to decode (foreign encodings). */
|
|
63
|
+
get decodeFailures(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Subscribe to incoming messages — every channel, or one `channelId`.
|
|
66
|
+
* @returns off.
|
|
67
|
+
*/
|
|
68
|
+
onMessage(handler: (message: InboxMessage<T>) => void, channelId?: string): () => void;
|
|
69
|
+
/**
|
|
70
|
+
* Send a typed payload to a channel (requires membership with
|
|
71
|
+
* `send_messages`; delivery is app-wide, not chunk-routed).
|
|
72
|
+
*/
|
|
73
|
+
send(channelId: string, payload: T): Promise<boolean>;
|
|
74
|
+
/** Drop history (one channel, or all). */
|
|
75
|
+
clear(channelId?: string): void;
|
|
76
|
+
private ring;
|
|
77
|
+
private senderUuid;
|
|
78
|
+
private nextSequence;
|
|
79
|
+
}
|
|
80
|
+
/** Attach a {@link ChannelInbox}. Prefer the `channelInbox` config key. */
|
|
81
|
+
export declare function attachChannelInbox<T = string>(ctx: WorldSessionContext, config?: ChannelInboxConfig<T>): ChannelInbox<T>;
|
|
82
|
+
/** Options for {@link attachActorInbox}. */
|
|
83
|
+
export interface ActorInboxConfig<T = string> {
|
|
84
|
+
/** Codec for message payloads. Defaults to UTF-8 text. */
|
|
85
|
+
codec?: StateCodec<T>;
|
|
86
|
+
/** Messages kept (oldest dropped). Defaults to 100. */
|
|
87
|
+
capacity?: number;
|
|
88
|
+
/** Clock override for tests. Defaults to `Date.now`. */
|
|
89
|
+
now?: () => number;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The SDK-managed **direct-message inbox**: `singleActorMessage`
|
|
93
|
+
* notifications decoded into a capped history with typed subscribe, plus a
|
|
94
|
+
* typed `send` (the sender must know the target's current chunk — pair with
|
|
95
|
+
* a {@link RemoteActorStore}, whose records carry it).
|
|
96
|
+
*/
|
|
97
|
+
export declare class ActorInbox<T = string> {
|
|
98
|
+
private readonly ctx;
|
|
99
|
+
private readonly history;
|
|
100
|
+
private readonly listeners;
|
|
101
|
+
private readonly codec;
|
|
102
|
+
private readonly capacity;
|
|
103
|
+
private readonly now;
|
|
104
|
+
private decodeFailureCount;
|
|
105
|
+
private sequence;
|
|
106
|
+
constructor(ctx: WorldSessionContext, config?: ActorInboxConfig<T>);
|
|
107
|
+
/** Received messages, oldest first (capped at `capacity`). */
|
|
108
|
+
messages(): Array<InboxMessage<T>>;
|
|
109
|
+
/** Payloads that failed to decode. */
|
|
110
|
+
get decodeFailures(): number;
|
|
111
|
+
/** Subscribe to incoming direct messages. @returns off. */
|
|
112
|
+
onMessage(listener: (message: InboxMessage<T>) => void): () => void;
|
|
113
|
+
/**
|
|
114
|
+
* Send a typed payload to one actor (identified by uuid + its current
|
|
115
|
+
* chunk). Fire-and-forget: `true` means accepted for sending.
|
|
116
|
+
*/
|
|
117
|
+
send(targetUuid: string, payload: T, targetChunk: ChunkCoordinatesInput): Promise<boolean>;
|
|
118
|
+
/** Drop the history. */
|
|
119
|
+
clear(): void;
|
|
120
|
+
private nextSequence;
|
|
121
|
+
}
|
|
122
|
+
/** Attach an {@link ActorInbox}. Prefer the `actorInbox` config key. */
|
|
123
|
+
export declare function attachActorInbox<T = string>(ctx: WorldSessionContext, config?: ActorInboxConfig<T>): ActorInbox<T>;
|
|
124
|
+
/** A decoded client/server event delivered by the {@link EventRouter}. */
|
|
125
|
+
export interface TypedEvent<T> {
|
|
126
|
+
eventType: number;
|
|
127
|
+
/** Whether another client or the server (model notification) emitted it. */
|
|
128
|
+
origin: 'client' | 'server';
|
|
129
|
+
/** The emitting actor/source uuid. */
|
|
130
|
+
uuid: string;
|
|
131
|
+
value: T;
|
|
132
|
+
chunk: ChunkCoordinatesInput;
|
|
133
|
+
epochMillis: number;
|
|
134
|
+
receivedAt: number;
|
|
135
|
+
}
|
|
136
|
+
/** Options for {@link attachEventRouter}. */
|
|
137
|
+
export interface EventRouterConfig {
|
|
138
|
+
/**
|
|
139
|
+
* The sender uuid stamped on outbound events. Wired from the session's
|
|
140
|
+
* local actor automatically; a random uuid otherwise.
|
|
141
|
+
*/
|
|
142
|
+
senderUuid?: string | (() => string | null);
|
|
143
|
+
/** Replication radius for outbound events (0-8). */
|
|
144
|
+
distance?: number;
|
|
145
|
+
/** Clock override for tests. Defaults to `Date.now`. */
|
|
146
|
+
now?: () => number;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The SDK-managed **event router**: register a codec + handler per
|
|
150
|
+
* app-defined `eventType` (uint16) and receive typed client AND server
|
|
151
|
+
* events; the latest decoded event per type stays queryable via
|
|
152
|
+
* {@link lastEvent}. Send typed events with {@link send}.
|
|
153
|
+
*/
|
|
154
|
+
export declare class EventRouter {
|
|
155
|
+
private readonly ctx;
|
|
156
|
+
private readonly config;
|
|
157
|
+
private readonly registrations;
|
|
158
|
+
private readonly lastByType;
|
|
159
|
+
private readonly now;
|
|
160
|
+
private readonly fallbackUuid;
|
|
161
|
+
private decodeFailureCount;
|
|
162
|
+
private sequence;
|
|
163
|
+
constructor(ctx: WorldSessionContext, config?: EventRouterConfig);
|
|
164
|
+
/**
|
|
165
|
+
* Register a typed handler for one `eventType`. Multiple handlers (even
|
|
166
|
+
* with different codecs) may coexist per type.
|
|
167
|
+
* @returns off.
|
|
168
|
+
*/
|
|
169
|
+
on<T>(eventType: number, codec: StateCodec<T>, handler: (event: TypedEvent<T>) => void): () => void;
|
|
170
|
+
/** The latest decoded event of one type (undefined before the first). */
|
|
171
|
+
lastEvent<T = unknown>(eventType: number): TypedEvent<T> | undefined;
|
|
172
|
+
/** Events whose registered codec failed to decode. */
|
|
173
|
+
get decodeFailures(): number;
|
|
174
|
+
/**
|
|
175
|
+
* Send a typed app-defined event to a chunk (fanned out to nearby actors
|
|
176
|
+
* as a `ClientEventNotification`).
|
|
177
|
+
*/
|
|
178
|
+
send<T>(eventType: number, codec: StateCodec<T>, value: T, chunk: ChunkCoordinatesInput): Promise<boolean>;
|
|
179
|
+
private senderUuid;
|
|
180
|
+
private nextSequence;
|
|
181
|
+
}
|
|
182
|
+
/** Attach an {@link EventRouter}. Prefer the `events` config key. */
|
|
183
|
+
export declare function attachEventRouter(ctx: WorldSessionContext, config?: EventRouterConfig): EventRouter;
|
|
184
|
+
//# sourceMappingURL=inbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox.d.ts","sourceRoot":"","sources":["../../src/stores/inbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messaging stores — typed inboxes over the three message-shaped realtime
|
|
3
|
+
* surfaces: channel messages ({@link ChannelInbox}), direct actor-to-actor
|
|
4
|
+
* messages ({@link ActorInbox}), and app-defined client/server events
|
|
5
|
+
* ({@link EventRouter}). Each keeps a queryable history and dispatches typed
|
|
6
|
+
* callbacks, replacing the per-app chat rings and payload plumbing.
|
|
7
|
+
*/
|
|
8
|
+
import { generateCrowdyUuid } from '../utils.js';
|
|
9
|
+
import { textCodec } from './codec.js';
|
|
10
|
+
/**
|
|
11
|
+
* The SDK-managed **channel inbox**: every `channelMessage` notification is
|
|
12
|
+
* decoded and appended to a per-channel history ring (chronological, capped),
|
|
13
|
+
* with typed send + subscribe. This is the inbound channel fan-out most
|
|
14
|
+
* games never get around to writing.
|
|
15
|
+
*/
|
|
16
|
+
export class ChannelInbox {
|
|
17
|
+
constructor(ctx, config = {}) {
|
|
18
|
+
this.ctx = ctx;
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.byChannel = new Map();
|
|
21
|
+
this.listeners = new Set();
|
|
22
|
+
this.fallbackUuid = generateCrowdyUuid();
|
|
23
|
+
this.decodeFailureCount = 0;
|
|
24
|
+
this.sequence = 0;
|
|
25
|
+
this.codec = config.codec ?? textCodec;
|
|
26
|
+
this.capacity = Math.max(1, config.capacity ?? 100);
|
|
27
|
+
this.now = config.now ?? Date.now;
|
|
28
|
+
ctx.onDispose(ctx.on('channelMessage', (notification) => {
|
|
29
|
+
let payload;
|
|
30
|
+
try {
|
|
31
|
+
payload = this.codec.decode(notification.payload);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
this.decodeFailureCount += 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const message = {
|
|
38
|
+
channelId: String(notification.channelId),
|
|
39
|
+
uuid: notification.uuid,
|
|
40
|
+
payload,
|
|
41
|
+
epochMillis: Number(notification.epochMillis),
|
|
42
|
+
receivedAt: this.now(),
|
|
43
|
+
};
|
|
44
|
+
const ring = this.ring(String(notification.channelId));
|
|
45
|
+
ring.push(message);
|
|
46
|
+
if (ring.length > this.capacity)
|
|
47
|
+
ring.splice(0, ring.length - this.capacity);
|
|
48
|
+
for (const entry of [...this.listeners]) {
|
|
49
|
+
if (entry.channelId === undefined || entry.channelId === message.channelId) {
|
|
50
|
+
entry.handler(message);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
/** One channel's history, oldest first (capped at `capacity`). */
|
|
56
|
+
messages(channelId) {
|
|
57
|
+
return [...(this.byChannel.get(String(channelId)) ?? [])];
|
|
58
|
+
}
|
|
59
|
+
/** Channel ids with recorded history. */
|
|
60
|
+
channels() {
|
|
61
|
+
return [...this.byChannel.keys()];
|
|
62
|
+
}
|
|
63
|
+
/** Payloads that failed to decode (foreign encodings). */
|
|
64
|
+
get decodeFailures() {
|
|
65
|
+
return this.decodeFailureCount;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Subscribe to incoming messages — every channel, or one `channelId`.
|
|
69
|
+
* @returns off.
|
|
70
|
+
*/
|
|
71
|
+
onMessage(handler, channelId) {
|
|
72
|
+
const entry = {
|
|
73
|
+
channelId: channelId !== undefined ? String(channelId) : undefined,
|
|
74
|
+
handler,
|
|
75
|
+
};
|
|
76
|
+
this.listeners.add(entry);
|
|
77
|
+
return () => this.listeners.delete(entry);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Send a typed payload to a channel (requires membership with
|
|
81
|
+
* `send_messages`; delivery is app-wide, not chunk-routed).
|
|
82
|
+
*/
|
|
83
|
+
async send(channelId, payload) {
|
|
84
|
+
const sequenceNumber = this.nextSequence();
|
|
85
|
+
this.ctx.trackSend({
|
|
86
|
+
kind: 'channelMessage',
|
|
87
|
+
sequenceNumber,
|
|
88
|
+
sentAt: this.now(),
|
|
89
|
+
uuid: this.senderUuid(),
|
|
90
|
+
detail: { channelId: String(channelId) },
|
|
91
|
+
});
|
|
92
|
+
return this.ctx.client.udp.sendChannelMessage({
|
|
93
|
+
channelId,
|
|
94
|
+
uuid: this.senderUuid(),
|
|
95
|
+
payload: this.codec.encode(payload),
|
|
96
|
+
sequenceNumber,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** Drop history (one channel, or all). */
|
|
100
|
+
clear(channelId) {
|
|
101
|
+
if (channelId !== undefined)
|
|
102
|
+
this.byChannel.delete(String(channelId));
|
|
103
|
+
else
|
|
104
|
+
this.byChannel.clear();
|
|
105
|
+
}
|
|
106
|
+
ring(channelId) {
|
|
107
|
+
let ring = this.byChannel.get(channelId);
|
|
108
|
+
if (!ring) {
|
|
109
|
+
ring = [];
|
|
110
|
+
this.byChannel.set(channelId, ring);
|
|
111
|
+
}
|
|
112
|
+
return ring;
|
|
113
|
+
}
|
|
114
|
+
senderUuid() {
|
|
115
|
+
const configured = typeof this.config.senderUuid === 'function'
|
|
116
|
+
? this.config.senderUuid()
|
|
117
|
+
: this.config.senderUuid;
|
|
118
|
+
return configured ?? this.fallbackUuid;
|
|
119
|
+
}
|
|
120
|
+
nextSequence() {
|
|
121
|
+
this.sequence = (this.sequence + 1) % 256;
|
|
122
|
+
return this.sequence;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Attach a {@link ChannelInbox}. Prefer the `channelInbox` config key. */
|
|
126
|
+
export function attachChannelInbox(ctx, config = {}) {
|
|
127
|
+
return new ChannelInbox(ctx, config);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The SDK-managed **direct-message inbox**: `singleActorMessage`
|
|
131
|
+
* notifications decoded into a capped history with typed subscribe, plus a
|
|
132
|
+
* typed `send` (the sender must know the target's current chunk — pair with
|
|
133
|
+
* a {@link RemoteActorStore}, whose records carry it).
|
|
134
|
+
*/
|
|
135
|
+
export class ActorInbox {
|
|
136
|
+
constructor(ctx, config = {}) {
|
|
137
|
+
this.ctx = ctx;
|
|
138
|
+
this.history = [];
|
|
139
|
+
this.listeners = new Set();
|
|
140
|
+
this.decodeFailureCount = 0;
|
|
141
|
+
this.sequence = 0;
|
|
142
|
+
this.codec = config.codec ?? textCodec;
|
|
143
|
+
this.capacity = Math.max(1, config.capacity ?? 100);
|
|
144
|
+
this.now = config.now ?? Date.now;
|
|
145
|
+
ctx.onDispose(ctx.on('singleActorMessage', (notification) => {
|
|
146
|
+
let payload;
|
|
147
|
+
try {
|
|
148
|
+
payload = this.codec.decode(notification.payload);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
this.decodeFailureCount += 1;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const message = {
|
|
155
|
+
uuid: notification.uuid,
|
|
156
|
+
payload,
|
|
157
|
+
epochMillis: Number(notification.epochMillis),
|
|
158
|
+
receivedAt: this.now(),
|
|
159
|
+
};
|
|
160
|
+
this.history.push(message);
|
|
161
|
+
if (this.history.length > this.capacity) {
|
|
162
|
+
this.history.splice(0, this.history.length - this.capacity);
|
|
163
|
+
}
|
|
164
|
+
for (const listener of [...this.listeners])
|
|
165
|
+
listener(message);
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
/** Received messages, oldest first (capped at `capacity`). */
|
|
169
|
+
messages() {
|
|
170
|
+
return [...this.history];
|
|
171
|
+
}
|
|
172
|
+
/** Payloads that failed to decode. */
|
|
173
|
+
get decodeFailures() {
|
|
174
|
+
return this.decodeFailureCount;
|
|
175
|
+
}
|
|
176
|
+
/** Subscribe to incoming direct messages. @returns off. */
|
|
177
|
+
onMessage(listener) {
|
|
178
|
+
this.listeners.add(listener);
|
|
179
|
+
return () => this.listeners.delete(listener);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Send a typed payload to one actor (identified by uuid + its current
|
|
183
|
+
* chunk). Fire-and-forget: `true` means accepted for sending.
|
|
184
|
+
*/
|
|
185
|
+
async send(targetUuid, payload, targetChunk) {
|
|
186
|
+
const sequenceNumber = this.nextSequence();
|
|
187
|
+
this.ctx.trackSend({
|
|
188
|
+
kind: 'singleActorMessage',
|
|
189
|
+
sequenceNumber,
|
|
190
|
+
sentAt: this.now(),
|
|
191
|
+
detail: { targetUuid },
|
|
192
|
+
});
|
|
193
|
+
return this.ctx.client.udp.sendSingleActorMessage({
|
|
194
|
+
appId: this.ctx.appId,
|
|
195
|
+
chunk: targetChunk,
|
|
196
|
+
targetUuid,
|
|
197
|
+
payload: this.codec.encode(payload),
|
|
198
|
+
sequenceNumber,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/** Drop the history. */
|
|
202
|
+
clear() {
|
|
203
|
+
this.history.length = 0;
|
|
204
|
+
}
|
|
205
|
+
nextSequence() {
|
|
206
|
+
this.sequence = (this.sequence + 1) % 256;
|
|
207
|
+
return this.sequence;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Attach an {@link ActorInbox}. Prefer the `actorInbox` config key. */
|
|
211
|
+
export function attachActorInbox(ctx, config = {}) {
|
|
212
|
+
return new ActorInbox(ctx, config);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* The SDK-managed **event router**: register a codec + handler per
|
|
216
|
+
* app-defined `eventType` (uint16) and receive typed client AND server
|
|
217
|
+
* events; the latest decoded event per type stays queryable via
|
|
218
|
+
* {@link lastEvent}. Send typed events with {@link send}.
|
|
219
|
+
*/
|
|
220
|
+
export class EventRouter {
|
|
221
|
+
constructor(ctx, config = {}) {
|
|
222
|
+
this.ctx = ctx;
|
|
223
|
+
this.config = config;
|
|
224
|
+
this.registrations = new Map();
|
|
225
|
+
this.lastByType = new Map();
|
|
226
|
+
this.fallbackUuid = generateCrowdyUuid();
|
|
227
|
+
this.decodeFailureCount = 0;
|
|
228
|
+
this.sequence = 0;
|
|
229
|
+
this.now = config.now ?? Date.now;
|
|
230
|
+
const dispatch = (origin) => (notification) => {
|
|
231
|
+
const set = this.registrations.get(notification.eventType);
|
|
232
|
+
if (!set || set.size === 0)
|
|
233
|
+
return;
|
|
234
|
+
for (const registration of [...set]) {
|
|
235
|
+
let value;
|
|
236
|
+
try {
|
|
237
|
+
value = registration.codec.decode(notification.state);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
this.decodeFailureCount += 1;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const event = {
|
|
244
|
+
eventType: notification.eventType,
|
|
245
|
+
origin,
|
|
246
|
+
uuid: notification.uuid,
|
|
247
|
+
value,
|
|
248
|
+
chunk: {
|
|
249
|
+
x: notification.chunkX,
|
|
250
|
+
y: notification.chunkY,
|
|
251
|
+
z: notification.chunkZ,
|
|
252
|
+
},
|
|
253
|
+
epochMillis: Number(notification.epochMillis),
|
|
254
|
+
receivedAt: this.now(),
|
|
255
|
+
};
|
|
256
|
+
this.lastByType.set(notification.eventType, event);
|
|
257
|
+
registration.handler(event);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
ctx.onDispose(ctx.on('clientEvent', dispatch('client')));
|
|
261
|
+
ctx.onDispose(ctx.on('serverEvent', dispatch('server')));
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Register a typed handler for one `eventType`. Multiple handlers (even
|
|
265
|
+
* with different codecs) may coexist per type.
|
|
266
|
+
* @returns off.
|
|
267
|
+
*/
|
|
268
|
+
on(eventType, codec, handler) {
|
|
269
|
+
let set = this.registrations.get(eventType);
|
|
270
|
+
if (!set) {
|
|
271
|
+
set = new Set();
|
|
272
|
+
this.registrations.set(eventType, set);
|
|
273
|
+
}
|
|
274
|
+
const registration = {
|
|
275
|
+
codec: codec,
|
|
276
|
+
handler: handler,
|
|
277
|
+
};
|
|
278
|
+
set.add(registration);
|
|
279
|
+
return () => set.delete(registration);
|
|
280
|
+
}
|
|
281
|
+
/** The latest decoded event of one type (undefined before the first). */
|
|
282
|
+
lastEvent(eventType) {
|
|
283
|
+
return this.lastByType.get(eventType);
|
|
284
|
+
}
|
|
285
|
+
/** Events whose registered codec failed to decode. */
|
|
286
|
+
get decodeFailures() {
|
|
287
|
+
return this.decodeFailureCount;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Send a typed app-defined event to a chunk (fanned out to nearby actors
|
|
291
|
+
* as a `ClientEventNotification`).
|
|
292
|
+
*/
|
|
293
|
+
async send(eventType, codec, value, chunk) {
|
|
294
|
+
const sequenceNumber = this.nextSequence();
|
|
295
|
+
this.ctx.trackSend({
|
|
296
|
+
kind: 'clientEvent',
|
|
297
|
+
sequenceNumber,
|
|
298
|
+
sentAt: this.now(),
|
|
299
|
+
uuid: this.senderUuid(),
|
|
300
|
+
detail: { eventType },
|
|
301
|
+
});
|
|
302
|
+
return this.ctx.client.udp.sendClientEvent({
|
|
303
|
+
appId: this.ctx.appId,
|
|
304
|
+
chunk,
|
|
305
|
+
uuid: this.senderUuid(),
|
|
306
|
+
eventType,
|
|
307
|
+
state: codec.encode(value),
|
|
308
|
+
sequenceNumber,
|
|
309
|
+
...(this.config.distance !== undefined ? { distance: this.config.distance } : {}),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
senderUuid() {
|
|
313
|
+
const configured = typeof this.config.senderUuid === 'function'
|
|
314
|
+
? this.config.senderUuid()
|
|
315
|
+
: this.config.senderUuid;
|
|
316
|
+
return configured ?? this.fallbackUuid;
|
|
317
|
+
}
|
|
318
|
+
nextSequence() {
|
|
319
|
+
this.sequence = (this.sequence + 1) % 256;
|
|
320
|
+
return this.sequence;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/** Attach an {@link EventRouter}. Prefer the `events` config key. */
|
|
324
|
+
export function attachEventRouter(ctx, config = {}) {
|
|
325
|
+
return new EventRouter(ctx, config);
|
|
326
|
+
}
|