archetype-ecs-net 0.1.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,166 @@
1
+ import { createSnapshotDiffer } from './DirtyTracker.js';
2
+ import { createClientView } from './InterestManager.js';
3
+ import { ProtocolEncoder } from './Protocol.js';
4
+ const EMPTY_KEY = '||';
5
+ function deltaKey(d) {
6
+ if (d.enters.length === 0 && d.leaves.length === 0 && d.updates.length === 0)
7
+ return EMPTY_KEY;
8
+ // Sort copies — don't mutate the original arrays
9
+ const e = d.enters.length > 1 ? d.enters.slice().sort((a, b) => a - b) : d.enters;
10
+ const l = d.leaves.length > 1 ? d.leaves.slice().sort((a, b) => a - b) : d.leaves;
11
+ const u = d.updates.length > 1 ? d.updates.slice().sort((a, b) => a - b) : d.updates;
12
+ return `${e.join(',')}|${l.join(',')}|${u.join(',')}`;
13
+ }
14
+ // ── ws transport (default) ──────────────────────────────
15
+ export function createWsTransport() {
16
+ let wss = null;
17
+ const clients = new Map();
18
+ return {
19
+ async start(port, handlers) {
20
+ const { WebSocketServer } = await import('ws');
21
+ let nextId = 1;
22
+ wss = new WebSocketServer({ port });
23
+ await new Promise((resolve, reject) => {
24
+ wss.once('listening', resolve);
25
+ wss.once('error', reject);
26
+ });
27
+ wss.on('connection', (ws) => {
28
+ const clientId = nextId++;
29
+ clients.set(clientId, ws);
30
+ handlers.onOpen(clientId);
31
+ ws.on('message', (data) => {
32
+ const ab = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
33
+ handlers.onMessage(clientId, ab);
34
+ });
35
+ ws.on('close', () => {
36
+ clients.delete(clientId);
37
+ handlers.onClose(clientId);
38
+ });
39
+ });
40
+ },
41
+ async stop() {
42
+ if (wss) {
43
+ for (const ws of clients.values())
44
+ ws.terminate();
45
+ clients.clear();
46
+ await new Promise(r => wss.close(() => r()));
47
+ wss = null;
48
+ }
49
+ },
50
+ send(clientId, data) {
51
+ const ws = clients.get(clientId);
52
+ if (ws && ws.readyState === 1) {
53
+ ws.send(data);
54
+ }
55
+ },
56
+ broadcast(data) {
57
+ for (const ws of clients.values()) {
58
+ if (ws.readyState === 1)
59
+ ws.send(data);
60
+ }
61
+ },
62
+ };
63
+ }
64
+ export function createNetServer(em, registry, config, transport) {
65
+ const encoder = new ProtocolEncoder();
66
+ const differ = createSnapshotDiffer(em, registry);
67
+ const tp = transport ?? createWsTransport();
68
+ const clientIds = new Set();
69
+ const clientViews = new Map();
70
+ const server = {
71
+ onConnect: null,
72
+ onDisconnect: null,
73
+ onMessage: null,
74
+ get clientCount() {
75
+ return clientIds.size;
76
+ },
77
+ get entityNetIds() {
78
+ return differ.entityNetIds;
79
+ },
80
+ send(clientId, data) {
81
+ tp.send(clientId, data);
82
+ },
83
+ start() {
84
+ return tp.start(config.port, {
85
+ onOpen(clientId) {
86
+ clientIds.add(clientId);
87
+ clientViews.set(clientId, createClientView());
88
+ const fullState = encoder.encodeFullState(em, registry, differ.entityNetIds);
89
+ tp.send(clientId, fullState);
90
+ // Initialize the client view's known set to match the full state we just sent
91
+ const knownNetIds = new Set(differ.entityNetIds.values());
92
+ clientViews.get(clientId).initKnown(knownNetIds);
93
+ server.onConnect?.(clientId);
94
+ },
95
+ onClose(clientId) {
96
+ clientIds.delete(clientId);
97
+ clientViews.delete(clientId);
98
+ server.onDisconnect?.(clientId);
99
+ },
100
+ onMessage(clientId, data) {
101
+ server.onMessage?.(clientId, data);
102
+ },
103
+ });
104
+ },
105
+ async stop() {
106
+ await tp.stop();
107
+ clientIds.clear();
108
+ clientViews.clear();
109
+ },
110
+ tick(filter) {
111
+ if (!filter) {
112
+ const buffer = differ.diffAndEncode(encoder);
113
+ if (clientIds.size === 0)
114
+ return;
115
+ if (buffer.byteLength <= 7)
116
+ return;
117
+ tp.broadcast(buffer);
118
+ return;
119
+ }
120
+ const changeset = differ.computeChangeset();
121
+ // Phase 1: compute all client deltas, group by identical content
122
+ const groups = new Map();
123
+ const extraEnterNetIds = new Set();
124
+ for (const clientId of clientIds) {
125
+ const view = clientViews.get(clientId);
126
+ if (!view)
127
+ continue;
128
+ const interest = filter(clientId);
129
+ const delta = view.update(interest, changeset);
130
+ const key = deltaKey(delta);
131
+ if (key === EMPTY_KEY)
132
+ continue;
133
+ let group = groups.get(key);
134
+ if (!group) {
135
+ // Copy arrays — clientView reuses them on next update() call
136
+ group = {
137
+ delta: { enters: [...delta.enters], leaves: [...delta.leaves], updates: [...delta.updates] },
138
+ clients: [],
139
+ };
140
+ groups.set(key, group);
141
+ }
142
+ group.clients.push(clientId);
143
+ // Collect view-enters that aren't global creates (need pre-encoding)
144
+ for (const netId of delta.enters) {
145
+ if (!changeset.createdSet.has(netId))
146
+ extraEnterNetIds.add(netId);
147
+ }
148
+ }
149
+ if (groups.size === 0) {
150
+ differ.flushSnapshots();
151
+ return;
152
+ }
153
+ // Phase 2: pre-encode all entities once
154
+ const cache = differ.preEncodeChangeset(encoder, changeset, extraEnterNetIds);
155
+ // Phase 3: compose per group from cached slices, send to all clients
156
+ for (const group of groups.values()) {
157
+ const buffer = differ.composeFromCache(encoder, cache, group.delta);
158
+ for (const clientId of group.clients) {
159
+ tp.send(clientId, buffer);
160
+ }
161
+ }
162
+ differ.flushSnapshots();
163
+ },
164
+ };
165
+ return server;
166
+ }
@@ -0,0 +1,58 @@
1
+ import type { EntityId, EntityManager } from 'archetype-ecs';
2
+ import type { ComponentRegistry } from './ComponentRegistry.js';
3
+ import type { NetMessage, WireType } from './types.js';
4
+ export declare class ProtocolEncoder {
5
+ private buf;
6
+ private view;
7
+ private offset;
8
+ constructor(initialSize?: number);
9
+ private ensure;
10
+ writeU8(v: number): void;
11
+ writeU16(v: number): void;
12
+ writeU32(v: number): void;
13
+ private writeF32;
14
+ private writeF64;
15
+ private writeI8;
16
+ private writeI16;
17
+ private writeI32;
18
+ private writeString;
19
+ /** Reserve a u8 slot, returns the offset for backpatching */
20
+ reserveU8(): number;
21
+ /** Reserve a u16 slot, returns the offset for backpatching */
22
+ reserveU16(): number;
23
+ /** Write a u8 value at a previously reserved offset */
24
+ patchU8(off: number, v: number): void;
25
+ /** Write a u16 value at a previously reserved offset */
26
+ patchU16(off: number, v: number): void;
27
+ writeVarint(v: number): void;
28
+ writeBytes(data: Uint8Array): void;
29
+ writeField(type: WireType, value: unknown): void;
30
+ private writeComponentData;
31
+ /** Reset write position for reuse (no reallocation) */
32
+ reset(): void;
33
+ /** Returns a trimmed copy of the written bytes */
34
+ finish(): ArrayBuffer;
35
+ encodeFullState(em: EntityManager, registry: ComponentRegistry, entityNetIds: ReadonlyMap<EntityId, number>): ArrayBuffer;
36
+ }
37
+ export declare class ProtocolDecoder {
38
+ private view;
39
+ private bytes;
40
+ private offset;
41
+ private readU8;
42
+ private readU16;
43
+ private readU32;
44
+ private readF32;
45
+ private readF64;
46
+ private readI8;
47
+ private readI16;
48
+ private readI32;
49
+ private readString;
50
+ private readVarint;
51
+ private readField;
52
+ private readComponentData;
53
+ decode(buffer: ArrayBuffer, registry: ComponentRegistry): NetMessage;
54
+ private decodeFullState;
55
+ private decodeDelta;
56
+ }
57
+ export declare function encodeFullState(em: EntityManager, registry: ComponentRegistry, entityNetIds: ReadonlyMap<EntityId, number>): ArrayBuffer;
58
+ export declare function decode(buffer: ArrayBuffer, registry: ComponentRegistry): NetMessage;
@@ -0,0 +1,356 @@
1
+ import { MSG_DELTA, MSG_FULL } from './types.js';
2
+ // ── Encoder ─────────────────────────────────────────────
3
+ const INITIAL_BUFFER_SIZE = 4096;
4
+ const textEncoder = new TextEncoder();
5
+ export class ProtocolEncoder {
6
+ buf;
7
+ view;
8
+ offset = 0;
9
+ constructor(initialSize = INITIAL_BUFFER_SIZE) {
10
+ this.buf = new ArrayBuffer(initialSize);
11
+ this.view = new DataView(this.buf);
12
+ }
13
+ ensure(bytes) {
14
+ if (this.offset + bytes <= this.buf.byteLength)
15
+ return;
16
+ let newSize = this.buf.byteLength * 2;
17
+ while (newSize < this.offset + bytes)
18
+ newSize *= 2;
19
+ const newBuf = new ArrayBuffer(newSize);
20
+ new Uint8Array(newBuf).set(new Uint8Array(this.buf));
21
+ this.buf = newBuf;
22
+ this.view = new DataView(this.buf);
23
+ }
24
+ writeU8(v) {
25
+ this.ensure(1);
26
+ this.view.setUint8(this.offset, v);
27
+ this.offset += 1;
28
+ }
29
+ writeU16(v) {
30
+ this.ensure(2);
31
+ this.view.setUint16(this.offset, v, true);
32
+ this.offset += 2;
33
+ }
34
+ writeU32(v) {
35
+ this.ensure(4);
36
+ this.view.setUint32(this.offset, v, true);
37
+ this.offset += 4;
38
+ }
39
+ writeF32(v) {
40
+ this.ensure(4);
41
+ this.view.setFloat32(this.offset, v, true);
42
+ this.offset += 4;
43
+ }
44
+ writeF64(v) {
45
+ this.ensure(8);
46
+ this.view.setFloat64(this.offset, v, true);
47
+ this.offset += 8;
48
+ }
49
+ writeI8(v) {
50
+ this.ensure(1);
51
+ this.view.setInt8(this.offset, v);
52
+ this.offset += 1;
53
+ }
54
+ writeI16(v) {
55
+ this.ensure(2);
56
+ this.view.setInt16(this.offset, v, true);
57
+ this.offset += 2;
58
+ }
59
+ writeI32(v) {
60
+ this.ensure(4);
61
+ this.view.setInt32(this.offset, v, true);
62
+ this.offset += 4;
63
+ }
64
+ writeString(s) {
65
+ const encoded = textEncoder.encode(s);
66
+ this.writeU16(encoded.byteLength);
67
+ this.ensure(encoded.byteLength);
68
+ new Uint8Array(this.buf, this.offset, encoded.byteLength).set(encoded);
69
+ this.offset += encoded.byteLength;
70
+ }
71
+ /** Reserve a u8 slot, returns the offset for backpatching */
72
+ reserveU8() {
73
+ const off = this.offset;
74
+ this.writeU8(0);
75
+ return off;
76
+ }
77
+ /** Reserve a u16 slot, returns the offset for backpatching */
78
+ reserveU16() {
79
+ const off = this.offset;
80
+ this.writeU16(0);
81
+ return off;
82
+ }
83
+ /** Write a u8 value at a previously reserved offset */
84
+ patchU8(off, v) {
85
+ this.view.setUint8(off, v);
86
+ }
87
+ /** Write a u16 value at a previously reserved offset */
88
+ patchU16(off, v) {
89
+ this.view.setUint16(off, v, true);
90
+ }
91
+ writeVarint(v) {
92
+ if (v < 0 || v > 0xFFFFFFFF)
93
+ throw new RangeError(`Varint out of range: ${v}`);
94
+ while (v >= 0x80) {
95
+ this.writeU8((v & 0x7F) | 0x80);
96
+ v >>>= 7;
97
+ }
98
+ this.writeU8(v);
99
+ }
100
+ writeBytes(data) {
101
+ this.ensure(data.byteLength);
102
+ new Uint8Array(this.buf, this.offset, data.byteLength).set(data);
103
+ this.offset += data.byteLength;
104
+ }
105
+ writeField(type, value) {
106
+ switch (type) {
107
+ case 'f32':
108
+ this.writeF32(value);
109
+ break;
110
+ case 'f64':
111
+ this.writeF64(value);
112
+ break;
113
+ case 'i8':
114
+ this.writeI8(value);
115
+ break;
116
+ case 'i16':
117
+ this.writeI16(value);
118
+ break;
119
+ case 'i32':
120
+ this.writeI32(value);
121
+ break;
122
+ case 'u8':
123
+ this.writeU8(value);
124
+ break;
125
+ case 'u16':
126
+ this.writeU16(value);
127
+ break;
128
+ case 'u32':
129
+ this.writeU32(value);
130
+ break;
131
+ case 'string':
132
+ this.writeString(value);
133
+ break;
134
+ }
135
+ }
136
+ writeComponentData(fields, data) {
137
+ for (const field of fields) {
138
+ this.writeField(field.type, data[field.name]);
139
+ }
140
+ }
141
+ /** Reset write position for reuse (no reallocation) */
142
+ reset() {
143
+ this.offset = 0;
144
+ }
145
+ /** Returns a trimmed copy of the written bytes */
146
+ finish() {
147
+ return this.buf.slice(0, this.offset);
148
+ }
149
+ // ── Full state encoding ─────────────────────────────
150
+ encodeFullState(em, registry, entityNetIds) {
151
+ this.reset();
152
+ this.writeU8(MSG_FULL);
153
+ this.writeU32(registry.hash);
154
+ this.writeU16(entityNetIds.size);
155
+ for (const [entityId, netId] of entityNetIds) {
156
+ this.writeVarint(netId);
157
+ // Count components this entity has
158
+ const comps = [];
159
+ for (const reg of registry.components) {
160
+ if (em.hasComponent(entityId, reg.component)) {
161
+ const data = em.getComponent(entityId, reg.component);
162
+ if (data)
163
+ comps.push({ reg, data: data });
164
+ }
165
+ }
166
+ this.writeU8(comps.length);
167
+ for (const { reg, data } of comps) {
168
+ this.writeU8(reg.wireId);
169
+ this.writeComponentData(reg.fields, data);
170
+ }
171
+ }
172
+ return this.finish();
173
+ }
174
+ }
175
+ // ── Decoder ─────────────────────────────────────────────
176
+ const textDecoder = new TextDecoder();
177
+ export class ProtocolDecoder {
178
+ view;
179
+ bytes;
180
+ offset = 0;
181
+ readU8() {
182
+ const v = this.view.getUint8(this.offset);
183
+ this.offset += 1;
184
+ return v;
185
+ }
186
+ readU16() {
187
+ const v = this.view.getUint16(this.offset, true);
188
+ this.offset += 2;
189
+ return v;
190
+ }
191
+ readU32() {
192
+ const v = this.view.getUint32(this.offset, true);
193
+ this.offset += 4;
194
+ return v;
195
+ }
196
+ readF32() {
197
+ const v = this.view.getFloat32(this.offset, true);
198
+ this.offset += 4;
199
+ return v;
200
+ }
201
+ readF64() {
202
+ const v = this.view.getFloat64(this.offset, true);
203
+ this.offset += 8;
204
+ return v;
205
+ }
206
+ readI8() {
207
+ const v = this.view.getInt8(this.offset);
208
+ this.offset += 1;
209
+ return v;
210
+ }
211
+ readI16() {
212
+ const v = this.view.getInt16(this.offset, true);
213
+ this.offset += 2;
214
+ return v;
215
+ }
216
+ readI32() {
217
+ const v = this.view.getInt32(this.offset, true);
218
+ this.offset += 4;
219
+ return v;
220
+ }
221
+ readString() {
222
+ const len = this.readU16();
223
+ const str = textDecoder.decode(this.bytes.subarray(this.offset, this.offset + len));
224
+ this.offset += len;
225
+ return str;
226
+ }
227
+ readVarint() {
228
+ let v = 0;
229
+ let shift = 0;
230
+ let b;
231
+ do {
232
+ if (shift >= 35)
233
+ throw new Error('Varint too long (corrupt data or >5 bytes)');
234
+ b = this.readU8();
235
+ v |= (b & 0x7F) << shift;
236
+ shift += 7;
237
+ } while (b >= 0x80);
238
+ return v >>> 0;
239
+ }
240
+ readField(type) {
241
+ switch (type) {
242
+ case 'f32': return this.readF32();
243
+ case 'f64': return this.readF64();
244
+ case 'i8': return this.readI8();
245
+ case 'i16': return this.readI16();
246
+ case 'i32': return this.readI32();
247
+ case 'u8': return this.readU8();
248
+ case 'u16': return this.readU16();
249
+ case 'u32': return this.readU32();
250
+ case 'string': return this.readString();
251
+ }
252
+ }
253
+ readComponentData(fields) {
254
+ const data = {};
255
+ for (const field of fields) {
256
+ data[field.name] = this.readField(field.type);
257
+ }
258
+ return data;
259
+ }
260
+ decode(buffer, registry) {
261
+ this.view = new DataView(buffer);
262
+ this.bytes = new Uint8Array(buffer);
263
+ this.offset = 0;
264
+ const msgType = this.readU8();
265
+ if (msgType === MSG_FULL) {
266
+ return this.decodeFullState(registry);
267
+ }
268
+ else if (msgType === MSG_DELTA) {
269
+ return this.decodeDelta(registry);
270
+ }
271
+ throw new Error(`Unknown message type: 0x${msgType.toString(16)}`);
272
+ }
273
+ decodeFullState(registry) {
274
+ const remoteHash = this.readU32();
275
+ if (remoteHash !== registry.hash) {
276
+ throw new Error(`Registry mismatch: server hash 0x${remoteHash.toString(16)} !== client hash 0x${registry.hash.toString(16)}. ` +
277
+ `Ensure server and client use identical component registrations (same names, fields, types, and order).`);
278
+ }
279
+ const entityCount = this.readU16();
280
+ const entities = new Map();
281
+ for (let e = 0; e < entityCount; e++) {
282
+ const netId = this.readVarint();
283
+ const compCount = this.readU8();
284
+ const compMap = new Map();
285
+ for (let c = 0; c < compCount; c++) {
286
+ const wireId = this.readU8();
287
+ const reg = registry.byWireId(wireId);
288
+ if (!reg)
289
+ throw new Error(`Unknown wire ID: ${wireId}`);
290
+ compMap.set(wireId, this.readComponentData(reg.fields));
291
+ }
292
+ entities.set(netId, compMap);
293
+ }
294
+ return { type: MSG_FULL, entities };
295
+ }
296
+ decodeDelta(registry) {
297
+ // Created
298
+ const createdCount = this.readU16();
299
+ const created = new Map();
300
+ for (let i = 0; i < createdCount; i++) {
301
+ const netId = this.readVarint();
302
+ const compCount = this.readU8();
303
+ const compMap = new Map();
304
+ for (let c = 0; c < compCount; c++) {
305
+ const wireId = this.readU8();
306
+ const reg = registry.byWireId(wireId);
307
+ if (!reg)
308
+ throw new Error(`Unknown wire ID: ${wireId}`);
309
+ compMap.set(wireId, this.readComponentData(reg.fields));
310
+ }
311
+ created.set(netId, compMap);
312
+ }
313
+ // Destroyed
314
+ const destroyedCount = this.readU16();
315
+ const destroyed = [];
316
+ for (let i = 0; i < destroyedCount; i++) {
317
+ destroyed.push(this.readVarint());
318
+ }
319
+ // Updated (grouped by entity)
320
+ const updatedEntityCount = this.readU16();
321
+ const updated = [];
322
+ for (let i = 0; i < updatedEntityCount; i++) {
323
+ const netId = this.readVarint();
324
+ const compCount = this.readU8();
325
+ for (let c = 0; c < compCount; c++) {
326
+ const wireId = this.readU8();
327
+ const fieldMask = this.readU16();
328
+ const reg = registry.byWireId(wireId);
329
+ if (!reg)
330
+ throw new Error(`Unknown wire ID: ${wireId}`);
331
+ const validMask = (1 << reg.fields.length) - 1;
332
+ if (fieldMask & ~validMask) {
333
+ throw new Error(`Invalid field mask 0x${fieldMask.toString(16)} for wire ID ${wireId}: ` +
334
+ `has bits set beyond ${reg.fields.length} fields`);
335
+ }
336
+ const data = {};
337
+ for (let f = 0; f < reg.fields.length; f++) {
338
+ if (fieldMask & (1 << f)) {
339
+ data[reg.fields[f].name] = this.readField(reg.fields[f].type);
340
+ }
341
+ }
342
+ updated.push({ netId, componentWireId: wireId, fieldMask, data });
343
+ }
344
+ }
345
+ return { type: MSG_DELTA, created, destroyed, updated };
346
+ }
347
+ }
348
+ // ── Convenience functions ───────────────────────────────
349
+ const sharedEncoder = new ProtocolEncoder();
350
+ const sharedDecoder = new ProtocolDecoder();
351
+ export function encodeFullState(em, registry, entityNetIds) {
352
+ return sharedEncoder.encodeFullState(em, registry, entityNetIds);
353
+ }
354
+ export function decode(buffer, registry) {
355
+ return sharedDecoder.decode(buffer, registry);
356
+ }
@@ -0,0 +1,9 @@
1
+ export { createComponentRegistry } from './ComponentRegistry.js';
2
+ export type { ComponentRegistry } from './ComponentRegistry.js';
3
+ export { Networked } from './DirtyTracker.js';
4
+ export { createNetServer, createWsTransport } from './NetServer.js';
5
+ export type { NetServer, ServerTransport, TransportHandlers } from './NetServer.js';
6
+ export { createNetClient } from './NetClient.js';
7
+ export type { NetClient } from './NetClient.js';
8
+ export type { NetworkConfig, ComponentRegistration, ClientId, } from './types.js';
9
+ export type { InterestFilter } from './InterestManager.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { createComponentRegistry } from './ComponentRegistry.js';
2
+ export { Networked } from './DirtyTracker.js';
3
+ export { createNetServer, createWsTransport } from './NetServer.js';
4
+ export { createNetClient } from './NetClient.js';
@@ -0,0 +1,40 @@
1
+ import type { ComponentDef } from 'archetype-ecs';
2
+ export interface NetworkConfig {
3
+ port: number;
4
+ maxClients?: number;
5
+ }
6
+ export interface ComponentRegistration {
7
+ component: ComponentDef<any>;
8
+ name: string;
9
+ }
10
+ export type WireType = 'f32' | 'f64' | 'i8' | 'i16' | 'i32' | 'u8' | 'u16' | 'u32' | 'string';
11
+ export interface FieldInfo {
12
+ name: string;
13
+ type: WireType;
14
+ byteSize: number;
15
+ }
16
+ export interface RegisteredComponent {
17
+ wireId: number;
18
+ name: string;
19
+ component: ComponentDef<any>;
20
+ fields: FieldInfo[];
21
+ }
22
+ export declare const MSG_FULL = 1;
23
+ export declare const MSG_DELTA = 2;
24
+ export interface FullStateMessage {
25
+ type: typeof MSG_FULL;
26
+ entities: Map<number, Map<number, Record<string, unknown>>>;
27
+ }
28
+ export interface DeltaMessage {
29
+ type: typeof MSG_DELTA;
30
+ created: Map<number, Map<number, Record<string, unknown>>>;
31
+ destroyed: number[];
32
+ updated: {
33
+ netId: number;
34
+ componentWireId: number;
35
+ fieldMask: number;
36
+ data: Record<string, unknown>;
37
+ }[];
38
+ }
39
+ export type NetMessage = FullStateMessage | DeltaMessage;
40
+ export type ClientId = number;
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // ── Protocol messages ───────────────────────────────────
2
+ export const MSG_FULL = 0x01;
3
+ export const MSG_DELTA = 0x02;
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "archetype-ecs-net",
3
+ "version": "0.1.1",
4
+ "description": "Network layer for archetype-ecs: dirty tracking + binary delta sync",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "test": "tsc --noEmit && npx tsx --test tests/*.test.ts example/example.test.ts"
17
+ },
18
+ "keywords": [
19
+ "ecs",
20
+ "networking",
21
+ "gamedev",
22
+ "archetype-ecs",
23
+ "binary-protocol",
24
+ "delta-sync"
25
+ ],
26
+ "license": "MIT",
27
+ "peerDependencies": {
28
+ "archetype-ecs": ">=1.2.0"
29
+ },
30
+ "dependencies": {
31
+ "ws": "^8.19.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/ws": "^8.18.1",
35
+ "archetype-ecs": "^1.2.0",
36
+ "tsx": "^4.21.0",
37
+ "typescript": "^5.4.0"
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "src"
42
+ ]
43
+ }