murow 0.0.73 → 0.1.3
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 +15 -1
- package/dist/cjs/core/binary-codec/binary-codec.js +1 -1
- package/dist/cjs/core/clock/clock.js +1 -0
- package/dist/cjs/core/clock/index.js +1 -0
- package/dist/cjs/core/driver/driver.js +1 -1
- package/dist/cjs/core/driver/drivers/immediate.js +1 -1
- package/dist/cjs/core/driver/drivers/raf.js +1 -1
- package/dist/cjs/core/driver/drivers/timeout.js +1 -1
- package/dist/cjs/core/hitbox/hitbox-library.js +1 -0
- package/dist/cjs/core/hitbox/hitbox.js +1 -0
- package/dist/cjs/core/hitbox/index.js +1 -0
- package/dist/cjs/core/hitbox/test.js +1 -0
- package/dist/cjs/core/index.js +1 -1
- package/dist/cjs/core/input/index.js +1 -1
- package/dist/cjs/core/input/mouse-look/index.js +1 -0
- package/dist/cjs/core/input/mouse-look/mouse-look.js +1 -0
- package/dist/cjs/core/input/scroll-zoom/index.js +1 -0
- package/dist/cjs/core/input/scroll-zoom/scroll-zoom.js +1 -0
- package/dist/cjs/core/prediction/prediction.js +1 -1
- package/dist/cjs/core/ray/ray-3d.js +1 -1
- package/dist/cjs/core/raycast/hit-buffer.js +1 -0
- package/dist/cjs/core/raycast/index.js +1 -0
- package/dist/cjs/core/raycast/raycaster.js +1 -0
- package/dist/cjs/core/slot-map/index.js +1 -0
- package/dist/cjs/core/slot-map/slot-map.js +1 -0
- package/dist/cjs/core/state-machine/index.js +1 -0
- package/dist/cjs/core/state-machine/state-machine.js +1 -0
- package/dist/cjs/core/timeline/index.js +1 -0
- package/dist/cjs/core/timeline/timeline.js +1 -0
- package/dist/cjs/ecs/component.js +1 -1
- package/dist/cjs/ecs/system-builder.js +1 -1
- package/dist/cjs/ecs/world.js +1 -1
- package/dist/cjs/game/loop/loop.js +1 -1
- package/dist/cjs/game/loop/ticker-schedule.js +1 -0
- package/dist/cjs/net/adapters/bun-websocket.js +1 -1
- package/dist/cjs/renderer/index.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/concrete.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/index.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/parsers.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/specs.js +1 -1
- package/dist/cjs/renderer/raycast/index.js +1 -0
- package/dist/cjs/renderer/raycast/raycast.js +1 -0
- package/dist/esm/core/binary-codec/binary-codec.js +1 -1
- package/dist/esm/core/clock/clock.js +1 -0
- package/dist/esm/core/clock/index.js +1 -0
- package/dist/esm/core/driver/drivers/immediate.js +1 -1
- package/dist/esm/core/driver/drivers/raf.js +1 -1
- package/dist/esm/core/driver/drivers/timeout.js +1 -1
- package/dist/esm/core/hitbox/hitbox-library.js +1 -0
- package/dist/esm/core/hitbox/hitbox.js +1 -0
- package/dist/esm/core/hitbox/index.js +1 -0
- package/dist/esm/core/hitbox/test.js +1 -0
- package/dist/esm/core/index.js +1 -1
- package/dist/esm/core/input/index.js +1 -1
- package/dist/esm/core/input/mouse-look/index.js +1 -0
- package/dist/esm/core/input/mouse-look/mouse-look.js +1 -0
- package/dist/esm/core/input/scroll-zoom/index.js +1 -0
- package/dist/esm/core/input/scroll-zoom/scroll-zoom.js +1 -0
- package/dist/esm/core/prediction/prediction.js +1 -1
- package/dist/esm/core/ray/ray-3d.js +1 -1
- package/dist/esm/core/raycast/hit-buffer.js +1 -0
- package/dist/esm/core/raycast/index.js +1 -0
- package/dist/esm/core/raycast/raycaster.js +1 -0
- package/dist/esm/core/slot-map/index.js +1 -0
- package/dist/esm/core/slot-map/slot-map.js +1 -0
- package/dist/esm/core/state-machine/index.js +1 -0
- package/dist/esm/core/state-machine/state-machine.js +1 -0
- package/dist/esm/core/timeline/index.js +1 -0
- package/dist/esm/core/timeline/timeline.js +1 -0
- package/dist/esm/ecs/component.js +1 -1
- package/dist/esm/ecs/system-builder.js +1 -1
- package/dist/esm/ecs/world.js +1 -1
- package/dist/esm/game/loop/loop.js +1 -1
- package/dist/esm/game/loop/ticker-schedule.js +1 -0
- package/dist/esm/net/adapters/bun-websocket.js +1 -1
- package/dist/esm/renderer/index.js +1 -1
- package/dist/esm/renderer/prefab-bucket/concrete.js +1 -1
- package/dist/esm/renderer/prefab-bucket/index.js +1 -1
- package/dist/esm/renderer/prefab-bucket/parsers.js +1 -1
- package/dist/esm/renderer/raycast/index.js +1 -0
- package/dist/esm/renderer/raycast/raycast.js +1 -0
- package/dist/netcode/cjs/index.js +1556 -0
- package/dist/netcode/esm/index.js +1534 -0
- package/dist/netcode/types/client/game-client.d.ts +139 -0
- package/dist/netcode/types/client/index.d.ts +1 -0
- package/dist/netcode/types/client/strategies/snapshot-interpolation.d.ts +33 -0
- package/dist/netcode/types/client/strategies/snapshot-interpolation.test.d.ts +1 -0
- package/dist/netcode/types/codec/delta-codec.d.ts +17 -0
- package/dist/netcode/types/codec/delta-codec.test.d.ts +1 -0
- package/dist/netcode/types/codec/index.d.ts +1 -0
- package/dist/netcode/types/components/index.d.ts +1 -0
- package/dist/netcode/types/components/sync-spec.d.ts +49 -0
- package/dist/netcode/types/components/sync-spec.test.d.ts +1 -0
- package/dist/netcode/types/ctx.d.ts +105 -0
- package/dist/netcode/types/ctx.test.d.ts +1 -0
- package/dist/netcode/types/handlers/define-handlers.d.ts +47 -0
- package/dist/netcode/types/handlers/index.d.ts +1 -0
- package/dist/netcode/types/index.d.ts +11 -0
- package/dist/netcode/types/integration.test.d.ts +1 -0
- package/dist/netcode/types/intents/define-intents.d.ts +53 -0
- package/dist/netcode/types/intents/define-intents.test.d.ts +1 -0
- package/dist/netcode/types/intents/index.d.ts +1 -0
- package/dist/netcode/types/network/base.d.ts +120 -0
- package/dist/netcode/types/network/index.d.ts +2 -0
- package/dist/netcode/types/network/transport.d.ts +1 -0
- package/dist/netcode/types/packets/convergence.test.d.ts +1 -0
- package/dist/netcode/types/packets/harness.d.ts +103 -0
- package/dist/netcode/types/packets/index.d.ts +2 -0
- package/dist/netcode/types/packets/intermittent-intents.test.d.ts +1 -0
- package/dist/netcode/types/packets/pathological.test.d.ts +1 -0
- package/dist/netcode/types/packets/peer-interpolation.test.d.ts +1 -0
- package/dist/netcode/types/packets/reordering.test.d.ts +1 -0
- package/dist/netcode/types/packets/virtual-network.d.ts +65 -0
- package/dist/netcode/types/predictions/define-predictions.d.ts +45 -0
- package/dist/netcode/types/predictions/define-predictions.test.d.ts +1 -0
- package/dist/netcode/types/predictions/index.d.ts +1 -0
- package/dist/netcode/types/reconciliation.test.d.ts +1 -0
- package/dist/netcode/types/rpcs/define-rpcs.d.ts +44 -0
- package/dist/netcode/types/rpcs/define-rpcs.test.d.ts +1 -0
- package/dist/netcode/types/rpcs/index.d.ts +1 -0
- package/dist/netcode/types/server/game-server.d.ts +77 -0
- package/dist/netcode/types/server/index.d.ts +2 -0
- package/dist/netcode/types/server/plugins/aoi-grid.d.ts +34 -0
- package/dist/netcode/types/server/plugins/index.d.ts +3 -0
- package/dist/netcode/types/server/plugins/lag-compensation.d.ts +34 -0
- package/dist/netcode/types/server/plugins/plugin.d.ts +24 -0
- package/dist/netcode/types/tick-rate.test.d.ts +1 -0
- package/dist/netcode/types/transports/index.d.ts +1 -0
- package/dist/netcode/types/transports/memory-transport.d.ts +51 -0
- package/dist/netcode/types/types.test.d.ts +1 -0
- package/dist/types/core/binary-codec/binary-codec.d.ts +89 -31
- package/dist/types/core/clock/clock.d.ts +37 -0
- package/dist/types/core/clock/index.d.ts +1 -0
- package/dist/types/core/driver/driver.d.ts +8 -8
- package/dist/types/core/driver/drivers/immediate.d.ts +4 -4
- package/dist/types/core/driver/drivers/raf.d.ts +6 -6
- package/dist/types/core/driver/drivers/timeout.d.ts +4 -4
- package/dist/types/core/hitbox/hitbox-library.d.ts +29 -0
- package/dist/types/core/hitbox/hitbox.d.ts +50 -0
- package/dist/types/core/hitbox/index.d.ts +3 -0
- package/dist/types/core/hitbox/test.d.ts +44 -0
- package/dist/types/core/index.d.ts +6 -0
- package/dist/types/core/input/index.d.ts +2 -0
- package/dist/types/core/input/mouse-look/index.d.ts +1 -0
- package/dist/types/core/input/mouse-look/mouse-look.d.ts +139 -0
- package/dist/types/core/input/scroll-zoom/index.d.ts +1 -0
- package/dist/types/core/input/scroll-zoom/scroll-zoom.d.ts +38 -0
- package/dist/types/core/prediction/prediction.d.ts +35 -58
- package/dist/types/core/ray/ray-3d.d.ts +21 -1
- package/dist/types/core/raycast/hit-buffer.d.ts +43 -0
- package/dist/types/core/raycast/index.d.ts +2 -0
- package/dist/types/core/raycast/raycaster.d.ts +54 -0
- package/dist/types/core/slot-map/index.d.ts +1 -0
- package/dist/types/core/slot-map/slot-map.d.ts +109 -0
- package/dist/types/core/state-machine/index.d.ts +1 -0
- package/dist/types/core/state-machine/state-machine.d.ts +114 -0
- package/dist/types/core/timeline/index.d.ts +1 -0
- package/dist/types/core/timeline/timeline.d.ts +34 -0
- package/dist/types/ecs/component.d.ts +67 -11
- package/dist/types/ecs/entity-handle.d.ts +5 -5
- package/dist/types/ecs/system-builder.d.ts +13 -0
- package/dist/types/ecs/world.d.ts +72 -4
- package/dist/types/game/loop/loop.d.ts +51 -2
- package/dist/types/game/loop/ticker-schedule.d.ts +52 -0
- package/dist/types/net/adapters/bun-websocket.d.ts +19 -3
- package/dist/types/renderer/index.d.ts +1 -0
- package/dist/types/renderer/prefab-bucket/concrete.d.ts +16 -6
- package/dist/types/renderer/prefab-bucket/index.d.ts +11 -7
- package/dist/types/renderer/prefab-bucket/specs.d.ts +10 -0
- package/dist/types/renderer/raycast/index.d.ts +1 -0
- package/dist/types/renderer/raycast/raycast.d.ts +24 -0
- package/dist/types/renderer/types.d.ts +1 -0
- package/dist/webgpu/cjs/index.js +1897 -592
- package/dist/webgpu/esm/index.js +1889 -578
- package/dist/webgpu/types/2d/raycast.d.ts +45 -0
- package/dist/webgpu/types/2d/renderer.d.ts +11 -0
- package/dist/webgpu/types/2d/sprite-accessor.d.ts +3 -1
- package/dist/webgpu/types/3d/hitbox.d.ts +32 -0
- package/dist/webgpu/types/3d/lights.d.ts +113 -0
- package/dist/webgpu/types/3d/lights.test.d.ts +1 -0
- package/dist/webgpu/types/3d/raycast.d.ts +44 -0
- package/dist/webgpu/types/3d/renderer.d.ts +88 -1
- package/dist/webgpu/types/3d/shader.d.ts +88 -5
- package/dist/webgpu/types/core/types.d.ts +55 -0
- package/dist/webgpu/types/geometry/geometry-builder.d.ts +1 -4
- package/dist/webgpu/types/index.d.ts +1 -0
- package/dist/webgpu/types/shaders/utils.d.ts +24 -0
- package/package.json +6 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { Entity, World } from 'murow/ecs';
|
|
2
|
+
import type { GameLoop } from 'murow/game';
|
|
3
|
+
import type { TransportAdapter } from 'murow/net';
|
|
4
|
+
import { Network } from '../network/base';
|
|
5
|
+
import { SnapshotInterpolation } from './strategies/snapshot-interpolation';
|
|
6
|
+
import type { DefinedIntents, IntentPayload, IntentSchemaMap } from '../intents/define-intents';
|
|
7
|
+
import type { DefinedRpcs, RpcPayload, RpcSchemaMap } from '../rpcs/define-rpcs';
|
|
8
|
+
import type { DefinedPredictions } from '../predictions/define-predictions';
|
|
9
|
+
/**
|
|
10
|
+
* Snapshot interpolation strategy. The client holds the last few
|
|
11
|
+
* snapshots and renders peer entities `delay` ms behind newest, lerping
|
|
12
|
+
* between the two snapshots straddling that past time. This is the
|
|
13
|
+
* Source-engine model and the right default for action games, MMOs,
|
|
14
|
+
* MOBAs, and `.io` titles.
|
|
15
|
+
*/
|
|
16
|
+
export interface SnapshotInterpolationStrategy {
|
|
17
|
+
kind: 'snapshot-interpolation';
|
|
18
|
+
/** Snapshot interpolation delay, ms. Default 100. */
|
|
19
|
+
delay?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Max ms between snapshots before history is dropped as stale.
|
|
22
|
+
* Defaults to `delay * 2 + 100`.
|
|
23
|
+
*/
|
|
24
|
+
staleWindow?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Desync past which the play-out clock snaps instead of warping, ms.
|
|
27
|
+
* Scaled by tick rate internally. Default 500.
|
|
28
|
+
*/
|
|
29
|
+
maxDesync?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Largest data gap a peer may have before its value is held instead of
|
|
32
|
+
* interpolated across, ms. Smaller gaps are bridged. Default 250.
|
|
33
|
+
*/
|
|
34
|
+
maxBridgeGap?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* How peer entities are rendered on the client. Local-player rendering
|
|
38
|
+
* is driven by `prediction` (separate axis) and is independent of the
|
|
39
|
+
* peer strategy.
|
|
40
|
+
*
|
|
41
|
+
* Today only `snapshot-interpolation` ships. Extrapolation and rollback
|
|
42
|
+
* variants will slot in here without protocol changes.
|
|
43
|
+
*/
|
|
44
|
+
export type PeerRenderStrategy = SnapshotInterpolationStrategy;
|
|
45
|
+
export interface GameClientOptions<I extends IntentSchemaMap, R extends RpcSchemaMap> {
|
|
46
|
+
world: World;
|
|
47
|
+
loop: GameLoop<any>;
|
|
48
|
+
transport: TransportAdapter;
|
|
49
|
+
protocol: {
|
|
50
|
+
intents: DefinedIntents<I>;
|
|
51
|
+
rpcs: DefinedRpcs<R>;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Peer-rendering strategy. Defaults to snapshot interpolation with
|
|
55
|
+
* a 100 ms delay.
|
|
56
|
+
*/
|
|
57
|
+
strategy?: PeerRenderStrategy;
|
|
58
|
+
prediction?: {
|
|
59
|
+
/** Max buffered unacked predictions kept for rollback. Default 64. */
|
|
60
|
+
bufferSize?: number;
|
|
61
|
+
};
|
|
62
|
+
now?: () => number;
|
|
63
|
+
}
|
|
64
|
+
export declare class GameClient<I extends IntentSchemaMap = IntentSchemaMap, R extends RpcSchemaMap = RpcSchemaMap> extends Network<'client', R> {
|
|
65
|
+
readonly world: World;
|
|
66
|
+
readonly loop: GameLoop<any>;
|
|
67
|
+
readonly transport: TransportAdapter;
|
|
68
|
+
readonly intents: DefinedIntents<I>;
|
|
69
|
+
readonly rpcs: DefinedRpcs<R>;
|
|
70
|
+
private predictionMap;
|
|
71
|
+
private predictionBufferSize;
|
|
72
|
+
private reconciler;
|
|
73
|
+
private localTick;
|
|
74
|
+
private intentSequence;
|
|
75
|
+
private lastServerTick;
|
|
76
|
+
private lastReconciledServerTick;
|
|
77
|
+
private serverToLocal;
|
|
78
|
+
private syncedComponents;
|
|
79
|
+
private syncedNumMaskWords;
|
|
80
|
+
private rng;
|
|
81
|
+
private lastDt;
|
|
82
|
+
private predictedEntities;
|
|
83
|
+
/** Set when MSG_ASSIGN_ENTITY lands before the matching spawn. */
|
|
84
|
+
private pendingAssignedServerEid;
|
|
85
|
+
/** Spawns discovered during a decode, emitted once values are written. */
|
|
86
|
+
private deferredSpawns;
|
|
87
|
+
/** Resolved local entity for the server's assignment. Default for sendIntent. */
|
|
88
|
+
private _assignedEntity;
|
|
89
|
+
interpBuffer: SnapshotInterpolation;
|
|
90
|
+
private _rttMs;
|
|
91
|
+
private now;
|
|
92
|
+
/**
|
|
93
|
+
* Local entity the server has assigned to this peer, or `null` if no
|
|
94
|
+
* assignment has been received yet. `sendIntent` is a no-op (emits an
|
|
95
|
+
* `'error'` event) until this is set.
|
|
96
|
+
*/
|
|
97
|
+
get assignedEntity(): Entity | null;
|
|
98
|
+
/**
|
|
99
|
+
* Last measured round-trip time in milliseconds, or `null` if no pong
|
|
100
|
+
* has come back yet. Updated whenever a `'pong'` event fires.
|
|
101
|
+
*/
|
|
102
|
+
get rttMs(): number | null;
|
|
103
|
+
constructor(opts: GameClientOptions<I, R>);
|
|
104
|
+
private discoverSyncedComponents;
|
|
105
|
+
private wireTransport;
|
|
106
|
+
private wireLoop;
|
|
107
|
+
private handleIncoming;
|
|
108
|
+
private handleSnapshot;
|
|
109
|
+
private ensureEntity;
|
|
110
|
+
private applyAssignment;
|
|
111
|
+
/**
|
|
112
|
+
* Rewind predicted entities to authoritative state, drop predictions
|
|
113
|
+
* the server has acked, replay the rest on top.
|
|
114
|
+
*/
|
|
115
|
+
private reconcile;
|
|
116
|
+
private handleRpc;
|
|
117
|
+
use(bundle: DefinedPredictions<I>): this;
|
|
118
|
+
/**
|
|
119
|
+
* Send an intent to the server. `ctx.entity` in the prediction is the
|
|
120
|
+
* server-assigned entity. Target entities (chestId, targetId, etc.)
|
|
121
|
+
* belong in the payload itself.
|
|
122
|
+
*
|
|
123
|
+
* Returns `true` when the intent was sent, `false` when blocked. Both
|
|
124
|
+
* blocked paths emit an `'error'` event so calling code can react:
|
|
125
|
+
* - Unknown intent name.
|
|
126
|
+
* - No entity assigned yet (server hasn't called `assignEntity` for
|
|
127
|
+
* this peer). Check `client.assignedEntity` if you want to know
|
|
128
|
+
* without sending.
|
|
129
|
+
*/
|
|
130
|
+
sendIntent<K extends keyof I & string>(name: K, payload: IntentPayload<I, K>): boolean;
|
|
131
|
+
sendRpc<K extends keyof R & string>(name: K, payload: RpcPayload<R, K>): void;
|
|
132
|
+
ping(): void;
|
|
133
|
+
getLocalTick(): number;
|
|
134
|
+
getPredictionDepth(): number;
|
|
135
|
+
get interpolationDelay(): number;
|
|
136
|
+
setInterpolationDelay(ms: number): void;
|
|
137
|
+
setMaxDesync(ms: number): void;
|
|
138
|
+
setMaxBridgeGap(ms: number): void;
|
|
139
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './game-client';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Component, Entity, World } from 'murow/ecs';
|
|
2
|
+
export interface BufferedSnapshot {
|
|
3
|
+
receivedAt: number;
|
|
4
|
+
serverTick: number;
|
|
5
|
+
entityIds: number[];
|
|
6
|
+
componentValuesByEntity: Map<number, Map<Component<any>, Record<string, number>>>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Snapshot-interpolation strategy: renders peer entities `delay` ms behind the
|
|
10
|
+
* newest snapshot, lerping between the two snapshots straddling that past time.
|
|
11
|
+
* Composes a core `Timeline` (snapshot history) and a core `SlewClock`
|
|
12
|
+
* (play-out clock). Predicted entities are skipped; reconciliation drives them.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SnapshotInterpolation {
|
|
15
|
+
private serverToLocal;
|
|
16
|
+
private timeline;
|
|
17
|
+
private clock;
|
|
18
|
+
private smoothedTickRateMs;
|
|
19
|
+
private nominalTickMs;
|
|
20
|
+
delay: number;
|
|
21
|
+
maxDesync: number;
|
|
22
|
+
maxBridgeGap: number;
|
|
23
|
+
constructor(serverToLocal: Map<number, Entity>, capacity: number, delayMs: number, staleWindowMs: number, maxDesyncMs?: number, nominalTickMs?: number, maxBridgeGapMs?: number);
|
|
24
|
+
get staleWindow(): number;
|
|
25
|
+
setDelay(delayMs: number): void;
|
|
26
|
+
setStaleWindow(staleWindowMs: number): void;
|
|
27
|
+
setMaxDesync(maxDesyncMs: number): void;
|
|
28
|
+
setMaxBridgeGap(maxBridgeGapMs: number): void;
|
|
29
|
+
record(snapshot: BufferedSnapshot): void;
|
|
30
|
+
clear(): void;
|
|
31
|
+
apply(world: World, now: number, components: Component<any>[], shouldSkip: (entity: Entity) => boolean): void;
|
|
32
|
+
private writeSnapshot;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Component, World } from 'murow/ecs';
|
|
2
|
+
export declare function encodeDelta(world: World, tick: number, entities: number[], components: Component<any>[], numMaskWords: number, despawned?: number[], clientAckTick?: number, includeComponent?: (entity: number, component: Component<any>) => boolean): Uint8Array;
|
|
3
|
+
export interface DecodedDelta {
|
|
4
|
+
tick: number;
|
|
5
|
+
clientAckTick: number;
|
|
6
|
+
entityIds: number[];
|
|
7
|
+
serverEntityIds: number[];
|
|
8
|
+
despawnedServerIds: number[];
|
|
9
|
+
valuesByServerEntity: Map<number, Map<Component<any>, Record<string, any>>>;
|
|
10
|
+
}
|
|
11
|
+
export declare function decodeDelta(world: World, buf: Uint8Array, components: Component<any>[], numMaskWords: number, ensureEntity: (serverEntityId: number, presentComponents: Component<any>[]) => number,
|
|
12
|
+
/**
|
|
13
|
+
* Return false to skip overwriting this entity's component values.
|
|
14
|
+
* Components missing on the entity are still archetype-initialized
|
|
15
|
+
* either way so the local entity has the right shape.
|
|
16
|
+
*/
|
|
17
|
+
shouldApply?: (localEntity: number) => boolean): DecodedDelta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './delta-codec';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './sync-spec';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* - `lerp` linear (positions, scalars)
|
|
3
|
+
* - `slerp` spherical (quaternions)
|
|
4
|
+
* - `step` hold older value until t >= 0.5, then snap (enums, ids)
|
|
5
|
+
* - `none` always newest (strings, references)
|
|
6
|
+
*/
|
|
7
|
+
export type InterpolationMode = 'lerp' | 'slerp' | 'step' | 'none';
|
|
8
|
+
/**
|
|
9
|
+
* - `every-tick` shipped every snapshot tick
|
|
10
|
+
* - `on-change` shipped only when a field changed
|
|
11
|
+
* - `{ every: N }` every N ticks regardless
|
|
12
|
+
*/
|
|
13
|
+
export type SyncRate = 'every-tick' | 'on-change' | {
|
|
14
|
+
every: number;
|
|
15
|
+
};
|
|
16
|
+
/** `'global'` or a plugin name (e.g. an `AoiGrid` instance's `name`). */
|
|
17
|
+
export type InterestRule = 'global' | (string & {});
|
|
18
|
+
export interface SyncSpec {
|
|
19
|
+
/** Snapshot eligibility cadence. */
|
|
20
|
+
rate: SyncRate;
|
|
21
|
+
/** Visibility filter (e.g. AOI plugin name) or `'global'`. */
|
|
22
|
+
interest: InterestRule;
|
|
23
|
+
/** Default interpolation mode for this component's fields. */
|
|
24
|
+
interp?: InterpolationMode;
|
|
25
|
+
/** Override the reconciliation snap threshold for this component. */
|
|
26
|
+
snapThreshold?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Identity helper that gives full TypeScript checking on the `sync` block
|
|
30
|
+
* of a `defineComponent` call. Plain object literals also work, but the
|
|
31
|
+
* function call site catches typos (`'evry-tick'`, `interest: 42`) at
|
|
32
|
+
* compile time and provides autocomplete on the union members.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* import { defineComponent, f32 } from 'murow';
|
|
36
|
+
* import { networked } from 'murow/netcode';
|
|
37
|
+
*
|
|
38
|
+
* const Position = defineComponent('Position', {
|
|
39
|
+
* schema: { x: f32, y: f32 },
|
|
40
|
+
* sync: networked({ rate: 'every-tick', interest: 'aoi', interp: 'lerp' }),
|
|
41
|
+
* });
|
|
42
|
+
*/
|
|
43
|
+
export declare function networked(spec: SyncSpec): SyncSpec;
|
|
44
|
+
/**
|
|
45
|
+
* Whether a component's value rides along in the snapshot for the given
|
|
46
|
+
* tick. `every-tick` always; `on-change` only when the field changed
|
|
47
|
+
* since the last snapshot; `{ every: N }` on every Nth tick or on change.
|
|
48
|
+
*/
|
|
49
|
+
export declare function rateIncludes(rate: SyncRate, dirty: boolean, tick: number): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Component, Entity, World } from 'murow/ecs';
|
|
2
|
+
import type { SimpleRNG } from 'murow/core/simple-rng';
|
|
3
|
+
/**
|
|
4
|
+
* Per-component typed-array bundle returned by `ctx.fields(C)`. Maps every
|
|
5
|
+
* field name in the component's schema to its underlying typed array.
|
|
6
|
+
*/
|
|
7
|
+
export type ComponentFields<T extends object> = Readonly<Record<keyof T & string, Float32Array | Int32Array | Uint32Array | Uint16Array | Uint8Array>>;
|
|
8
|
+
export interface PredictionContext {
|
|
9
|
+
world: World;
|
|
10
|
+
entity: Entity;
|
|
11
|
+
tick: number;
|
|
12
|
+
/** Seconds since last tick. */
|
|
13
|
+
deltaTime: number;
|
|
14
|
+
/** Deterministic per-tick RNG. Don't use Math.random in predictions. */
|
|
15
|
+
rng: SimpleRNG;
|
|
16
|
+
/**
|
|
17
|
+
* Get raw typed-array access to a component's fields for `ctx.entity`.
|
|
18
|
+
*
|
|
19
|
+
* Same shared bundle as `world.fields(C)`, plus automatic dirty tracking
|
|
20
|
+
* for networked components: calling `ctx.fields(C)` marks `ctx.entity`
|
|
21
|
+
* dirty for `C`, so the next snapshot picks up the write. Zero garbage,
|
|
22
|
+
* RAW-speed reads/writes.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* move: ({ dx, dz }, ctx) => {
|
|
27
|
+
* const pos = ctx.fields(Position);
|
|
28
|
+
* pos.x[ctx.entity] += dx * MOVE_SPEED * ctx.deltaTime;
|
|
29
|
+
* pos.z[ctx.entity] += dz * MOVE_SPEED * ctx.deltaTime;
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
fields<T extends object>(component: Component<T>): ComponentFields<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Explicitly mark an entity dirty for one or more networked components.
|
|
36
|
+
*
|
|
37
|
+
* Use this when you reach into `ctx.world.fields(C)` directly (bypassing
|
|
38
|
+
* `ctx.fields`'s auto-mark) and want to control exactly when the dirty
|
|
39
|
+
* bit gets set - typically to skip the cost on no-op paths, to batch
|
|
40
|
+
* dirty marks across multiple components after a multi-field write, or
|
|
41
|
+
* to mark a target entity other than `ctx.entity` (e.g. a damage handler
|
|
42
|
+
* that mutates the victim's Health bundle).
|
|
43
|
+
*
|
|
44
|
+
* Silently no-ops for components without `sync` metadata, and for
|
|
45
|
+
* components that were never registered with the world. Defaults to
|
|
46
|
+
* `ctx.entity` if no entity argument is supplied.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* // Single component, default entity.
|
|
51
|
+
* ctx.markDirty(Position);
|
|
52
|
+
*
|
|
53
|
+
* // Multiple components, default entity (one call instead of two).
|
|
54
|
+
* ctx.markDirty([Position, Health]);
|
|
55
|
+
*
|
|
56
|
+
* // Single component on a different entity.
|
|
57
|
+
* ctx.markDirty(Health, targetId);
|
|
58
|
+
*
|
|
59
|
+
* // Multiple components on a different entity.
|
|
60
|
+
* ctx.markDirty([Position, Health], targetId);
|
|
61
|
+
*
|
|
62
|
+
* // Conditional write - only pay the dirty-mark cost when something changed.
|
|
63
|
+
* const pos = ctx.world.fields(Position);
|
|
64
|
+
* if (Math.abs(pos.x[ctx.entity] - targetX) > EPSILON) {
|
|
65
|
+
* pos.x[ctx.entity] = targetX;
|
|
66
|
+
* ctx.markDirty(Position);
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
markDirty(component: Component<any>, entity?: Entity): void;
|
|
71
|
+
markDirty(components: ReadonlyArray<Component<any>>, entity?: Entity): void;
|
|
72
|
+
}
|
|
73
|
+
export interface Peer {
|
|
74
|
+
peerId: string;
|
|
75
|
+
/** -1 until `server.assignEntity` is called. */
|
|
76
|
+
entity: Entity | -1;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build the `fields` accessor for a prediction or handler context.
|
|
80
|
+
*
|
|
81
|
+
* Returns a closure that, when called with a component, marks the entity
|
|
82
|
+
* dirty (for synced components) and returns the world's pre-built field
|
|
83
|
+
* bundle. Bundle objects are stable for the world's lifetime, so this
|
|
84
|
+
* adds zero allocations on the hot path beyond the closure itself.
|
|
85
|
+
*/
|
|
86
|
+
export declare function makeFieldsAccessor(world: World, entity: Entity): <T extends object>(component: Component<T>) => ComponentFields<T>;
|
|
87
|
+
/**
|
|
88
|
+
* Build the `markDirty` accessor for a prediction or handler context.
|
|
89
|
+
*
|
|
90
|
+
* Returns a closure that marks one or more (component, entity) pairs dirty
|
|
91
|
+
* for the snapshot pipeline. Accepts either a single component or an array;
|
|
92
|
+
* defaults to `ctxEntity` when no entity arg is passed. No-ops per component
|
|
93
|
+
* for unsynced or unregistered components.
|
|
94
|
+
*/
|
|
95
|
+
export declare function makeMarkDirty(world: World, ctxEntity: Entity): {
|
|
96
|
+
(component: Component<any>, entity?: Entity): void;
|
|
97
|
+
(components: ReadonlyArray<Component<any>>, entity?: Entity): void;
|
|
98
|
+
};
|
|
99
|
+
export interface ServerHandlerContext extends PredictionContext {
|
|
100
|
+
peer: Peer;
|
|
101
|
+
/** Tick the client claimed they were on when they sent the intent. */
|
|
102
|
+
clientTick: number;
|
|
103
|
+
/** Rewind world state to `clientTick` for the duration of `fn`. */
|
|
104
|
+
lagCompensated<T>(fn: () => T): T;
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { DefinedIntents, IntentPayload, IntentSchemaMap } from '../intents/define-intents';
|
|
2
|
+
import type { ServerHandlerContext } from '../ctx';
|
|
3
|
+
/**
|
|
4
|
+
* A single server-only handler. Runs only when an intent arrives from a
|
|
5
|
+
* peer. May read/write server-only context (`peer`, `clientTick`,
|
|
6
|
+
* `lagCompensated`) and is never replayed on the client.
|
|
7
|
+
*
|
|
8
|
+
* Use for intents whose effect is intrinsically server-authoritative:
|
|
9
|
+
* hit detection with lag compensation, item purchases, anti-cheat
|
|
10
|
+
* checks, chat broadcasts.
|
|
11
|
+
*/
|
|
12
|
+
export type HandlerFn<P> = (payload: P, ctx: ServerHandlerContext) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Bundle of server-only handlers keyed by intent name. Partial: most
|
|
15
|
+
* games mix predictable intents (movement) with server-only handlers
|
|
16
|
+
* (combat, economy).
|
|
17
|
+
*/
|
|
18
|
+
export type HandlerMap<I extends IntentSchemaMap> = {
|
|
19
|
+
[K in keyof I]?: HandlerFn<IntentPayload<I, K>>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Tagged bundle returned by `defineHandlers`. Pass to
|
|
23
|
+
* `server.use(handlers)`. Calling `client.use(handlers)` is a type error
|
|
24
|
+
* because the bundle's `__kind` is server-specific.
|
|
25
|
+
*/
|
|
26
|
+
export interface DefinedHandlers<I extends IntentSchemaMap> {
|
|
27
|
+
readonly __kind: 'handlers';
|
|
28
|
+
readonly intents: DefinedIntents<I>;
|
|
29
|
+
readonly map: HandlerMap<I>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Bundle server-only intent handlers. Register via
|
|
33
|
+
* `server.use(handlers)`.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const handlers = defineHandlers(intents, {
|
|
37
|
+
* shoot: ({ from, dir }, ctx) => {
|
|
38
|
+
* ctx.lagCompensated(() => {
|
|
39
|
+
* const hit = raycast(ctx.world, from, dir);
|
|
40
|
+
* if (hit) ctx.world.update(hit.entity, Health, { hp: ... });
|
|
41
|
+
* });
|
|
42
|
+
* },
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* server.use(handlers);
|
|
46
|
+
*/
|
|
47
|
+
export declare function defineHandlers<I extends IntentSchemaMap>(intents: DefinedIntents<I>, map: HandlerMap<I>): DefinedHandlers<I>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './define-handlers';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './intents';
|
|
2
|
+
export * from './rpcs';
|
|
3
|
+
export * from './predictions';
|
|
4
|
+
export * from './handlers';
|
|
5
|
+
export * from './network';
|
|
6
|
+
export * from './server';
|
|
7
|
+
export * from './client';
|
|
8
|
+
export * from './codec';
|
|
9
|
+
export * from './transports';
|
|
10
|
+
export * from './components';
|
|
11
|
+
export * from './ctx';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { IntentRegistry, defineIntent } from 'murow/protocol';
|
|
2
|
+
/**
|
|
3
|
+
* User-supplied schema map for `defineIntents`. Each entry maps an
|
|
4
|
+
* intent name to a field schema. The reserved `kind` and `tick` fields
|
|
5
|
+
* are added automatically when the wire packet is built; users only
|
|
6
|
+
* declare payload fields.
|
|
7
|
+
*/
|
|
8
|
+
export type IntentSchemaMap = Record<string, Record<string, any>>;
|
|
9
|
+
/** Payload type for a named intent, inferred from its schema. */
|
|
10
|
+
export type IntentPayload<I extends IntentSchemaMap, K extends keyof I> = {
|
|
11
|
+
[P in keyof I[K]]: I[K][P] extends {
|
|
12
|
+
read(dv: DataView, o: number): infer R;
|
|
13
|
+
} ? R : never;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Result of `defineIntents`. `registry` plugs into `GameServer` and
|
|
17
|
+
* `GameClient` via the `protocol` option; `__payloads` is a phantom used
|
|
18
|
+
* purely for type inference at `sendIntent` call sites.
|
|
19
|
+
*/
|
|
20
|
+
export interface DefinedIntents<I extends IntentSchemaMap> {
|
|
21
|
+
/** Underlying `DefinedIntent` objects keyed by name. */
|
|
22
|
+
readonly defs: {
|
|
23
|
+
readonly [K in keyof I]: ReturnType<typeof defineIntent<number, I[K]>>;
|
|
24
|
+
};
|
|
25
|
+
/** Intent name to numeric kind. */
|
|
26
|
+
readonly kindByName: {
|
|
27
|
+
readonly [K in keyof I]: number;
|
|
28
|
+
};
|
|
29
|
+
/** Numeric kind back to intent name. */
|
|
30
|
+
readonly nameByKind: Readonly<Record<number, keyof I & string>>;
|
|
31
|
+
/** Pre-populated registry ready to plug into the network layer. */
|
|
32
|
+
readonly registry: IntentRegistry;
|
|
33
|
+
/** Phantom field for type inference, never read at runtime. */
|
|
34
|
+
readonly __payloads: {
|
|
35
|
+
[K in keyof I]: IntentPayload<I, K>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Declare every intent the game uses in one map. Numeric kinds are
|
|
40
|
+
* auto-assigned in insertion order starting at 1 (0 is reserved for
|
|
41
|
+
* engine control frames).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const intents = defineIntents({
|
|
45
|
+
* move: { dx: f32, dy: f32 },
|
|
46
|
+
* jump: {},
|
|
47
|
+
* shoot: { from: vec2_le, dir: vec2_le },
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* client.sendIntent('move', { dx: 1, dy: 0 });
|
|
51
|
+
* server.on('intent', ({ name, payload }) => { ... });
|
|
52
|
+
*/
|
|
53
|
+
export declare function defineIntents<I extends IntentSchemaMap>(intents: I): DefinedIntents<I>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './define-intents';
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { EventSystem } from 'murow/core/events';
|
|
2
|
+
import type { Peer } from '../ctx';
|
|
3
|
+
import type { RpcPayload, RpcSchemaMap } from '../rpcs/define-rpcs';
|
|
4
|
+
/**
|
|
5
|
+
* Discriminated union over every RPC name in `R`. `name` narrows
|
|
6
|
+
* `payload` to that RPC's typed payload.
|
|
7
|
+
*/
|
|
8
|
+
export type RpcEvent<R extends RpcSchemaMap> = {
|
|
9
|
+
[K in keyof R & string]: {
|
|
10
|
+
name: K;
|
|
11
|
+
payload: RpcPayload<R, K>;
|
|
12
|
+
};
|
|
13
|
+
}[keyof R & string];
|
|
14
|
+
/** Same as `RpcEvent<R>` but with the originating `peer` field. */
|
|
15
|
+
export type ServerRpcEvent<R extends RpcSchemaMap> = {
|
|
16
|
+
[K in keyof R & string]: {
|
|
17
|
+
peer: Peer;
|
|
18
|
+
name: K;
|
|
19
|
+
payload: RpcPayload<R, K>;
|
|
20
|
+
};
|
|
21
|
+
}[keyof R & string];
|
|
22
|
+
export interface ServerEventPayloads<R extends RpcSchemaMap = RpcSchemaMap> {
|
|
23
|
+
/** A new peer connected. */
|
|
24
|
+
connection: {
|
|
25
|
+
peer: Peer;
|
|
26
|
+
};
|
|
27
|
+
/** A peer disconnected. */
|
|
28
|
+
disconnection: {
|
|
29
|
+
peer: Peer;
|
|
30
|
+
reason: string;
|
|
31
|
+
};
|
|
32
|
+
/** An intent arrived from a peer and dispatched to its handler. */
|
|
33
|
+
intent: {
|
|
34
|
+
peer: Peer;
|
|
35
|
+
kind: number;
|
|
36
|
+
name: string;
|
|
37
|
+
payload: unknown;
|
|
38
|
+
tick: number;
|
|
39
|
+
};
|
|
40
|
+
/** Engine-level intent failure: decode error or unknown kind. */
|
|
41
|
+
'intent-failed': {
|
|
42
|
+
peer: Peer;
|
|
43
|
+
kind: number;
|
|
44
|
+
reason: string;
|
|
45
|
+
};
|
|
46
|
+
/** An RPC arrived from a peer. `name` and `payload` are correlated. */
|
|
47
|
+
rpc: ServerRpcEvent<R>;
|
|
48
|
+
/** A snapshot was packed and sent to a peer. */
|
|
49
|
+
snapshot: {
|
|
50
|
+
peer: Peer;
|
|
51
|
+
tick: number;
|
|
52
|
+
byteSize: number;
|
|
53
|
+
};
|
|
54
|
+
/** Unhandled error. */
|
|
55
|
+
error: {
|
|
56
|
+
error: Error;
|
|
57
|
+
context: string;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export interface ClientEventPayloads<R extends RpcSchemaMap = RpcSchemaMap> {
|
|
61
|
+
/** Transport opened. */
|
|
62
|
+
connected: {};
|
|
63
|
+
/** Transport closed. */
|
|
64
|
+
disconnected: {
|
|
65
|
+
reason: string;
|
|
66
|
+
};
|
|
67
|
+
/** Server sent a kick frame; disconnect imminent. */
|
|
68
|
+
kicked: {
|
|
69
|
+
reason: string;
|
|
70
|
+
};
|
|
71
|
+
/** A snapshot arrived. */
|
|
72
|
+
snapshot: {
|
|
73
|
+
tick: number;
|
|
74
|
+
byteSize: number;
|
|
75
|
+
};
|
|
76
|
+
/** An RPC arrived from the server. `name` and `payload` are correlated. */
|
|
77
|
+
rpc: RpcEvent<R>;
|
|
78
|
+
/** A new networked entity appeared in the client's view. */
|
|
79
|
+
spawn: {
|
|
80
|
+
entity: number;
|
|
81
|
+
components: Record<string, unknown>;
|
|
82
|
+
};
|
|
83
|
+
/** A networked entity left the client's view (despawn or out-of-AOI). */
|
|
84
|
+
despawn: {
|
|
85
|
+
entity: number;
|
|
86
|
+
};
|
|
87
|
+
/** Reconciliation rollback occurred. */
|
|
88
|
+
reconciled: {
|
|
89
|
+
rewindTick: number;
|
|
90
|
+
replayed: number;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Fires once the server-assigned entity exists locally. The engine
|
|
94
|
+
* buffers assignments that arrive before the matching spawn and
|
|
95
|
+
* resolves them when the entity appears. The entity is auto-marked
|
|
96
|
+
* predicted.
|
|
97
|
+
*/
|
|
98
|
+
assigned: {
|
|
99
|
+
entity: number;
|
|
100
|
+
};
|
|
101
|
+
/** Round-trip measurement from the most recent `client.ping()`. */
|
|
102
|
+
pong: {
|
|
103
|
+
rtt: number;
|
|
104
|
+
};
|
|
105
|
+
/** Unhandled error. */
|
|
106
|
+
error: {
|
|
107
|
+
error: Error;
|
|
108
|
+
context: string;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
type ToEventTuple<P> = {
|
|
112
|
+
[K in keyof P]: [K, P[K]];
|
|
113
|
+
}[keyof P];
|
|
114
|
+
export type NetworkEvents<T extends 'client' | 'server', R extends RpcSchemaMap = RpcSchemaMap> = T extends 'server' ? Array<ToEventTuple<ServerEventPayloads<R>>> : Array<ToEventTuple<ClientEventPayloads<R>>>;
|
|
115
|
+
export declare class Network<T extends 'client' | 'server', R extends RpcSchemaMap = RpcSchemaMap> extends EventSystem<NetworkEvents<T, R> extends [string, unknown][] ? NetworkEvents<T, R> : never> {
|
|
116
|
+
constructor(events: string[]);
|
|
117
|
+
}
|
|
118
|
+
/** Narrowed event surface. `emit` is engine-internal. */
|
|
119
|
+
export type PublicEventSurface<T extends 'client' | 'server', R extends RpcSchemaMap = RpcSchemaMap> = Pick<Network<T, R>, 'on' | 'once' | 'off'>;
|
|
120
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { TransportAdapter, ServerTransportAdapter, NetworkConfig, PeerState, LagSimulation, } from 'murow/net';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|