@replicast/core 1.0.0-rc.7 → 1.0.0-rc.9
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/dist/decorators/entity.d.ts +43 -0
- package/dist/decorators/entity.d.ts.map +1 -0
- package/dist/decorators/entity.js +75 -0
- package/dist/decorators/entity.js.map +1 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +1 -0
- package/dist/decorators/index.js.map +1 -1
- package/dist/decorators/metadata.d.ts +5 -1
- package/dist/decorators/metadata.d.ts.map +1 -1
- package/dist/decorators/metadata.js.map +1 -1
- package/dist/entity/base-entity.d.ts +19 -0
- package/dist/entity/base-entity.d.ts.map +1 -0
- package/dist/entity/base-entity.js +18 -0
- package/dist/entity/base-entity.js.map +1 -0
- package/dist/entity/fields.d.ts +21 -0
- package/dist/entity/fields.d.ts.map +1 -0
- package/dist/entity/fields.js +67 -0
- package/dist/entity/fields.js.map +1 -0
- package/dist/entity/registry.d.ts +53 -0
- package/dist/entity/registry.d.ts.map +1 -0
- package/dist/entity/registry.js +167 -0
- package/dist/entity/registry.js.map +1 -0
- package/dist/entity/replicator.d.ts +40 -0
- package/dist/entity/replicator.d.ts.map +1 -0
- package/dist/entity/replicator.js +82 -0
- package/dist/entity/replicator.js.map +1 -0
- package/dist/entity/types.d.ts +29 -0
- package/dist/entity/types.d.ts.map +1 -0
- package/dist/entity/types.js +4 -0
- package/dist/entity/types.js.map +1 -0
- package/dist/entity/visibility.d.ts +11 -0
- package/dist/entity/visibility.d.ts.map +1 -0
- package/dist/entity/visibility.js +10 -0
- package/dist/entity/visibility.js.map +1 -0
- package/dist/entity/world.d.ts +67 -0
- package/dist/entity/world.d.ts.map +1 -0
- package/dist/entity/world.js +183 -0
- package/dist/entity/world.js.map +1 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/store/base-store.d.ts +9 -4
- package/dist/store/base-store.d.ts.map +1 -1
- package/dist/store/base-store.js +21 -18
- package/dist/store/base-store.js.map +1 -1
- package/dist/transport/app.d.ts +10 -2
- package/dist/transport/app.d.ts.map +1 -1
- package/dist/transport/app.js +4 -1
- package/dist/transport/app.js.map +1 -1
- package/dist/transport/client.d.ts +12 -0
- package/dist/transport/client.d.ts.map +1 -1
- package/dist/transport/client.js +31 -1
- package/dist/transport/client.js.map +1 -1
- package/dist/transport/protocol.d.ts +18 -1
- package/dist/transport/protocol.d.ts.map +1 -1
- package/dist/transport/protocol.js.map +1 -1
- package/dist/transport/server.d.ts +12 -0
- package/dist/transport/server.d.ts.map +1 -1
- package/dist/transport/server.js +74 -4
- package/dist/transport/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { snapshotFields, diffFields, ownerIdField } from './fields.js';
|
|
2
|
+
import { isEntityVisible } from './visibility.js';
|
|
3
|
+
/**
|
|
4
|
+
* Tracks, per client, which entities that client has been sent and the fields it last saw, and turns
|
|
5
|
+
* the current world into that client's `{ enter, update, leave }` delta.
|
|
6
|
+
*
|
|
7
|
+
* Cost is O(clients x entities) per flush — deliberate for now; a spatial index hook comes later.
|
|
8
|
+
*/
|
|
9
|
+
export class EntityReplicator {
|
|
10
|
+
/** playerId → entityId → last-sent field snapshot. */
|
|
11
|
+
views = new Map();
|
|
12
|
+
/** True when `playerId` currently has `id` in their set (i.e. as of the last flush). */
|
|
13
|
+
hasInView(playerId, id) {
|
|
14
|
+
return this.views.get(playerId)?.has(id) ?? false;
|
|
15
|
+
}
|
|
16
|
+
/** The clients that currently see `id` — the audience for a `@Multicast`. */
|
|
17
|
+
viewersOf(id) {
|
|
18
|
+
const viewers = [];
|
|
19
|
+
for (const [playerId, view] of this.views) {
|
|
20
|
+
if (view.has(id))
|
|
21
|
+
viewers.push(playerId);
|
|
22
|
+
}
|
|
23
|
+
return viewers;
|
|
24
|
+
}
|
|
25
|
+
/** Forget a disconnected client so they re-enter from scratch on reconnect. */
|
|
26
|
+
dropViewer(playerId) {
|
|
27
|
+
this.views.delete(playerId);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Records `id` as already sent to `playerId` with `fields`, without waiting for the next flush —
|
|
31
|
+
* for a caller that must guarantee delivery order (an `@Client`/`@Multicast` send that needs the
|
|
32
|
+
* recipient's mirror to exist before the rpc arrives). The next real `flush()` then sees this id
|
|
33
|
+
* already in `view` and sends only a diff, never a duplicate `enter`.
|
|
34
|
+
*/
|
|
35
|
+
recordEnter(playerId, id, fields) {
|
|
36
|
+
let view = this.views.get(playerId);
|
|
37
|
+
if (!view) {
|
|
38
|
+
view = new Map();
|
|
39
|
+
this.views.set(playerId, view);
|
|
40
|
+
}
|
|
41
|
+
view.set(id, fields);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Builds one client's delta and records it as sent. An entity is visible when the client owns it
|
|
45
|
+
* (owners always see their own entities) or the type's `visibleTo` hook allows it.
|
|
46
|
+
*/
|
|
47
|
+
computeSync(playerId, entries, world) {
|
|
48
|
+
let view = this.views.get(playerId);
|
|
49
|
+
if (!view) {
|
|
50
|
+
view = new Map();
|
|
51
|
+
this.views.set(playerId, view);
|
|
52
|
+
}
|
|
53
|
+
const sync = { enter: [], update: [], leave: [] };
|
|
54
|
+
const stillVisible = new Set();
|
|
55
|
+
for (const { id, entity, type, plan, visibleTo } of entries) {
|
|
56
|
+
const isOwner = entity[ownerIdField] === playerId;
|
|
57
|
+
if (!isEntityVisible(isOwner, playerId, entity, visibleTo, world))
|
|
58
|
+
continue;
|
|
59
|
+
stillVisible.add(id);
|
|
60
|
+
const fields = snapshotFields(entity, plan, isOwner);
|
|
61
|
+
const previous = view.get(id);
|
|
62
|
+
if (previous === undefined) {
|
|
63
|
+
sync.enter.push({ type, id, fields });
|
|
64
|
+
view.set(id, fields);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const patch = diffFields(previous, fields);
|
|
68
|
+
if (Object.keys(patch).length > 0) {
|
|
69
|
+
sync.update.push({ id, fields: patch });
|
|
70
|
+
view.set(id, fields);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const id of view.keys()) {
|
|
74
|
+
if (!stillVisible.has(id)) {
|
|
75
|
+
sync.leave.push(id);
|
|
76
|
+
view.delete(id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return sync;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=replicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replicator.js","sourceRoot":"","sources":["../../src/entity/replicator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Server-assigned entity id. Numeric so it stays compact on the msgpack wire. */
|
|
2
|
+
export type EntityId = number;
|
|
3
|
+
/** Any class usable as a replicated entity type: it must be constructible with no arguments. */
|
|
4
|
+
export type EntityConstructor<T extends object = object> = new () => T;
|
|
5
|
+
/** Read-only view of the server world handed to `visibleTo` / `canCall` hooks. */
|
|
6
|
+
export interface EntityWorldView {
|
|
7
|
+
get(id: EntityId): object | undefined;
|
|
8
|
+
byType<T extends object>(Type: EntityConstructor<T>): T[];
|
|
9
|
+
all(): object[];
|
|
10
|
+
}
|
|
11
|
+
/** One entity entering a client's view: the full replicated field set for that client. */
|
|
12
|
+
export interface EntityEnter {
|
|
13
|
+
type: string;
|
|
14
|
+
id: EntityId;
|
|
15
|
+
fields: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
/** One entity already in view whose replicated fields changed. */
|
|
18
|
+
export interface EntityUpdate {
|
|
19
|
+
id: EntityId;
|
|
20
|
+
fields: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
/** A single client's per-flush entity delta. */
|
|
23
|
+
export interface EntitySync {
|
|
24
|
+
enter: EntityEnter[];
|
|
25
|
+
update: EntityUpdate[];
|
|
26
|
+
leave: EntityId[];
|
|
27
|
+
}
|
|
28
|
+
export declare function isEmptySync(sync: EntitySync): boolean;
|
|
29
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/entity/types.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAA;AAE7B,gGAAgG;AAChG,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,UAAU,CAAC,CAAA;AAEtE,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAA;IACrC,MAAM,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAA;IACzD,GAAG,IAAI,MAAM,EAAE,CAAA;CAChB;AAED,0FAA0F;AAC1F,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,QAAQ,CAAA;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,QAAQ,CAAA;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,gDAAgD;AAChD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,KAAK,EAAE,QAAQ,EAAE,CAAA;CAClB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAErD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/entity/types.ts"],"names":[],"mappings":"AAiCA,MAAM,UAAU,WAAW,CAAC,IAAgB;IAC1C,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAA;AACvF,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { EntityWorldView } from './types.js';
|
|
2
|
+
/** Scopes replication and RPC visibility for an `@Entity` type. Default (undefined): visible to everyone. */
|
|
3
|
+
export type VisibleTo = (viewerId: string, entity: object, world: EntityWorldView) => boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Whether `viewerId` can see `entity`: an owner always can; everyone else defers to `visibleTo`
|
|
6
|
+
* (no hook means visible to everyone). Shared by replication (who gets `enter`/`update`) and by
|
|
7
|
+
* `@Server` RPC authorisation (`EntityWorld.invoke`), so a caller can never act on an entity the
|
|
8
|
+
* server would not otherwise show them.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isEntityVisible(isOwner: boolean, viewerId: string, entity: object, visibleTo: VisibleTo | undefined, world: EntityWorldView): boolean;
|
|
11
|
+
//# sourceMappingURL=visibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visibility.d.ts","sourceRoot":"","sources":["../../src/entity/visibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,6GAA6G;AAC7G,MAAM,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,KAAK,OAAO,CAAA;AAE7F;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,KAAK,EAAE,eAAe,GACrB,OAAO,CAET"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether `viewerId` can see `entity`: an owner always can; everyone else defers to `visibleTo`
|
|
3
|
+
* (no hook means visible to everyone). Shared by replication (who gets `enter`/`update`) and by
|
|
4
|
+
* `@Server` RPC authorisation (`EntityWorld.invoke`), so a caller can never act on an entity the
|
|
5
|
+
* server would not otherwise show them.
|
|
6
|
+
*/
|
|
7
|
+
export function isEntityVisible(isOwner, viewerId, entity, visibleTo, world) {
|
|
8
|
+
return isOwner || !visibleTo || visibleTo(viewerId, entity, world);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=visibility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visibility.js","sourceRoot":"","sources":["../../src/entity/visibility.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAgB,EAChB,QAAgB,EAChB,MAAc,EACd,SAAgC,EAChC,KAAsB;IAEtB,OAAO,OAAO,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;AACpE,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { EntityConstructor, EntityId, EntitySync, EntityWorldView } from './types.js';
|
|
2
|
+
/** How the world reaches connected clients. Injected so the world stays testable without sockets. */
|
|
3
|
+
export interface EntityTransport {
|
|
4
|
+
/** Push one entity-scoped `@Client` / `@Multicast` call to a client. */
|
|
5
|
+
sendRpc(playerId: string, id: EntityId, method: string, args: unknown[]): void;
|
|
6
|
+
/** Push one client their `{ enter, update, leave }` delta. */
|
|
7
|
+
sendSync(playerId: string, sync: EntitySync): void;
|
|
8
|
+
/** The currently connected players. */
|
|
9
|
+
players(): string[];
|
|
10
|
+
}
|
|
11
|
+
export interface EntityWorldOptions {
|
|
12
|
+
/** Entity classes registered up front (also auto-registered lazily on first spawn). */
|
|
13
|
+
types?: EntityConstructor<object>[];
|
|
14
|
+
transport?: EntityTransport;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Server-side entity registry: spawns entities, runs their `@ServerTick`, authorises their
|
|
18
|
+
* `@Server` calls, and flushes each client's interest-scoped delta.
|
|
19
|
+
*/
|
|
20
|
+
export declare class EntityWorld implements EntityWorldView {
|
|
21
|
+
private entities;
|
|
22
|
+
private infoByCtor;
|
|
23
|
+
private replicator;
|
|
24
|
+
private transport;
|
|
25
|
+
private nextId;
|
|
26
|
+
constructor(options?: EntityWorldOptions);
|
|
27
|
+
private register;
|
|
28
|
+
private infoOf;
|
|
29
|
+
/** Registers an entity, assigns its id and owner, and wires its server → client dispatch. */
|
|
30
|
+
spawn<T extends object>(entity: T, options?: {
|
|
31
|
+
ownerId?: string | null;
|
|
32
|
+
}): T;
|
|
33
|
+
despawn(id: EntityId): void;
|
|
34
|
+
get(id: EntityId): object | undefined;
|
|
35
|
+
byType<T extends object>(Type: EntityConstructor<T>): T[];
|
|
36
|
+
all(): object[];
|
|
37
|
+
/** Forget a disconnected client's view so they re-enter from scratch on reconnect. */
|
|
38
|
+
dropViewer(playerId: string): void;
|
|
39
|
+
/** Runs every entity's `@ServerTick` methods. Called by the server's fixed-step scheduler. */
|
|
40
|
+
tick(dt: number): void;
|
|
41
|
+
/** Computes and sends every connected client's delta. Called at the replication rate. */
|
|
42
|
+
flush(): void;
|
|
43
|
+
private replicationEntries;
|
|
44
|
+
/**
|
|
45
|
+
* Runs a client-initiated `@Server` call on one entity after checking the caller is allowed.
|
|
46
|
+
* Rejects on an unknown entity, a method that is not `@Server` on that type, an entity the caller
|
|
47
|
+
* cannot see, or a failed `canCall` — `canCall` alone would otherwise let a `canCall: 'anyone'`
|
|
48
|
+
* entity be driven by a client the `visibleTo` hook was written to hide it from entirely.
|
|
49
|
+
*/
|
|
50
|
+
invoke(callerId: string, id: EntityId, method: string, args: unknown[]): Promise<unknown>;
|
|
51
|
+
private canCall;
|
|
52
|
+
/**
|
|
53
|
+
* Replaces `@Client` / `@Multicast` methods on this instance with sends, so the server stays
|
|
54
|
+
* independent of the Vite transform (plain tsx/node works) and tick code can call them directly.
|
|
55
|
+
*
|
|
56
|
+
* Both recompute visibility live against `this.transport.players()` rather than asking the
|
|
57
|
+
* replicator who was already sent an `enter` for this id, because `flush()` runs on its own timer:
|
|
58
|
+
* an entity spawned and called on within the same tick would otherwise have no recorded viewers yet
|
|
59
|
+
* and the rpc would reach nobody. That alone isn't sufficient though — a recipient who is newly
|
|
60
|
+
* visible but has not yet had a flush also has no client-side mirror to run the rpc against, so
|
|
61
|
+
* `sendToRecipient` first catches that one recipient up with a synthetic `enter` (recorded into the
|
|
62
|
+
* replicator so the next real flush sends a diff, not a duplicate enter) before sending the rpc, to
|
|
63
|
+
* guarantee enter-before-rpc per `(player, entity)`.
|
|
64
|
+
*/
|
|
65
|
+
private bindClientDispatch;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=world.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../../src/entity/world.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE1F,qGAAqG;AACrG,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC9E,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;IAClD,uCAAuC;IACvC,OAAO,IAAI,MAAM,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,uFAAuF;IACvF,KAAK,CAAC,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAA;IACnC,SAAS,CAAC,EAAE,eAAe,CAAA;CAC5B;AAeD;;;GAGG;AACH,qBAAa,WAAY,YAAW,eAAe;IACjD,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,MAAM,CAAI;gBAEN,OAAO,GAAE,kBAAuB;IAK5C,OAAO,CAAC,QAAQ;IAuBhB,OAAO,CAAC,MAAM;IAId,6FAA6F;IAC7F,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAO,GAAG,CAAC;IAahF,OAAO,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI;IAI3B,GAAG,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS;IAIrC,MAAM,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;IAQzD,GAAG,IAAI,MAAM,EAAE;IAIf,sFAAsF;IACtF,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAIlC,8FAA8F;IAC9F,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAQtB,yFAAyF;IACzF,KAAK,IAAI,IAAI;IAQb,OAAO,CAAC,kBAAkB;IAS1B;;;;;OAKG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAmB/F,OAAO,CAAC,OAAO;IAOf;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,kBAAkB;CA2B3B"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { getMethodRoles } from '../decorators/metadata.js';
|
|
2
|
+
import { getEntityHooks, getEntityName, getEntityOptions, isEntityClass } from '../decorators/entity.js';
|
|
3
|
+
import { fieldPlanOf, ownerIdField, snapshotFields } from './fields.js';
|
|
4
|
+
import { EntityReplicator } from './replicator.js';
|
|
5
|
+
import { isEntityVisible } from './visibility.js';
|
|
6
|
+
import { isEmptySync } from './types.js';
|
|
7
|
+
const noopTransport = {
|
|
8
|
+
sendRpc() { }, sendSync() { }, players: () => [],
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Server-side entity registry: spawns entities, runs their `@ServerTick`, authorises their
|
|
12
|
+
* `@Server` calls, and flushes each client's interest-scoped delta.
|
|
13
|
+
*/
|
|
14
|
+
export class EntityWorld {
|
|
15
|
+
entities = new Map();
|
|
16
|
+
infoByCtor = new Map();
|
|
17
|
+
replicator = new EntityReplicator();
|
|
18
|
+
transport;
|
|
19
|
+
nextId = 1;
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.transport = options.transport ?? noopTransport;
|
|
22
|
+
for (const Type of options.types ?? [])
|
|
23
|
+
this.register(Type);
|
|
24
|
+
}
|
|
25
|
+
register(Type) {
|
|
26
|
+
const existing = this.infoByCtor.get(Type);
|
|
27
|
+
if (existing)
|
|
28
|
+
return existing;
|
|
29
|
+
if (!isEntityClass(Type)) {
|
|
30
|
+
throw new Error(`${Type.name || 'class'} is not decorated with @Entity`);
|
|
31
|
+
}
|
|
32
|
+
const proto = Type.prototype;
|
|
33
|
+
const hooks = getEntityHooks(proto);
|
|
34
|
+
const roles = getMethodRoles(proto);
|
|
35
|
+
const info = {
|
|
36
|
+
name: getEntityName(Type),
|
|
37
|
+
plan: fieldPlanOf(Type),
|
|
38
|
+
options: getEntityOptions(Type) ?? {},
|
|
39
|
+
serverTicks: Object.keys(hooks).filter(m => hooks[m] === 'serverTick'),
|
|
40
|
+
clientMethods: Object.fromEntries(Object.entries(roles).filter(([, r]) => r === 'client' || r === 'multicast')),
|
|
41
|
+
serverMethods: new Set(Object.keys(roles).filter(m => roles[m] === 'server')),
|
|
42
|
+
};
|
|
43
|
+
this.infoByCtor.set(Type, info);
|
|
44
|
+
return info;
|
|
45
|
+
}
|
|
46
|
+
infoOf(entity) {
|
|
47
|
+
return this.register(Object.getPrototypeOf(entity).constructor);
|
|
48
|
+
}
|
|
49
|
+
/** Registers an entity, assigns its id and owner, and wires its server → client dispatch. */
|
|
50
|
+
spawn(entity, options = {}) {
|
|
51
|
+
const info = this.infoOf(entity);
|
|
52
|
+
const id = this.nextId++;
|
|
53
|
+
const target = entity;
|
|
54
|
+
target['id'] = id;
|
|
55
|
+
target['ownerId'] = options.ownerId ?? null;
|
|
56
|
+
target['isServer'] = true;
|
|
57
|
+
target['hasAuthority'] = true;
|
|
58
|
+
this.entities.set(id, entity);
|
|
59
|
+
this.bindClientDispatch(entity, id, info);
|
|
60
|
+
return entity;
|
|
61
|
+
}
|
|
62
|
+
despawn(id) {
|
|
63
|
+
this.entities.delete(id);
|
|
64
|
+
}
|
|
65
|
+
get(id) {
|
|
66
|
+
return this.entities.get(id);
|
|
67
|
+
}
|
|
68
|
+
byType(Type) {
|
|
69
|
+
const out = [];
|
|
70
|
+
for (const entity of this.entities.values()) {
|
|
71
|
+
if (entity instanceof Type)
|
|
72
|
+
out.push(entity);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
all() {
|
|
77
|
+
return [...this.entities.values()];
|
|
78
|
+
}
|
|
79
|
+
/** Forget a disconnected client's view so they re-enter from scratch on reconnect. */
|
|
80
|
+
dropViewer(playerId) {
|
|
81
|
+
this.replicator.dropViewer(playerId);
|
|
82
|
+
}
|
|
83
|
+
/** Runs every entity's `@ServerTick` methods. Called by the server's fixed-step scheduler. */
|
|
84
|
+
tick(dt) {
|
|
85
|
+
for (const entity of [...this.entities.values()]) {
|
|
86
|
+
for (const method of this.infoOf(entity).serverTicks) {
|
|
87
|
+
entity[method].call(entity, dt);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Computes and sends every connected client's delta. Called at the replication rate. */
|
|
92
|
+
flush() {
|
|
93
|
+
const entries = this.replicationEntries();
|
|
94
|
+
for (const playerId of this.transport.players()) {
|
|
95
|
+
const sync = this.replicator.computeSync(playerId, entries, this);
|
|
96
|
+
if (!isEmptySync(sync))
|
|
97
|
+
this.transport.sendSync(playerId, sync);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
replicationEntries() {
|
|
101
|
+
const entries = [];
|
|
102
|
+
for (const [id, entity] of this.entities) {
|
|
103
|
+
const info = this.infoOf(entity);
|
|
104
|
+
entries.push({ id, entity, type: info.name, plan: info.plan, visibleTo: info.options.visibleTo });
|
|
105
|
+
}
|
|
106
|
+
return entries;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Runs a client-initiated `@Server` call on one entity after checking the caller is allowed.
|
|
110
|
+
* Rejects on an unknown entity, a method that is not `@Server` on that type, an entity the caller
|
|
111
|
+
* cannot see, or a failed `canCall` — `canCall` alone would otherwise let a `canCall: 'anyone'`
|
|
112
|
+
* entity be driven by a client the `visibleTo` hook was written to hide it from entirely.
|
|
113
|
+
*/
|
|
114
|
+
async invoke(callerId, id, method, args) {
|
|
115
|
+
const entity = this.entities.get(id);
|
|
116
|
+
if (!entity)
|
|
117
|
+
throw new Error(`Unknown entity ${id}`);
|
|
118
|
+
const info = this.infoOf(entity);
|
|
119
|
+
const [typeName, methodName] = method.split('.');
|
|
120
|
+
if (typeName !== info.name || !methodName || !info.serverMethods.has(methodName)) {
|
|
121
|
+
throw new Error(`Unknown entity method: ${method}`);
|
|
122
|
+
}
|
|
123
|
+
const isOwner = entity[ownerIdField] === callerId;
|
|
124
|
+
if (!isEntityVisible(isOwner, callerId, entity, info.options.visibleTo, this)) {
|
|
125
|
+
// Same message as a missing id: a caller with no visibility shouldn't learn the entity exists.
|
|
126
|
+
throw new Error(`Unknown entity ${id}`);
|
|
127
|
+
}
|
|
128
|
+
if (!this.canCall(callerId, entity, info)) {
|
|
129
|
+
throw new Error(`Not allowed: ${method}`);
|
|
130
|
+
}
|
|
131
|
+
return entity[methodName].apply(entity, args);
|
|
132
|
+
}
|
|
133
|
+
canCall(callerId, entity, info) {
|
|
134
|
+
const rule = info.options.canCall ?? 'owner';
|
|
135
|
+
if (rule === 'anyone')
|
|
136
|
+
return true;
|
|
137
|
+
if (rule === 'owner')
|
|
138
|
+
return entity['ownerId'] === callerId;
|
|
139
|
+
return rule(callerId, entity, this);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Replaces `@Client` / `@Multicast` methods on this instance with sends, so the server stays
|
|
143
|
+
* independent of the Vite transform (plain tsx/node works) and tick code can call them directly.
|
|
144
|
+
*
|
|
145
|
+
* Both recompute visibility live against `this.transport.players()` rather than asking the
|
|
146
|
+
* replicator who was already sent an `enter` for this id, because `flush()` runs on its own timer:
|
|
147
|
+
* an entity spawned and called on within the same tick would otherwise have no recorded viewers yet
|
|
148
|
+
* and the rpc would reach nobody. That alone isn't sufficient though — a recipient who is newly
|
|
149
|
+
* visible but has not yet had a flush also has no client-side mirror to run the rpc against, so
|
|
150
|
+
* `sendToRecipient` first catches that one recipient up with a synthetic `enter` (recorded into the
|
|
151
|
+
* replicator so the next real flush sends a diff, not a duplicate enter) before sending the rpc, to
|
|
152
|
+
* guarantee enter-before-rpc per `(player, entity)`.
|
|
153
|
+
*/
|
|
154
|
+
bindClientDispatch(entity, id, info) {
|
|
155
|
+
const sendToRecipient = (playerId, isOwner, key, args) => {
|
|
156
|
+
if (!this.replicator.hasInView(playerId, id)) {
|
|
157
|
+
const fields = snapshotFields(entity, info.plan, isOwner);
|
|
158
|
+
this.replicator.recordEnter(playerId, id, fields);
|
|
159
|
+
this.transport.sendSync(playerId, { enter: [{ type: info.name, id, fields }], update: [], leave: [] });
|
|
160
|
+
}
|
|
161
|
+
this.transport.sendRpc(playerId, id, key, args);
|
|
162
|
+
};
|
|
163
|
+
for (const [methodName, role] of Object.entries(info.clientMethods)) {
|
|
164
|
+
const key = `${info.name}.${methodName}`;
|
|
165
|
+
const send = role === 'client'
|
|
166
|
+
? (...args) => {
|
|
167
|
+
const ownerId = entity['ownerId'];
|
|
168
|
+
if (typeof ownerId === 'string')
|
|
169
|
+
sendToRecipient(ownerId, true, key, args);
|
|
170
|
+
}
|
|
171
|
+
: (...args) => {
|
|
172
|
+
for (const playerId of this.transport.players()) {
|
|
173
|
+
const isOwner = entity[ownerIdField] === playerId;
|
|
174
|
+
if (isEntityVisible(isOwner, playerId, entity, info.options.visibleTo, this)) {
|
|
175
|
+
sendToRecipient(playerId, isOwner, key, args);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
Object.defineProperty(entity, methodName, { value: send, configurable: true, writable: true });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=world.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"world.js","sourceRoot":"","sources":["../../src/entity/world.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,13 +2,27 @@ export { GlobalStore, OwnerStore, Global, OwnerOnly, ServerOnly } from './decora
|
|
|
2
2
|
export { System, Server, Client, Multicast } from './decorators/system.js';
|
|
3
3
|
export { getStoreType, getPropertyRoles, getMethodRoles, setPropertyRole, setMethodRole } from './decorators/metadata.js';
|
|
4
4
|
export type { StoreType, PropertyRole, MethodRole } from './decorators/metadata.js';
|
|
5
|
+
export { Entity, Replicated, OwnerReplicated, Quantized, ServerTick, ClientSpawn, ClientDespawn, isEntityClass, getEntityName, getEntityOptions, getEntityHooks, getQuantizedFields, } from './decorators/entity.js';
|
|
6
|
+
export type { EntityOptions, EntityHook, CanCall } from './decorators/entity.js';
|
|
7
|
+
export { BaseEntity } from './entity/base-entity.js';
|
|
8
|
+
export { EntityWorld } from './entity/world.js';
|
|
9
|
+
export type { EntityTransport, EntityWorldOptions } from './entity/world.js';
|
|
10
|
+
export { ClientEntityRegistry } from './entity/registry.js';
|
|
11
|
+
export type { EntityCaller, ClientEntityRegistryOptions } from './entity/registry.js';
|
|
12
|
+
export { EntityReplicator } from './entity/replicator.js';
|
|
13
|
+
export type { ReplicationEntry } from './entity/replicator.js';
|
|
14
|
+
export { fieldPlanOf, snapshotFields, diffFields, quantize } from './entity/fields.js';
|
|
15
|
+
export type { FieldPlan } from './entity/fields.js';
|
|
16
|
+
export { isEntityVisible } from './entity/visibility.js';
|
|
17
|
+
export type { VisibleTo } from './entity/visibility.js';
|
|
18
|
+
export type { EntityId, EntityConstructor, EntityWorldView, EntitySync, EntityEnter, EntityUpdate, } from './entity/types.js';
|
|
5
19
|
export { BaseStore } from './store/base-store.js';
|
|
6
20
|
export type { ReplicationBatch } from './store/base-store.js';
|
|
7
21
|
export type { ConnectRequest, ConnectPayload, ServerContext } from './lifecycle/types.js';
|
|
8
22
|
export { BaseServerLifecycle } from './lifecycle/base-server.js';
|
|
9
23
|
export { BaseClientLifecycle } from './lifecycle/base-client.js';
|
|
10
24
|
export { createRPCServer } from './transport/server.js';
|
|
11
|
-
export type { RpcServer, TickOptions } from './transport/server.js';
|
|
25
|
+
export type { RpcServer, TickOptions, ReplicationOptions } from './transport/server.js';
|
|
12
26
|
export { ConnectionLostError, AuthRejectedError, RpcError, TimeoutError } from './transport/protocol.js';
|
|
13
27
|
export { WsClient } from './transport/client.js';
|
|
14
28
|
export type { ConnectionStatus, ConnectionQuality, WsClientOptions, LatencyThresholds } from './transport/client.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAC9F,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAC1E,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACzH,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAGnF,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAG7D,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGzF,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAGhE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAC9F,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAC1E,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACzH,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAGnF,OAAO,EACL,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EACtF,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,EAAE,kBAAkB,GACnF,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAGhF,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,YAAY,EAAE,YAAY,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAA;AACrF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AACtF,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,YAAY,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AACvD,YAAY,EACV,QAAQ,EAAE,iBAAiB,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,GACpF,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAG7D,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGzF,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAGhE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AACvF,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACxG,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACpH,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,YAAY,EAAE,GAAG,EAAE,iBAAiB,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
export { GlobalStore, OwnerStore, Global, OwnerOnly, ServerOnly } from './decorators/store.js';
|
|
3
3
|
export { System, Server, Client, Multicast } from './decorators/system.js';
|
|
4
4
|
export { getStoreType, getPropertyRoles, getMethodRoles, setPropertyRole, setMethodRole } from './decorators/metadata.js';
|
|
5
|
+
// Entity decorators
|
|
6
|
+
export { Entity, Replicated, OwnerReplicated, Quantized, ServerTick, ClientSpawn, ClientDespawn, isEntityClass, getEntityName, getEntityOptions, getEntityHooks, getQuantizedFields, } from './decorators/entity.js';
|
|
7
|
+
// Entities
|
|
8
|
+
export { BaseEntity } from './entity/base-entity.js';
|
|
9
|
+
export { EntityWorld } from './entity/world.js';
|
|
10
|
+
export { ClientEntityRegistry } from './entity/registry.js';
|
|
11
|
+
export { EntityReplicator } from './entity/replicator.js';
|
|
12
|
+
export { fieldPlanOf, snapshotFields, diffFields, quantize } from './entity/fields.js';
|
|
13
|
+
export { isEntityVisible } from './entity/visibility.js';
|
|
5
14
|
// Store
|
|
6
15
|
export { BaseStore } from './store/base-store.js';
|
|
7
16
|
// Lifecycle base classes
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAC9F,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAC1E,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAGzH,QAAQ;AACR,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAMjD,yBAAyB;AACzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAEhE,wCAAwC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACxG,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAEhD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAC9F,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAC1E,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAGzH,oBAAoB;AACpB,OAAO,EACL,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EACtF,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,EAAE,kBAAkB,GACnF,MAAM,wBAAwB,CAAA;AAG/B,WAAW;AACX,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAEzD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAEtF,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAMxD,QAAQ;AACR,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAMjD,yBAAyB;AACzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAEhE,wCAAwC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACxG,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAEhD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -11,7 +11,6 @@ export declare class BaseStore {
|
|
|
11
11
|
private _ownerData;
|
|
12
12
|
private _dirtyGlobal;
|
|
13
13
|
private _dirtyOwner;
|
|
14
|
-
private _flushScheduled;
|
|
15
14
|
private _globalSeq;
|
|
16
15
|
private _ownerSeqs;
|
|
17
16
|
private _listeners;
|
|
@@ -31,10 +30,16 @@ export declare class BaseStore {
|
|
|
31
30
|
/** True if anything is listening (legacy slot or subscribers). */
|
|
32
31
|
get _hasListeners(): boolean;
|
|
33
32
|
set(partial: Record<string, unknown>): void;
|
|
34
|
-
setOwner(key: string, playerId: string,
|
|
33
|
+
setOwner(key: string, playerId: string, rawValue: unknown): void;
|
|
35
34
|
getOwner<T>(key: string, playerId: string): T;
|
|
36
35
|
getSnapshot(playerId?: string): Record<string, unknown>;
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Drains accumulated dirty state (from `set()`/`setOwner()`) into one replication batch and
|
|
38
|
+
* emits it to listeners. `set()`/`setOwner()` no longer self-schedule this — the server calls
|
|
39
|
+
* `flush()` once per replication tick for every registered store (see `createRPCServer`'s
|
|
40
|
+
* replication timer), so store updates share one cadence with entity replication instead of
|
|
41
|
+
* running on an independent `process.nextTick()` clock. Cheap to call when nothing is dirty.
|
|
42
|
+
*/
|
|
43
|
+
flush(): void;
|
|
39
44
|
}
|
|
40
45
|
//# sourceMappingURL=base-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-store.d.ts","sourceRoot":"","sources":["../../src/store/base-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"base-store.d.ts","sourceRoot":"","sources":["../../src/store/base-store.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CACvC;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,UAAU,CAA0C;IAC5D,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,UAAU,CAA4B;IAE9C,OAAO,CAAC,UAAU,CAA+C;IAEjE;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAA;IAElD;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI;IAKlE,kHAAkH;IAClH,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAK/C,kEAAkE;IAClE,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAsB3C,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI;IAiBhE,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC;IAI7C,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAcvD;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;CAyCd"}
|
package/dist/store/base-store.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { getPropertyRoles, getStoreType } from '../decorators/metadata.js';
|
|
2
|
+
import { getQuantizedFields } from '../decorators/entity.js';
|
|
3
|
+
import { quantize } from '../entity/fields.js';
|
|
2
4
|
export class BaseStore {
|
|
3
5
|
_ownerData = new Map();
|
|
4
6
|
_dirtyGlobal = new Set();
|
|
5
7
|
_dirtyOwner = new Map();
|
|
6
|
-
_flushScheduled = false;
|
|
7
8
|
_globalSeq = 0;
|
|
8
9
|
_ownerSeqs = new Map(); // playerId → seq
|
|
9
10
|
_listeners = new Set();
|
|
@@ -32,11 +33,13 @@ export class BaseStore {
|
|
|
32
33
|
return this._onReplication !== undefined || this._listeners.size > 0;
|
|
33
34
|
}
|
|
34
35
|
set(partial) {
|
|
35
|
-
|
|
36
|
+
const proto = Object.getPrototypeOf(this);
|
|
37
|
+
if (getStoreType(proto.constructor) === 'owner') {
|
|
36
38
|
throw new Error('@OwnerStore does not support set(). Use setOwner() for all properties.');
|
|
37
39
|
}
|
|
38
|
-
const roles = getPropertyRoles(
|
|
39
|
-
|
|
40
|
+
const roles = getPropertyRoles(proto);
|
|
41
|
+
const quantized = getQuantizedFields(proto);
|
|
42
|
+
for (const [key, rawValue] of Object.entries(partial)) {
|
|
40
43
|
const role = roles[key];
|
|
41
44
|
if (role === 'ownerOnly') {
|
|
42
45
|
throw new Error(`Use setOwner() for @OwnerOnly property "${key}"`);
|
|
@@ -45,15 +48,15 @@ export class BaseStore {
|
|
|
45
48
|
throw new Error(`Unknown property "${key}" — must be decorated with @Global, @ServerOnly, or @OwnerOnly`);
|
|
46
49
|
}
|
|
47
50
|
;
|
|
48
|
-
this[key] =
|
|
51
|
+
this[key] = quantize(rawValue, quantized[key]);
|
|
49
52
|
if (role === 'global') {
|
|
50
53
|
this._dirtyGlobal.add(key);
|
|
51
|
-
this._scheduleFlush();
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
|
-
setOwner(key, playerId,
|
|
56
|
-
const
|
|
57
|
+
setOwner(key, playerId, rawValue) {
|
|
58
|
+
const proto = Object.getPrototypeOf(this);
|
|
59
|
+
const roles = getPropertyRoles(proto);
|
|
57
60
|
const role = roles[key];
|
|
58
61
|
if (role === undefined) {
|
|
59
62
|
throw new Error(`Unknown property "${key}" — must be decorated with @Global, @ServerOnly, or @OwnerOnly`);
|
|
@@ -61,13 +64,13 @@ export class BaseStore {
|
|
|
61
64
|
if (role !== 'ownerOnly') {
|
|
62
65
|
throw new Error(`Use set() for @${role === 'global' ? 'Global' : 'ServerOnly'} property "${key}"`);
|
|
63
66
|
}
|
|
67
|
+
const value = quantize(rawValue, getQuantizedFields(proto)[key]);
|
|
64
68
|
if (!this._ownerData.has(key))
|
|
65
69
|
this._ownerData.set(key, new Map());
|
|
66
70
|
this._ownerData.get(key).set(playerId, value);
|
|
67
71
|
if (!this._dirtyOwner.has(key))
|
|
68
72
|
this._dirtyOwner.set(key, new Set());
|
|
69
73
|
this._dirtyOwner.get(key).add(playerId);
|
|
70
|
-
this._scheduleFlush();
|
|
71
74
|
}
|
|
72
75
|
getOwner(key, playerId) {
|
|
73
76
|
return this._ownerData.get(key)?.get(playerId);
|
|
@@ -87,16 +90,16 @@ export class BaseStore {
|
|
|
87
90
|
}
|
|
88
91
|
return snapshot;
|
|
89
92
|
}
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Drains accumulated dirty state (from `set()`/`setOwner()`) into one replication batch and
|
|
95
|
+
* emits it to listeners. `set()`/`setOwner()` no longer self-schedule this — the server calls
|
|
96
|
+
* `flush()` once per replication tick for every registered store (see `createRPCServer`'s
|
|
97
|
+
* replication timer), so store updates share one cadence with entity replication instead of
|
|
98
|
+
* running on an independent `process.nextTick()` clock. Cheap to call when nothing is dirty.
|
|
99
|
+
*/
|
|
100
|
+
flush() {
|
|
101
|
+
if (this._dirtyGlobal.size === 0 && this._dirtyOwner.size === 0)
|
|
92
102
|
return;
|
|
93
|
-
this._flushScheduled = true;
|
|
94
|
-
process.nextTick(() => {
|
|
95
|
-
this._flushScheduled = false;
|
|
96
|
-
this._flush();
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
_flush() {
|
|
100
103
|
if (!this._hasListeners) {
|
|
101
104
|
// Nobody listening yet — discard dirty state so it doesn't accumulate
|
|
102
105
|
// and leak into the first incremental patch after _onReplication is set.
|