@irtio/runtime 0.2.0 → 0.4.0
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/{chunk-GKMD3ICD.js → chunk-5ZDQAAFJ.js} +66 -15
- package/dist/{chunk-FA4IOC2Z.js → chunk-HFOMXKSO.js} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/{room-BfALTh7M.d.ts → room-9ZQoy9yi.d.ts} +28 -1
- package/dist/test/index.d.ts +2 -2
- package/dist/test/index.js +1 -1
- package/dist/worker/index.js +2 -2
- package/package.json +4 -4
|
@@ -421,7 +421,10 @@ function applyWrite(core, clientId, payload) {
|
|
|
421
421
|
return { ok: false };
|
|
422
422
|
}
|
|
423
423
|
const judged = core.clients.get(clientId);
|
|
424
|
-
if (judged && delta.tick > judged.lastClientTick)
|
|
424
|
+
if (judged && delta.tick > judged.lastClientTick) {
|
|
425
|
+
judged.lastClientTick = delta.tick;
|
|
426
|
+
judged.lastAppliedTick = core.tick;
|
|
427
|
+
}
|
|
425
428
|
for (const dc of delta.collections) {
|
|
426
429
|
const c = collectionDescOf(core.ext, dc.name);
|
|
427
430
|
if (!c) continue;
|
|
@@ -672,6 +675,7 @@ var Loop = class {
|
|
|
672
675
|
this.fireDueTimers();
|
|
673
676
|
checkTimeouts(this.core);
|
|
674
677
|
const config = this.core.definition.config;
|
|
678
|
+
let failed;
|
|
675
679
|
if (config.tick) {
|
|
676
680
|
const fn = config.tick;
|
|
677
681
|
const dt = 1 / config.tickRate;
|
|
@@ -679,26 +683,30 @@ var Loop = class {
|
|
|
679
683
|
"tick",
|
|
680
684
|
() => fn(this.core.anyState, dt, this.core.room)
|
|
681
685
|
);
|
|
682
|
-
if (ran.ok)
|
|
683
|
-
else {
|
|
684
|
-
this.consecutiveThrows++;
|
|
685
|
-
if (this.consecutiveThrows >= CRASH_AFTER_THROWS) {
|
|
686
|
-
this.core.flush();
|
|
687
|
-
const reason = `tick() threw ${this.consecutiveThrows} times in a row`;
|
|
688
|
-
this.core.stopped = true;
|
|
689
|
-
this.stop();
|
|
690
|
-
host.crashed(reason);
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
686
|
+
if (!ran.ok) failed = "tick()";
|
|
694
687
|
}
|
|
695
688
|
const physics = this.core.physics;
|
|
696
689
|
if (physics) {
|
|
697
|
-
this.core.tryRun("physics", () => {
|
|
690
|
+
const ran = this.core.tryRun("physics", () => {
|
|
698
691
|
physics.reconcile();
|
|
699
692
|
physics.step();
|
|
700
693
|
physics.sync();
|
|
701
694
|
});
|
|
695
|
+
if (!ran.ok) {
|
|
696
|
+
failed = failed === void 0 ? "the physics step" : `${failed} and the physics step`;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
if (failed === void 0) this.consecutiveThrows = 0;
|
|
700
|
+
else {
|
|
701
|
+
this.consecutiveThrows++;
|
|
702
|
+
if (this.consecutiveThrows >= CRASH_AFTER_THROWS) {
|
|
703
|
+
this.core.flush();
|
|
704
|
+
const reason = `${failed} threw ${this.consecutiveThrows} times in a row`;
|
|
705
|
+
this.core.stopped = true;
|
|
706
|
+
this.stop();
|
|
707
|
+
host.crashed(reason);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
702
710
|
}
|
|
703
711
|
this.core.flush();
|
|
704
712
|
const elapsed = Math.max(0, host.now() - started);
|
|
@@ -852,6 +860,25 @@ function decodePhysicsSection(bytes) {
|
|
|
852
860
|
function bodyKey(collection, id) {
|
|
853
861
|
return `${collection}\0${id}`;
|
|
854
862
|
}
|
|
863
|
+
function planarLockWarning(spec) {
|
|
864
|
+
const boxed = (spec.colliders ?? []).some(
|
|
865
|
+
(c) => c.shape.type === CUBOID_SHAPE || c.shape.type === ROUND_CUBOID_SHAPE
|
|
866
|
+
);
|
|
867
|
+
if (!boxed) return void 0;
|
|
868
|
+
const b = spec.body;
|
|
869
|
+
const t = [b.translationsEnabledX, b.translationsEnabledY, b.translationsEnabledZ];
|
|
870
|
+
const r = [b.rotationsEnabledX, b.rotationsEnabledY, b.rotationsEnabledZ];
|
|
871
|
+
const axes = ["x", "y", "z"];
|
|
872
|
+
for (let i = 0; i < 3; i++) {
|
|
873
|
+
if (t[i]) continue;
|
|
874
|
+
if (r[(i + 1) % 3] || r[(i + 2) % 3]) continue;
|
|
875
|
+
const free = axes[i] === "z" ? "enabledRotations(true, false, true)" : "one of the other two rotations";
|
|
876
|
+
return `locks translation on ${axes[i]} and both rotations across that plane on a box-shaped body. That removes friction entirely: the box slides at a constant speed until it hits something. Free one out-of-plane rotation \u2014 in the xy plane that is ${free}, and it costs nothing, because geometry symmetric about the plane generates no torque about it.`;
|
|
877
|
+
}
|
|
878
|
+
return void 0;
|
|
879
|
+
}
|
|
880
|
+
var CUBOID_SHAPE = 1;
|
|
881
|
+
var ROUND_CUBOID_SHAPE = 12;
|
|
855
882
|
var PhysicsRuntime = class {
|
|
856
883
|
rapier;
|
|
857
884
|
world;
|
|
@@ -862,6 +889,8 @@ var PhysicsRuntime = class {
|
|
|
862
889
|
/** Physics-backed collections, in schema (name-sorted) order. */
|
|
863
890
|
collections;
|
|
864
891
|
bodies = /* @__PURE__ */ new Map();
|
|
892
|
+
/** Collections already warned about the friction-killing 2D lock recipe (bug 6). */
|
|
893
|
+
warnedPlanar = /* @__PURE__ */ new Set();
|
|
865
894
|
/**
|
|
866
895
|
* Bodies whose sleep has already been synced. Rapier zeroes a body's velocity when it puts it
|
|
867
896
|
* to sleep — *after* the last awake-tick sync — so a body must be synced **once more** on the
|
|
@@ -936,6 +965,11 @@ var PhysicsRuntime = class {
|
|
|
936
965
|
);
|
|
937
966
|
return void 0;
|
|
938
967
|
}
|
|
968
|
+
const planar = planarLockWarning(spec);
|
|
969
|
+
if (planar !== void 0 && !this.warnedPlanar.has(desc.name)) {
|
|
970
|
+
this.warnedPlanar.add(desc.name);
|
|
971
|
+
this.core.log("warn", `irtio: physics.bodies.${desc.name} ${planar}`);
|
|
972
|
+
}
|
|
939
973
|
const body = this.world.createRigidBody(spec.body);
|
|
940
974
|
for (const collider of spec.colliders ?? []) this.world.createCollider(collider, body);
|
|
941
975
|
this.applyRecordToBody(desc, body, record);
|
|
@@ -1809,6 +1843,13 @@ var RoomCore = class _RoomCore {
|
|
|
1809
1843
|
};
|
|
1810
1844
|
tick = 0;
|
|
1811
1845
|
stopped = false;
|
|
1846
|
+
/**
|
|
1847
|
+
* Called for every handler throw `tryRun` swallows, before it is logged. A test harness sets
|
|
1848
|
+
* this so a room that breaks fails the test that broke it (bug 2): the runtime's job is to
|
|
1849
|
+
* keep the room up in production, but under test that same guarding turns a broken handler
|
|
1850
|
+
* into a timeout in an unrelated assertion ten seconds later. Unset in production.
|
|
1851
|
+
*/
|
|
1852
|
+
onHandlerError;
|
|
1812
1853
|
seed;
|
|
1813
1854
|
api;
|
|
1814
1855
|
internals;
|
|
@@ -1925,6 +1966,10 @@ var RoomCore = class _RoomCore {
|
|
|
1925
1966
|
`${name}: ${err instanceof Error ? err.message : String(err)}`
|
|
1926
1967
|
);
|
|
1927
1968
|
this.host.log("error", [`irtio: ${name} threw`, err]);
|
|
1969
|
+
try {
|
|
1970
|
+
this.onHandlerError?.(name, err);
|
|
1971
|
+
} catch {
|
|
1972
|
+
}
|
|
1928
1973
|
return { ok: false };
|
|
1929
1974
|
}
|
|
1930
1975
|
}
|
|
@@ -2072,6 +2117,7 @@ var RoomCore = class _RoomCore {
|
|
|
2072
2117
|
correction: void 0,
|
|
2073
2118
|
accepted: /* @__PURE__ */ new Map(),
|
|
2074
2119
|
lastClientTick: 0,
|
|
2120
|
+
lastAppliedTick: 0,
|
|
2075
2121
|
pendingJoinAdds: void 0,
|
|
2076
2122
|
spatialMembership: /* @__PURE__ */ new Map()
|
|
2077
2123
|
};
|
|
@@ -2335,7 +2381,12 @@ var RoomCore = class _RoomCore {
|
|
|
2335
2381
|
this.internals,
|
|
2336
2382
|
membershipSafeDirty(this.ext, entry.correction, memberships)
|
|
2337
2383
|
);
|
|
2338
|
-
if (payload)
|
|
2384
|
+
if (payload) {
|
|
2385
|
+
this.send(
|
|
2386
|
+
entry.clientId,
|
|
2387
|
+
encodeCorrectFrame(payload, entry.lastClientTick, entry.lastAppliedTick)
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2339
2390
|
}
|
|
2340
2391
|
let delta;
|
|
2341
2392
|
if (spatialDescs.length > 0) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { E as EVENT_RING_SIZE, H as HOST_CALL_TIMEOUT_MS, a as HostCall, b as HostCallResult, J as JoinOptions, c as JoinResult, L as LogLevel, R as RoomCoreApi, d as RoomCoreOptions, e as RoomEvent, f as RoomEventKind, g as RoomFullError, h as RoomHost, i as RoomInspection, j as RoomStats, k as inspectState } from './contract-B8QSO0MH.js';
|
|
2
|
-
export { C as CRASH_AFTER_THROWS, M as MAX_CATCHUP, a as Mulberry32, P as PhysicsSection, R as RoomCore, d as decodePhysicsSection, e as encodePhysicsSection, i as initPhysics, l as loadedPhysics, r as resetPhysicsForTests } from './room-
|
|
2
|
+
export { C as CRASH_AFTER_THROWS, M as MAX_CATCHUP, a as Mulberry32, P as PhysicsSection, R as RoomCore, d as decodePhysicsSection, e as encodePhysicsSection, i as initPhysics, l as loadedPhysics, r as resetPhysicsForTests } from './room-9ZQoy9yi.js';
|
|
3
3
|
import { CollectionDesc, AnySchema, PlainState, DirtySet } from '@irtio/schema';
|
|
4
4
|
import '@irtio/protocol';
|
|
5
5
|
import '@irtio/server';
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
fromMigrationState,
|
|
3
3
|
migrateSnapshot,
|
|
4
4
|
toMigrationState
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-HFOMXKSO.js";
|
|
6
6
|
import {
|
|
7
7
|
CRASH_AFTER_THROWS,
|
|
8
8
|
EVENT_RING_SIZE,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
visibleNames,
|
|
31
31
|
visibleTo,
|
|
32
32
|
writeHibernationBlob
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-5ZDQAAFJ.js";
|
|
34
34
|
export {
|
|
35
35
|
CRASH_AFTER_THROWS,
|
|
36
36
|
EVENT_RING_SIZE,
|
|
@@ -47,6 +47,13 @@ interface ClientEntry {
|
|
|
47
47
|
* unchanged — for a client that never wrote, that is `0`.
|
|
48
48
|
*/
|
|
49
49
|
lastClientTick: number;
|
|
50
|
+
/**
|
|
51
|
+
* The room tick `lastClientTick`'s write was applied at, `0` before this client's first write.
|
|
52
|
+
* Sent alongside it on every `CORRECT` (bug 1). The two are a pair — one number on the
|
|
53
|
+
* client's stamp clock, one on the room's — and the client uses the gap between them to learn
|
|
54
|
+
* where on the server's tick stream its still-unjudged intents will land.
|
|
55
|
+
*/
|
|
56
|
+
lastAppliedTick: number;
|
|
50
57
|
/**
|
|
51
58
|
* Set at `join`: this client's own record(s) — its presence row and whatever entities its own
|
|
52
59
|
* `onJoin` added, owned by it — as they stood right after join, per collection. Consumed once
|
|
@@ -164,7 +171,18 @@ interface RoomInternals {
|
|
|
164
171
|
|
|
165
172
|
/** Most ticks one wake may run before the backlog is dropped. */
|
|
166
173
|
declare const MAX_CATCHUP = 5;
|
|
167
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* Consecutive ticks in which a guarded stage threw before the host is told the room crashed.
|
|
176
|
+
*
|
|
177
|
+
* Any of them counts, not just `tick()` (bug 2). A room that cannot step its physics is not
|
|
178
|
+
* running — it is a socket that answers with a frozen world — and the runtime is the only thing
|
|
179
|
+
* that can tell the difference, because it is the only thing that sees the throw. The specific
|
|
180
|
+
* way this was found is worth keeping: one Rapier getter called from a room handler at the wrong
|
|
181
|
+
* moment leaves the rigid-body set borrowed in WASM, and *every* Rapier call after it throws.
|
|
182
|
+
* Nothing in JS can un-poison that world, so the honest response is to stop claiming to be up:
|
|
183
|
+
* crashing hands the room to the supervisor, which restarts it from its snapshot with a fresh
|
|
184
|
+
* world, and gives up out loud if the same thing happens again (`maxRestartsPerMin`).
|
|
185
|
+
*/
|
|
168
186
|
declare const CRASH_AFTER_THROWS = 3;
|
|
169
187
|
declare class Loop implements LoopApi {
|
|
170
188
|
private readonly core;
|
|
@@ -280,6 +298,8 @@ declare class PhysicsRuntime {
|
|
|
280
298
|
/** Physics-backed collections, in schema (name-sorted) order. */
|
|
281
299
|
private readonly collections;
|
|
282
300
|
private readonly bodies;
|
|
301
|
+
/** Collections already warned about the friction-killing 2D lock recipe (bug 6). */
|
|
302
|
+
private readonly warnedPlanar;
|
|
283
303
|
/**
|
|
284
304
|
* Bodies whose sleep has already been synced. Rapier zeroes a body's velocity when it puts it
|
|
285
305
|
* to sleep — *after* the last awake-tick sync — so a body must be synced **once more** on the
|
|
@@ -335,6 +355,13 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
335
355
|
readonly stats: RoomStats;
|
|
336
356
|
tick: number;
|
|
337
357
|
stopped: boolean;
|
|
358
|
+
/**
|
|
359
|
+
* Called for every handler throw `tryRun` swallows, before it is logged. A test harness sets
|
|
360
|
+
* this so a room that breaks fails the test that broke it (bug 2): the runtime's job is to
|
|
361
|
+
* keep the room up in production, but under test that same guarding turns a broken handler
|
|
362
|
+
* into a timeout in an unrelated assertion ten seconds later. Unset in production.
|
|
363
|
+
*/
|
|
364
|
+
onHandlerError: ((name: string, err: unknown) => void) | undefined;
|
|
338
365
|
private readonly seed;
|
|
339
366
|
private readonly api;
|
|
340
367
|
private readonly internals;
|
package/dist/test/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RoomCore } from '../room-
|
|
2
|
-
export { i as initPhysics } from '../room-
|
|
1
|
+
import { R as RoomCore } from '../room-9ZQoy9yi.js';
|
|
2
|
+
export { i as initPhysics } from '../room-9ZQoy9yi.js';
|
|
3
3
|
import { ErrorCodeName, FrameType } from '@irtio/protocol';
|
|
4
4
|
import { h as RoomHost, L as LogLevel, R as RoomCoreApi, a as HostCall, b as HostCallResult, j as RoomStats } from '../contract-B8QSO0MH.js';
|
|
5
5
|
import { AnySchema, PlainState, EntityCollection, State } from '@irtio/schema';
|
package/dist/test/index.js
CHANGED
package/dist/worker/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
migrateSnapshot
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-HFOMXKSO.js";
|
|
4
4
|
import {
|
|
5
5
|
RoomCore,
|
|
6
6
|
RoomFullError,
|
|
7
7
|
initPhysics
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-5ZDQAAFJ.js";
|
|
9
9
|
|
|
10
10
|
// src/worker/index.ts
|
|
11
11
|
import { isMainThread, parentPort } from "worker_threads";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irtio/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "irtio room runtime: RoomCore, worker_threads host, in-process test harness",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@dimforge/rapier3d-compat": "0.20.0",
|
|
32
|
-
"@irtio/protocol": "0.
|
|
33
|
-
"@irtio/
|
|
34
|
-
"@irtio/
|
|
32
|
+
"@irtio/protocol": "0.4.0",
|
|
33
|
+
"@irtio/server": "0.4.0",
|
|
34
|
+
"@irtio/schema": "0.4.0"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsup",
|