@irtio/runtime 0.7.0 → 0.8.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-ZNNTF7Y3.js → chunk-DBGMG2S3.js} +1 -1
- package/dist/{chunk-EXPFVRD4.js → chunk-ZCD2TBXB.js} +641 -16
- package/dist/{contract-DwxqjeWP.d.ts → contract-CRilLVdx.d.ts} +18 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +12 -2
- package/dist/{room-CoQDczh2.d.ts → room-YJGz6Caw.d.ts} +134 -10
- package/dist/test/index.d.ts +14 -3
- package/dist/test/index.js +10 -2
- package/dist/worker/index.d.ts +9 -1
- package/dist/worker/index.js +14 -3
- package/package.json +5 -4
|
@@ -1255,9 +1255,28 @@ function encodePhysicsSection(section) {
|
|
|
1255
1255
|
return w.finish();
|
|
1256
1256
|
}
|
|
1257
1257
|
var MATTER_TAG = 1;
|
|
1258
|
+
var RAPIER2D_TAG = 2;
|
|
1258
1259
|
function physicsSectionEngine(bytes) {
|
|
1259
1260
|
const r = new ByteReader2(bytes);
|
|
1260
|
-
|
|
1261
|
+
if (r.varint() !== 0) return "rapier3d";
|
|
1262
|
+
const tag = r.u8();
|
|
1263
|
+
if (tag === MATTER_TAG) return "matter2d";
|
|
1264
|
+
if (tag === RAPIER2D_TAG) return "rapier2d";
|
|
1265
|
+
return "rapier3d";
|
|
1266
|
+
}
|
|
1267
|
+
function encodeRapier2dSectionEnvelope(payload) {
|
|
1268
|
+
const w = new ByteWriter2(payload.length + 8);
|
|
1269
|
+
w.varint(0);
|
|
1270
|
+
w.u8(RAPIER2D_TAG);
|
|
1271
|
+
w.bytes(payload);
|
|
1272
|
+
return w.finish();
|
|
1273
|
+
}
|
|
1274
|
+
function decodeRapier2dSectionEnvelope(bytes) {
|
|
1275
|
+
const r = new ByteReader2(bytes);
|
|
1276
|
+
if (r.varint() !== 0 || r.u8() !== RAPIER2D_TAG) {
|
|
1277
|
+
throw new Error("irtio: this physics section was not written by rapier2d");
|
|
1278
|
+
}
|
|
1279
|
+
return r.rest();
|
|
1261
1280
|
}
|
|
1262
1281
|
function encodeMatterSectionEnvelope(payload) {
|
|
1263
1282
|
const w = new ByteWriter2(payload.length + 8);
|
|
@@ -1627,6 +1646,262 @@ var Mulberry32 = class {
|
|
|
1627
1646
|
}
|
|
1628
1647
|
};
|
|
1629
1648
|
|
|
1649
|
+
// src/core/rapier2d.ts
|
|
1650
|
+
import { angleFrom2d, applyChannel2d as applyChannel2d2, channelOf2d as channelOf2d2 } from "@irtio/schema";
|
|
1651
|
+
var engine3;
|
|
1652
|
+
var loading3;
|
|
1653
|
+
async function initRapier2d() {
|
|
1654
|
+
if (engine3) return engine3;
|
|
1655
|
+
loading3 ??= (async () => {
|
|
1656
|
+
const mod = await import("@dimforge/rapier2d-compat");
|
|
1657
|
+
const ns = mod.default ?? mod;
|
|
1658
|
+
await ns.init();
|
|
1659
|
+
engine3 = ns;
|
|
1660
|
+
return ns;
|
|
1661
|
+
})();
|
|
1662
|
+
return loading3;
|
|
1663
|
+
}
|
|
1664
|
+
function loadedRapier2d() {
|
|
1665
|
+
return engine3;
|
|
1666
|
+
}
|
|
1667
|
+
function resetRapier2dForTests() {
|
|
1668
|
+
engine3 = void 0;
|
|
1669
|
+
loading3 = void 0;
|
|
1670
|
+
}
|
|
1671
|
+
function bodyKey3(collection, id) {
|
|
1672
|
+
return `${collection}\0${id}`;
|
|
1673
|
+
}
|
|
1674
|
+
var Rapier2dRuntime = class {
|
|
1675
|
+
engineKind = "rapier2d";
|
|
1676
|
+
/** matter2d only; present so every runtime satisfies one internal shape. */
|
|
1677
|
+
matter = void 0;
|
|
1678
|
+
matterEngine = void 0;
|
|
1679
|
+
rapier;
|
|
1680
|
+
world;
|
|
1681
|
+
/** `true` when the world was built from scratch and `setup` has to run. */
|
|
1682
|
+
rebuilt;
|
|
1683
|
+
/** Rapier restores a whole world, contacts included, so `setup` runs only on a rebuild. */
|
|
1684
|
+
get needsSetup() {
|
|
1685
|
+
return this.rebuilt;
|
|
1686
|
+
}
|
|
1687
|
+
core;
|
|
1688
|
+
config;
|
|
1689
|
+
/** Physics-backed collections, in schema (name-sorted) order. */
|
|
1690
|
+
collections;
|
|
1691
|
+
bodies = /* @__PURE__ */ new Map();
|
|
1692
|
+
/** See `PhysicsRuntime.sleepSynced`: the one extra sync a body owes on the tick it sleeps. */
|
|
1693
|
+
sleepSynced = /* @__PURE__ */ new Set();
|
|
1694
|
+
constructor(core, rapier, options) {
|
|
1695
|
+
const config = core.definition.config.physics;
|
|
1696
|
+
if (!config) throw new Error("Rapier2dRuntime: the room config declares no physics");
|
|
1697
|
+
this.core = core;
|
|
1698
|
+
this.config = config;
|
|
1699
|
+
this.rapier = rapier;
|
|
1700
|
+
this.collections = core.ext.collections.filter(
|
|
1701
|
+
(c) => c.physics !== void 0
|
|
1702
|
+
);
|
|
1703
|
+
if (options.restore) {
|
|
1704
|
+
this.world = rapier.World.restoreSnapshot(options.restore.world);
|
|
1705
|
+
this.rebuilt = false;
|
|
1706
|
+
for (const [name, id, handle] of options.restore.bodies) {
|
|
1707
|
+
const body = this.world.getRigidBody(handle);
|
|
1708
|
+
if (body) this.bodies.set(bodyKey3(name, id), body);
|
|
1709
|
+
}
|
|
1710
|
+
} else {
|
|
1711
|
+
const g = config.gravity;
|
|
1712
|
+
this.world = new rapier.World({ x: g.x, y: g.y });
|
|
1713
|
+
this.rebuilt = true;
|
|
1714
|
+
}
|
|
1715
|
+
this.world.timestep = config.timestep ?? options.defaultTimestep;
|
|
1716
|
+
}
|
|
1717
|
+
get timestep() {
|
|
1718
|
+
return this.world.timestep;
|
|
1719
|
+
}
|
|
1720
|
+
/** Runs the room's `setup` — static geometry — on a world that was built rather than restored. */
|
|
1721
|
+
runSetup(room) {
|
|
1722
|
+
const setup = this.config.setup;
|
|
1723
|
+
if (!setup) return;
|
|
1724
|
+
this.core.guard("physics.setup", () => setup(this.world, this.rapier, room));
|
|
1725
|
+
}
|
|
1726
|
+
free() {
|
|
1727
|
+
this.bodies.clear();
|
|
1728
|
+
this.world.free();
|
|
1729
|
+
}
|
|
1730
|
+
// -------------------------------------------------------------------------
|
|
1731
|
+
// Bodies
|
|
1732
|
+
// -------------------------------------------------------------------------
|
|
1733
|
+
/** The body behind an instance, created on demand so a handler's own `add` is usable at once. */
|
|
1734
|
+
bodyFor(collection, id) {
|
|
1735
|
+
const existing = this.bodies.get(bodyKey3(collection, id));
|
|
1736
|
+
if (existing) return existing;
|
|
1737
|
+
const desc = this.collections.find((c) => c.name === collection);
|
|
1738
|
+
if (!desc) return void 0;
|
|
1739
|
+
const coll = plainEntity(this.core.plain, collection);
|
|
1740
|
+
const record = coll.get(id);
|
|
1741
|
+
if (record === void 0) return void 0;
|
|
1742
|
+
return this.create(desc, id, record);
|
|
1743
|
+
}
|
|
1744
|
+
create(desc, id, record) {
|
|
1745
|
+
const factory = this.config.bodies?.[desc.name];
|
|
1746
|
+
if (!factory) {
|
|
1747
|
+
this.core.log("error", `irtio: physics.bodies.${desc.name} is missing; no body created`);
|
|
1748
|
+
return void 0;
|
|
1749
|
+
}
|
|
1750
|
+
const spec = this.core.guard(
|
|
1751
|
+
`physics.bodies.${desc.name}`,
|
|
1752
|
+
() => factory(this.rapier, record, id)
|
|
1753
|
+
);
|
|
1754
|
+
if (!spec || !spec.body) {
|
|
1755
|
+
this.core.log(
|
|
1756
|
+
"error",
|
|
1757
|
+
`irtio: physics.bodies.${desc.name} returned no { body } for ${JSON.stringify(id)}`
|
|
1758
|
+
);
|
|
1759
|
+
return void 0;
|
|
1760
|
+
}
|
|
1761
|
+
const body = this.world.createRigidBody(spec.body);
|
|
1762
|
+
for (const collider of spec.colliders ?? []) this.world.createCollider(collider, body);
|
|
1763
|
+
this.applyRecordToBody(desc, body, record);
|
|
1764
|
+
this.bodies.set(bodyKey3(desc.name, id), body);
|
|
1765
|
+
return body;
|
|
1766
|
+
}
|
|
1767
|
+
// ---- M6 lane F: rewind ----
|
|
1768
|
+
/**
|
|
1769
|
+
* D72: every tracked body, in the order `sync()` walks them (collection order, then instance
|
|
1770
|
+
* order). The rapier2d third of the same read-only accessor the other two runtimes carry.
|
|
1771
|
+
*/
|
|
1772
|
+
eachTrackedBody(fn) {
|
|
1773
|
+
for (const desc of this.collections) {
|
|
1774
|
+
const plainColl = plainEntity(this.core.plain, desc.name);
|
|
1775
|
+
for (const id of plainColl.ids()) {
|
|
1776
|
+
const body = this.bodies.get(bodyKey3(desc.name, id));
|
|
1777
|
+
if (body) fn(desc.name, id, body);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
applyRecordToBody(desc, body, record) {
|
|
1782
|
+
const physics = desc.physics;
|
|
1783
|
+
if (!physics) return;
|
|
1784
|
+
const t = body.translation();
|
|
1785
|
+
const v = body.linvel();
|
|
1786
|
+
const angle = body.rotation();
|
|
1787
|
+
const target = {
|
|
1788
|
+
x: t.x,
|
|
1789
|
+
y: t.y,
|
|
1790
|
+
qz: Math.sin(angle / 2),
|
|
1791
|
+
qw: Math.cos(angle / 2),
|
|
1792
|
+
vx: v.x,
|
|
1793
|
+
vy: v.y,
|
|
1794
|
+
wz: body.angvel()
|
|
1795
|
+
};
|
|
1796
|
+
for (const [channel, field] of physics.channels) {
|
|
1797
|
+
const raw = record[field];
|
|
1798
|
+
if (typeof raw !== "number") continue;
|
|
1799
|
+
applyChannel2d2(channel, raw, target);
|
|
1800
|
+
}
|
|
1801
|
+
body.setTranslation({ x: target.x, y: target.y }, true);
|
|
1802
|
+
body.setRotation(angleFrom2d(target.qz, target.qw), true);
|
|
1803
|
+
body.setLinvel({ x: target.vx, y: target.vy }, true);
|
|
1804
|
+
body.setAngvel(target.wz, true);
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Creates bodies for new instances and destroys bodies whose instance is gone. Runs once per
|
|
1808
|
+
* tick, right before the step, in collection order then instance order.
|
|
1809
|
+
*/
|
|
1810
|
+
reconcile() {
|
|
1811
|
+
const live = /* @__PURE__ */ new Set();
|
|
1812
|
+
for (const desc of this.collections) {
|
|
1813
|
+
const coll = plainEntity(this.core.plain, desc.name);
|
|
1814
|
+
for (const id of coll.ids()) {
|
|
1815
|
+
const key = bodyKey3(desc.name, id);
|
|
1816
|
+
live.add(key);
|
|
1817
|
+
if (this.bodies.has(key)) continue;
|
|
1818
|
+
const record = coll.get(id);
|
|
1819
|
+
if (record !== void 0) this.create(desc, id, record);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
for (const [key, body] of [...this.bodies]) {
|
|
1823
|
+
if (live.has(key)) continue;
|
|
1824
|
+
this.bodies.delete(key);
|
|
1825
|
+
this.sleepSynced.delete(key);
|
|
1826
|
+
this.world.removeRigidBody(body);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
// -------------------------------------------------------------------------
|
|
1830
|
+
// Step and sync
|
|
1831
|
+
// -------------------------------------------------------------------------
|
|
1832
|
+
step() {
|
|
1833
|
+
this.world.step();
|
|
1834
|
+
if (!rapierHasStepped()) noteRapierStep();
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Body → schema, through the tracked proxies, so movement leaves the room as an ordinary delta.
|
|
1838
|
+
* Values are `Math.fround`ed for f32 fields, so room code reads exactly what the wire carries.
|
|
1839
|
+
*/
|
|
1840
|
+
sync() {
|
|
1841
|
+
for (const desc of this.collections) {
|
|
1842
|
+
const physics = desc.physics;
|
|
1843
|
+
if (!physics) continue;
|
|
1844
|
+
const tracked = this.core.anyState[desc.name];
|
|
1845
|
+
const plainColl = plainEntity(this.core.plain, desc.name);
|
|
1846
|
+
const rounders = roundersFor3(desc);
|
|
1847
|
+
for (const id of plainColl.ids()) {
|
|
1848
|
+
const key = bodyKey3(desc.name, id);
|
|
1849
|
+
const body = this.bodies.get(key);
|
|
1850
|
+
if (!body) continue;
|
|
1851
|
+
if (body.isSleeping()) {
|
|
1852
|
+
if (this.sleepSynced.has(key)) continue;
|
|
1853
|
+
this.sleepSynced.add(key);
|
|
1854
|
+
} else {
|
|
1855
|
+
this.sleepSynced.delete(key);
|
|
1856
|
+
}
|
|
1857
|
+
const record = tracked.get(id);
|
|
1858
|
+
if (!record) continue;
|
|
1859
|
+
const t = body.translation();
|
|
1860
|
+
const v = body.linvel();
|
|
1861
|
+
const state = {
|
|
1862
|
+
x: t.x,
|
|
1863
|
+
y: t.y,
|
|
1864
|
+
angle: body.rotation(),
|
|
1865
|
+
vx: v.x,
|
|
1866
|
+
vy: v.y,
|
|
1867
|
+
angularVelocity: body.angvel()
|
|
1868
|
+
};
|
|
1869
|
+
for (const [channel, field] of physics.channels) {
|
|
1870
|
+
const next = (rounders[field] ?? identity3)(channelOf2d2(channel, state));
|
|
1871
|
+
if (record[field] !== next) record[field] = next;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
// -------------------------------------------------------------------------
|
|
1877
|
+
// Hibernation
|
|
1878
|
+
// -------------------------------------------------------------------------
|
|
1879
|
+
/**
|
|
1880
|
+
* The same `PhysicsSection` the 3D runtime writes — a real engine snapshot plus the
|
|
1881
|
+
* entity↔handle map, whose handles must ride as f64 for the reason spelled out on
|
|
1882
|
+
* {@link PhysicsSection}. What tells the two apart on the way back in is the envelope
|
|
1883
|
+
* `core/physics.ts` wraps this in, not anything in here.
|
|
1884
|
+
*/
|
|
1885
|
+
serialize() {
|
|
1886
|
+
const bodies = [];
|
|
1887
|
+
for (const [key, body] of this.bodies) {
|
|
1888
|
+
const sep = key.indexOf("\0");
|
|
1889
|
+
bodies.push([key.slice(0, sep), key.slice(sep + 1), body.handle]);
|
|
1890
|
+
}
|
|
1891
|
+
return { world: this.world.takeSnapshot(), bodies };
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1894
|
+
function identity3(v) {
|
|
1895
|
+
return v;
|
|
1896
|
+
}
|
|
1897
|
+
function roundersFor3(desc) {
|
|
1898
|
+
const out = {};
|
|
1899
|
+
for (const f of desc.fields) {
|
|
1900
|
+
if (f.type.kind === "f32") out[f.name] = Math.fround;
|
|
1901
|
+
}
|
|
1902
|
+
return out;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1630
1905
|
// src/core/views.ts
|
|
1631
1906
|
import {
|
|
1632
1907
|
collectionDirty,
|
|
@@ -2038,9 +2313,10 @@ var PoseHistory = class {
|
|
|
2038
2313
|
entry.tick = tick;
|
|
2039
2314
|
entry.count = 0;
|
|
2040
2315
|
const pose = this.scratchPose;
|
|
2041
|
-
const
|
|
2316
|
+
const kind = physics.engineKind;
|
|
2042
2317
|
physics.eachTrackedBody((collection, id, body) => {
|
|
2043
|
-
if (
|
|
2318
|
+
if (kind === "rapier3d") readRapierPose(body, pose);
|
|
2319
|
+
else if (kind === "rapier2d") readRapier2dPose(body, pose);
|
|
2044
2320
|
else readMatterPose(body, pose);
|
|
2045
2321
|
this.push(entry, collection, id, pose);
|
|
2046
2322
|
});
|
|
@@ -2142,6 +2418,24 @@ function readRapierPose(body, into) {
|
|
|
2142
2418
|
into.wy = w.y;
|
|
2143
2419
|
into.wz = w.z;
|
|
2144
2420
|
}
|
|
2421
|
+
function readRapier2dPose(body, into) {
|
|
2422
|
+
const t = body.translation();
|
|
2423
|
+
const angle = body.rotation();
|
|
2424
|
+
const v = body.linvel();
|
|
2425
|
+
into.x = t.x;
|
|
2426
|
+
into.y = t.y;
|
|
2427
|
+
into.z = 0;
|
|
2428
|
+
into.qx = 0;
|
|
2429
|
+
into.qy = 0;
|
|
2430
|
+
into.qz = Math.sin(angle / 2);
|
|
2431
|
+
into.qw = Math.cos(angle / 2);
|
|
2432
|
+
into.vx = v.x;
|
|
2433
|
+
into.vy = v.y;
|
|
2434
|
+
into.vz = 0;
|
|
2435
|
+
into.wx = 0;
|
|
2436
|
+
into.wy = 0;
|
|
2437
|
+
into.wz = body.angvel();
|
|
2438
|
+
}
|
|
2145
2439
|
function readMatterPose(body, into) {
|
|
2146
2440
|
into.x = body.position.x;
|
|
2147
2441
|
into.y = body.position.y;
|
|
@@ -2346,6 +2640,95 @@ var RapierScratch = class extends BaseScratch {
|
|
|
2346
2640
|
this.world.free();
|
|
2347
2641
|
}
|
|
2348
2642
|
};
|
|
2643
|
+
var Rapier2dScratch = class extends BaseScratch {
|
|
2644
|
+
world;
|
|
2645
|
+
rapier;
|
|
2646
|
+
owners = /* @__PURE__ */ new Map();
|
|
2647
|
+
/** Live rigid-body handles the room tracks, so the statics pass knows what to skip. */
|
|
2648
|
+
trackedLive = /* @__PURE__ */ new Set();
|
|
2649
|
+
staticsCloned = false;
|
|
2650
|
+
constructor(physics, depth, tickNow) {
|
|
2651
|
+
super(physics, depth, tickNow);
|
|
2652
|
+
this.rapier = physics.rapier;
|
|
2653
|
+
this.world = new this.rapier.World({ x: 0, y: 0 });
|
|
2654
|
+
this.world.timestep = 0;
|
|
2655
|
+
}
|
|
2656
|
+
partsOf(body) {
|
|
2657
|
+
return body.numColliders();
|
|
2658
|
+
}
|
|
2659
|
+
clone(collection, id, body, parts) {
|
|
2660
|
+
const live = body;
|
|
2661
|
+
this.trackedLive.add(live.handle);
|
|
2662
|
+
const made = this.world.createRigidBody(this.rapier.RigidBodyDesc.fixed());
|
|
2663
|
+
const origin = live.translation();
|
|
2664
|
+
const angle = live.rotation();
|
|
2665
|
+
const cos = Math.cos(-angle);
|
|
2666
|
+
const sin = Math.sin(-angle);
|
|
2667
|
+
for (let i = 0; i < parts; i++) {
|
|
2668
|
+
const c = live.collider(i);
|
|
2669
|
+
const desc = new this.rapier.ColliderDesc(c.shape);
|
|
2670
|
+
desc.setSensor(c.isSensor());
|
|
2671
|
+
const w = c.translation();
|
|
2672
|
+
const dx = w.x - origin.x;
|
|
2673
|
+
const dy = w.y - origin.y;
|
|
2674
|
+
desc.setTranslation(dx * cos - dy * sin, dx * sin + dy * cos);
|
|
2675
|
+
desc.setRotation(c.rotation() - angle);
|
|
2676
|
+
this.world.createCollider(desc, made);
|
|
2677
|
+
}
|
|
2678
|
+
this.owners.set(made.handle, { collection, id });
|
|
2679
|
+
return { collection, id, body: made, parts, lastLive: -1 };
|
|
2680
|
+
}
|
|
2681
|
+
place(entry, pose) {
|
|
2682
|
+
const b = entry.body;
|
|
2683
|
+
if (!b.isEnabled()) b.setEnabled(true);
|
|
2684
|
+
b.setTranslation({ x: pose.x, y: pose.y }, false);
|
|
2685
|
+
b.setRotation(angleOf(pose.qz, pose.qw), false);
|
|
2686
|
+
}
|
|
2687
|
+
hide(entry) {
|
|
2688
|
+
if (entry.body.isEnabled()) entry.body.setEnabled(false);
|
|
2689
|
+
}
|
|
2690
|
+
destroy(entry) {
|
|
2691
|
+
this.owners.delete(entry.body.handle);
|
|
2692
|
+
this.world.removeRigidBody(entry.body);
|
|
2693
|
+
}
|
|
2694
|
+
build(tick, requested, clamped) {
|
|
2695
|
+
this.cloneStatics();
|
|
2696
|
+
this.world.step();
|
|
2697
|
+
const owners = this.owners;
|
|
2698
|
+
return {
|
|
2699
|
+
tick,
|
|
2700
|
+
requested,
|
|
2701
|
+
clamped,
|
|
2702
|
+
rapier2d: {
|
|
2703
|
+
world: this.world,
|
|
2704
|
+
who: (collider) => {
|
|
2705
|
+
const parent = collider.parent();
|
|
2706
|
+
return parent ? owners.get(parent.handle) : void 0;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
cloneStatics() {
|
|
2712
|
+
if (this.staticsCloned) return;
|
|
2713
|
+
this.staticsCloned = true;
|
|
2714
|
+
const live = this.physics.world;
|
|
2715
|
+
if (!live) return;
|
|
2716
|
+
live.forEachCollider((c) => {
|
|
2717
|
+
const parent = c.parent();
|
|
2718
|
+
if (parent !== null && (!parent.isFixed() || this.trackedLive.has(parent.handle))) return;
|
|
2719
|
+
const desc = new this.rapier.ColliderDesc(c.shape);
|
|
2720
|
+
desc.setSensor(c.isSensor());
|
|
2721
|
+
const t = c.translation();
|
|
2722
|
+
desc.setTranslation(t.x, t.y);
|
|
2723
|
+
desc.setRotation(c.rotation());
|
|
2724
|
+
this.world.createCollider(desc);
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2727
|
+
free() {
|
|
2728
|
+
this.owners.clear();
|
|
2729
|
+
this.world.free();
|
|
2730
|
+
}
|
|
2731
|
+
};
|
|
2349
2732
|
var MatterScratch = class extends BaseScratch {
|
|
2350
2733
|
matter;
|
|
2351
2734
|
owners = /* @__PURE__ */ new Map();
|
|
@@ -2395,9 +2778,9 @@ var MatterScratch = class extends BaseScratch {
|
|
|
2395
2778
|
}
|
|
2396
2779
|
build(tick, requested, clamped) {
|
|
2397
2780
|
const M = this.matter;
|
|
2398
|
-
const
|
|
2399
|
-
if (
|
|
2400
|
-
for (const b of M.Composite.allBodies(
|
|
2781
|
+
const engine4 = this.physics.matterEngine;
|
|
2782
|
+
if (engine4) {
|
|
2783
|
+
for (const b of M.Composite.allBodies(engine4.world)) {
|
|
2401
2784
|
if (b.isStatic) this.visible.push(b);
|
|
2402
2785
|
}
|
|
2403
2786
|
}
|
|
@@ -2440,7 +2823,7 @@ var RewindState = class {
|
|
|
2440
2823
|
`room.rewind: this room has recorded no ticks yet, so there is no past to answer from. A woken room starts with an empty history and fills it over its next ${this.history.depth} tick(s).`
|
|
2441
2824
|
);
|
|
2442
2825
|
}
|
|
2443
|
-
this.scratch ??= physics.engineKind === "rapier3d" ? new RapierScratch(physics, this.history.depth, tickNow) : new MatterScratch(physics, this.history.depth, tickNow);
|
|
2826
|
+
this.scratch ??= physics.engineKind === "rapier3d" ? new RapierScratch(physics, this.history.depth, tickNow) : physics.engineKind === "rapier2d" ? new Rapier2dScratch(physics, this.history.depth, tickNow) : new MatterScratch(physics, this.history.depth, tickNow);
|
|
2444
2827
|
const view = this.scratch.view(resolved.entry, resolved.tick, requested, resolved.clamped);
|
|
2445
2828
|
this.inside = true;
|
|
2446
2829
|
try {
|
|
@@ -2451,6 +2834,152 @@ var RewindState = class {
|
|
|
2451
2834
|
}
|
|
2452
2835
|
};
|
|
2453
2836
|
|
|
2837
|
+
// src/core/lobby.ts
|
|
2838
|
+
import {
|
|
2839
|
+
LOBBY_MEMBERS,
|
|
2840
|
+
LOBBY_MIN_PLAYERS,
|
|
2841
|
+
LOBBY_STATE,
|
|
2842
|
+
schemaHasLobby
|
|
2843
|
+
} from "@irtio/server";
|
|
2844
|
+
var runtimes = /* @__PURE__ */ new WeakMap();
|
|
2845
|
+
function runtimeOf(core) {
|
|
2846
|
+
let r = runtimes.get(core);
|
|
2847
|
+
if (!r) {
|
|
2848
|
+
r = { onStart: [], started: false };
|
|
2849
|
+
runtimes.set(core, r);
|
|
2850
|
+
}
|
|
2851
|
+
return r;
|
|
2852
|
+
}
|
|
2853
|
+
function lobbyConfigOf(core) {
|
|
2854
|
+
const declared = core.definition.config.lobby;
|
|
2855
|
+
if (declared === void 0) return void 0;
|
|
2856
|
+
if (!schemaHasLobby(core.definition.schema)) return void 0;
|
|
2857
|
+
return declared;
|
|
2858
|
+
}
|
|
2859
|
+
function stateOf3(core) {
|
|
2860
|
+
return core.anyState[LOBBY_STATE];
|
|
2861
|
+
}
|
|
2862
|
+
function evaluateLobby(core) {
|
|
2863
|
+
const config = lobbyConfigOf(core);
|
|
2864
|
+
if (config === void 0) return;
|
|
2865
|
+
const state = stateOf3(core);
|
|
2866
|
+
if (state === void 0) return;
|
|
2867
|
+
const members = trackedEntity(core, LOBBY_MEMBERS);
|
|
2868
|
+
const policy = config.start ?? "when-full";
|
|
2869
|
+
const present = [];
|
|
2870
|
+
for (const [clientId, entry] of core.clients) {
|
|
2871
|
+
if (!entry.connected) continue;
|
|
2872
|
+
present.push(clientId);
|
|
2873
|
+
if (!members.has(clientId)) members.add(clientId, { ready: false }, { owner: clientId });
|
|
2874
|
+
else if (members.ownerOf(clientId) !== clientId) members.setOwner(clientId, clientId);
|
|
2875
|
+
}
|
|
2876
|
+
const here = new Set(present);
|
|
2877
|
+
for (const id of [...members.ids()]) {
|
|
2878
|
+
if (!here.has(id)) members.remove(id);
|
|
2879
|
+
}
|
|
2880
|
+
const readyCount = policy === "when-ready" ? present.filter((id) => members.get(id)?.ready === true).length : 0;
|
|
2881
|
+
const capacity = Math.min(
|
|
2882
|
+
255,
|
|
2883
|
+
config.size ?? core.definition.config.maxClients,
|
|
2884
|
+
core.definition.config.maxClients
|
|
2885
|
+
);
|
|
2886
|
+
const min = Math.max(LOBBY_MIN_PLAYERS, config.min ?? LOBBY_MIN_PLAYERS);
|
|
2887
|
+
if (state.readyUi !== (policy === "when-ready")) state.readyUi = policy === "when-ready";
|
|
2888
|
+
if (state.present !== present.length) state.present = Math.min(255, present.length);
|
|
2889
|
+
if (state.ready !== readyCount) state.ready = readyCount;
|
|
2890
|
+
if (state.capacity !== capacity) state.capacity = capacity;
|
|
2891
|
+
if (state.phase !== "lobby") return;
|
|
2892
|
+
if (policy === "when-full") {
|
|
2893
|
+
if (present.length >= capacity && capacity >= LOBBY_MIN_PLAYERS) startLobby(core, "when-full");
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
if (policy === "when-ready") {
|
|
2897
|
+
if (present.length >= min && readyCount === present.length) startLobby(core, "when-ready");
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
function startLobby(core, why) {
|
|
2902
|
+
const runtime = runtimeOf(core);
|
|
2903
|
+
if (runtime.started) return;
|
|
2904
|
+
const state = stateOf3(core);
|
|
2905
|
+
if (state === void 0 || state.phase === "started") {
|
|
2906
|
+
runtime.started = true;
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
runtime.started = true;
|
|
2910
|
+
state.phase = "started";
|
|
2911
|
+
state.public = false;
|
|
2912
|
+
core.host.setLobby({ started: true });
|
|
2913
|
+
core.log("info", `lobby: the game started (${why})`);
|
|
2914
|
+
const declared = lobbyConfigOf(core)?.onStart;
|
|
2915
|
+
if (declared) {
|
|
2916
|
+
core.guard("lobby.onStart", () => declared(core.anyState, core.room));
|
|
2917
|
+
}
|
|
2918
|
+
for (const cb of [...runtime.onStart]) core.guard("lobby.onStart(cb)", cb);
|
|
2919
|
+
}
|
|
2920
|
+
function makeLobby(core) {
|
|
2921
|
+
const config = lobbyConfigOf(core);
|
|
2922
|
+
if (config === void 0) {
|
|
2923
|
+
throw new Error(
|
|
2924
|
+
"room.lobby: this room has no lobby. Spread lobbyCollections into the schema and declare lobby: {} in defineRoom \u2014 both are needed, and defineRoom refuses either one alone"
|
|
2925
|
+
);
|
|
2926
|
+
}
|
|
2927
|
+
const state = stateOf3(core);
|
|
2928
|
+
const members = trackedEntity(core, LOBBY_MEMBERS);
|
|
2929
|
+
return {
|
|
2930
|
+
get phase() {
|
|
2931
|
+
return state.phase ?? "lobby";
|
|
2932
|
+
},
|
|
2933
|
+
get ready() {
|
|
2934
|
+
const out = [];
|
|
2935
|
+
for (const [clientId, entry] of core.clients) {
|
|
2936
|
+
if (!entry.connected) continue;
|
|
2937
|
+
out.push({
|
|
2938
|
+
clientId,
|
|
2939
|
+
ready: members.get(clientId)?.ready === true
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
return out;
|
|
2943
|
+
},
|
|
2944
|
+
get public() {
|
|
2945
|
+
return state.public === true;
|
|
2946
|
+
},
|
|
2947
|
+
start() {
|
|
2948
|
+
startLobby(core, "room.lobby.start()");
|
|
2949
|
+
},
|
|
2950
|
+
onStart(cb) {
|
|
2951
|
+
if (typeof cb !== "function") {
|
|
2952
|
+
core.log("warn", "room.lobby.onStart: expects a function; ignored");
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
runtimeOf(core).onStart.push(cb);
|
|
2956
|
+
},
|
|
2957
|
+
setPublic(value) {
|
|
2958
|
+
setLobbyPublic(core, value === true);
|
|
2959
|
+
}
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2962
|
+
function setLobbyPublic(core, value) {
|
|
2963
|
+
const config = lobbyConfigOf(core);
|
|
2964
|
+
if (config === void 0) return;
|
|
2965
|
+
const state = stateOf3(core);
|
|
2966
|
+
if (state === void 0) return;
|
|
2967
|
+
if (state.phase === "started") {
|
|
2968
|
+
core.log(
|
|
2969
|
+
"warn",
|
|
2970
|
+
"room.lobby.setPublic: the game has started; the room stays out of the registry"
|
|
2971
|
+
);
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2974
|
+
const veto = config.onSetPublic;
|
|
2975
|
+
if (veto) {
|
|
2976
|
+
const outcome = core.tryRun("lobby.onSetPublic", () => veto(core.anyState, value));
|
|
2977
|
+
if (!outcome.ok || outcome.value === false) return;
|
|
2978
|
+
}
|
|
2979
|
+
if (state.public !== value) state.public = value;
|
|
2980
|
+
core.host.setLobby({ public: value, queue: config.queue ?? "default" });
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2454
2983
|
// src/core/messages.ts
|
|
2455
2984
|
import { FrameType as FrameType3, decodeMsg, encodeFrame as encodeFrame2, encodeMsg } from "@irtio/protocol";
|
|
2456
2985
|
import { decodeFields as decodeFields2 } from "@irtio/schema";
|
|
@@ -2601,6 +3130,22 @@ function makeMatterApi(p) {
|
|
|
2601
3130
|
}
|
|
2602
3131
|
};
|
|
2603
3132
|
}
|
|
3133
|
+
function makeRapier2dApi(p) {
|
|
3134
|
+
return {
|
|
3135
|
+
get rapier() {
|
|
3136
|
+
return p.rapier;
|
|
3137
|
+
},
|
|
3138
|
+
get world() {
|
|
3139
|
+
return p.world;
|
|
3140
|
+
},
|
|
3141
|
+
get timestep() {
|
|
3142
|
+
return p.timestep;
|
|
3143
|
+
},
|
|
3144
|
+
body(collection, id) {
|
|
3145
|
+
return p.bodyFor(collection, id);
|
|
3146
|
+
}
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
2604
3149
|
function makeKv(core) {
|
|
2605
3150
|
return {
|
|
2606
3151
|
get(playerId, key) {
|
|
@@ -2716,6 +3261,7 @@ function createRoomApi(core, publicUrl) {
|
|
|
2716
3261
|
let lastNow = Number.NEGATIVE_INFINITY;
|
|
2717
3262
|
let physicsApi;
|
|
2718
3263
|
let matterApi;
|
|
3264
|
+
let rapier2dApi;
|
|
2719
3265
|
let kvApi;
|
|
2720
3266
|
let busApi;
|
|
2721
3267
|
let leaderboardApi;
|
|
@@ -2777,7 +3323,7 @@ function createRoomApi(core, publicUrl) {
|
|
|
2777
3323
|
}
|
|
2778
3324
|
if (p.engineKind !== "rapier3d") {
|
|
2779
3325
|
throw new Error(
|
|
2780
|
-
"room.physics: this room runs matter2d. Read room.physics2d instead (it has .matter and .engine, where this one has .rapier and .world)"
|
|
3326
|
+
p.engineKind === "matter2d" ? "room.physics: this room runs matter2d. Read room.physics2d instead (it has .matter and .engine, where this one has .rapier and .world)" : "room.physics: this room runs rapier2d. Read room.physicsRapier2d instead (same .rapier and .world, but the 2D module: a Vector2 world, not a Vector3 one)"
|
|
2781
3327
|
);
|
|
2782
3328
|
}
|
|
2783
3329
|
return physicsApi ?? (physicsApi = makePhysicsApi(p));
|
|
@@ -2791,11 +3337,25 @@ function createRoomApi(core, publicUrl) {
|
|
|
2791
3337
|
}
|
|
2792
3338
|
if (p.engineKind !== "matter2d") {
|
|
2793
3339
|
throw new Error(
|
|
2794
|
-
"room.physics2d: this room runs rapier3d. Read room.physics instead (it has .rapier and .world, where this one has .matter and .engine)"
|
|
3340
|
+
p.engineKind === "rapier2d" ? "room.physics2d: this room runs rapier2d, not matter2d. Read room.physicsRapier2d instead (it has .rapier and .world, where this one has .matter and .engine)" : "room.physics2d: this room runs rapier3d. Read room.physics instead (it has .rapier and .world, where this one has .matter and .engine)"
|
|
2795
3341
|
);
|
|
2796
3342
|
}
|
|
2797
3343
|
return matterApi ?? (matterApi = makeMatterApi(p));
|
|
2798
3344
|
},
|
|
3345
|
+
get physicsRapier2d() {
|
|
3346
|
+
const p = core.physics;
|
|
3347
|
+
if (!p) {
|
|
3348
|
+
throw new Error(
|
|
3349
|
+
"room.physicsRapier2d: this room has no physics \u2014 add physics: { engine: 'rapier2d', gravity, bodies } to defineRoom(...)"
|
|
3350
|
+
);
|
|
3351
|
+
}
|
|
3352
|
+
if (p.engineKind !== "rapier2d") {
|
|
3353
|
+
throw new Error(
|
|
3354
|
+
p.engineKind === "matter2d" ? "room.physicsRapier2d: this room runs matter2d. Read room.physics2d instead (it has .matter and .engine, where this one has .rapier and .world)" : "room.physicsRapier2d: this room runs rapier3d. Read room.physics instead (same .rapier and .world, but the 3D module: a Vector3 world, not a Vector2 one)"
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
return rapier2dApi ?? (rapier2dApi = makeRapier2dApi(p));
|
|
3358
|
+
},
|
|
2799
3359
|
// ---- M6 lane F: rewind ----
|
|
2800
3360
|
// D72: one line, and deliberately only one. Everything the rewind does — the buffer, the
|
|
2801
3361
|
// clamping, the scratch worlds, the reentrancy refusal — lives in `core/history.ts` behind
|
|
@@ -2872,6 +3432,10 @@ function createRoomApi(core, publicUrl) {
|
|
|
2872
3432
|
get bus() {
|
|
2873
3433
|
return busApi ?? (busApi = makeBus(core));
|
|
2874
3434
|
},
|
|
3435
|
+
// ---- M6 lane H: lobby front door ----
|
|
3436
|
+
get lobby() {
|
|
3437
|
+
return makeLobby(core);
|
|
3438
|
+
},
|
|
2875
3439
|
alarm(name, atMs) {
|
|
2876
3440
|
if (!isAlarmName(core, name, "room.alarm")) return;
|
|
2877
3441
|
if (!Number.isFinite(atMs)) {
|
|
@@ -3209,14 +3773,24 @@ var RoomCore = class _RoomCore {
|
|
|
3209
3773
|
const config = this.definition.config.physics;
|
|
3210
3774
|
if (!config) return void 0;
|
|
3211
3775
|
if (config.engine === "matter2d") return this.buildMatter(config, restored);
|
|
3212
|
-
|
|
3213
|
-
|
|
3776
|
+
if (config.engine === "rapier2d") return this.buildRapier2d(restored);
|
|
3777
|
+
const engine4 = loadedPhysics();
|
|
3778
|
+
if (!engine4) {
|
|
3214
3779
|
throw new Error(
|
|
3215
3780
|
"RoomCore: this room declares physics but the engine is not initialized \u2014 the host must `await initPhysics()` (from '@irtio/runtime') before constructing the room; handlers are synchronous, so the WASM cannot be loaded later"
|
|
3216
3781
|
);
|
|
3217
3782
|
}
|
|
3218
|
-
|
|
3219
|
-
|
|
3783
|
+
let section;
|
|
3784
|
+
if (restored?.physics) {
|
|
3785
|
+
const wrote = physicsSectionEngine(restored.physics);
|
|
3786
|
+
if (wrote === "rapier3d") section = decodePhysicsSection(restored.physics);
|
|
3787
|
+
else {
|
|
3788
|
+
this.host.log("warn", [
|
|
3789
|
+
`irtio: this snapshot carries a ${wrote} world and the room now runs rapier3d. The world is rebuilt from schema state and physics.setup runs again.`
|
|
3790
|
+
]);
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
const physics = new PhysicsRuntime(this.internals, engine4, {
|
|
3220
3794
|
...section ? { restore: section } : {},
|
|
3221
3795
|
defaultTimestep: 1 / this.definition.config.tickRate
|
|
3222
3796
|
});
|
|
@@ -3247,11 +3821,12 @@ var RoomCore = class _RoomCore {
|
|
|
3247
3821
|
}
|
|
3248
3822
|
let section;
|
|
3249
3823
|
if (restored?.physics) {
|
|
3250
|
-
|
|
3824
|
+
const wrote = physicsSectionEngine(restored.physics);
|
|
3825
|
+
if (wrote === "matter2d") {
|
|
3251
3826
|
section = decodeMatterBodies(decodeMatterSectionEnvelope(restored.physics));
|
|
3252
3827
|
} else {
|
|
3253
3828
|
this.host.log("warn", [
|
|
3254
|
-
|
|
3829
|
+
`irtio: this snapshot carries a ${wrote} world and the room now runs matter2d. The world is rebuilt from schema state and physics.setup runs again.`
|
|
3255
3830
|
]);
|
|
3256
3831
|
}
|
|
3257
3832
|
}
|
|
@@ -3268,6 +3843,44 @@ var RoomCore = class _RoomCore {
|
|
|
3268
3843
|
physics.reconcile();
|
|
3269
3844
|
return physics;
|
|
3270
3845
|
}
|
|
3846
|
+
/**
|
|
3847
|
+
* The rapier2d half. It follows the *rapier3d* shape rather than matter2d's, because the 2D
|
|
3848
|
+
* build has a real `takeSnapshot()`: a restored world comes back whole and `setup` does not run
|
|
3849
|
+
* again. The only thing that differs from `buildPhysics` is which engine and which envelope.
|
|
3850
|
+
*/
|
|
3851
|
+
buildRapier2d(restored) {
|
|
3852
|
+
const engine4 = loadedRapier2d();
|
|
3853
|
+
if (!engine4) {
|
|
3854
|
+
throw new Error(
|
|
3855
|
+
"RoomCore: this room declares rapier2d physics but the engine is not initialized \u2014 the host must `await initRapier2d()` (from '@irtio/runtime') before constructing the room; handlers are synchronous, so the WASM cannot be loaded later"
|
|
3856
|
+
);
|
|
3857
|
+
}
|
|
3858
|
+
let section;
|
|
3859
|
+
if (restored?.physics) {
|
|
3860
|
+
const wrote = physicsSectionEngine(restored.physics);
|
|
3861
|
+
if (wrote === "rapier2d") {
|
|
3862
|
+
section = decodePhysicsSection(decodeRapier2dSectionEnvelope(restored.physics));
|
|
3863
|
+
} else {
|
|
3864
|
+
this.host.log("warn", [
|
|
3865
|
+
`irtio: this snapshot carries a ${wrote} world and the room now runs rapier2d. The world is rebuilt from schema state and physics.setup runs again.`
|
|
3866
|
+
]);
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
const physics = new Rapier2dRuntime(this.internals, engine4, {
|
|
3870
|
+
...section ? { restore: section } : {},
|
|
3871
|
+
defaultTimestep: 1 / this.definition.config.tickRate
|
|
3872
|
+
});
|
|
3873
|
+
if (physics.rebuilt) {
|
|
3874
|
+
if (restored) {
|
|
3875
|
+
this.host.log("info", [
|
|
3876
|
+
`irtio: no rapier2d world in this snapshot (format v${restored.version}) \u2014 rebuilding it from schema state and re-running physics.setup; contact state is not restored`
|
|
3877
|
+
]);
|
|
3878
|
+
}
|
|
3879
|
+
physics.runSetup(this.room);
|
|
3880
|
+
physics.reconcile();
|
|
3881
|
+
}
|
|
3882
|
+
return physics;
|
|
3883
|
+
}
|
|
3271
3884
|
/** Convenience for hosts: `RoomCore.restore(def, bytes, host, opts)`. */
|
|
3272
3885
|
static restore(definition, bytes, host, options) {
|
|
3273
3886
|
return new _RoomCore(definition, host, { ...options, restoreFrom: bytes });
|
|
@@ -3756,6 +4369,7 @@ var RoomCore = class _RoomCore {
|
|
|
3756
4369
|
* its view's `DELTA` — encoded once per distinct view.
|
|
3757
4370
|
*/
|
|
3758
4371
|
flush() {
|
|
4372
|
+
evaluateLobby(this.internals);
|
|
3759
4373
|
const dirty = this.tracked.flush();
|
|
3760
4374
|
serverWinsCorrections(this.internals, dirty);
|
|
3761
4375
|
this.ledger?.newFlush();
|
|
@@ -3885,7 +4499,13 @@ var RoomCore = class _RoomCore {
|
|
|
3885
4499
|
}
|
|
3886
4500
|
};
|
|
3887
4501
|
function encodeSection(physics) {
|
|
3888
|
-
|
|
4502
|
+
if (physics.engineKind === "matter2d") {
|
|
4503
|
+
return encodeMatterSectionEnvelope(encodeMatterBodies(physics.serialize()));
|
|
4504
|
+
}
|
|
4505
|
+
if (physics.engineKind === "rapier2d") {
|
|
4506
|
+
return encodeRapier2dSectionEnvelope(encodePhysicsSection(physics.serialize()));
|
|
4507
|
+
}
|
|
4508
|
+
return encodePhysicsSection(physics.serialize());
|
|
3889
4509
|
}
|
|
3890
4510
|
|
|
3891
4511
|
export {
|
|
@@ -3908,10 +4528,15 @@ export {
|
|
|
3908
4528
|
onFirstRapierStep,
|
|
3909
4529
|
encodePhysicsSection,
|
|
3910
4530
|
physicsSectionEngine,
|
|
4531
|
+
encodeRapier2dSectionEnvelope,
|
|
4532
|
+
decodeRapier2dSectionEnvelope,
|
|
3911
4533
|
encodeMatterSectionEnvelope,
|
|
3912
4534
|
decodeMatterSectionEnvelope,
|
|
3913
4535
|
decodePhysicsSection,
|
|
3914
4536
|
Mulberry32,
|
|
4537
|
+
initRapier2d,
|
|
4538
|
+
loadedRapier2d,
|
|
4539
|
+
resetRapier2dForTests,
|
|
3915
4540
|
isVisible,
|
|
3916
4541
|
visibleTo,
|
|
3917
4542
|
visibleNames,
|
|
@@ -250,6 +250,24 @@ interface RoomHost {
|
|
|
250
250
|
* A host with no room list (the in-process test harness) records it and does nothing else.
|
|
251
251
|
*/
|
|
252
252
|
setBackfill(open: boolean): void;
|
|
253
|
+
/**
|
|
254
|
+
* D75: `room.lobby.setPublic(v)`, and the terminal deregistration a game start performs.
|
|
255
|
+
*
|
|
256
|
+
* Fire-and-forget, and host state rather than room state, for exactly `setBackfill`'s reason:
|
|
257
|
+
* the supervisor holds it, reports it on the room list the agent already polls, and control
|
|
258
|
+
* turns that report into a registry row. None of which the room could arrange for itself and
|
|
259
|
+
* none of which belongs in the hibernation blob.
|
|
260
|
+
*
|
|
261
|
+
* `started` is a one-way door. A room that reports it is deregistered terminally, and no later
|
|
262
|
+
* report — raced, stale or forged — can put it back in front of a stranger.
|
|
263
|
+
*
|
|
264
|
+
* A host with no room list (the in-process test harness) records it and does nothing else.
|
|
265
|
+
*/
|
|
266
|
+
setLobby(update: {
|
|
267
|
+
readonly public?: boolean;
|
|
268
|
+
readonly queue?: string;
|
|
269
|
+
readonly started?: boolean;
|
|
270
|
+
}): void;
|
|
253
271
|
/**
|
|
254
272
|
* D44: `room.spawnNPC(config)`. The host opens a loopback client session under `clientId` and
|
|
255
273
|
* runs the named entry of the room definition's `npcs` map against it. The room minted the id,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { D as DEFAULT_TIMELINE_MAX_RECORDS, a as DEFAULT_TIMELINE_MAX_TICKS, E as EVENT_RING_SIZE, H as HOST_CALL_TIMEOUT_MS, b as HostCall, c as HostCallResult, J as JoinOptions, d as JoinResult, L as LogLevel, R as RoomCoreApi, e as RoomCoreOptions, f as RoomEvent, g as RoomEventKind, h as RoomFullError, i as RoomHost, j as RoomInspection, k as RoomStats, T as TimelineDump, l as TimelineFrame, m as TimelineRecorder, n as TimelineRecorderOptions, o as inspectState } from './contract-
|
|
2
|
-
export { C as CRASH_AFTER_THROWS, M as MAX_CATCHUP, a as MatterBodyRecord, b as MatterSection, c as Mulberry32, P as PhysicsEngineTag, d as PhysicsSection, R as RoomCore, e as decodeMatterBodies, f as decodeMatterSectionEnvelope, g as decodePhysicsSection, h as
|
|
1
|
+
export { D as DEFAULT_TIMELINE_MAX_RECORDS, a as DEFAULT_TIMELINE_MAX_TICKS, E as EVENT_RING_SIZE, H as HOST_CALL_TIMEOUT_MS, b as HostCall, c as HostCallResult, J as JoinOptions, d as JoinResult, L as LogLevel, R as RoomCoreApi, e as RoomCoreOptions, f as RoomEvent, g as RoomEventKind, h as RoomFullError, i as RoomHost, j as RoomInspection, k as RoomStats, T as TimelineDump, l as TimelineFrame, m as TimelineRecorder, n as TimelineRecorderOptions, o as inspectState } from './contract-CRilLVdx.js';
|
|
2
|
+
export { C as CRASH_AFTER_THROWS, M as MAX_CATCHUP, a as MatterBodyRecord, b as MatterSection, c as Mulberry32, P as PhysicsEngineTag, d as PhysicsSection, R as RoomCore, e as decodeMatterBodies, f as decodeMatterSectionEnvelope, g as decodePhysicsSection, h as decodeRapier2dSectionEnvelope, i as encodeMatterBodies, j as encodeMatterSectionEnvelope, k as encodePhysicsSection, l as encodeRapier2dSectionEnvelope, m as initMatter, n as initPhysics, o as initRapier2d, p as loadedMatter, q as loadedPhysics, r as loadedRapier2d, s as physicsSectionEngine, t as resetMatterForTests, u as resetPhysicsForTests, v as resetRapier2dForTests } from './room-YJGz6Caw.js';
|
|
3
3
|
import { CollectionDesc, AnySchema, PlainState, DirtySet } from '@irtio/schema';
|
|
4
4
|
import '@irtio/protocol';
|
|
5
5
|
import '@irtio/server';
|
|
@@ -121,7 +121,7 @@ interface DecodedSave extends SnapshotHeader {
|
|
|
121
121
|
* there is no section at all. A pre-D45 blob has no discriminant and reads as `'rapier3d'`,
|
|
122
122
|
* which is what it is.
|
|
123
123
|
*/
|
|
124
|
-
readonly physicsEngine: 'rapier3d' | 'matter2d' | undefined;
|
|
124
|
+
readonly physicsEngine: 'rapier3d' | 'matter2d' | 'rapier2d' | undefined;
|
|
125
125
|
/**
|
|
126
126
|
* The room's authoritative state, in exactly the shape `inspectState` and the recorded
|
|
127
127
|
* timeline use: entity collections as `{ id: { owner, value } }`, singletons as their value.
|
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-DBGMG2S3.js";
|
|
6
6
|
import {
|
|
7
7
|
CRASH_AFTER_THROWS,
|
|
8
8
|
DEFAULT_TIMELINE_MAX_RECORDS,
|
|
@@ -22,27 +22,32 @@ import {
|
|
|
22
22
|
decodeMatterBodies,
|
|
23
23
|
decodeMatterSectionEnvelope,
|
|
24
24
|
decodePhysicsSection,
|
|
25
|
+
decodeRapier2dSectionEnvelope,
|
|
25
26
|
decodeSave,
|
|
26
27
|
encodeMatterBodies,
|
|
27
28
|
encodeMatterSectionEnvelope,
|
|
28
29
|
encodePhysicsSection,
|
|
30
|
+
encodeRapier2dSectionEnvelope,
|
|
29
31
|
encodeViewDelta,
|
|
30
32
|
encodeViewSnapshot,
|
|
31
33
|
initMatter,
|
|
32
34
|
initPhysics,
|
|
35
|
+
initRapier2d,
|
|
33
36
|
inspectState,
|
|
34
37
|
isVisible,
|
|
35
38
|
loadedMatter,
|
|
36
39
|
loadedPhysics,
|
|
40
|
+
loadedRapier2d,
|
|
37
41
|
parseHibernationBlob,
|
|
38
42
|
physicsSectionEngine,
|
|
39
43
|
resetMatterForTests,
|
|
40
44
|
resetPhysicsForTests,
|
|
45
|
+
resetRapier2dForTests,
|
|
41
46
|
viewKeyFor,
|
|
42
47
|
visibleNames,
|
|
43
48
|
visibleTo,
|
|
44
49
|
writeHibernationBlob
|
|
45
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-ZCD2TBXB.js";
|
|
46
51
|
export {
|
|
47
52
|
CRASH_AFTER_THROWS,
|
|
48
53
|
DEFAULT_TIMELINE_MAX_RECORDS,
|
|
@@ -62,24 +67,29 @@ export {
|
|
|
62
67
|
decodeMatterBodies,
|
|
63
68
|
decodeMatterSectionEnvelope,
|
|
64
69
|
decodePhysicsSection,
|
|
70
|
+
decodeRapier2dSectionEnvelope,
|
|
65
71
|
decodeSave,
|
|
66
72
|
encodeMatterBodies,
|
|
67
73
|
encodeMatterSectionEnvelope,
|
|
68
74
|
encodePhysicsSection,
|
|
75
|
+
encodeRapier2dSectionEnvelope,
|
|
69
76
|
encodeViewDelta,
|
|
70
77
|
encodeViewSnapshot,
|
|
71
78
|
fromMigrationState,
|
|
72
79
|
initMatter,
|
|
73
80
|
initPhysics,
|
|
81
|
+
initRapier2d,
|
|
74
82
|
inspectState,
|
|
75
83
|
isVisible,
|
|
76
84
|
loadedMatter,
|
|
77
85
|
loadedPhysics,
|
|
86
|
+
loadedRapier2d,
|
|
78
87
|
migrateSnapshot,
|
|
79
88
|
parseHibernationBlob,
|
|
80
89
|
physicsSectionEngine,
|
|
81
90
|
resetMatterForTests,
|
|
82
91
|
resetPhysicsForTests,
|
|
92
|
+
resetRapier2dForTests,
|
|
83
93
|
toMigrationState,
|
|
84
94
|
viewKeyFor,
|
|
85
95
|
visibleNames,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AttributeOptions, ProfileSnapshot } from '@irtio/protocol';
|
|
2
2
|
import { DirtySet, AnySchema, PlainState, Tracked, State } from '@irtio/schema';
|
|
3
|
-
import { RoomDefinition, RoomMode, Room, RewindView, Ctx, MatterModule, MatterEngine, MatterBody, RapierModule, RapierWorld, RapierRigidBody, ResolvedRoomConfig, LeaveReason } from '@irtio/server';
|
|
4
|
-
import { i as RoomHost, k as RoomStats, L as LogLevel, R as RoomCoreApi, e as RoomCoreOptions, g as RoomEventKind, n as TimelineRecorderOptions, T as TimelineDump, j as RoomInspection, J as JoinOptions, d as JoinResult, c as HostCallResult } from './contract-
|
|
3
|
+
import { RoomDefinition, RoomMode, Room, RewindView, Ctx, MatterModule, MatterEngine, MatterBody, RapierModule, RapierWorld, RapierRigidBody, Rapier2dModule, Rapier2dWorld, Rapier2dRigidBody, ResolvedRoomConfig, LeaveReason } from '@irtio/server';
|
|
4
|
+
import { i as RoomHost, k as RoomStats, L as LogLevel, R as RoomCoreApi, e as RoomCoreOptions, g as RoomEventKind, n as TimelineRecorderOptions, T as TimelineDump, j as RoomInspection, J as JoinOptions, d as JoinResult, c as HostCallResult } from './contract-CRilLVdx.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* `room.random()` — mulberry32 over a u32 seed. Deterministic, tiny, and serializable: the
|
|
@@ -93,8 +93,8 @@ interface QueuedFrame {
|
|
|
93
93
|
}
|
|
94
94
|
/** What the core modules may do with the physics world (`core/physics.ts` implements it). */
|
|
95
95
|
interface PhysicsApi {
|
|
96
|
-
/** D45: which of the
|
|
97
|
-
readonly engineKind: 'rapier3d' | 'matter2d';
|
|
96
|
+
/** D45: which of the blessed engines this room is running. */
|
|
97
|
+
readonly engineKind: 'rapier3d' | 'matter2d' | 'rapier2d';
|
|
98
98
|
readonly timestep: number;
|
|
99
99
|
/** Creates bodies for new instances, destroys bodies whose instance is gone. */
|
|
100
100
|
reconcile(): void;
|
|
@@ -110,9 +110,9 @@ interface PhysicsApi {
|
|
|
110
110
|
* `bodyFor`, and it never hands out the map itself.
|
|
111
111
|
*/
|
|
112
112
|
eachTrackedBody(fn: (collection: string, id: string, body: unknown) => void): void;
|
|
113
|
-
/** rapier3d
|
|
113
|
+
/** rapier3d and rapier2d: the engine namespace. `engineKind` says which module it is. */
|
|
114
114
|
readonly rapier: unknown;
|
|
115
|
-
/** rapier3d
|
|
115
|
+
/** rapier3d and rapier2d: the live `World`. */
|
|
116
116
|
readonly world: unknown;
|
|
117
117
|
/** matter2d only: the `matter-js` namespace. */
|
|
118
118
|
readonly matter: unknown;
|
|
@@ -431,9 +431,27 @@ declare function encodePhysicsSection(section: PhysicsSection): Uint8Array;
|
|
|
431
431
|
* pre-D45 blob is therefore byte-identical and reads as Rapier without a version bump, which is
|
|
432
432
|
* what the recommendation in the plan asked for and why format 3 was not needed.
|
|
433
433
|
*/
|
|
434
|
-
type PhysicsEngineTag = 'rapier3d' | 'matter2d';
|
|
435
|
-
/**
|
|
434
|
+
type PhysicsEngineTag = 'rapier3d' | 'matter2d' | 'rapier2d';
|
|
435
|
+
/**
|
|
436
|
+
* Reads the engine out of an encoded section without decoding the rest of it.
|
|
437
|
+
*
|
|
438
|
+
* The `else` is deliberately bare rather than a third tag comparison: a pre-D45 blob has no
|
|
439
|
+
* discriminant at all, and every byte sequence that is not the zero-length-world envelope is a
|
|
440
|
+
* rapier3d world snapshot. Adding rapier2d must not change that, or every blob written before
|
|
441
|
+
* this engine existed stops decoding.
|
|
442
|
+
*/
|
|
436
443
|
declare function physicsSectionEngine(bytes: Uint8Array): PhysicsEngineTag;
|
|
444
|
+
/**
|
|
445
|
+
* Wraps a rapier2d section in the same discriminated envelope, under its own tag.
|
|
446
|
+
*
|
|
447
|
+
* The payload is an ordinary {@link encodePhysicsSection} — rapier2d has a real
|
|
448
|
+
* `world.takeSnapshot()`, so unlike matter2d it has a world to write, and the only thing the
|
|
449
|
+
* envelope buys is telling the two Rapiers apart. Restoring a 2D snapshot into a 3D world (or the
|
|
450
|
+
* reverse) does not fail cleanly, which is exactly why the discriminant is not optional here.
|
|
451
|
+
*/
|
|
452
|
+
declare function encodeRapier2dSectionEnvelope(payload: Uint8Array): Uint8Array;
|
|
453
|
+
/** The payload inside a rapier2d envelope. Throws if the section was written by another engine. */
|
|
454
|
+
declare function decodeRapier2dSectionEnvelope(bytes: Uint8Array): Uint8Array;
|
|
437
455
|
/** Wraps a matter2d body-state payload in the discriminated envelope. */
|
|
438
456
|
declare function encodeMatterSectionEnvelope(payload: Uint8Array): Uint8Array;
|
|
439
457
|
/** The payload inside a matter2d envelope. Throws if the section is a Rapier one. */
|
|
@@ -501,6 +519,106 @@ declare class PhysicsRuntime {
|
|
|
501
519
|
serialize(): PhysicsSection;
|
|
502
520
|
}
|
|
503
521
|
|
|
522
|
+
/**
|
|
523
|
+
* The rapier2d world inside the room: Rapier, held in a plane.
|
|
524
|
+
*
|
|
525
|
+
* It is modelled on `core/physics.ts` rather than on `core/matter.ts`, and the choice is the whole
|
|
526
|
+
* point of the engine existing. Everything that made the Rapier path good is kept:
|
|
527
|
+
*
|
|
528
|
+
* - **A real engine snapshot.** `world.takeSnapshot()` / `World.restoreSnapshot()` exist in the 2D
|
|
529
|
+
* build too, so a woken room is byte-for-byte the world that went to sleep, contacts and sleep
|
|
530
|
+
* timers included, and `setup` runs only on a genuine rebuild. matter2d has no equivalent and
|
|
531
|
+
* rebuilds every wake; that difference is why a settled pile can jolt there and does not here.
|
|
532
|
+
* - **The world applies gravity.** It goes into the `World` constructor and dynamic bodies fall
|
|
533
|
+
* without the room lifting a finger. A matter2d room's hooks apply gravity themselves; a
|
|
534
|
+
* rapier2d room that copied that would fall twice as fast.
|
|
535
|
+
* - **Velocities are per second.** `linvel()` is metres per second, not matter's per-step
|
|
536
|
+
* displacement, so nothing here rescales anything.
|
|
537
|
+
*
|
|
538
|
+
* What it takes from the 2D side is only the plane: `{ x, y }` gravity, and the channel mapping in
|
|
539
|
+
* `@irtio/schema` (`channelOf2d` / `applyChannel2d` / `angleFrom2d`), which is the single place the
|
|
540
|
+
* six planar quantities are written onto the thirteen 3D wire channels. A second copy of that
|
|
541
|
+
* mapping here would be a way for the two sides to disagree by a sign, so there is not one.
|
|
542
|
+
*
|
|
543
|
+
* There is no planar-lock warning in this file. Bug 6's friction trap needs a third axis to lock,
|
|
544
|
+
* and this engine does not have one.
|
|
545
|
+
*
|
|
546
|
+
* ## Async init
|
|
547
|
+
*
|
|
548
|
+
* Rapier's WASM needs `await RAPIER.init()`, and room handlers are synchronous, so the engine is
|
|
549
|
+
* initialized before the room is constructed — by the worker host at bundle-load time, and by
|
|
550
|
+
* `initRapier2d()` in the test harness. This is `initPhysics()`'s twin, against the 2D module.
|
|
551
|
+
*/
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Loads and initializes `@dimforge/rapier2d-compat` once per process. Idempotent and safe to call
|
|
555
|
+
* concurrently. It is a *separate* WASM module from the 3D build — the two do not share an
|
|
556
|
+
* instance and loading one does not load the other — so a rapier2d room pays the 2D module's
|
|
557
|
+
* footprint and nothing else.
|
|
558
|
+
*/
|
|
559
|
+
declare function initRapier2d(): Promise<Rapier2dModule>;
|
|
560
|
+
/** The initialized engine, or `undefined` when `initRapier2d()` has not resolved yet. */
|
|
561
|
+
declare function loadedRapier2d(): Rapier2dModule | undefined;
|
|
562
|
+
/** Test seam: forget the loaded engine (never used in production paths). */
|
|
563
|
+
declare function resetRapier2dForTests(): void;
|
|
564
|
+
interface Rapier2dRuntimeOptions {
|
|
565
|
+
/** From a v2 hibernation blob's rapier2d section. Absent → a fresh world, and `setup` runs. */
|
|
566
|
+
readonly restore?: PhysicsSection;
|
|
567
|
+
/** Seconds per step when the config does not name one (the tick interval). */
|
|
568
|
+
readonly defaultTimestep: number;
|
|
569
|
+
}
|
|
570
|
+
declare class Rapier2dRuntime {
|
|
571
|
+
readonly engineKind: "rapier2d";
|
|
572
|
+
/** matter2d only; present so every runtime satisfies one internal shape. */
|
|
573
|
+
readonly matter: undefined;
|
|
574
|
+
readonly matterEngine: undefined;
|
|
575
|
+
readonly rapier: Rapier2dModule;
|
|
576
|
+
readonly world: Rapier2dWorld;
|
|
577
|
+
/** `true` when the world was built from scratch and `setup` has to run. */
|
|
578
|
+
readonly rebuilt: boolean;
|
|
579
|
+
/** Rapier restores a whole world, contacts included, so `setup` runs only on a rebuild. */
|
|
580
|
+
get needsSetup(): boolean;
|
|
581
|
+
private readonly core;
|
|
582
|
+
private readonly config;
|
|
583
|
+
/** Physics-backed collections, in schema (name-sorted) order. */
|
|
584
|
+
private readonly collections;
|
|
585
|
+
private readonly bodies;
|
|
586
|
+
/** See `PhysicsRuntime.sleepSynced`: the one extra sync a body owes on the tick it sleeps. */
|
|
587
|
+
private readonly sleepSynced;
|
|
588
|
+
constructor(core: RoomInternals, rapier: Rapier2dModule, options: Rapier2dRuntimeOptions);
|
|
589
|
+
get timestep(): number;
|
|
590
|
+
/** Runs the room's `setup` — static geometry — on a world that was built rather than restored. */
|
|
591
|
+
runSetup(room: Room): void;
|
|
592
|
+
free(): void;
|
|
593
|
+
/** The body behind an instance, created on demand so a handler's own `add` is usable at once. */
|
|
594
|
+
bodyFor(collection: string, id: string): Rapier2dRigidBody | undefined;
|
|
595
|
+
private create;
|
|
596
|
+
/**
|
|
597
|
+
* D72: every tracked body, in the order `sync()` walks them (collection order, then instance
|
|
598
|
+
* order). The rapier2d third of the same read-only accessor the other two runtimes carry.
|
|
599
|
+
*/
|
|
600
|
+
eachTrackedBody(fn: (collection: string, id: string, body: Rapier2dRigidBody) => void): void;
|
|
601
|
+
private applyRecordToBody;
|
|
602
|
+
/**
|
|
603
|
+
* Creates bodies for new instances and destroys bodies whose instance is gone. Runs once per
|
|
604
|
+
* tick, right before the step, in collection order then instance order.
|
|
605
|
+
*/
|
|
606
|
+
reconcile(): void;
|
|
607
|
+
step(): void;
|
|
608
|
+
/**
|
|
609
|
+
* Body → schema, through the tracked proxies, so movement leaves the room as an ordinary delta.
|
|
610
|
+
* Values are `Math.fround`ed for f32 fields, so room code reads exactly what the wire carries.
|
|
611
|
+
*/
|
|
612
|
+
sync(): void;
|
|
613
|
+
/**
|
|
614
|
+
* The same `PhysicsSection` the 3D runtime writes — a real engine snapshot plus the
|
|
615
|
+
* entity↔handle map, whose handles must ride as f64 for the reason spelled out on
|
|
616
|
+
* {@link PhysicsSection}. What tells the two apart on the way back in is the envelope
|
|
617
|
+
* `core/physics.ts` wraps this in, not anything in here.
|
|
618
|
+
*/
|
|
619
|
+
serialize(): PhysicsSection;
|
|
620
|
+
}
|
|
621
|
+
|
|
504
622
|
/**
|
|
505
623
|
* `RoomCore` — the host-agnostic room runtime. It owns the extended schema, the
|
|
506
624
|
* tracked authority state, presence, the loop, and the frame dispatch; everything outside comes
|
|
@@ -521,7 +639,7 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
521
639
|
readonly clients: Map<string, ClientEntry>;
|
|
522
640
|
readonly loop: Loop;
|
|
523
641
|
/** D22: the Rapier world, or `undefined` in a room whose config declares no physics. */
|
|
524
|
-
readonly physics: PhysicsRuntime | MatterRuntime | undefined;
|
|
642
|
+
readonly physics: PhysicsRuntime | MatterRuntime | Rapier2dRuntime | undefined;
|
|
525
643
|
readonly stats: RoomStats;
|
|
526
644
|
tick: number;
|
|
527
645
|
stopped: boolean;
|
|
@@ -596,6 +714,12 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
596
714
|
* worth logging.
|
|
597
715
|
*/
|
|
598
716
|
private buildMatter;
|
|
717
|
+
/**
|
|
718
|
+
* The rapier2d half. It follows the *rapier3d* shape rather than matter2d's, because the 2D
|
|
719
|
+
* build has a real `takeSnapshot()`: a restored world comes back whole and `setup` does not run
|
|
720
|
+
* again. The only thing that differs from `buildPhysics` is which engine and which envelope.
|
|
721
|
+
*/
|
|
722
|
+
private buildRapier2d;
|
|
599
723
|
/** Convenience for hosts: `RoomCore.restore(def, bytes, host, opts)`. */
|
|
600
724
|
static restore<S2 extends AnySchema>(definition: RoomDefinition<S2>, bytes: Uint8Array, host: RoomHost, options: Omit<RoomCoreOptions, 'restoreFrom'>): RoomCore<S2>;
|
|
601
725
|
get schema(): S;
|
|
@@ -707,4 +831,4 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
707
831
|
flush(): void;
|
|
708
832
|
}
|
|
709
833
|
|
|
710
|
-
export { CRASH_AFTER_THROWS as C, MAX_CATCHUP as M, type PhysicsEngineTag as P, RoomCore as R, type MatterBodyRecord as a, type MatterSection as b, Mulberry32 as c, type PhysicsSection as d, decodeMatterBodies as e, decodeMatterSectionEnvelope as f, decodePhysicsSection as g,
|
|
834
|
+
export { CRASH_AFTER_THROWS as C, MAX_CATCHUP as M, type PhysicsEngineTag as P, RoomCore as R, type MatterBodyRecord as a, type MatterSection as b, Mulberry32 as c, type PhysicsSection as d, decodeMatterBodies as e, decodeMatterSectionEnvelope as f, decodePhysicsSection as g, decodeRapier2dSectionEnvelope as h, encodeMatterBodies as i, encodeMatterSectionEnvelope as j, encodePhysicsSection as k, encodeRapier2dSectionEnvelope as l, initMatter as m, initPhysics as n, initRapier2d as o, loadedMatter as p, loadedPhysics as q, loadedRapier2d as r, physicsSectionEngine as s, resetMatterForTests as t, resetPhysicsForTests as u, resetRapier2dForTests as v };
|
package/dist/test/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { R as RoomCore } from '../room-
|
|
2
|
-
export {
|
|
1
|
+
import { R as RoomCore } from '../room-YJGz6Caw.js';
|
|
2
|
+
export { m as initMatter, n as initPhysics, o as initRapier2d } from '../room-YJGz6Caw.js';
|
|
3
3
|
import { ErrorCodeName, FrameType } from '@irtio/protocol';
|
|
4
4
|
import { NpcConfig, LeaveReason, Room, RoomDefinition } from '@irtio/server';
|
|
5
|
-
import { i as RoomHost, L as LogLevel, R as RoomCoreApi, b as HostCall, c as HostCallResult, k as RoomStats } from '../contract-
|
|
5
|
+
import { i as RoomHost, L as LogLevel, R as RoomCoreApi, b as HostCall, c as HostCallResult, k as RoomStats } from '../contract-CRilLVdx.js';
|
|
6
6
|
import { AnySchema, PlainState, EntityCollection, State } from '@irtio/schema';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -166,6 +166,17 @@ declare class HarnessHost implements RoomHost {
|
|
|
166
166
|
/** D63-e: the harness has no room list to report on, so it records the value and stops. */
|
|
167
167
|
backfillOpen: boolean | undefined;
|
|
168
168
|
setBackfill(open: boolean): void;
|
|
169
|
+
/** D75: recorded, like the backfill flag, and for the same reason — no room list to report on. */
|
|
170
|
+
lobby: {
|
|
171
|
+
public?: boolean;
|
|
172
|
+
queue?: string;
|
|
173
|
+
started?: boolean;
|
|
174
|
+
};
|
|
175
|
+
setLobby(update: {
|
|
176
|
+
readonly public?: boolean;
|
|
177
|
+
readonly queue?: string;
|
|
178
|
+
readonly started?: boolean;
|
|
179
|
+
}): void;
|
|
169
180
|
busPublish(channel: string, payload: string): void;
|
|
170
181
|
busSubscribe(channel: string, subscribed: boolean): void;
|
|
171
182
|
setAlarm(name: string, atMs: number | undefined): void;
|
package/dist/test/index.js
CHANGED
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
createVisibilityPolicy,
|
|
4
4
|
initMatter,
|
|
5
5
|
initPhysics,
|
|
6
|
+
initRapier2d,
|
|
6
7
|
visibleNames
|
|
7
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-ZCD2TBXB.js";
|
|
8
9
|
|
|
9
10
|
// src/test/clock.ts
|
|
10
11
|
var FakeClock = class {
|
|
@@ -235,6 +236,12 @@ var HarnessHost = class {
|
|
|
235
236
|
setBackfill(open) {
|
|
236
237
|
this.backfillOpen = open;
|
|
237
238
|
}
|
|
239
|
+
// ---- M6 lane H: lobby front door ----
|
|
240
|
+
/** D75: recorded, like the backfill flag, and for the same reason — no room list to report on. */
|
|
241
|
+
lobby = {};
|
|
242
|
+
setLobby(update) {
|
|
243
|
+
this.lobby = { ...this.lobby, ...update };
|
|
244
|
+
}
|
|
238
245
|
busPublish(channel, payload) {
|
|
239
246
|
this.busPublishes.push({ channel, payload });
|
|
240
247
|
}
|
|
@@ -974,5 +981,6 @@ export {
|
|
|
974
981
|
createRoomHarness,
|
|
975
982
|
frameTypeName,
|
|
976
983
|
initMatter,
|
|
977
|
-
initPhysics
|
|
984
|
+
initPhysics,
|
|
985
|
+
initRapier2d
|
|
978
986
|
};
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as LogLevel, T as TimelineDump, k as RoomStats, b as HostCall, c as HostCallResult, R as RoomCoreApi, i as RoomHost } from '../contract-
|
|
1
|
+
import { L as LogLevel, T as TimelineDump, k as RoomStats, b as HostCall, c as HostCallResult, R as RoomCoreApi, i as RoomHost } from '../contract-CRilLVdx.js';
|
|
2
2
|
import { ErrorCodeName, ProfileSnapshot } from '@irtio/protocol';
|
|
3
3
|
import { NpcConfig, LeaveReason } from '@irtio/server';
|
|
4
4
|
import '@irtio/schema';
|
|
@@ -151,6 +151,9 @@ type FromWorker = {
|
|
|
151
151
|
/** D63-e: the room type's declared backfill eligibility. The ceiling on
|
|
152
152
|
* `room.backfill.set`, and `false` for every room that declares nothing. */
|
|
153
153
|
backfill?: boolean;
|
|
154
|
+
/** D75: the room type declared a lobby. The ceiling on `setLobby`, and the supervisor
|
|
155
|
+
* re-checks it for exactly the reason it re-checks `backfill`. */
|
|
156
|
+
lobby?: boolean;
|
|
154
157
|
/** D47: the physics engine this room runs, when it declares one. Reported so room-hours
|
|
155
158
|
* rows carry the engine as a dimension; the supervisor never acts on it. */
|
|
156
159
|
engine?: string;
|
|
@@ -289,6 +292,11 @@ type FromWorker = {
|
|
|
289
292
|
| {
|
|
290
293
|
t: 'setBackfill';
|
|
291
294
|
open: boolean;
|
|
295
|
+
} | {
|
|
296
|
+
t: 'setLobby';
|
|
297
|
+
public?: boolean;
|
|
298
|
+
queue?: string;
|
|
299
|
+
started?: boolean;
|
|
292
300
|
}
|
|
293
301
|
/**
|
|
294
302
|
* D59: `room.bus.publish(channel, payload)`. Fire-and-forget by design — the room gets no
|
package/dist/worker/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
migrateSnapshot
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-DBGMG2S3.js";
|
|
4
4
|
import {
|
|
5
5
|
RoomCore,
|
|
6
6
|
RoomFullError,
|
|
7
7
|
initMatter,
|
|
8
8
|
initPhysics,
|
|
9
|
+
initRapier2d,
|
|
9
10
|
onFirstRapierStep,
|
|
10
11
|
rapierHasStepped
|
|
11
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-ZCD2TBXB.js";
|
|
12
13
|
|
|
13
14
|
// src/worker/index.ts
|
|
14
15
|
import { getHeapStatistics } from "v8";
|
|
@@ -79,6 +80,10 @@ function createWorkerHost(post) {
|
|
|
79
80
|
setBackfill(open) {
|
|
80
81
|
post({ t: "setBackfill", open });
|
|
81
82
|
},
|
|
83
|
+
// ---- M6 lane H: lobby front door ----
|
|
84
|
+
setLobby(update) {
|
|
85
|
+
post({ t: "setLobby", ...update });
|
|
86
|
+
},
|
|
82
87
|
setAlarm(name, atMs) {
|
|
83
88
|
if (atMs === void 0) {
|
|
84
89
|
post({ t: "setAlarm", name });
|
|
@@ -153,7 +158,8 @@ async function handleMessage(state, host, post, msg) {
|
|
|
153
158
|
try {
|
|
154
159
|
if (def.config.physics.engine === "matter2d") await initMatter();
|
|
155
160
|
else {
|
|
156
|
-
await
|
|
161
|
+
if (def.config.physics.engine === "rapier2d") await initRapier2d();
|
|
162
|
+
else await initPhysics();
|
|
157
163
|
onFirstRapierStep(() => {
|
|
158
164
|
const core = state.core;
|
|
159
165
|
if (core) post(statsPayload(core.stats, void 0, core.profile()));
|
|
@@ -211,6 +217,11 @@ async function handleMessage(state, host, post, msg) {
|
|
|
211
217
|
// `engine` is: `ready.config` replaces the supervisor's copy, so a flag threaded
|
|
212
218
|
// only through `loadBundle` would be dropped the moment a worker started.
|
|
213
219
|
backfill: c.backfill === true,
|
|
220
|
+
// ---- M6 lane H: lobby front door ----
|
|
221
|
+
// D75: the declared ceiling on `setLobby`, carried for exactly `backfill`'s reason.
|
|
222
|
+
// The supervisor re-checks it, because a check only the tenant's own worker makes is
|
|
223
|
+
// advice rather than a boundary.
|
|
224
|
+
lobby: c.lobby !== void 0,
|
|
214
225
|
// D47: `ready.config` REPLACES the supervisor's copy of the bundle config, so an
|
|
215
226
|
// engine threaded only through `loadBundle` would be dropped here the moment a
|
|
216
227
|
// worker started. That is exactly how the first known-shape run read `engine: none`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irtio/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "irtio room runtime: RoomCore, worker_threads host, in-process test harness",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -28,11 +28,12 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@dimforge/rapier2d-compat": "0.20.0",
|
|
31
32
|
"@dimforge/rapier3d-compat": "0.20.0",
|
|
32
33
|
"matter-js": "0.20.0",
|
|
33
|
-
"@irtio/protocol": "0.
|
|
34
|
-
"@irtio/schema": "0.
|
|
35
|
-
"@irtio/server": "0.
|
|
34
|
+
"@irtio/protocol": "0.8.0",
|
|
35
|
+
"@irtio/schema": "0.8.0",
|
|
36
|
+
"@irtio/server": "0.8.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@types/matter-js": "0.20.2"
|