murow 0.1.1 → 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 +1 -1
- package/dist/cjs/core/clock/clock.js +1 -0
- package/dist/cjs/core/clock/index.js +1 -0
- 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/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/game/loop/loop.js +1 -1
- package/dist/cjs/game/loop/ticker-schedule.js +1 -0
- 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/clock/clock.js +1 -0
- package/dist/esm/core/clock/index.js +1 -0
- 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/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/game/loop/loop.js +1 -1
- package/dist/esm/game/loop/ticker-schedule.js +1 -0
- 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 +144 -140
- package/dist/netcode/esm/index.js +144 -140
- package/dist/netcode/types/client/game-client.d.ts +17 -3
- package/dist/netcode/types/client/strategies/snapshot-interpolation.d.ts +33 -0
- package/dist/netcode/types/codec/delta-codec.d.ts +1 -1
- package/dist/netcode/types/components/sync-spec.d.ts +6 -0
- package/dist/types/core/clock/clock.d.ts +37 -0
- package/dist/types/core/clock/index.d.ts +1 -0
- 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/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/game/loop/loop.d.ts +30 -0
- package/dist/types/game/loop/ticker-schedule.d.ts +52 -0
- 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 +1777 -587
- package/dist/webgpu/esm/index.js +1769 -573
- 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 +50 -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 +1 -1
- package/dist/netcode/types/client/interpolation-buffer.d.ts +0 -37
- /package/dist/netcode/types/client/{interpolation-buffer.test.d.ts → strategies/snapshot-interpolation.test.d.ts} +0 -0
|
@@ -34,7 +34,8 @@ __export(src_exports, {
|
|
|
34
34
|
encodeDelta: () => encodeDelta,
|
|
35
35
|
makeFieldsAccessor: () => makeFieldsAccessor,
|
|
36
36
|
makeMarkDirty: () => makeMarkDirty,
|
|
37
|
-
networked: () => networked
|
|
37
|
+
networked: () => networked,
|
|
38
|
+
rateIncludes: () => rateIncludes
|
|
38
39
|
});
|
|
39
40
|
module.exports = __toCommonJS(src_exports);
|
|
40
41
|
|
|
@@ -108,7 +109,7 @@ var import_protocol3 = require("murow/protocol");
|
|
|
108
109
|
|
|
109
110
|
// src/codec/delta-codec.ts
|
|
110
111
|
var HEADER_BYTES = 4 + 4 + 2 + 2;
|
|
111
|
-
function encodeDelta(world, tick, entities, components, numMaskWords, despawned = [], clientAckTick = 0) {
|
|
112
|
+
function encodeDelta(world, tick, entities, components, numMaskWords, despawned = [], clientAckTick = 0, includeComponent = () => true) {
|
|
112
113
|
const perEntityMasks = new Array(entities.length);
|
|
113
114
|
const perEntityBitmaskBytes = numMaskWords * 4;
|
|
114
115
|
let bodyBytes = 0;
|
|
@@ -120,6 +121,8 @@ function encodeDelta(world, tick, entities, components, numMaskWords, despawned
|
|
|
120
121
|
const c = components[ci];
|
|
121
122
|
if (!world.has(eid, c))
|
|
122
123
|
continue;
|
|
124
|
+
if (!includeComponent(eid, c))
|
|
125
|
+
continue;
|
|
123
126
|
const wordIndex = ci >>> 5;
|
|
124
127
|
const bitIndex = ci & 31;
|
|
125
128
|
mask[wordIndex] |= 1 << bitIndex;
|
|
@@ -271,6 +274,18 @@ function decodeDelta(world, buf, components, numMaskWords, ensureEntity, shouldA
|
|
|
271
274
|
};
|
|
272
275
|
}
|
|
273
276
|
|
|
277
|
+
// src/components/sync-spec.ts
|
|
278
|
+
function networked(spec) {
|
|
279
|
+
return spec;
|
|
280
|
+
}
|
|
281
|
+
function rateIncludes(rate, dirty, tick) {
|
|
282
|
+
if (rate === "every-tick")
|
|
283
|
+
return true;
|
|
284
|
+
if (rate === "on-change")
|
|
285
|
+
return dirty;
|
|
286
|
+
return dirty || rate.every > 0 && tick % rate.every === 0;
|
|
287
|
+
}
|
|
288
|
+
|
|
274
289
|
// src/ctx.ts
|
|
275
290
|
function makeFieldsAccessor(world, entity) {
|
|
276
291
|
return function fields(component) {
|
|
@@ -501,6 +516,7 @@ var GameServer = class extends Network {
|
|
|
501
516
|
}
|
|
502
517
|
if (current.length === 0 && despawned.length === 0 && !peer.needsBaseline)
|
|
503
518
|
continue;
|
|
519
|
+
const include = peer.needsBaseline ? void 0 : (eid, c) => rateIncludes(c.__sync.rate, this.world.isDirty(eid, c), this.tickCounter);
|
|
504
520
|
const buf = encodeDelta(
|
|
505
521
|
this.world,
|
|
506
522
|
this.tickCounter,
|
|
@@ -508,7 +524,8 @@ var GameServer = class extends Network {
|
|
|
508
524
|
this.syncedComponents,
|
|
509
525
|
this.syncedNumMaskWords,
|
|
510
526
|
despawned,
|
|
511
|
-
peer.lastAckedClientTick
|
|
527
|
+
peer.lastAckedClientTick,
|
|
528
|
+
include
|
|
512
529
|
);
|
|
513
530
|
const framed = new Uint8Array(buf.length + 1);
|
|
514
531
|
framed[0] = MSG_SNAPSHOT;
|
|
@@ -850,67 +867,67 @@ var LagCompensation = class {
|
|
|
850
867
|
// src/client/game-client.ts
|
|
851
868
|
var import_binary_codec2 = require("murow/core/binary-codec");
|
|
852
869
|
var import_simple_rng2 = require("murow/core/simple-rng");
|
|
870
|
+
var import_prediction = require("murow/core/prediction");
|
|
853
871
|
var import_protocol4 = require("murow/protocol");
|
|
854
872
|
|
|
855
|
-
// src/client/interpolation
|
|
873
|
+
// src/client/strategies/snapshot-interpolation.ts
|
|
856
874
|
var import_lerp = require("murow/core/lerp");
|
|
875
|
+
var import_timeline = require("murow/core/timeline");
|
|
876
|
+
var import_clock = require("murow/core/clock");
|
|
857
877
|
function modeFor(c) {
|
|
858
878
|
const sync = c.__sync;
|
|
859
879
|
return sync?.interp ?? "lerp";
|
|
860
880
|
}
|
|
861
|
-
var
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
this.
|
|
866
|
-
this.smoothedTickRateMs = 0;
|
|
881
|
+
var DEFAULT_MAX_DESYNC = 500;
|
|
882
|
+
var DEFAULT_MAX_BRIDGE_GAP = 250;
|
|
883
|
+
var SnapshotInterpolation = class {
|
|
884
|
+
constructor(serverToLocal, capacity, delayMs, staleWindowMs, maxDesyncMs = DEFAULT_MAX_DESYNC, nominalTickMs = 0, maxBridgeGapMs = DEFAULT_MAX_BRIDGE_GAP) {
|
|
885
|
+
this.clock = new import_clock.SlewClock();
|
|
867
886
|
this.serverToLocal = serverToLocal;
|
|
868
|
-
this.
|
|
887
|
+
this.timeline = new import_timeline.Timeline(capacity, staleWindowMs);
|
|
869
888
|
this.delay = delayMs;
|
|
870
|
-
this.
|
|
889
|
+
this.maxDesync = maxDesyncMs;
|
|
890
|
+
this.nominalTickMs = nominalTickMs;
|
|
891
|
+
this.smoothedTickRateMs = nominalTickMs;
|
|
892
|
+
this.maxBridgeGap = maxBridgeGapMs;
|
|
893
|
+
}
|
|
894
|
+
get staleWindow() {
|
|
895
|
+
return this.timeline.staleWindow;
|
|
871
896
|
}
|
|
872
897
|
setDelay(delayMs) {
|
|
873
898
|
this.delay = delayMs;
|
|
874
899
|
}
|
|
875
900
|
setStaleWindow(staleWindowMs) {
|
|
876
|
-
this.
|
|
901
|
+
this.timeline.setStaleWindow(staleWindowMs);
|
|
902
|
+
}
|
|
903
|
+
setMaxDesync(maxDesyncMs) {
|
|
904
|
+
this.maxDesync = maxDesyncMs;
|
|
905
|
+
}
|
|
906
|
+
setMaxBridgeGap(maxBridgeGapMs) {
|
|
907
|
+
this.maxBridgeGap = maxBridgeGapMs;
|
|
877
908
|
}
|
|
878
909
|
record(snapshot) {
|
|
879
|
-
const
|
|
880
|
-
if (
|
|
881
|
-
this.
|
|
882
|
-
this.
|
|
883
|
-
this.latestReceivedAt = -Infinity;
|
|
884
|
-
this.smoothedTickRateMs = 0;
|
|
885
|
-
}
|
|
886
|
-
if (snapshot.receivedAt > this.latestReceivedAt) {
|
|
887
|
-
this.latestReceivedAt = snapshot.receivedAt;
|
|
910
|
+
const reset = this.timeline.record(snapshot.serverTick, snapshot.receivedAt, snapshot);
|
|
911
|
+
if (reset) {
|
|
912
|
+
this.smoothedTickRateMs = this.nominalTickMs;
|
|
913
|
+
this.clock.reset();
|
|
888
914
|
}
|
|
889
|
-
let insertAt = this.buffer.length;
|
|
890
|
-
while (insertAt > 0 && this.buffer[insertAt - 1].serverTick >= snapshot.serverTick) {
|
|
891
|
-
if (this.buffer[insertAt - 1].serverTick === snapshot.serverTick)
|
|
892
|
-
return;
|
|
893
|
-
insertAt--;
|
|
894
|
-
}
|
|
895
|
-
this.buffer.splice(insertAt, 0, snapshot);
|
|
896
|
-
while (this.buffer.length > this.capacity)
|
|
897
|
-
this.buffer.shift();
|
|
898
915
|
}
|
|
899
916
|
clear() {
|
|
900
|
-
this.
|
|
901
|
-
this.
|
|
902
|
-
this.
|
|
903
|
-
this.smoothedTickRateMs = 0;
|
|
917
|
+
this.timeline.clear();
|
|
918
|
+
this.clock.reset();
|
|
919
|
+
this.smoothedTickRateMs = this.nominalTickMs;
|
|
904
920
|
}
|
|
905
921
|
apply(world, now, components, shouldSkip) {
|
|
906
|
-
|
|
922
|
+
const tl = this.timeline;
|
|
923
|
+
if (tl.length === 0)
|
|
907
924
|
return;
|
|
908
|
-
const newest =
|
|
909
|
-
const oldest =
|
|
925
|
+
const newest = tl.newest().sample;
|
|
926
|
+
const oldest = tl.oldest().sample;
|
|
910
927
|
let tickRateMs = 0;
|
|
911
|
-
if (
|
|
928
|
+
if (tl.length >= 2) {
|
|
912
929
|
const tickSpan = newest.serverTick - oldest.serverTick;
|
|
913
|
-
const wallSpan =
|
|
930
|
+
const wallSpan = tl.latestReceivedAt - oldest.receivedAt;
|
|
914
931
|
const rawTickRateMs = tickSpan > 0 && wallSpan > 0 ? wallSpan / tickSpan : 0;
|
|
915
932
|
if (rawTickRateMs > 0) {
|
|
916
933
|
if (this.smoothedTickRateMs === 0)
|
|
@@ -928,42 +945,23 @@ var InterpolationBuffer = class {
|
|
|
928
945
|
}
|
|
929
946
|
const ageBeyondDelay = now - newest.receivedAt - this.delay;
|
|
930
947
|
const targetTick = newest.serverTick + ageBeyondDelay / tickRateMs;
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
const drift = targetTick - this.renderTick;
|
|
935
|
-
if (drift > 2) {
|
|
936
|
-
this.renderTick = targetTick;
|
|
937
|
-
} else {
|
|
938
|
-
const warp = Math.max(0.9, Math.min(1.1, 1 + drift * 0.05));
|
|
939
|
-
this.renderTick += warp;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
const renderTick = this.renderTick;
|
|
943
|
-
let a = null;
|
|
944
|
-
let b = null;
|
|
945
|
-
for (let i = 0; i < this.buffer.length - 1; i++) {
|
|
946
|
-
const s0 = this.buffer[i];
|
|
947
|
-
const s1 = this.buffer[i + 1];
|
|
948
|
-
if (s0.serverTick <= renderTick && renderTick <= s1.serverTick) {
|
|
949
|
-
a = s0;
|
|
950
|
-
b = s1;
|
|
951
|
-
break;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
if (a === null || b === null) {
|
|
948
|
+
const renderTick = this.clock.advance(targetTick, this.maxDesync / tickRateMs);
|
|
949
|
+
const straddle = tl.straddle(renderTick);
|
|
950
|
+
if (straddle === null) {
|
|
955
951
|
if (renderTick < oldest.serverTick)
|
|
956
952
|
return;
|
|
957
953
|
this.writeSnapshot(world, newest, components, shouldSkip);
|
|
958
954
|
return;
|
|
959
955
|
}
|
|
956
|
+
const [aIndex, bIndex] = straddle;
|
|
957
|
+
const a = tl.at(aIndex).sample;
|
|
958
|
+
const b = tl.at(bIndex).sample;
|
|
960
959
|
const seen = /* @__PURE__ */ new Set();
|
|
961
960
|
for (const eid of a.entityIds)
|
|
962
961
|
seen.add(eid);
|
|
963
962
|
for (const eid of b.entityIds)
|
|
964
963
|
seen.add(eid);
|
|
965
|
-
const
|
|
966
|
-
const bIndex = this.buffer.indexOf(b);
|
|
964
|
+
const maxBridgeTicks = this.maxBridgeGap / tickRateMs;
|
|
967
965
|
for (const serverEid of seen) {
|
|
968
966
|
const localEid = this.serverToLocal.get(serverEid);
|
|
969
967
|
if (localEid === void 0)
|
|
@@ -975,13 +973,13 @@ var InterpolationBuffer = class {
|
|
|
975
973
|
let va = a.componentValuesByEntity.get(serverEid)?.get(c);
|
|
976
974
|
while (va === void 0 && aIdx > 0) {
|
|
977
975
|
aIdx--;
|
|
978
|
-
va =
|
|
976
|
+
va = tl.at(aIdx).sample.componentValuesByEntity.get(serverEid)?.get(c);
|
|
979
977
|
}
|
|
980
978
|
let bIdx = bIndex;
|
|
981
979
|
let vb = b.componentValuesByEntity.get(serverEid)?.get(c);
|
|
982
|
-
while (vb === void 0 && bIdx <
|
|
980
|
+
while (vb === void 0 && bIdx < tl.length - 1) {
|
|
983
981
|
bIdx++;
|
|
984
|
-
vb =
|
|
982
|
+
vb = tl.at(bIdx).sample.componentValuesByEntity.get(serverEid)?.get(c);
|
|
985
983
|
}
|
|
986
984
|
if (va === void 0 && vb === void 0)
|
|
987
985
|
continue;
|
|
@@ -995,8 +993,10 @@ var InterpolationBuffer = class {
|
|
|
995
993
|
if (mode === "none") {
|
|
996
994
|
toWrite = vb;
|
|
997
995
|
} else {
|
|
998
|
-
const
|
|
999
|
-
|
|
996
|
+
const bTick = tl.at(bIdx).sample.serverTick;
|
|
997
|
+
let aTick = tl.at(aIdx).sample.serverTick;
|
|
998
|
+
if (bTick - aTick > maxBridgeTicks)
|
|
999
|
+
aTick = bTick - 1;
|
|
1000
1000
|
const wideSpan = bTick - aTick;
|
|
1001
1001
|
const wideT = wideSpan > 0 ? Math.min(1, Math.max(0, (renderTick - aTick) / wideSpan)) : 0;
|
|
1002
1002
|
if (mode === "step") {
|
|
@@ -1073,7 +1073,6 @@ var GameClient = class extends Network {
|
|
|
1073
1073
|
"error"
|
|
1074
1074
|
]);
|
|
1075
1075
|
this.predictionMap = null;
|
|
1076
|
-
this.predictionHistory = [];
|
|
1077
1076
|
this.localTick = 0;
|
|
1078
1077
|
this.intentSequence = 0;
|
|
1079
1078
|
this.lastServerTick = 0;
|
|
@@ -1084,6 +1083,8 @@ var GameClient = class extends Network {
|
|
|
1084
1083
|
this.predictedEntities = /* @__PURE__ */ new Set();
|
|
1085
1084
|
/** Set when MSG_ASSIGN_ENTITY lands before the matching spawn. */
|
|
1086
1085
|
this.pendingAssignedServerEid = null;
|
|
1086
|
+
/** Spawns discovered during a decode, emitted once values are written. */
|
|
1087
|
+
this.deferredSpawns = [];
|
|
1087
1088
|
/** Resolved local entity for the server's assignment. Default for sendIntent. */
|
|
1088
1089
|
this._assignedEntity = null;
|
|
1089
1090
|
this._rttMs = null;
|
|
@@ -1104,12 +1105,60 @@ var GameClient = class extends Network {
|
|
|
1104
1105
|
this.rng = new import_simple_rng2.SimpleRNG(1);
|
|
1105
1106
|
this.lastDt = opts.loop.ticker.intervalMs / 1e3;
|
|
1106
1107
|
this.now = opts.now ?? (() => performance.now());
|
|
1107
|
-
this.interpBuffer = new
|
|
1108
|
+
this.interpBuffer = new SnapshotInterpolation(
|
|
1108
1109
|
this.serverToLocal,
|
|
1109
1110
|
16,
|
|
1110
1111
|
interpolationDelay,
|
|
1111
|
-
staleWindowMs
|
|
1112
|
+
staleWindowMs,
|
|
1113
|
+
strategy.maxDesync,
|
|
1114
|
+
opts.loop.ticker.intervalMs,
|
|
1115
|
+
strategy.maxBridgeGap
|
|
1112
1116
|
);
|
|
1117
|
+
this.reconciler = new import_prediction.Reconciler({
|
|
1118
|
+
bufferSize: this.predictionBufferSize,
|
|
1119
|
+
restore: ({ decoded, resetEntities }) => {
|
|
1120
|
+
for (const serverEid of decoded.serverEntityIds) {
|
|
1121
|
+
const localEid = this.serverToLocal.get(serverEid);
|
|
1122
|
+
if (localEid === void 0)
|
|
1123
|
+
continue;
|
|
1124
|
+
if (!this.predictedEntities.has(localEid))
|
|
1125
|
+
continue;
|
|
1126
|
+
const comps = decoded.valuesByServerEntity.get(serverEid);
|
|
1127
|
+
if (comps === void 0)
|
|
1128
|
+
continue;
|
|
1129
|
+
for (const [c, value] of comps) {
|
|
1130
|
+
if (this.world.has(localEid, c)) {
|
|
1131
|
+
this.world.update(localEid, c, value);
|
|
1132
|
+
} else {
|
|
1133
|
+
this.world.add(localEid, c, value);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
resetEntities.add(localEid);
|
|
1137
|
+
}
|
|
1138
|
+
},
|
|
1139
|
+
replay: (preds, { decoded, resetEntities }) => {
|
|
1140
|
+
let replayedCount = 0;
|
|
1141
|
+
for (const pred of preds) {
|
|
1142
|
+
if (!resetEntities.has(pred.entity))
|
|
1143
|
+
continue;
|
|
1144
|
+
const predFn = this.predictionMap?.[pred.name];
|
|
1145
|
+
if (predFn === void 0)
|
|
1146
|
+
continue;
|
|
1147
|
+
const ctx = {
|
|
1148
|
+
world: this.world,
|
|
1149
|
+
entity: pred.entity,
|
|
1150
|
+
tick: pred.tick,
|
|
1151
|
+
deltaTime: pred.deltaTime,
|
|
1152
|
+
rng: this.rng,
|
|
1153
|
+
fields: makeFieldsAccessor(this.world, pred.entity),
|
|
1154
|
+
markDirty: makeMarkDirty(this.world, pred.entity)
|
|
1155
|
+
};
|
|
1156
|
+
predFn(pred.payload, ctx);
|
|
1157
|
+
replayedCount++;
|
|
1158
|
+
}
|
|
1159
|
+
this.emit("reconciled", { rewindTick: decoded.clientAckTick, replayed: replayedCount });
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1113
1162
|
this.discoverSyncedComponents();
|
|
1114
1163
|
this.wireTransport();
|
|
1115
1164
|
this.wireLoop();
|
|
@@ -1196,6 +1245,7 @@ var GameClient = class extends Network {
|
|
|
1196
1245
|
}
|
|
1197
1246
|
handleSnapshot(payload) {
|
|
1198
1247
|
try {
|
|
1248
|
+
this.deferredSpawns.length = 0;
|
|
1199
1249
|
const decoded = decodeDelta(
|
|
1200
1250
|
this.world,
|
|
1201
1251
|
payload,
|
|
@@ -1209,6 +1259,12 @@ var GameClient = class extends Network {
|
|
|
1209
1259
|
() => false
|
|
1210
1260
|
);
|
|
1211
1261
|
this.lastServerTick = decoded.tick;
|
|
1262
|
+
for (const spawn of this.deferredSpawns) {
|
|
1263
|
+
this.emit("spawn", { entity: spawn.entity, components: spawn.components });
|
|
1264
|
+
if (spawn.assigned)
|
|
1265
|
+
this.emit("assigned", { entity: spawn.entity });
|
|
1266
|
+
}
|
|
1267
|
+
this.deferredSpawns.length = 0;
|
|
1212
1268
|
this.emit("snapshot", { tick: decoded.tick, byteSize: payload.length + 1 });
|
|
1213
1269
|
this.interpBuffer.record({
|
|
1214
1270
|
receivedAt: this.now(),
|
|
@@ -1241,13 +1297,14 @@ var GameClient = class extends Network {
|
|
|
1241
1297
|
const components = {};
|
|
1242
1298
|
for (const c of present)
|
|
1243
1299
|
components[c.name] = true;
|
|
1244
|
-
|
|
1300
|
+
let assigned = false;
|
|
1245
1301
|
if (this.pendingAssignedServerEid === serverEid) {
|
|
1246
1302
|
this.pendingAssignedServerEid = null;
|
|
1247
1303
|
this.predictedEntities.add(localEid);
|
|
1248
1304
|
this._assignedEntity = localEid;
|
|
1249
|
-
|
|
1305
|
+
assigned = true;
|
|
1250
1306
|
}
|
|
1307
|
+
this.deferredSpawns.push({ entity: localEid, components, assigned });
|
|
1251
1308
|
}
|
|
1252
1309
|
return localEid;
|
|
1253
1310
|
}
|
|
@@ -1266,60 +1323,9 @@ var GameClient = class extends Network {
|
|
|
1266
1323
|
* the server has acked, replay the rest on top.
|
|
1267
1324
|
*/
|
|
1268
1325
|
reconcile(decoded) {
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
if (localEid === void 0)
|
|
1273
|
-
continue;
|
|
1274
|
-
if (!this.predictedEntities.has(localEid))
|
|
1275
|
-
continue;
|
|
1276
|
-
const comps = decoded.valuesByServerEntity.get(serverEid);
|
|
1277
|
-
if (comps === void 0)
|
|
1278
|
-
continue;
|
|
1279
|
-
for (const [c, value] of comps) {
|
|
1280
|
-
if (this.world.has(localEid, c)) {
|
|
1281
|
-
this.world.update(localEid, c, value);
|
|
1282
|
-
} else {
|
|
1283
|
-
this.world.add(localEid, c, value);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
resetEntities.add(localEid);
|
|
1287
|
-
}
|
|
1288
|
-
const ackSequence = decoded.clientAckTick;
|
|
1289
|
-
let cut = 0;
|
|
1290
|
-
while (cut < this.predictionHistory.length && this.predictionHistory[cut].sequence <= ackSequence) {
|
|
1291
|
-
cut++;
|
|
1292
|
-
}
|
|
1293
|
-
if (cut > 0)
|
|
1294
|
-
this.predictionHistory.splice(0, cut);
|
|
1295
|
-
const remaining = this.predictionHistory.length;
|
|
1296
|
-
if (remaining === 0) {
|
|
1297
|
-
this.emit("reconciled", { rewindTick: ackSequence, replayed: 0 });
|
|
1298
|
-
return;
|
|
1299
|
-
}
|
|
1300
|
-
let replayedCount = 0;
|
|
1301
|
-
for (let i = 0; i < remaining; i++) {
|
|
1302
|
-
const pred = this.predictionHistory[i];
|
|
1303
|
-
if (!resetEntities.has(pred.entity))
|
|
1304
|
-
continue;
|
|
1305
|
-
const predFn = this.predictionMap?.[pred.name];
|
|
1306
|
-
if (predFn === void 0)
|
|
1307
|
-
continue;
|
|
1308
|
-
const ctx = {
|
|
1309
|
-
world: this.world,
|
|
1310
|
-
entity: pred.entity,
|
|
1311
|
-
tick: pred.tick,
|
|
1312
|
-
deltaTime: pred.deltaTime,
|
|
1313
|
-
rng: this.rng,
|
|
1314
|
-
fields: makeFieldsAccessor(this.world, pred.entity),
|
|
1315
|
-
markDirty: makeMarkDirty(this.world, pred.entity)
|
|
1316
|
-
};
|
|
1317
|
-
predFn(pred.payload, ctx);
|
|
1318
|
-
replayedCount++;
|
|
1319
|
-
}
|
|
1320
|
-
this.emit("reconciled", {
|
|
1321
|
-
rewindTick: ackSequence,
|
|
1322
|
-
replayed: replayedCount
|
|
1326
|
+
this.reconciler.reconcile(decoded.clientAckTick, {
|
|
1327
|
+
decoded,
|
|
1328
|
+
resetEntities: /* @__PURE__ */ new Set()
|
|
1323
1329
|
});
|
|
1324
1330
|
}
|
|
1325
1331
|
handleRpc(payload) {
|
|
@@ -1393,7 +1399,7 @@ var GameClient = class extends Network {
|
|
|
1393
1399
|
markDirty: makeMarkDirty(this.world, entity)
|
|
1394
1400
|
};
|
|
1395
1401
|
predFn(payload, ctx);
|
|
1396
|
-
this.
|
|
1402
|
+
this.reconciler.record(sequence, {
|
|
1397
1403
|
tick: this.localTick,
|
|
1398
1404
|
sequence,
|
|
1399
1405
|
name,
|
|
@@ -1402,9 +1408,6 @@ var GameClient = class extends Network {
|
|
|
1402
1408
|
deltaTime
|
|
1403
1409
|
});
|
|
1404
1410
|
this.predictedEntities.add(entity);
|
|
1405
|
-
while (this.predictionHistory.length > this.predictionBufferSize) {
|
|
1406
|
-
this.predictionHistory.shift();
|
|
1407
|
-
}
|
|
1408
1411
|
}
|
|
1409
1412
|
return true;
|
|
1410
1413
|
}
|
|
@@ -1435,7 +1438,7 @@ var GameClient = class extends Network {
|
|
|
1435
1438
|
return this.localTick;
|
|
1436
1439
|
}
|
|
1437
1440
|
getPredictionDepth() {
|
|
1438
|
-
return this.
|
|
1441
|
+
return this.reconciler.pending;
|
|
1439
1442
|
}
|
|
1440
1443
|
get interpolationDelay() {
|
|
1441
1444
|
return this.interpBuffer.delay;
|
|
@@ -1443,6 +1446,12 @@ var GameClient = class extends Network {
|
|
|
1443
1446
|
setInterpolationDelay(ms) {
|
|
1444
1447
|
this.interpBuffer.setDelay(ms);
|
|
1445
1448
|
}
|
|
1449
|
+
setMaxDesync(ms) {
|
|
1450
|
+
this.interpBuffer.setMaxDesync(ms);
|
|
1451
|
+
}
|
|
1452
|
+
setMaxBridgeGap(ms) {
|
|
1453
|
+
this.interpBuffer.setMaxBridgeGap(ms);
|
|
1454
|
+
}
|
|
1446
1455
|
};
|
|
1447
1456
|
|
|
1448
1457
|
// src/transports/memory-transport.ts
|
|
@@ -1545,8 +1554,3 @@ var MemoryPeerTransport = class {
|
|
|
1545
1554
|
queueMicrotask(() => this.clientOpenHandler?.());
|
|
1546
1555
|
}
|
|
1547
1556
|
};
|
|
1548
|
-
|
|
1549
|
-
// src/components/sync-spec.ts
|
|
1550
|
-
function networked(spec) {
|
|
1551
|
-
return spec;
|
|
1552
|
-
}
|