@aethervtt/shard-net 0.0.0-stage → 0.4.1

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.
@@ -0,0 +1,239 @@
1
+ import { type Entity, ShardError, type World } from '@aethervtt/shard-core';
2
+ import { type PeerInfo } from './components.js';
3
+ import { type InterestPeer, type InterestPolicy, RelevanceSet } from './interest.js';
4
+ import type { NetCommandDef, NetEventDef } from './registry.js';
5
+ import type { Channel, NetConnection, NetListener, PeerId } from './transport.js';
6
+ import { NumberList } from './vec.js';
7
+ import { Fragmenter, type Manifest } from './wire.js';
8
+ /** What `accept` returns: `{ user }` (or nothing) admits, a ShardError refuses (0090). */
9
+ export type AcceptResult<U = unknown> = {
10
+ readonly user?: U;
11
+ } | ShardError | undefined;
12
+ export interface JoinContext<U = unknown> {
13
+ readonly peer: PeerId;
14
+ readonly user: U;
15
+ readonly token: string;
16
+ }
17
+ export interface LeaveContext<U = unknown> {
18
+ readonly peer: PeerId;
19
+ readonly user: U;
20
+ /** Entities whose `Replicated.owner` is the peer. */
21
+ readonly owned: readonly Entity[];
22
+ }
23
+ /**
24
+ * Loading and saving a player around a session (0090). The async hooks never get the world;
25
+ * the ones that get the world are synchronous, so nothing reads it after an `await`.
26
+ */
27
+ export interface PlayerHooks<U = unknown, L = unknown, S = unknown> {
28
+ /** Async: fetch what the player needs (a character row). Runs after accept, before welcome. */
29
+ load?(ctx: JoinContext<U>): L | Promise<L>;
30
+ /** Sync, in `net/receive`: spawn the player's entities, owned by `ctx.peer`. */
31
+ spawn?(world: World, ctx: JoinContext<U> & {
32
+ readonly data: L;
33
+ }): void;
34
+ /** Sync: read what to save from the owned entities, before they're despawned. */
35
+ capture?(world: World, ctx: LeaveContext<U>): S;
36
+ /** Async: write it. A rejection fires `PlayerSaveFailed`. */
37
+ store?(ctx: {
38
+ readonly peer: PeerId;
39
+ readonly user: U;
40
+ readonly data: S;
41
+ }): void | Promise<void>;
42
+ /** Despawn the owned entities when the player leaves. Default true. */
43
+ despawn?: boolean;
44
+ }
45
+ export interface ServerConfig {
46
+ readonly manifest: Manifest;
47
+ readonly interest: InterestPolicy;
48
+ readonly ticksPerNet: number;
49
+ readonly maxPeers: number;
50
+ readonly bytesPerTick: number;
51
+ readonly maxMessage: number;
52
+ readonly commandsPerSecond: number;
53
+ readonly accept?: (peer: PeerId, token: string, world: World) => AcceptResult | Promise<AcceptResult>;
54
+ readonly players?: PlayerHooks;
55
+ /** Milliseconds for accept + load before `net/join-timeout`. */
56
+ readonly joinTimeout: number;
57
+ readonly now: () => number;
58
+ }
59
+ /** What one peer has of one entity, as far as the server knows. */
60
+ interface Known {
61
+ readonly netId: number;
62
+ readonly entity: Entity;
63
+ /** Per manifest component: on the client. */
64
+ readonly present: Uint8Array;
65
+ /** Per component: the change tick last sent, and the newest one the client confirmed. */
66
+ readonly sent: Uint32Array;
67
+ readonly acked: Uint32Array;
68
+ /** Per component: the fixed tick it was last sent on, for resends. */
69
+ readonly sentAt: Float64Array;
70
+ /** The fixed tick anything of it was last sent: older entities go first under a budget. */
71
+ lastSent: number;
72
+ }
73
+ interface PacketRecord {
74
+ seq: number;
75
+ acked: boolean;
76
+ readonly netIds: NumberList;
77
+ readonly comps: NumberList;
78
+ readonly ticks: NumberList;
79
+ }
80
+ /** A command the server received, until a system answers it (0089). */
81
+ export declare class ServerCommand<T = Record<string, unknown>> {
82
+ readonly def: NetCommandDef;
83
+ readonly peer: PeerId;
84
+ readonly seq: number;
85
+ readonly data: T;
86
+ readonly arrivedTick: number;
87
+ status: 'pending' | 'accepted' | 'rejected';
88
+ error: ShardError | undefined;
89
+ private readonly answer;
90
+ constructor(def: NetCommandDef, peer: PeerId, seq: number, data: T, arrivedTick: number, answer: (cmd: ServerCommand<T>) => void);
91
+ accept(): void;
92
+ reject(code: string, message?: string, hint?: string): void;
93
+ }
94
+ interface QueuedEvent {
95
+ readonly index: number;
96
+ readonly data: Record<string, unknown>;
97
+ readonly to: PeerId | 'all' | ((peer: PeerId) => boolean);
98
+ }
99
+ interface Answer {
100
+ seq: number;
101
+ error: ShardError | undefined;
102
+ }
103
+ export interface PeerStats {
104
+ bytesSent: number;
105
+ bytesByComponent: Record<string, number>;
106
+ deferred: number;
107
+ resends: number;
108
+ commands: number;
109
+ rejected: number;
110
+ }
111
+ declare class PeerState implements InterestPeer {
112
+ readonly id: PeerId;
113
+ readonly info: PeerInfo;
114
+ readonly connection: NetConnection;
115
+ readonly fragmenter: Fragmenter;
116
+ readonly inbox: {
117
+ channel: Channel;
118
+ bytes: Uint8Array;
119
+ }[];
120
+ readonly known: Map<number, Known>;
121
+ relevant: RelevanceSet;
122
+ /** Last tick's relevant set, for hysteresis (`knows`). */
123
+ previous: RelevanceSet;
124
+ readonly focus: Entity[];
125
+ readonly packets: PacketRecord[];
126
+ readonly answers: Answer[];
127
+ nextSeq: number;
128
+ closed: string | undefined;
129
+ /** When the join started (config.now), for the timeout. */
130
+ joinStarted: number;
131
+ /** Command rate limit: tokens refill at commandsPerSecond. */
132
+ tokens: number;
133
+ lastRefill: number;
134
+ readonly stats: PeerStats;
135
+ constructor(connection: NetConnection, id: PeerId, tick: number, tokens: number);
136
+ /** Whether the peer had the entity after the last network tick. */
137
+ knows(entity: Entity): boolean;
138
+ }
139
+ export declare class NetServer {
140
+ readonly world: World;
141
+ readonly config: ServerConfig;
142
+ readonly peers: Map<number, PeerState>;
143
+ /** The same peers as an array, so per-frame loops don't allocate iterators. */
144
+ private readonly peerList;
145
+ readonly byNetId: Map<number, number>;
146
+ readonly commands: ServerCommand[];
147
+ /** Recent commands for `net.commands`. */
148
+ readonly recentCommands: ServerCommand[];
149
+ readonly recentEvents: {
150
+ name: string;
151
+ tick: number;
152
+ to: string;
153
+ }[];
154
+ private nextNetId;
155
+ private nextPeer;
156
+ /** Joins whose async accept/load finished, applied in the next receive. */
157
+ private readonly settled;
158
+ private readonly saveFailures;
159
+ private readonly stores;
160
+ private readonly incoming;
161
+ private readonly events;
162
+ private readonly codecs;
163
+ private readonly commandCodecs;
164
+ private readonly eventCodecs;
165
+ private readonly commandIndex;
166
+ private readonly eventIndex;
167
+ private readonly reader;
168
+ private readonly out;
169
+ private readonly state;
170
+ private readonly spawns;
171
+ private readonly sets;
172
+ private readonly removes;
173
+ private readonly despawns;
174
+ private readonly tail;
175
+ private current;
176
+ private pendingRef;
177
+ private despawnCount;
178
+ private lastNetTick;
179
+ private readonly unassigned;
180
+ private readonly replicatedQuery;
181
+ private readonly focusQuery;
182
+ private readonly relevanceQuery;
183
+ private readonly newEntities;
184
+ private readonly candidates;
185
+ private readonly scores;
186
+ private order;
187
+ private readonly despawnKnown;
188
+ listener: NetListener | undefined;
189
+ private unsubscribe;
190
+ constructor(world: World, config: ServerConfig);
191
+ attach(listener: NetListener): void;
192
+ /** Saves every connected player (0090), closes their connections, and waits for the stores. */
193
+ dispose(): Promise<void>;
194
+ /** Resolves when every player store in flight has settled. */
195
+ flush(): Promise<void>;
196
+ private toWire;
197
+ private fromWire;
198
+ netIdOf(entity: Entity): number | undefined;
199
+ receive(): void;
200
+ private handle;
201
+ private hello;
202
+ private join;
203
+ private joinAsync;
204
+ /** Applies finished joins, and times out the slow ones. */
205
+ private settleJoins;
206
+ private finishJoin;
207
+ private ownedBy;
208
+ /** Captures a connected player's data and starts its store. Undefined without hooks. */
209
+ save(peer: PeerId): Promise<void> | undefined;
210
+ private captureAndStore;
211
+ private saveFailed;
212
+ /** A connected peer left: save it, despawn what it owned, tell the game. */
213
+ private leave;
214
+ private refuse;
215
+ private drop;
216
+ private sendDisconnect;
217
+ /** Disconnects a peer (`net.disconnect`). */
218
+ disconnect(peer: PeerId, reason?: string): boolean;
219
+ private ack;
220
+ private command;
221
+ private remember;
222
+ /** Delivers a command from the server's own world (single-player, or a host's local player). */
223
+ local(def: NetCommandDef, data: Record<string, unknown>, onAnswer: (cmd: ServerCommand) => void): ServerCommand<Record<string, unknown>>;
224
+ private answer;
225
+ /** Commands of one type still waiting for an answer. */
226
+ pending(def: NetCommandDef): ServerCommand[];
227
+ sendEvent(def: NetEventDef, data: Record<string, unknown>, to: QueuedEvent['to']): void;
228
+ commandIndexOf(def: NetCommandDef): number | undefined;
229
+ /** Runs in Last: answers unhandled commands, and on network ticks sends state to every peer. */
230
+ send(): void;
231
+ private assignIds;
232
+ private collectFocus;
233
+ private readonly keepRelevant;
234
+ private relevance;
235
+ private sendTo;
236
+ /** Describes one entity for `net.inspect`. */
237
+ inspect(entity: Entity): Record<string, unknown>;
238
+ }
239
+ export {};