@irtio/server 0.8.0 → 0.10.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/index.d.ts +74 -4
- package/dist/index.js +38 -2
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -441,10 +441,41 @@ interface RapierPhysicsConfig<S extends AnySchema> {
|
|
|
441
441
|
setup?(world: RapierWorld, rapier: RapierModule, room: Room<S>): void;
|
|
442
442
|
readonly bodies: BodyFactories<S>;
|
|
443
443
|
/** D72: how many ticks of body poses to keep for `room.rewind`. See {@link HISTORY_DOC}. */
|
|
444
|
-
readonly history?:
|
|
444
|
+
readonly history?: HistoryConfig;
|
|
445
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* D72+: one physics collection's declared history channels.
|
|
448
|
+
*
|
|
449
|
+
* Method syntax for `read`, so a hook written against its own instance type is accepted: the
|
|
450
|
+
* values really passed are the schema's records for that collection. Same precedent as
|
|
451
|
+
* {@link Matter2dIntentHook}.
|
|
452
|
+
*/
|
|
453
|
+
interface HistoryChannelSpec {
|
|
454
|
+
/** How many f32s per entity per tick, 1..{@link HISTORY_MAX_CHANNELS}. */
|
|
455
|
+
readonly count: number;
|
|
456
|
+
/**
|
|
457
|
+
* Fills `out` (length `count`, pre-zeroed) from the instance's record, once per body per tick.
|
|
458
|
+
* `instance` is a read-only view: writes through it do not dirty synced state.
|
|
459
|
+
*/
|
|
460
|
+
read(instance: Record<string, unknown>, out: Float32Array): void;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* D72: the `physics.history` declaration. A bare number is a depth in ticks and means exactly what
|
|
464
|
+
* it has always meant; the object form adds per-collection channels on the same ring and the same
|
|
465
|
+
* lifecycle. Keys of `channels` name physics collections.
|
|
466
|
+
*/
|
|
467
|
+
type HistoryConfig = number | {
|
|
468
|
+
readonly depth: number;
|
|
469
|
+
readonly channels?: Readonly<Record<string, HistoryChannelSpec>>;
|
|
470
|
+
};
|
|
446
471
|
/** D72: the deepest history a room may declare, in ticks. One second at the maximum tick rate. */
|
|
447
472
|
declare const HISTORY_MAX_TICKS = 240;
|
|
473
|
+
/**
|
|
474
|
+
* D72+: the most history channels one physics collection may declare. Enough for a crouch phase, a
|
|
475
|
+
* run phase and a few bone parameters; a room wanting a full skeleton per tick should keep its own
|
|
476
|
+
* ring rather than push this up.
|
|
477
|
+
*/
|
|
478
|
+
declare const HISTORY_MAX_CHANNELS = 8;
|
|
448
479
|
/**
|
|
449
480
|
* D72: what `room.rewind(tick, fn)` hands `fn`.
|
|
450
481
|
*
|
|
@@ -466,6 +497,14 @@ interface RewindView {
|
|
|
466
497
|
* remember", and a room that would rather refuse a clamped answer can read this and do so.
|
|
467
498
|
*/
|
|
468
499
|
readonly clamped: boolean;
|
|
500
|
+
/**
|
|
501
|
+
* D72+: the declared history channels for one entity at the answered tick, in declaration order,
|
|
502
|
+
* length `count`. `undefined` when the collection declares no channels or when the entity was not
|
|
503
|
+
* present at that tick.
|
|
504
|
+
*
|
|
505
|
+
* The array is a view into the ring: read it inside `fn`, do not hold it and do not write to it.
|
|
506
|
+
*/
|
|
507
|
+
channels(collection: string, id: string): Float32Array | undefined;
|
|
469
508
|
/** rapier3d rooms: a scratch world to `castRay` / `castShape` / `intersectionsWithShape` on. */
|
|
470
509
|
readonly rapier?: {
|
|
471
510
|
readonly world: RapierWorld;
|
|
@@ -546,7 +585,7 @@ interface Matter2dPhysicsConfig<S extends AnySchema> {
|
|
|
546
585
|
*/
|
|
547
586
|
readonly intents?: Readonly<Partial<Record<PhysicsKeys<S> & string, Matter2dIntentHook>>>;
|
|
548
587
|
/** D72: how many ticks of body poses to keep for `room.rewind`. See {@link HISTORY_DOC}. */
|
|
549
|
-
readonly history?:
|
|
588
|
+
readonly history?: HistoryConfig;
|
|
550
589
|
}
|
|
551
590
|
/**
|
|
552
591
|
* The rapier2d counterpart to {@link Matter2dIntentHook}: one step of steering for one body, from
|
|
@@ -595,7 +634,7 @@ interface Rapier2dPhysicsConfig<S extends AnySchema> {
|
|
|
595
634
|
*/
|
|
596
635
|
readonly intents?: Readonly<Partial<Record<PhysicsKeys<S> & string, Rapier2dIntentHook>>>;
|
|
597
636
|
/** D72: how many ticks of body poses to keep for `room.rewind`. See {@link HISTORY_DOC}. */
|
|
598
|
-
readonly history?:
|
|
637
|
+
readonly history?: HistoryConfig;
|
|
599
638
|
}
|
|
600
639
|
/** `room.physicsRapier2d` in a rapier2d room. */
|
|
601
640
|
interface Rapier2dRoomApi<S extends AnySchema> {
|
|
@@ -674,6 +713,21 @@ interface ClientInfo {
|
|
|
674
713
|
type TimerHandle = number;
|
|
675
714
|
interface Room<S extends AnySchema = AnySchema> {
|
|
676
715
|
readonly id: string;
|
|
716
|
+
/**
|
|
717
|
+
* The room's tracked state — the very object every handler already gets as its first argument.
|
|
718
|
+
*
|
|
719
|
+
* It is here for the one place that has a `room` and no `state` parameter: `physics.setup`.
|
|
720
|
+
* Static geometry that depends on what the room holds (a level chosen at create, a layout
|
|
721
|
+
* restored from a snapshot) had no way to read it, and the workaround was a module-level
|
|
722
|
+
* variable set by `onCreate` — which runs *after* setup on a fresh room, so it was empty
|
|
723
|
+
* exactly when it mattered.
|
|
724
|
+
*
|
|
725
|
+
* What it holds at setup time: on a wake that had to rebuild the world, the snapshot is already
|
|
726
|
+
* decoded, so restored state is fully readable. On a fresh room it is the schema's defaults with
|
|
727
|
+
* no entities: `onCreate` has not run yet. Do not reach for `room.physics` inside setup — the
|
|
728
|
+
* world being built is the one setup is holding.
|
|
729
|
+
*/
|
|
730
|
+
readonly state: State<S>;
|
|
677
731
|
/** Shareable URL (`?room=<id>`). */
|
|
678
732
|
readonly link: string;
|
|
679
733
|
readonly tick: number;
|
|
@@ -683,6 +737,12 @@ interface Room<S extends AnySchema = AnySchema> {
|
|
|
683
737
|
readonly clients: readonly ClientInfo[];
|
|
684
738
|
/** Seeded, recorded for replay. */
|
|
685
739
|
random(): number;
|
|
740
|
+
/**
|
|
741
|
+
* The resolved RNG seed: `options.seed` when given, otherwise derived from the room id.
|
|
742
|
+
* Stable for the room's whole life, survives hibernation. Use it directly for procedural
|
|
743
|
+
* generation that every client must be able to reproduce.
|
|
744
|
+
*/
|
|
745
|
+
readonly seed: number;
|
|
686
746
|
send(target: MessageTarget, bytes: Uint8Array): void;
|
|
687
747
|
/**
|
|
688
748
|
* D70: `room.messages.<name>.send(target, value)` — one of the shapes the schema declares,
|
|
@@ -709,6 +769,16 @@ interface Room<S extends AnySchema = AnySchema> {
|
|
|
709
769
|
call(clientId: string): ServerCallProxy<SchemaRpc<S>>;
|
|
710
770
|
/** Server → client void RPCs to every connected client. */
|
|
711
771
|
readonly broadcast: BroadcastProxy<SchemaRpc<S>>;
|
|
772
|
+
/**
|
|
773
|
+
* The same broadcast, minus the client(s) named — `room.broadcastExcept(ctx.clientId).hit({…})`.
|
|
774
|
+
*
|
|
775
|
+
* The thing it replaces: a `from` field on the payload, and a filter on receipt in every client.
|
|
776
|
+
* That shape sends the actor its own echo, costs a schema field per RPC, and gets the comparison
|
|
777
|
+
* wrong exactly once per game. Typed identically to `broadcast` — void client-direction RPCs
|
|
778
|
+
* only. An id that is not connected (or never existed) is skipped rather than refused, so
|
|
779
|
+
* "everyone except the player who just left" is safe to say.
|
|
780
|
+
*/
|
|
781
|
+
broadcastExcept(clientId: string | readonly string[]): BroadcastProxy<SchemaRpc<S>>;
|
|
712
782
|
/**
|
|
713
783
|
* D24: write a retained, addressable snapshot generation of this room right now and resolve
|
|
714
784
|
* with its save id. Like every promise-returning room API, the continuation runs as its own
|
|
@@ -1390,4 +1460,4 @@ declare function defineRoom<S extends AnySchema>(schema: S, config: RoomConfig<S
|
|
|
1390
1460
|
/** Type guard for what a bundle's default export should be. */
|
|
1391
1461
|
declare function isRoomDefinition(v: unknown): v is RoomDefinition;
|
|
1392
1462
|
|
|
1393
|
-
export { type Behaviour, type BodyFactories, type BodySpec, type BusConfig, type BusEvent, type BusMessage, type ClientInfo, type Ctx, DEFAULTS, HISTORY_MAX_TICKS, LOBBY_MEMBERS, LOBBY_MIN_PLAYERS, LOBBY_STATE, type LeaderboardSubmitOptions, type LeaveReason, type LobbyConfig, type LobbyPhase, type LobbyStart, MAX_AWAKE_MAX, MEMORY_MB_MAX, MEMORY_MB_MIN, type Matter2dBodyFactories, type Matter2dBodySpec, type Matter2dIntentHook, type Matter2dPhysicsConfig, type Matter2dRoomApi, type MatterBody, type MatterConstraint, type MatterEngine, type MatterModule, type MessageTarget, type Npc, type NpcCollection, type NpcConfig, type NpcHandle, type NpcRoom, type NpcScript, type NpcScriptBrain, type NpcState, type PatrolStep, type PhysicsConfig, type PhysicsRoomApi, type PlayerKv, RETENTION_MAX_MS, RETENTION_MIN_MS, RETENTION_RE, ROOM_DEFINITION_VERSION, type Rapier2dBodyFactories, type Rapier2dBodySpec, type Rapier2dCollider, type Rapier2dColliderDesc, type Rapier2dIntentHook, type Rapier2dModule, type Rapier2dPhysicsConfig, type Rapier2dRigidBody, type Rapier2dRigidBodyDesc, type Rapier2dRoomApi, type Rapier2dWorld, type RapierCollider, type RapierColliderDesc, type RapierModule, type RapierPhysicsConfig, type RapierRigidBody, type RapierRigidBodyDesc, type RapierWorld, type ResolvedRoomConfig, type RewindView, type Room, type RoomBackfill, type RoomBus, type RoomConfig, type RoomConfigBase, type RoomDefinition, type RoomLeaderboard, type RoomLobby, type RoomMode, type RoomRatingResult, type RoomRatings, type RpcImplementations, type TimerHandle, type TypedMessage, type Validators, type Vec2, type Vector2, type Vector3, type WanderState, arrive, choose, defineRoom, flee, isRoomDefinition, lobbyCollections, lobbyConfigProblem, lobbyMemberEntity, lobbyStateSingleton, newWander, patrol, schemaHasLobby, seek, wander };
|
|
1463
|
+
export { type Behaviour, type BodyFactories, type BodySpec, type BusConfig, type BusEvent, type BusMessage, type ClientInfo, type Ctx, DEFAULTS, HISTORY_MAX_CHANNELS, HISTORY_MAX_TICKS, type HistoryChannelSpec, type HistoryConfig, LOBBY_MEMBERS, LOBBY_MIN_PLAYERS, LOBBY_STATE, type LeaderboardSubmitOptions, type LeaveReason, type LobbyConfig, type LobbyPhase, type LobbyStart, MAX_AWAKE_MAX, MEMORY_MB_MAX, MEMORY_MB_MIN, type Matter2dBodyFactories, type Matter2dBodySpec, type Matter2dIntentHook, type Matter2dPhysicsConfig, type Matter2dRoomApi, type MatterBody, type MatterConstraint, type MatterEngine, type MatterModule, type MessageTarget, type Npc, type NpcCollection, type NpcConfig, type NpcHandle, type NpcRoom, type NpcScript, type NpcScriptBrain, type NpcState, type PatrolStep, type PhysicsConfig, type PhysicsRoomApi, type PlayerKv, RETENTION_MAX_MS, RETENTION_MIN_MS, RETENTION_RE, ROOM_DEFINITION_VERSION, type Rapier2dBodyFactories, type Rapier2dBodySpec, type Rapier2dCollider, type Rapier2dColliderDesc, type Rapier2dIntentHook, type Rapier2dModule, type Rapier2dPhysicsConfig, type Rapier2dRigidBody, type Rapier2dRigidBodyDesc, type Rapier2dRoomApi, type Rapier2dWorld, type RapierCollider, type RapierColliderDesc, type RapierModule, type RapierPhysicsConfig, type RapierRigidBody, type RapierRigidBodyDesc, type RapierWorld, type ResolvedRoomConfig, type RewindView, type Room, type RoomBackfill, type RoomBus, type RoomConfig, type RoomConfigBase, type RoomDefinition, type RoomLeaderboard, type RoomLobby, type RoomMode, type RoomRatingResult, type RoomRatings, type RpcImplementations, type TimerHandle, type TypedMessage, type Validators, type Vec2, type Vector2, type Vector3, type WanderState, arrive, choose, defineRoom, flee, isRoomDefinition, lobbyCollections, lobbyConfigProblem, lobbyMemberEntity, lobbyStateSingleton, newWander, patrol, schemaHasLobby, seek, wander };
|
package/dist/index.js
CHANGED
|
@@ -71,6 +71,7 @@ function lobbyConfigProblem(value) {
|
|
|
71
71
|
|
|
72
72
|
// src/physics.ts
|
|
73
73
|
var HISTORY_MAX_TICKS = 240;
|
|
74
|
+
var HISTORY_MAX_CHANNELS = 8;
|
|
74
75
|
|
|
75
76
|
// src/npc.ts
|
|
76
77
|
var ZERO = { x: 0, y: 0 };
|
|
@@ -317,11 +318,45 @@ function checkPhysics(schema, config, mode) {
|
|
|
317
318
|
}
|
|
318
319
|
const history = physics.history;
|
|
319
320
|
if (history !== void 0) {
|
|
320
|
-
|
|
321
|
+
const depth = typeof history === "object" && history !== null ? history.depth : history;
|
|
322
|
+
if (!Number.isInteger(depth) || depth < 0 || depth > HISTORY_MAX_TICKS) {
|
|
323
|
+
const field = typeof history === "object" && history !== null ? "physics.history.depth" : "physics.history";
|
|
321
324
|
throw new Error(
|
|
322
|
-
`defineRoom:
|
|
325
|
+
`defineRoom: ${field} must be an integer in 0..${HISTORY_MAX_TICKS} ticks (0 or absent means no history and no cost), got ${String(depth)}`
|
|
323
326
|
);
|
|
324
327
|
}
|
|
328
|
+
const channels = typeof history === "object" && history !== null ? history.channels : void 0;
|
|
329
|
+
if (channels !== void 0) {
|
|
330
|
+
if (typeof channels !== "object" || channels === null) {
|
|
331
|
+
throw new Error(
|
|
332
|
+
`defineRoom: physics.history.channels must be an object keyed by physics collection, got ${String(channels)}`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
const collections = Object.keys(physics.bodies ?? {});
|
|
336
|
+
for (const [name, spec] of Object.entries(channels)) {
|
|
337
|
+
if (!collections.includes(name)) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
`defineRoom: physics.history.channels.${name} names no physics collection`
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
if (typeof spec !== "object" || spec === null) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
`defineRoom: physics.history.channels.${name} must be { count, read }, got ${String(spec)}`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
const count = spec.count;
|
|
348
|
+
if (!Number.isInteger(count) || count < 1 || count > HISTORY_MAX_CHANNELS) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
`defineRoom: physics.history.channels.${name}.count must be an integer in 1..${HISTORY_MAX_CHANNELS}, got ${String(count)}`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
if (typeof spec.read !== "function") {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`defineRoom: physics.history.channels.${name}.read must be a function (instance, out) => void`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
325
360
|
}
|
|
326
361
|
assertSyncHandler(physics.setup, "physics.setup");
|
|
327
362
|
const bodies = physics.bodies ?? {};
|
|
@@ -367,6 +402,7 @@ function isRoomDefinition(v) {
|
|
|
367
402
|
}
|
|
368
403
|
export {
|
|
369
404
|
DEFAULTS,
|
|
405
|
+
HISTORY_MAX_CHANNELS,
|
|
370
406
|
HISTORY_MAX_TICKS,
|
|
371
407
|
LOBBY_MEMBERS,
|
|
372
408
|
LOBBY_MIN_PLAYERS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irtio/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "irtio room-file API: defineRoom, handler and ctx types",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dist"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@irtio/schema": "0.
|
|
23
|
+
"@irtio/schema": "0.10.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@dimforge/rapier2d-compat": ">=0.20.0",
|