@voltro/plugin-rooms 0.0.0-stage → 0.88.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/CHANGELOG.md +24492 -0
- package/LICENSE +61 -0
- package/README.md +9204 -2
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +12496 -0
- package/dist/actor.js +1473 -0
- package/dist/apiBinding.js +677 -0
- package/dist/apiRegistryBinding.js +398 -0
- package/dist/apiReservations.js +432 -0
- package/dist/baselines.js +72 -0
- package/dist/bots.d.ts +1485 -0
- package/dist/bots.js +562 -0
- package/dist/catalog.js +79 -0
- package/dist/checkpointJson.js +23 -0
- package/dist/checkpointStore.js +385 -0
- package/dist/client.js +55 -0
- package/dist/clockSync.js +50 -0
- package/dist/collision.js +155 -0
- package/dist/commandDelivery.js +100 -0
- package/dist/commandRecord.js +10 -0
- package/dist/commandWithdrawal.js +182 -0
- package/dist/connection.js +496 -0
- package/dist/controlCarrier.js +374 -0
- package/dist/controlRegistry.js +78 -0
- package/dist/controlTarget.js +44 -0
- package/dist/controlTransport.js +573 -0
- package/dist/developmentBinding.js +112 -0
- package/dist/directory.js +322 -0
- package/dist/directoryProjection.js +116 -0
- package/dist/errors.d.ts +125 -0
- package/dist/errors.js +52 -0
- package/dist/fixed.d.ts +284 -0
- package/dist/fixed.js +10 -0
- package/dist/fixed32.js +133 -0
- package/dist/fixedCollision.js +59 -0
- package/dist/fixedCollision32.js +108 -0
- package/dist/fixedMath.js +55 -0
- package/dist/fixedRaw.js +10 -0
- package/dist/fixedSlabs.js +33 -0
- package/dist/fixedTrig.js +47 -0
- package/dist/fixedTrig32.js +37 -0
- package/dist/fixedVectors.js +112 -0
- package/dist/fixedVectors32.js +94 -0
- package/dist/fixedWide.js +49 -0
- package/dist/fixedWords.js +45 -0
- package/dist/gateway.js +56 -0
- package/dist/geometry-retention-app/rooms.geometryJournalRetention.workflow.server.tsx +4 -0
- package/dist/geometry-retention-app/rooms.geometryJournalRetention.workflow.tsx +14 -0
- package/dist/geometry-worker/worker.d.mts +2 -0
- package/dist/geometry-worker/worker.mjs +50 -0
- package/dist/geometry.d.ts +2249 -0
- package/dist/geometry.js +40 -0
- package/dist/geometryActivation.js +81 -0
- package/dist/geometryActorInventory.js +107 -0
- package/dist/geometryAdminClient.js +86 -0
- package/dist/geometryAdminData.js +31 -0
- package/dist/geometryAdminFixtures.js +49 -0
- package/dist/geometryAdminPorts.js +4 -0
- package/dist/geometryAdminRpc.js +57 -0
- package/dist/geometryAdminRpcRoutes.js +48 -0
- package/dist/geometryAdminSchemas.js +38 -0
- package/dist/geometryAdministration.js +189 -0
- package/dist/geometryAdministrationPorts.js +4 -0
- package/dist/geometryApiBinding.js +164 -0
- package/dist/geometryAppStorage.js +57 -0
- package/dist/geometryArtifacts.js +197 -0
- package/dist/geometryAssetBundle.js +116 -0
- package/dist/geometryAssetClient.js +24 -0
- package/dist/geometryAssetData.js +105 -0
- package/dist/geometryAssetHttp.js +122 -0
- package/dist/geometryAssetPorts.js +4 -0
- package/dist/geometryAssetRpc.js +29 -0
- package/dist/geometryAssetRpcRoutes.js +39 -0
- package/dist/geometryAssetStorageGrant.js +102 -0
- package/dist/geometryBoxOverlap.js +109 -0
- package/dist/geometryCatalogueDigest.js +110 -0
- package/dist/geometryCellCoverage.js +84 -0
- package/dist/geometryCellLoader.js +27 -0
- package/dist/geometryChannel.js +133 -0
- package/dist/geometryChunkHttp.js +53 -0
- package/dist/geometryChunkLeases.js +21 -0
- package/dist/geometryClient.d.ts +1429 -0
- package/dist/geometryClient.js +148 -0
- package/dist/geometryClientChunks.js +329 -0
- package/dist/geometryClientDelta.js +24 -0
- package/dist/geometryClientHttp.js +36 -0
- package/dist/geometryClientInterest.js +108 -0
- package/dist/geometryClientStatic.js +33 -0
- package/dist/geometryCms.d.ts +763 -0
- package/dist/geometryCms.js +216 -0
- package/dist/geometryCommitCoordinator.js +240 -0
- package/dist/geometryCompactionFixture.js +83 -0
- package/dist/geometryCompactionStrategies.js +37 -0
- package/dist/geometryConnectionClient.js +22 -0
- package/dist/geometryDerivationFixtures.js +140 -0
- package/dist/geometryDerivationStrategies.js +26 -0
- package/dist/geometryDigest.js +12 -0
- package/dist/geometryEditChannel.js +69 -0
- package/dist/geometryEditIngress.js +262 -0
- package/dist/geometryEdits.js +365 -0
- package/dist/geometryEnvelope.js +40 -0
- package/dist/geometryGridRaycast.js +91 -0
- package/dist/geometryGridSweep.js +91 -0
- package/dist/geometryHeightfield.js +165 -0
- package/dist/geometryHeightfieldRaycast.js +73 -0
- package/dist/geometryHeightfieldSweep.js +84 -0
- package/dist/geometryHeightfieldVolume.js +44 -0
- package/dist/geometryHost.js +321 -0
- package/dist/geometryHostDirectory.js +206 -0
- package/dist/geometryHostMutations.js +136 -0
- package/dist/geometryHostStreams.js +123 -0
- package/dist/geometryHttpBody.js +75 -0
- package/dist/geometryHttpRoutes.js +111 -0
- package/dist/geometryImportGltf.js +299 -0
- package/dist/geometryImportLdtk.js +53 -0
- package/dist/geometryImportPng.js +143 -0
- package/dist/geometryImportPublication.js +66 -0
- package/dist/geometryImportTiled.js +38 -0
- package/dist/geometryImportTiles.js +135 -0
- package/dist/geometryImportVox.js +129 -0
- package/dist/geometryImportedSource.js +100 -0
- package/dist/geometryInspectEndpoint.js +247 -0
- package/dist/geometryInspection.js +91 -0
- package/dist/geometryInstall.js +120 -0
- package/dist/geometryJournalArchiveData.js +20 -0
- package/dist/geometryJournalArchiveIndex.js +329 -0
- package/dist/geometryJournalArchiveTable.js +20 -0
- package/dist/geometryJournalCompaction.js +99 -0
- package/dist/geometryJournalPatch.js +46 -0
- package/dist/geometryJournalRetention.js +111 -0
- package/dist/geometryJournalRetentionSchemas.js +35 -0
- package/dist/geometryJournalRetentionSweep.js +111 -0
- package/dist/geometryJournalRetentionWorkflow.js +27 -0
- package/dist/geometryJournalRetentionWorkflowData.js +30 -0
- package/dist/geometryJournalStorage.js +147 -0
- package/dist/geometryLoader.js +117 -0
- package/dist/geometryManifest.js +112 -0
- package/dist/geometryManifestChannel.js +92 -0
- package/dist/geometryManifestClient.js +121 -0
- package/dist/geometryManifestData.js +48 -0
- package/dist/geometryManifestPorts.js +4 -0
- package/dist/geometryManifestPublication.js +108 -0
- package/dist/geometryManifestSource.js +67 -0
- package/dist/geometryMigrationJournal.js +13 -0
- package/dist/geometryMigrationStrategies.js +6 -0
- package/dist/geometryMigrations.js +322 -0
- package/dist/geometryNativeMetadata.js +51 -0
- package/dist/geometryPatch.js +169 -0
- package/dist/geometryPersistence.js +448 -0
- package/dist/geometryPersistenceCapture.js +68 -0
- package/dist/geometryPersistenceData.js +139 -0
- package/dist/geometryPersistencePorts.js +4 -0
- package/dist/geometryPlayerAdmission.js +33 -0
- package/dist/geometryPlayerEditOwner.js +264 -0
- package/dist/geometryPlayerEdits.js +48 -0
- package/dist/geometryPrepared.js +187 -0
- package/dist/geometryPrivacyFence.js +23 -0
- package/dist/geometryProceduralPublication.js +90 -0
- package/dist/geometryProceduralWorker.js +163 -0
- package/dist/geometryPublication.js +109 -0
- package/dist/geometryRayAlgebra.js +115 -0
- package/dist/geometryReadClient.js +156 -0
- package/dist/geometryReadPorts.js +4 -0
- package/dist/geometryReadRpc.js +26 -0
- package/dist/geometryReadRpcRoutes.js +46 -0
- package/dist/geometryReliablePublication.js +81 -0
- package/dist/geometryReloadPorts.js +45 -0
- package/dist/geometryReloadSource.js +277 -0
- package/dist/geometryRenderCache.js +164 -0
- package/dist/geometryRenderLod.js +101 -0
- package/dist/geometryRollback.js +124 -0
- package/dist/geometryRollbackCoordinator.js +202 -0
- package/dist/geometryRollbackPorts.js +45 -0
- package/dist/geometryRollbackSource.js +208 -0
- package/dist/geometryRollbackStore.js +197 -0
- package/dist/geometrySchemas.js +26 -0
- package/dist/geometryServer.d.ts +2766 -0
- package/dist/geometryServer.js +62 -0
- package/dist/geometryShapeRaycast.js +110 -0
- package/dist/geometryShapeSegments.js +33 -0
- package/dist/geometryShapes.js +222 -0
- package/dist/geometrySnapshot.js +75 -0
- package/dist/geometrySnapshotChannel.js +159 -0
- package/dist/geometrySnapshotClient.js +158 -0
- package/dist/geometrySnapshotPreparation.js +75 -0
- package/dist/geometrySnapshotServer.js +190 -0
- package/dist/geometrySqlOwner.js +20 -0
- package/dist/geometryStorage.js +126 -0
- package/dist/geometryStorageGrant.js +68 -0
- package/dist/geometryStorageIdentity.js +8 -0
- package/dist/geometryTileArtifact.js +9 -0
- package/dist/geometryTileCodec.js +193 -0
- package/dist/geometryTileCollision.js +95 -0
- package/dist/geometryTileNavigation.js +127 -0
- package/dist/geometryTileView.js +34 -0
- package/dist/geometryVersionActivation.js +113 -0
- package/dist/geometryVersionClient.js +237 -0
- package/dist/geometryVersionCompare.js +39 -0
- package/dist/geometryVersionMigration.js +105 -0
- package/dist/geometryVoxel.js +252 -0
- package/dist/geometryWorldEntitySubject.js +31 -0
- package/dist/geometryWorldExtent.js +11 -0
- package/dist/geometryWorldMetadata.js +37 -0
- package/dist/geometryWorldStrategyFixtures.js +126 -0
- package/dist/geometryWorldView.js +306 -0
- package/dist/host.js +127 -0
- package/dist/hostBinding.js +276 -0
- package/dist/hostCellObservation.js +113 -0
- package/dist/hostClientCapacity.js +89 -0
- package/dist/hostCommandScope.js +63 -0
- package/dist/hostController.js +3093 -0
- package/dist/hostDeployment.js +64 -0
- package/dist/hostDrain.js +151 -0
- package/dist/hostDrainPorts.js +4 -0
- package/dist/hostDriver.js +178 -0
- package/dist/hostLane.js +287 -0
- package/dist/hostListener.js +78 -0
- package/dist/hostListenerBinding.js +18 -0
- package/dist/hostLoopObservation.js +89 -0
- package/dist/hostMetrics.js +55 -0
- package/dist/hostRegistryBinding.js +417 -0
- package/dist/hostSelection.js +15 -0
- package/dist/hostShutdown.js +46 -0
- package/dist/hostTurnObservation.js +52 -0
- package/dist/identities.js +3 -0
- package/dist/index.d.ts +8632 -0
- package/dist/index.js +231 -0
- package/dist/inputReceipts.js +62 -0
- package/dist/inputRedundancy.js +54 -0
- package/dist/inspect.js +237 -0
- package/dist/lockstep.d.ts +436 -0
- package/dist/lockstep.js +381 -0
- package/dist/lockstepAdmission.js +261 -0
- package/dist/lockstepAdmissionCodec.js +93 -0
- package/dist/lockstepAdmissionPorts.js +4 -0
- package/dist/lockstepClientSession.js +577 -0
- package/dist/lockstepDefinition.js +147 -0
- package/dist/lockstepHostLoop.js +242 -0
- package/dist/lockstepInitialAdmission.js +66 -0
- package/dist/lockstepInitialStrategyFixtures.js +44 -0
- package/dist/lockstepJournal.js +394 -0
- package/dist/lockstepJournalCodec.js +253 -0
- package/dist/lockstepJournalPorts.js +4 -0
- package/dist/lockstepJournalRebuild.js +207 -0
- package/dist/lockstepJournalRows.js +56 -0
- package/dist/lockstepMembers.js +130 -0
- package/dist/lockstepMessages.js +46 -0
- package/dist/lockstepOwnedRoom.js +763 -0
- package/dist/lockstepPolicies.js +83 -0
- package/dist/lockstepReact.js +46 -0
- package/dist/lockstepRecoveryTruth.js +120 -0
- package/dist/lockstepRosterConfig.js +31 -0
- package/dist/lockstepSeed.js +11 -0
- package/dist/lockstepSeedFixture.js +19 -0
- package/dist/lockstepServer.d.ts +3897 -0
- package/dist/lockstepServer.js +324 -0
- package/dist/lockstepServerConfig.js +100 -0
- package/dist/lockstepServerRoles.js +402 -0
- package/dist/lockstepServerStrategyFixtures.js +32 -0
- package/dist/lockstepStrategies.js +5 -0
- package/dist/lockstepStrategyFixtures.js +136 -0
- package/dist/lockstepTerminal.js +89 -0
- package/dist/lockstepTerminalPorts.js +4 -0
- package/dist/lockstepTransport.js +772 -0
- package/dist/lockstepTransportConfig.js +31 -0
- package/dist/lockstepWireRoster.js +33 -0
- package/dist/matchRoomProvider.js +31 -0
- package/dist/matchRooms.js +18 -0
- package/dist/membershipObserver.js +46 -0
- package/dist/moderation.js +97 -0
- package/dist/movementSpecials.js +103 -0
- package/dist/ownership.js +16 -0
- package/dist/ownershipWithdrawal.js +17 -0
- package/dist/pcg32.js +95 -0
- package/dist/ports.d.ts +1241 -0
- package/dist/ports.js +10 -0
- package/dist/quaternion.js +69 -0
- package/dist/react.d.ts +406 -0
- package/dist/react.js +108 -0
- package/dist/reaper-app/rooms.reaper.workflow.server.tsx +5 -0
- package/dist/reaper-app/rooms.reaper.workflow.tsx +13 -0
- package/dist/reaper.js +242 -0
- package/dist/reaperSchemas.js +41 -0
- package/dist/reaperWorkflow.js +69 -0
- package/dist/recovery.js +188 -0
- package/dist/recoverySeats.js +25 -0
- package/dist/replay.d.ts +273 -0
- package/dist/replay.js +484 -0
- package/dist/replayPorts.js +4 -0
- package/dist/replayRecording.js +66 -0
- package/dist/replaySchema.js +156 -0
- package/dist/replayStorage.d.ts +149 -0
- package/dist/replayStorage.js +484 -0
- package/dist/reservationBatch.js +421 -0
- package/dist/reservationSchemas.js +16 -0
- package/dist/reservations.js +258 -0
- package/dist/reservePort.js +68 -0
- package/dist/resolvedOptions.js +3 -0
- package/dist/room.d.ts +1006 -0
- package/dist/room.js +283 -0
- package/dist/roomAccessSweep.js +94 -0
- package/dist/roomAppCommandControl.js +58 -0
- package/dist/roomAuthoringStrategy.js +16 -0
- package/dist/roomErasure.js +197 -0
- package/dist/roomPoolRuntimeDefaults.js +23 -0
- package/dist/roomPoolSelection.js +18 -0
- package/dist/roomPoolTopology.js +211 -0
- package/dist/rpc.d.ts +551 -0
- package/dist/rpc.js +111 -0
- package/dist/rpcClient.js +151 -0
- package/dist/rpcRoutes.js +27 -0
- package/dist/runtime.js +1122 -0
- package/dist/schemaHistory.js +97 -0
- package/dist/seatStore.js +150 -0
- package/dist/seatStrategyFixtures.js +51 -0
- package/dist/seatSubject.js +31 -0
- package/dist/seats.js +129 -0
- package/dist/serverSession.js +810 -0
- package/dist/serverSessionConfig.js +42 -0
- package/dist/sessionAdmission.js +52 -0
- package/dist/sidecarAdmission.js +102 -0
- package/dist/sidecarConfig.js +115 -0
- package/dist/sidecarConfigPublication.js +63 -0
- package/dist/sidecarHostListener.js +207 -0
- package/dist/sidecarIpc.js +204 -0
- package/dist/sidecarPeer.js +309 -0
- package/dist/sidecarProcessPort.js +27 -0
- package/dist/sidecarRuntime.js +167 -0
- package/dist/sidecarSelection.js +141 -0
- package/dist/sidecarWire.js +177 -0
- package/dist/sidecarWrites.js +162 -0
- package/dist/simulation.d.ts +391 -0
- package/dist/simulation.js +281 -0
- package/dist/simulationAuthoring.js +28 -0
- package/dist/simulationBytes.js +26 -0
- package/dist/simulationConformance.js +304 -0
- package/dist/simulationInputHistoryCapture.js +34 -0
- package/dist/simulationInputHistoryWire.js +192 -0
- package/dist/simulationOptionsWire.js +77 -0
- package/dist/simulationRecoveryWire.js +133 -0
- package/dist/simulationReplay.js +126 -0
- package/dist/simulationSnapshotWire.js +111 -0
- package/dist/simulationState.js +177 -0
- package/dist/snapshotBlob.js +125 -0
- package/dist/snapshotBlobCleanup.js +92 -0
- package/dist/snapshotBlobIndex.js +37 -0
- package/dist/snapshotBudget.js +79 -0
- package/dist/snapshotChain.js +184 -0
- package/dist/snapshotErasure.js +171 -0
- package/dist/snapshotInspection.js +104 -0
- package/dist/snapshotSegmentBlobs.js +57 -0
- package/dist/snapshotUploads.js +165 -0
- package/dist/socketTransport.js +252 -0
- package/dist/spatial.js +103 -0
- package/dist/spectatorPolicy.js +25 -0
- package/dist/spectatorProducer.js +88 -0
- package/dist/startFailureCause.js +3 -0
- package/dist/stateCodec.js +657 -0
- package/dist/strategies.d.ts +210 -0
- package/dist/strategies.js +8 -0
- package/dist/strategiesServer.d.ts +286 -0
- package/dist/strategiesServer.js +11 -0
- package/dist/strategyFixtures.js +181 -0
- package/dist/streamFraming.js +121 -0
- package/dist/streamTransport.js +265 -0
- package/dist/subjectAdmission.js +49 -0
- package/dist/subjectPrivacy.js +280 -0
- package/dist/subjectPrivacyBinding.js +89 -0
- package/dist/systemManifest.js +181 -0
- package/dist/tables.js +158 -0
- package/dist/testHarness.js +88 -0
- package/dist/testing.d.ts +3684 -0
- package/dist/testing.js +220 -0
- package/dist/testingPorts.js +270 -0
- package/dist/testingServer.d.ts +4060 -0
- package/dist/testingServer.js +18 -0
- package/dist/transform.d.ts +692 -0
- package/dist/transform.js +216 -0
- package/dist/transform3d.js +117 -0
- package/dist/transformCollection.js +23 -0
- package/dist/transformCollection3d.js +23 -0
- package/dist/transformCollectionCore.js +155 -0
- package/dist/transformFrames.js +120 -0
- package/dist/transformPrediction3d.js +126 -0
- package/dist/transformRules.js +122 -0
- package/dist/transformSchemas.js +19 -0
- package/dist/transport.js +31 -0
- package/dist/transportAuthoring.js +28 -0
- package/dist/transportCleanup.js +21 -0
- package/dist/transportConformance.js +123 -0
- package/dist/transportConnector.js +81 -0
- package/dist/transportEndpoints.js +37 -0
- package/dist/transportsServer.d.ts +5515 -0
- package/dist/transportsServer.js +6 -0
- package/dist/viewProjection.js +15 -0
- package/dist/web.d.ts +2286 -0
- package/dist/web.js +517 -0
- package/dist/webTransportClient.js +172 -0
- package/dist/wire.d.ts +992 -0
- package/dist/wire.js +491 -0
- package/dist/withdrawalPort.js +27 -0
- package/dist/world.d.ts +1894 -0
- package/dist/world.js +332 -0
- package/dist/worldActorReceipts.js +47 -0
- package/dist/worldActorTransferSnapshot.js +13 -0
- package/dist/worldAdmissionPending.js +7 -0
- package/dist/worldBoundaryCoordinator.js +147 -0
- package/dist/worldCatalog.js +80 -0
- package/dist/worldCellAllocation.js +202 -0
- package/dist/worldCellDestination.js +67 -0
- package/dist/worldCells.js +488 -0
- package/dist/worldChange.js +31 -0
- package/dist/worldClient.js +663 -0
- package/dist/worldClientControl.js +140 -0
- package/dist/worldClientPendingInput.js +15 -0
- package/dist/worldClientRpc.js +132 -0
- package/dist/worldClientRpcRoutes.js +42 -0
- package/dist/worldClientSchemas.js +27 -0
- package/dist/worldCommands.js +139 -0
- package/dist/worldData.js +41 -0
- package/dist/worldDefaultBinding.js +376 -0
- package/dist/worldDefaultGroupAdmission.js +81 -0
- package/dist/worldDefaultGroupAppServices.js +30 -0
- package/dist/worldDefaultGroupCompensation.js +108 -0
- package/dist/worldDefaultGroupContinuation.js +95 -0
- package/dist/worldDefaultGroupControl.js +71 -0
- package/dist/worldDefaultGroupMembers.js +84 -0
- package/dist/worldDefaultGroups.js +20 -0
- package/dist/worldDefaultPending.js +78 -0
- package/dist/worldDefaultService.js +244 -0
- package/dist/worldEntities.js +2979 -0
- package/dist/worldEntityCodec.js +20 -0
- package/dist/worldEntityHandoffView.js +153 -0
- package/dist/worldGeometry.js +169 -0
- package/dist/worldGhostAdmission.js +64 -0
- package/dist/worldGhostDestination.js +117 -0
- package/dist/worldGhostGateway.js +131 -0
- package/dist/worldGhostHandshake.js +107 -0
- package/dist/worldGhostHost.js +41 -0
- package/dist/worldGhostIngress.js +131 -0
- package/dist/worldGhostNeighborhood.js +69 -0
- package/dist/worldGhostNeighbors.js +85 -0
- package/dist/worldGhostOutbound.js +109 -0
- package/dist/worldGhostPublisher.js +215 -0
- package/dist/worldGhostReceiver.js +212 -0
- package/dist/worldGhostSender.js +145 -0
- package/dist/worldGhostSocket.js +77 -0
- package/dist/worldGhostWire.js +171 -0
- package/dist/worldGroupCommand.js +48 -0
- package/dist/worldGroupDeadline.js +114 -0
- package/dist/worldGroupPreparation.js +114 -0
- package/dist/worldGroupReadyControl.js +127 -0
- package/dist/worldGroupSupervisor.js +119 -0
- package/dist/worldHandoffMetrics.js +58 -0
- package/dist/worldHandoffView.js +99 -0
- package/dist/worldHostSource.js +68 -0
- package/dist/worldInspectEndpoint.js +249 -0
- package/dist/worldInspection.js +108 -0
- package/dist/worldInstanceAdmission.js +30 -0
- package/dist/worldInstanceAllocation.js +115 -0
- package/dist/worldInstanceDestination.js +63 -0
- package/dist/worldInstanceExpiry.js +281 -0
- package/dist/worldInstanceExpirySupervisor.js +87 -0
- package/dist/worldInstanceLifecycle.js +102 -0
- package/dist/worldInstanceState.js +51 -0
- package/dist/worldInstances.js +477 -0
- package/dist/worldJoin.js +55 -0
- package/dist/worldLayers.js +150 -0
- package/dist/worldLeases.js +77 -0
- package/dist/worldManifest.js +24 -0
- package/dist/worldMovement.js +62 -0
- package/dist/worldPersistence.js +133 -0
- package/dist/worldProjection.js +220 -0
- package/dist/worldReact.js +109 -0
- package/dist/worldRecoveryActor.js +34 -0
- package/dist/worldReturnAnchor.js +48 -0
- package/dist/worldRoomHooks.js +56 -0
- package/dist/worldRoomPool.js +26 -0
- package/dist/worldRoomSteps.js +132 -0
- package/dist/worldSeatBinding.js +35 -0
- package/dist/worldServer.d.ts +6936 -0
- package/dist/worldServer.js +48 -0
- package/dist/worldSourceSeatPending.js +8 -0
- package/dist/worldStrategyFixtures.js +163 -0
- package/dist/worldTesting.js +26 -0
- package/dist/worldTransitionDefaults.js +197 -0
- package/dist/worldTransitionExecutor.js +518 -0
- package/dist/worldTransitionGroup.js +112 -0
- package/dist/worldTransitionPhysicalSteps.js +39 -0
- package/dist/worldTransitionResolver.js +227 -0
- package/dist/worldTransitionSeatGroup.js +63 -0
- package/dist/worldTransitionSeats.js +259 -0
- package/dist/worldTransitionState.js +94 -0
- package/dist/worldTransitions.js +100 -0
- package/package.json +189 -4
package/dist/actor.js
ADDED
|
@@ -0,0 +1,1473 @@
|
|
|
1
|
+
import { BudgetExceeded as e, NotRoomMember as t, ProtocolError as n, RoomAuthoringError as r, RoomForbidden as i, RoomFull as a, RoomGone as o, RoomLocked as s } from "./errors.js";
|
|
2
|
+
import { captureWorldData as c } from "./worldData.js";
|
|
3
|
+
import { encodeRoomFrame as l } from "./wire.js";
|
|
4
|
+
import { makeWorldCellMovement as u } from "./worldMovement.js";
|
|
5
|
+
import { captureWorldTargetSeatBinding as d } from "./worldSeatBinding.js";
|
|
6
|
+
import { captureRoomConsumedSeat as f, captureRoomInputReceipts as p } from "./inputReceipts.js";
|
|
7
|
+
import { WorldAdmissionPending as m } from "./worldAdmissionPending.js";
|
|
8
|
+
import { captureWorldActorReceipts as h, worldActorCanonicalJson as g, worldActorFingerprint as _ } from "./worldActorReceipts.js";
|
|
9
|
+
import { captureGeometryWorldView as v } from "./geometryWorldView.js";
|
|
10
|
+
import { Effect as y, Result as b, Schema as x } from "effect";
|
|
11
|
+
// src/actor.ts
|
|
12
|
+
var S = (e, t, n) => {
|
|
13
|
+
if (t === void 0) return;
|
|
14
|
+
let i = t(n);
|
|
15
|
+
if (i !== void 0) throw i instanceof Promise && i.catch(() => void 0), new r({
|
|
16
|
+
room: e,
|
|
17
|
+
detail: "Room actor hooks must return undefined; async work belongs outside the tick"
|
|
18
|
+
});
|
|
19
|
+
}, C = (e, t, n) => {
|
|
20
|
+
if (t === void 0) return;
|
|
21
|
+
let i = t(n);
|
|
22
|
+
if (i !== void 0) throw i instanceof Promise && i.catch(() => void 0), new r({
|
|
23
|
+
room: e,
|
|
24
|
+
detail: "Use the asynchronous host lifecycle path for Effect hooks"
|
|
25
|
+
});
|
|
26
|
+
}, w = class {
|
|
27
|
+
id;
|
|
28
|
+
descriptor;
|
|
29
|
+
clock;
|
|
30
|
+
tenantId;
|
|
31
|
+
currentState;
|
|
32
|
+
lastGoodState;
|
|
33
|
+
lastGoodTick = 0;
|
|
34
|
+
options;
|
|
35
|
+
members = /* @__PURE__ */ new Map();
|
|
36
|
+
grace = /* @__PURE__ */ new Map();
|
|
37
|
+
inputs = [];
|
|
38
|
+
outbox = [];
|
|
39
|
+
clientOperations = [];
|
|
40
|
+
schedules = /* @__PURE__ */ new Map();
|
|
41
|
+
extensionDurationMs = Object.create(null);
|
|
42
|
+
extensionOverruns = Object.create(null);
|
|
43
|
+
tick = 0;
|
|
44
|
+
droppedTicks = 0;
|
|
45
|
+
commandSeq = 0;
|
|
46
|
+
epoch;
|
|
47
|
+
nextTickAt;
|
|
48
|
+
emptyAt;
|
|
49
|
+
locked = !1;
|
|
50
|
+
disposed = !1;
|
|
51
|
+
publicationHeldAt = null;
|
|
52
|
+
drainFrozen = !1;
|
|
53
|
+
drainAt = null;
|
|
54
|
+
retiring = !1;
|
|
55
|
+
faulted = !1;
|
|
56
|
+
ticking = !1;
|
|
57
|
+
lastNow = -1;
|
|
58
|
+
app;
|
|
59
|
+
scheduler;
|
|
60
|
+
worldPreviews = /* @__PURE__ */ new Set();
|
|
61
|
+
worldReceipts = /* @__PURE__ */ new Map();
|
|
62
|
+
deferredWorldLeaves = /* @__PURE__ */ new Map();
|
|
63
|
+
worldMovement;
|
|
64
|
+
worldMovementClamps = 0;
|
|
65
|
+
worldMovementMargin = 0;
|
|
66
|
+
worldMovementIntents = Object.freeze([]);
|
|
67
|
+
worldFinalizationHold = null;
|
|
68
|
+
worldPublishedTransitions = /* @__PURE__ */ new Set();
|
|
69
|
+
lifecycleTail = Promise.resolve();
|
|
70
|
+
lifecycleRequests = 0;
|
|
71
|
+
lifecycleActive = !1;
|
|
72
|
+
geometry;
|
|
73
|
+
constructor(t, n, i, a, o, s, c = {
|
|
74
|
+
epoch: 0,
|
|
75
|
+
commandCursor: 0
|
|
76
|
+
}, l) {
|
|
77
|
+
if (this.id = t, this.descriptor = n, this.clock = o, this.tenantId = s, this.geometry = l === void 0 ? void 0 : v(l, s), !s.trim()) throw new r({
|
|
78
|
+
room: n.name,
|
|
79
|
+
detail: "Every actor requires its explicit tenant identity"
|
|
80
|
+
});
|
|
81
|
+
if (![
|
|
82
|
+
c.epoch,
|
|
83
|
+
c.commandCursor,
|
|
84
|
+
c.tick ?? 0
|
|
85
|
+
].every((e) => Number.isSafeInteger(e) && e >= 0)) throw new r({
|
|
86
|
+
room: n.name,
|
|
87
|
+
detail: "Actor recovery epoch and command cursor must be nonnegative safe integers"
|
|
88
|
+
});
|
|
89
|
+
for (let e of h(c.worldReceipts ?? [], n.world?.maxPendingTransitions ?? 128)) {
|
|
90
|
+
if (!n.world) throw new r({
|
|
91
|
+
room: n.name,
|
|
92
|
+
detail: "World receipts require the original configured hooks"
|
|
93
|
+
});
|
|
94
|
+
this.worldReceipts.set(e.requestId, e);
|
|
95
|
+
}
|
|
96
|
+
this.epoch = c.epoch, this.commandSeq = c.commandCursor, this.tick = c.tick ?? 0, this.lastGoodTick = this.tick;
|
|
97
|
+
let u = x.decodeUnknownSync(x.toType(n.state))(i);
|
|
98
|
+
this.currentState = structuredClone(u), this.options = structuredClone(x.decodeUnknownSync(x.toType(n.options))(a));
|
|
99
|
+
let d = this.now();
|
|
100
|
+
this.nextTickAt = d + 1e3 / n.tickRate, this.emptyAt = d, this.app = Object.freeze({
|
|
101
|
+
publish: (e, t, n) => this.stage("publish", e, t, n.key),
|
|
102
|
+
run: (e, t) => this.stage("run", e, t)
|
|
103
|
+
}), this.scheduler = Object.freeze({
|
|
104
|
+
after: (e, t) => this.schedule(e, t, !1),
|
|
105
|
+
every: (e, t) => this.schedule(e, t, !0),
|
|
106
|
+
cancel: (e) => {
|
|
107
|
+
this.schedules.delete(e);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
for (let e of n.extensions) for (let t of e.fields) if (typeof this.state != "object" || this.state === null || !Object.hasOwn(this.state, t)) throw new r({
|
|
111
|
+
room: n.name,
|
|
112
|
+
detail: `Extension ${e.name} requires state field ${t}`
|
|
113
|
+
});
|
|
114
|
+
if ((c.schedules?.length ?? 0) > n.budgets.maxScheduledHooks) throw new e({
|
|
115
|
+
budget: "maxScheduledHooks",
|
|
116
|
+
limit: n.budgets.maxScheduledHooks
|
|
117
|
+
});
|
|
118
|
+
for (let e of c.schedules ?? []) {
|
|
119
|
+
if (!e.label.trim() || this.schedules.has(e.label) || !Number.isFinite(e.remainingMs) || e.remainingMs < 0 || e.intervalMs !== null && (!Number.isFinite(e.intervalMs) || e.intervalMs <= 0)) throw new r({
|
|
120
|
+
room: n.name,
|
|
121
|
+
detail: "Invalid recovered schedule"
|
|
122
|
+
});
|
|
123
|
+
this.schedules.set(e.label, {
|
|
124
|
+
label: e.label,
|
|
125
|
+
at: d + e.remainingMs,
|
|
126
|
+
interval: e.intervalMs
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
S(n.name, c.rehydrate ? n.onRehydrate : n.onCreate, {
|
|
130
|
+
state: this.state,
|
|
131
|
+
options: this.options,
|
|
132
|
+
app: this.app,
|
|
133
|
+
clock: o,
|
|
134
|
+
scheduler: this.scheduler,
|
|
135
|
+
...this.geometry ? { geometry: this.geometry } : {}
|
|
136
|
+
}), x.decodeUnknownSync(x.toType(n.state))(this.state), this.lastGoodState = structuredClone(this.state);
|
|
137
|
+
}
|
|
138
|
+
get state() {
|
|
139
|
+
return this.currentState;
|
|
140
|
+
}
|
|
141
|
+
now() {
|
|
142
|
+
let e = this.clock.now();
|
|
143
|
+
if (!Number.isFinite(e) || e < 0 || e < this.lastNow) throw new r({
|
|
144
|
+
room: this.descriptor.name,
|
|
145
|
+
detail: "The injected monotonic clock must return finite nonnegative nondecreasing time"
|
|
146
|
+
});
|
|
147
|
+
return this.lastNow = e, e;
|
|
148
|
+
}
|
|
149
|
+
ensureOpen() {
|
|
150
|
+
if (this.disposed || this.faulted || this.retiring || this.drainFrozen) throw new o({ roomId: this.id });
|
|
151
|
+
}
|
|
152
|
+
stage(t, n, i, a) {
|
|
153
|
+
if (this.ensureOpen(), !n.trim() || t === "publish" && !a?.trim()) throw new r({
|
|
154
|
+
room: this.descriptor.name,
|
|
155
|
+
detail: "App commands require a name and publishes require a key"
|
|
156
|
+
});
|
|
157
|
+
if (this.outbox.length >= this.descriptor.budgets.maxOutboxCommands) throw new e({
|
|
158
|
+
budget: "maxOutboxCommands",
|
|
159
|
+
limit: this.descriptor.budgets.maxOutboxCommands
|
|
160
|
+
});
|
|
161
|
+
if (this.commandSeq >= 2 ** 53 - 1) throw new r({
|
|
162
|
+
room: this.descriptor.name,
|
|
163
|
+
detail: "Room command cursor is exhausted"
|
|
164
|
+
});
|
|
165
|
+
let o = `${this.id}:${this.epoch}:${++this.commandSeq}`;
|
|
166
|
+
return this.outbox.push(Object.freeze({
|
|
167
|
+
requestId: o,
|
|
168
|
+
kind: t,
|
|
169
|
+
name: n,
|
|
170
|
+
payload: structuredClone(i),
|
|
171
|
+
...a === void 0 ? {} : { key: a }
|
|
172
|
+
})), o;
|
|
173
|
+
}
|
|
174
|
+
schedule(t, n, i) {
|
|
175
|
+
if (this.ensureOpen(), !Number.isFinite(t) || t <= 0 || !n.trim()) throw new r({
|
|
176
|
+
room: this.descriptor.name,
|
|
177
|
+
detail: "Scheduled hooks require a positive duration and nonempty label"
|
|
178
|
+
});
|
|
179
|
+
if (!this.schedules.has(n) && this.schedules.size >= this.descriptor.budgets.maxScheduledHooks) throw new e({
|
|
180
|
+
budget: "maxScheduledHooks",
|
|
181
|
+
limit: this.descriptor.budgets.maxScheduledHooks
|
|
182
|
+
});
|
|
183
|
+
this.schedules.set(n, {
|
|
184
|
+
label: n,
|
|
185
|
+
at: (this.publicationHeldAt ?? this.now()) + t,
|
|
186
|
+
interval: i ? t : null
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
join(e) {
|
|
190
|
+
this.assertSynchronousLifecycle();
|
|
191
|
+
let t = this.prepareJoin(e);
|
|
192
|
+
this.mutateMember(() => C(this.descriptor.name, this.descriptor.onJoin, t.context(this.state))), t.commit();
|
|
193
|
+
}
|
|
194
|
+
joinAsync(e, t) {
|
|
195
|
+
return this.serializeLifecycle(async () => {
|
|
196
|
+
let n = this.prepareJoin(e);
|
|
197
|
+
await this.mutateMemberAsync((e) => this.descriptor.onJoin?.(n.context(e)), t), n.commit();
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
joinReservedAsync(e, t, r, i) {
|
|
201
|
+
let a = f(t);
|
|
202
|
+
if (a.worldTarget) throw new n({ detail: "World target Seat requires transition-aware actor admission" });
|
|
203
|
+
if (typeof e?.send != "function" || typeof e.kick != "function") throw new n({ detail: "Reserved membership requires actual client callbacks" });
|
|
204
|
+
let o = Object.freeze({
|
|
205
|
+
...e,
|
|
206
|
+
send: e.send.bind(e),
|
|
207
|
+
kick: e.kick.bind(e)
|
|
208
|
+
});
|
|
209
|
+
return this.serializeLifecycle(async () => {
|
|
210
|
+
let e = i ? await i() ?? void 0 : void 0, t = this.prepareJoin(o, !1, !0, a.lastAppliedSeq);
|
|
211
|
+
await this.mutateMemberAsync((n) => this.descriptor.onJoin?.({
|
|
212
|
+
...t.context(n),
|
|
213
|
+
...e === void 0 ? {} : { world: e }
|
|
214
|
+
}), r, !0, void 0, i === void 0 ? void 0 : async () => {
|
|
215
|
+
await i();
|
|
216
|
+
}), t.commit();
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
joinWorldReservedAsync(t, r, i, a) {
|
|
220
|
+
let o = f(r), s = o.worldTarget;
|
|
221
|
+
if (!s || !this.descriptor.world || typeof i != "function" || typeof t?.send != "function" || typeof t.kick != "function") throw new n({ detail: "Pending World membership requires bound Seat and actual Host admission" });
|
|
222
|
+
let c = Object.freeze({
|
|
223
|
+
...t,
|
|
224
|
+
send: t.send.bind(t),
|
|
225
|
+
kick: t.kick.bind(t)
|
|
226
|
+
});
|
|
227
|
+
return this.serializeLifecycle(async () => {
|
|
228
|
+
if (a?.aborted) throw new n({ detail: "World membership admission aborted" });
|
|
229
|
+
let t = this.prepareJoin(c, !1, !0, o.lastAppliedSeq, s, o.discardedThrough ?? -1), r = await i();
|
|
230
|
+
if (a?.aborted) throw new n({ detail: "World membership admission aborted" });
|
|
231
|
+
let l = r === "settled" || r === "committed" && this.worldPublishedTransitions.has(s.transitionId);
|
|
232
|
+
if (!l && [...this.members.values()].filter((e) => e.worldPending).length >= (this.descriptor.world.maxPendingTransitions ?? 128)) throw new e({
|
|
233
|
+
budget: "worldPendingMembers",
|
|
234
|
+
limit: this.descriptor.world.maxPendingTransitions ?? 128
|
|
235
|
+
});
|
|
236
|
+
if (l && (this.assertWorldMemberEntity(s), !t.discardsPersisted)) throw new m();
|
|
237
|
+
t.commit();
|
|
238
|
+
let u = this.members.get(c.sessionId);
|
|
239
|
+
l && (u.worldPending = !1, await this.worldMemberEntered(u.client, s.entityId, a));
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async worldMemberEntered(e, t, n) {
|
|
243
|
+
let r = this.descriptor.world?.onMemberEnter;
|
|
244
|
+
r && await this.mutateMemberAsync((n) => r({
|
|
245
|
+
state: n,
|
|
246
|
+
client: e,
|
|
247
|
+
entityId: t
|
|
248
|
+
}), n);
|
|
249
|
+
}
|
|
250
|
+
worldEntityPoint(e) {
|
|
251
|
+
let t = this.descriptor.world?.movement, n = Object.getOwnPropertyDescriptor(this.lastGoodState, "entities"), r = n && "value" in n ? n.value : void 0, i = t && typeof e == "string" && r && typeof r == "object" ? Object.getOwnPropertyDescriptor(r, e) : void 0;
|
|
252
|
+
if (!t || !i || !("value" in i)) return null;
|
|
253
|
+
try {
|
|
254
|
+
let e = t.position(c(i.value, t.maxBytesPerTick));
|
|
255
|
+
if (e instanceof Promise) return e.catch(() => void 0), null;
|
|
256
|
+
let n = c(e, 128);
|
|
257
|
+
return Array.isArray(n) && n.length === 2 && n.every((e) => typeof e == "number" && Number.isFinite(e)) ? Object.freeze([n[0], n[1]]) : null;
|
|
258
|
+
} catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
activateWorldReservedAsync(e, t) {
|
|
263
|
+
let r = d(e);
|
|
264
|
+
if (typeof t != "function") throw new n({ detail: "World member activation requires actual Host authority" });
|
|
265
|
+
return this.serializeLifecycle(async () => {
|
|
266
|
+
this.ensureOpen();
|
|
267
|
+
let e = this.worldReceipts.get(`${r.transitionId}:enter`), i = Object.getOwnPropertyDescriptor(this.state, "entities"), a = i && "value" in i && i.value && typeof i.value == "object" ? Object.getOwnPropertyDescriptor(i.value, r.entityId) : void 0;
|
|
268
|
+
if (!this.descriptor.world || e?.operation !== "enter" || !a || !("value" in a) || a.value?.id !== r.entityId) throw new n({ detail: "World member activation lacks actual target enter" });
|
|
269
|
+
let o = [...this.members.values()].filter((e) => e.worldTarget?.transitionId === r.transitionId);
|
|
270
|
+
if (o.some((e) => JSON.stringify(e.worldTarget) !== JSON.stringify(r))) throw new n({ detail: "World member activation binding changed" });
|
|
271
|
+
if (await t(), o.some((e) => e.lastDiscardedSeq > e.checkpointedDiscardedSeq)) throw new n({ detail: "Pending World input discard must be checkpointed before activation" });
|
|
272
|
+
this.worldPublishedTransitions.add(r.transitionId);
|
|
273
|
+
for (let e of o) e.worldPending = !1;
|
|
274
|
+
for (let e of o) await this.worldMemberEntered(e.client, r.entityId);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
assertWorldMemberEntity(e) {
|
|
278
|
+
let t = Object.getOwnPropertyDescriptor(this.state, "entities"), r = t && "value" in t && t.value && typeof t.value == "object" ? Object.getOwnPropertyDescriptor(t.value, e.entityId) : void 0;
|
|
279
|
+
if (!this.descriptor.world || !r || !("value" in r) || r.value?.id !== e.entityId) throw new n({ detail: "World reconnect lacks the actual owned target entity" });
|
|
280
|
+
}
|
|
281
|
+
isWorldPending(e) {
|
|
282
|
+
let t = this.members.get(e);
|
|
283
|
+
return t?.worldPending === !0 || t?.worldDeparture !== void 0;
|
|
284
|
+
}
|
|
285
|
+
joinBotAsync(e, t = !0, r) {
|
|
286
|
+
if (typeof t != "boolean") return Promise.reject(new n({ detail: "Bot capacity policy must be a boolean" }));
|
|
287
|
+
let i = Object.freeze({
|
|
288
|
+
sessionId: e.sessionId,
|
|
289
|
+
subjectId: e.subjectId,
|
|
290
|
+
tenantId: e.tenantId,
|
|
291
|
+
send: e.send.bind(e),
|
|
292
|
+
kick: e.kick.bind(e)
|
|
293
|
+
});
|
|
294
|
+
return this.serializeLifecycle(async () => {
|
|
295
|
+
let e = this.prepareJoin(i, !0, t);
|
|
296
|
+
await this.mutateMemberAsync((t) => this.descriptor.onJoin?.(e.context(t)), r), e.commit();
|
|
297
|
+
let n = this.members.get(i.sessionId);
|
|
298
|
+
return Object.freeze({
|
|
299
|
+
sessionId: i.sessionId,
|
|
300
|
+
isCurrent: () => this.members.get(i.sessionId) === n,
|
|
301
|
+
input: (e, t, r, a) => {
|
|
302
|
+
if (this.members.get(i.sessionId) !== n) throw new o({ roomId: this.id });
|
|
303
|
+
return this.input(i.sessionId, e, t, r, a);
|
|
304
|
+
},
|
|
305
|
+
leave: (e) => this.serializeLifecycle(async () => {
|
|
306
|
+
if (this.members.get(i.sessionId) !== n) return;
|
|
307
|
+
let t = this.prepareLeave(i.sessionId, "left");
|
|
308
|
+
await this.mutateMemberAsync((e) => this.descriptor.onLeave?.(t.context(e)), e), t.commit();
|
|
309
|
+
})
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
playerCount() {
|
|
314
|
+
return [...this.members.values()].filter((e) => e.client.countsAsPlayer !== !1).length;
|
|
315
|
+
}
|
|
316
|
+
availablePlayerSlots() {
|
|
317
|
+
return this.pruneGrace(this.now()), Math.max(0, this.descriptor.maxClients - this.playerCount() - this.grace.size);
|
|
318
|
+
}
|
|
319
|
+
monotonicNow() {
|
|
320
|
+
return this.now();
|
|
321
|
+
}
|
|
322
|
+
roster() {
|
|
323
|
+
return Object.freeze([...this.members.values()].map(({ client: e }) => Object.freeze({
|
|
324
|
+
sessionId: e.sessionId,
|
|
325
|
+
subjectId: e.subjectId,
|
|
326
|
+
tenantId: e.tenantId,
|
|
327
|
+
isBot: e.isBot === !0,
|
|
328
|
+
countsAsPlayer: e.countsAsPlayer !== !1
|
|
329
|
+
})));
|
|
330
|
+
}
|
|
331
|
+
humanAdmissionNeedsReplacement(e) {
|
|
332
|
+
let { resumed: t } = this.prepareClientIdentity(e, !1);
|
|
333
|
+
return this.playerCount() + this.grace.size - +!!t >= this.descriptor.maxClients;
|
|
334
|
+
}
|
|
335
|
+
prepareClientIdentity(e, t) {
|
|
336
|
+
if (this.ensureOpen(), !e || ![e.sessionId, e.tenantId].every((e) => typeof e == "string" && e.length > 0 && e.length <= 191) || e.subjectId !== null && (typeof e.subjectId != "string" || !e.subjectId || e.subjectId.length > 191)) throw new n({ detail: "Room membership requires bounded trusted identity" });
|
|
337
|
+
if (!t && (e.isBot === !0 || e.countsAsPlayer === !1)) throw new n({ detail: "Bot/NPC classification requires trusted local admission" });
|
|
338
|
+
if (e.tenantId !== this.tenantId || e.subjectId === null && this.descriptor.auth !== "public") throw new i({ roomId: this.id });
|
|
339
|
+
let a = this.now();
|
|
340
|
+
this.pruneGrace(a);
|
|
341
|
+
let o = this.grace.get(e.sessionId);
|
|
342
|
+
if (t && o) throw new n({ detail: "A local bot cannot resume a socket member grace seat" });
|
|
343
|
+
if (o && o.subjectId !== e.subjectId) throw new i({ roomId: this.id });
|
|
344
|
+
if (this.locked && !o) throw new s({ roomId: this.id });
|
|
345
|
+
if (this.members.has(e.sessionId)) throw new r({
|
|
346
|
+
room: this.descriptor.name,
|
|
347
|
+
detail: "A session is already connected"
|
|
348
|
+
});
|
|
349
|
+
return {
|
|
350
|
+
now: a,
|
|
351
|
+
resumed: o
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
prepareJoin(t, r = !1, i = !0, o = -1, s, c = -1) {
|
|
355
|
+
let { now: l, resumed: u } = this.prepareClientIdentity(t, r);
|
|
356
|
+
if (u?.worldDeparture) throw new n({ detail: "Departed World source membership awaits transfer compensation" });
|
|
357
|
+
if (u?.worldTarget && JSON.stringify(u.worldTarget) !== JSON.stringify(s)) throw new n({ detail: "World reconnect cannot discard its pending membership binding" });
|
|
358
|
+
if (i && this.playerCount() + this.grace.size - +!!u >= this.descriptor.maxClients) throw new a({ roomId: this.id });
|
|
359
|
+
if (!i && this.members.size - this.playerCount() >= this.descriptor.budgets.maxNpcs) throw new e({
|
|
360
|
+
budget: "maxNpcs",
|
|
361
|
+
limit: this.descriptor.budgets.maxNpcs
|
|
362
|
+
});
|
|
363
|
+
let d = Object.freeze({
|
|
364
|
+
sessionId: t.sessionId,
|
|
365
|
+
subjectId: t.subjectId,
|
|
366
|
+
tenantId: t.tenantId,
|
|
367
|
+
...r ? {
|
|
368
|
+
isBot: !0,
|
|
369
|
+
countsAsPlayer: i
|
|
370
|
+
} : {},
|
|
371
|
+
...t.attributes === void 0 ? {} : { attributes: Object.freeze({ ...t.attributes }) },
|
|
372
|
+
send: (e, r) => {
|
|
373
|
+
let i = this.descriptor.messages[e];
|
|
374
|
+
if (!i || i.direction === "client") throw new n({ detail: "Channel is not server-sendable" });
|
|
375
|
+
let a = structuredClone(x.decodeUnknownSync(i.schema)(r));
|
|
376
|
+
this.stageClientOperation(() => t.send(e, a));
|
|
377
|
+
},
|
|
378
|
+
kick: (e) => {
|
|
379
|
+
this.stageClientOperation(() => t.kick(e));
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
discardsPersisted: (u?.discardedThrough ?? -1) <= c,
|
|
384
|
+
context: (e) => ({
|
|
385
|
+
state: e,
|
|
386
|
+
options: structuredClone(this.options),
|
|
387
|
+
client: d,
|
|
388
|
+
app: this.app,
|
|
389
|
+
clock: this.clock,
|
|
390
|
+
scheduler: this.scheduler,
|
|
391
|
+
...this.geometry ? { geometry: this.geometry } : {}
|
|
392
|
+
}),
|
|
393
|
+
commit: () => {
|
|
394
|
+
this.grace.delete(t.sessionId);
|
|
395
|
+
let e = Math.max(o, u?.lastAppliedSeq ?? -1);
|
|
396
|
+
this.members.set(t.sessionId, {
|
|
397
|
+
client: d,
|
|
398
|
+
lastSeq: Math.max(e, c, u?.discardedThrough ?? -1),
|
|
399
|
+
lastAppliedSeq: e,
|
|
400
|
+
lastDiscardedSeq: Math.max(c, u?.discardedThrough ?? -1),
|
|
401
|
+
checkpointedDiscardedSeq: c,
|
|
402
|
+
...s ? {
|
|
403
|
+
worldTarget: s,
|
|
404
|
+
worldPending: !0
|
|
405
|
+
} : {},
|
|
406
|
+
inputTokens: this.descriptor.budgets.burst,
|
|
407
|
+
byteTokens: this.descriptor.budgets.bytesPerSecond,
|
|
408
|
+
replenishedAt: l
|
|
409
|
+
}), this.emptyAt = null;
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
leave(e, t = "left") {
|
|
414
|
+
this.assertSynchronousLifecycle();
|
|
415
|
+
let n = this.prepareLeave(e, t);
|
|
416
|
+
n.pending || this.mutateMember(() => C(this.descriptor.name, this.descriptor.onLeave, n.context(this.state))), n.commit();
|
|
417
|
+
}
|
|
418
|
+
leaveAsync(e, n = "left", r) {
|
|
419
|
+
return this.serializeLifecycle(async () => {
|
|
420
|
+
if (this.worldFinalizationHold) {
|
|
421
|
+
if (!this.members.has(e)) throw new t({ sessionId: e });
|
|
422
|
+
this.deferredWorldLeaves.set(e, n);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
let i = this.prepareLeave(e, n);
|
|
426
|
+
i.pending || await this.mutateMemberAsync((e) => this.descriptor.onLeave?.(i.context(e)), r), i.commit();
|
|
427
|
+
}, () => this.worldFinalizationHold?.requestId);
|
|
428
|
+
}
|
|
429
|
+
prepareLeave(e, r) {
|
|
430
|
+
this.ensureOpen();
|
|
431
|
+
let i = this.members.get(e);
|
|
432
|
+
if (!i) throw new t({ sessionId: e });
|
|
433
|
+
if (i.client.isBot && r === "reconnecting") throw new n({ detail: "Local bots do not own socket reconnect grace" });
|
|
434
|
+
return {
|
|
435
|
+
pending: i.worldPending === !0,
|
|
436
|
+
context: (e) => ({
|
|
437
|
+
state: e,
|
|
438
|
+
options: structuredClone(this.options),
|
|
439
|
+
client: i.client,
|
|
440
|
+
app: this.app,
|
|
441
|
+
clock: this.clock,
|
|
442
|
+
scheduler: this.scheduler,
|
|
443
|
+
...this.geometry ? { geometry: this.geometry } : {},
|
|
444
|
+
reason: r
|
|
445
|
+
}),
|
|
446
|
+
commit: () => {
|
|
447
|
+
this.members.delete(e), r === "reconnecting" ? this.grace.set(e, {
|
|
448
|
+
subjectId: i.client.subjectId,
|
|
449
|
+
expiresAt: this.now() + this.descriptor.reconnect.graceMs,
|
|
450
|
+
lastAppliedSeq: i.lastAppliedSeq,
|
|
451
|
+
...i.worldDeparture ? { worldDeparture: i.worldDeparture } : {},
|
|
452
|
+
...i.worldTarget ? {
|
|
453
|
+
worldTarget: i.worldTarget,
|
|
454
|
+
discardedThrough: i.lastDiscardedSeq
|
|
455
|
+
} : {}
|
|
456
|
+
}) : this.grace.delete(e);
|
|
457
|
+
for (let t = this.inputs.length - 1; t >= 0; t--) this.inputs[t]?.client.sessionId === e && this.inputs.splice(t, 1);
|
|
458
|
+
this.members.size === 0 && (this.emptyAt = this.now());
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
assertWorldArrival(e) {
|
|
463
|
+
if (e !== void 0) {
|
|
464
|
+
if (!e || Object.keys(e).sort().join(",") !== "at,epoch,layer,roomId,worldId,zoneId" || e.roomId !== this.id || e.epoch !== this.epoch || ![
|
|
465
|
+
e.worldId,
|
|
466
|
+
e.zoneId,
|
|
467
|
+
e.roomId
|
|
468
|
+
].every((e) => typeof e == "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,190}$/.test(e)) || !Number.isSafeInteger(e.layer) || e.layer < 1 || !Array.isArray(e.at) || e.at.length !== 2 || !e.at.every(Number.isFinite)) throw new n({ detail: "World arrival must name this actual target actor" });
|
|
469
|
+
this.worldMovement && this.worldMovement.constrain(e.at, e.at);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
enterWorldDraft(e, t, r, i) {
|
|
473
|
+
let a = this.descriptor.world, o = t.arrival, s = () => {
|
|
474
|
+
if (!o || !a.movement) return;
|
|
475
|
+
this.assertWorldHookResult(e, "enter", t.entity.id);
|
|
476
|
+
let r = Object.getOwnPropertyDescriptor(e, "entities").value, s = Object.getOwnPropertyDescriptor(r, t.entity.id), l = structuredClone(s.value), u = a.movement.move(l, o.at);
|
|
477
|
+
if (u !== void 0) throw u instanceof Promise && u.catch(() => void 0), new n({ detail: "World arrival movement must return synchronous void" });
|
|
478
|
+
let d = c(l, i), f = a.movement.position(d);
|
|
479
|
+
if (f instanceof Promise) throw f.catch(() => void 0), new n({ detail: "World arrival position must be synchronous" });
|
|
480
|
+
if (d.id !== t.entity.id || g(c(f, 128)) !== g(o.at)) throw new n({ detail: "World arrival writer did not establish the admitted target position" });
|
|
481
|
+
r[t.entity.id] = l;
|
|
482
|
+
}, l = a.onEnter({
|
|
483
|
+
state: e,
|
|
484
|
+
phase: r,
|
|
485
|
+
entity: t.entity,
|
|
486
|
+
from: t.location,
|
|
487
|
+
...t.requestId === void 0 ? {} : { requestId: t.requestId },
|
|
488
|
+
...o ? { to: o } : {}
|
|
489
|
+
});
|
|
490
|
+
if (l === void 0) s();
|
|
491
|
+
else {
|
|
492
|
+
if (!y.isEffect(l)) throw l instanceof Promise && l.catch(() => void 0), new n({ detail: "World entry hooks require synchronous void or Effect" });
|
|
493
|
+
return l.pipe(y.flatMap((e) => e === void 0 ? y.sync(s) : y.fail(new n({ detail: "World entry Effect must produce void" }))));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
previewWorldEnter(t, r, i) {
|
|
497
|
+
let a = c(t, r);
|
|
498
|
+
return a.epoch !== this.epoch || !Number.isSafeInteger(a.epoch) || a.epoch < 1 || typeof a.requestId != "string" || !a.requestId || a.requestId.length > 191 || !a.entity || typeof a.entity.id != "string" || !a.entity.id || a.entity.id.length > 191 || !a.location || Object.keys(a.location).length !== 6 || !Array.isArray(a.location.at) || a.location.at.length !== 2 || !a.location.at.every((e) => typeof e == "number" && Number.isFinite(e)) || ![
|
|
499
|
+
a.location.worldId,
|
|
500
|
+
a.location.zoneId,
|
|
501
|
+
a.location.roomId
|
|
502
|
+
].every((e) => typeof e == "string" && e.length > 0 && e.length <= 191) || ![a.location.epoch, a.location.layer].every((e) => Number.isSafeInteger(e) && e > 0) ? Promise.reject(new n({ detail: "World preview requires the actual actor epoch and bounded request/entity/location" })) : this.serializeLifecycle(async () => {
|
|
503
|
+
this.ensureOpen();
|
|
504
|
+
let t = this.descriptor.world;
|
|
505
|
+
if (!t) throw new n({ detail: "World preview requires configured actor hooks" });
|
|
506
|
+
if (this.assertWorldArrival(a.arrival), this.assertWorldEnterVacant(a.entity.id), !this.worldPreviews.has(a.requestId) && this.worldPreviews.size + this.worldReceipts.size >= (t.maxPendingTransitions ?? 128)) throw new e({
|
|
507
|
+
budget: "worldActorReceipts",
|
|
508
|
+
limit: t.maxPendingTransitions ?? 128
|
|
509
|
+
});
|
|
510
|
+
await this.mutateMemberAsync((e) => this.enterWorldDraft(e, a, "prepare", r), i, !1, (e) => this.assertWorldHookResult(e, "enter", a.entity.id)), this.worldPreviews.add(a.requestId);
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
applyWorldHook(t, r, i, a, o, s = !1, l, u = !1, d) {
|
|
514
|
+
let f = c(r, i), p = l === void 0 ? void 0 : c(l, i), m = d === void 0 ? [] : c(d, 1048576);
|
|
515
|
+
if (!Array.isArray(m) || m.length > 4096 || m.length > 0 && (t !== "enter" || !o)) return Promise.reject(new n({ detail: "World promotion requires bounded guarded target entry" }));
|
|
516
|
+
let v = f.location;
|
|
517
|
+
return ![
|
|
518
|
+
"enter",
|
|
519
|
+
"exit",
|
|
520
|
+
"ghost"
|
|
521
|
+
].includes(t) || f.epoch !== this.epoch || !Number.isSafeInteger(f.epoch) || f.epoch < 1 || !f.entity || typeof f.entity.id != "string" || !f.entity.id || f.entity.id.length > 191 || f.requestId !== void 0 && (typeof f.requestId != "string" || !f.requestId || f.requestId.length > 191) || !v || Object.keys(v).length !== 6 || ![
|
|
522
|
+
v.worldId,
|
|
523
|
+
v.zoneId,
|
|
524
|
+
v.roomId
|
|
525
|
+
].every((e) => typeof e == "string" && e.length > 0 && e.length <= 191) || ![v.epoch, v.layer].every((e) => Number.isSafeInteger(e) && e > 0) || !Array.isArray(v.at) || v.at.length !== 2 || !v.at.every((e) => typeof e == "number" && Number.isFinite(e)) ? Promise.reject(new n({ detail: "World hook lost its actual actor epoch or entity identity" })) : this.serializeLifecycle(async () => {
|
|
526
|
+
this.ensureOpen();
|
|
527
|
+
let r = this.descriptor.world;
|
|
528
|
+
if (!r) throw new n({ detail: "Room has no configured World lifecycle hooks" });
|
|
529
|
+
if (f.arrival && (t !== "enter" || s || !o)) throw new n({ detail: "World arrival belongs only to guarded target entry" });
|
|
530
|
+
if (this.assertWorldArrival(f.arrival), u && (t !== "ghost" || !o || !/^wghost-[0-9a-f]{64}:ghost$/.test(f.requestId ?? ""))) throw new n({ detail: "Retained ghost projection requires guarded streaming identity" });
|
|
531
|
+
let l = f.requestId === void 0 ? void 0 : await _({
|
|
532
|
+
roomId: this.id,
|
|
533
|
+
tenantId: this.tenantId,
|
|
534
|
+
operation: t,
|
|
535
|
+
entity: f.entity,
|
|
536
|
+
location: f.location,
|
|
537
|
+
...f.arrival ? { arrival: f.arrival } : {}
|
|
538
|
+
}), d, v;
|
|
539
|
+
if (s) {
|
|
540
|
+
if (t !== "enter" || !f.requestId?.endsWith(":enter") || !o) throw new n({ detail: "World compensation requires guarded canonical enter receipt" });
|
|
541
|
+
d = `${f.requestId.slice(0, -6)}:exit`;
|
|
542
|
+
let e = this.worldReceipts.get(d);
|
|
543
|
+
if (e && (e.operation !== "exit" || e.fingerprint !== await _({
|
|
544
|
+
roomId: this.id,
|
|
545
|
+
tenantId: this.tenantId,
|
|
546
|
+
operation: "exit",
|
|
547
|
+
entity: f.entity,
|
|
548
|
+
location: f.location
|
|
549
|
+
}))) throw new n({ detail: "World compensation conflicts with captured source exit" });
|
|
550
|
+
if (!e && !this.worldReceipts.has(f.requestId)) throw new n({ detail: "World compensation requires its retained exit receipt" });
|
|
551
|
+
}
|
|
552
|
+
if (p !== void 0) {
|
|
553
|
+
if (t !== "ghost" || s || !o || !f.requestId?.endsWith(":ghost") || p.epoch !== this.epoch || !p.requestId?.endsWith(":ghost") || p.requestId === f.requestId || p.entity?.id !== f.entity.id) throw new n({ detail: "Ghost replacement requires exact prior projection and current authority" });
|
|
554
|
+
v = p.requestId;
|
|
555
|
+
let e = this.worldReceipts.get(v);
|
|
556
|
+
if (e && (e.operation !== "ghost" || e.fingerprint !== await _({
|
|
557
|
+
roomId: this.id,
|
|
558
|
+
tenantId: this.tenantId,
|
|
559
|
+
operation: "ghost",
|
|
560
|
+
entity: p.entity,
|
|
561
|
+
location: p.location
|
|
562
|
+
}))) throw new n({ detail: "Ghost replacement differs from its retained predecessor" });
|
|
563
|
+
if (!e && !this.worldReceipts.has(f.requestId)) throw new n({ detail: "Ghost replacement predecessor is absent" });
|
|
564
|
+
if (e && this.worldReceipts.has(f.requestId)) throw new n({ detail: "Ghost replacement has contradictory receipts" });
|
|
565
|
+
}
|
|
566
|
+
let b = /* @__PURE__ */ new Set();
|
|
567
|
+
for (let e of m) {
|
|
568
|
+
let t = c(e, i);
|
|
569
|
+
if (t.epoch !== this.epoch || t.entity?.id !== f.entity.id || !t.requestId?.endsWith(":ghost") || b.has(t.requestId)) throw new n({ detail: "World promotion escaped its exact target entity" });
|
|
570
|
+
let r = this.worldReceipts.get(t.requestId);
|
|
571
|
+
if (!r) {
|
|
572
|
+
if (f.requestId && this.worldReceipts.get(f.requestId)?.operation === "enter") continue;
|
|
573
|
+
throw new n({ detail: "World promotion requires its retained ghost receipt" });
|
|
574
|
+
}
|
|
575
|
+
if (r.operation !== "ghost" || r.fingerprint !== await _({
|
|
576
|
+
roomId: this.id,
|
|
577
|
+
tenantId: this.tenantId,
|
|
578
|
+
operation: "ghost",
|
|
579
|
+
entity: t.entity,
|
|
580
|
+
location: t.location
|
|
581
|
+
})) throw new n({ detail: "World promotion differs from its retained projection" });
|
|
582
|
+
b.add(t.requestId);
|
|
583
|
+
}
|
|
584
|
+
if (f.requestId !== void 0) {
|
|
585
|
+
let i = this.worldReceipts.get(f.requestId);
|
|
586
|
+
if (i) {
|
|
587
|
+
if (i.operation !== t || i.fingerprint !== l) throw new n({ detail: "World actor request conflicts with its retained receipt" });
|
|
588
|
+
if (u && !i.projection) {
|
|
589
|
+
let e = {
|
|
590
|
+
...i,
|
|
591
|
+
projection: {
|
|
592
|
+
entity: f.entity,
|
|
593
|
+
location: f.location
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
h([...this.worldReceipts.values()].filter((e) => e.requestId !== i.requestId).concat(e)), await o(), this.worldReceipts.set(i.requestId, Object.freeze(e));
|
|
597
|
+
}
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
if (this.worldReceipts.size + this.worldPreviews.size - b.size - (t === "enter" && this.worldPreviews.has(f.requestId) ? 1 : 0) >= (r.maxPendingTransitions ?? 128) && !(d && this.worldReceipts.has(d)) && !(v && this.worldReceipts.has(v))) throw new e({
|
|
601
|
+
budget: "worldActorReceipts",
|
|
602
|
+
limit: r.maxPendingTransitions ?? 128
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
let x = f.requestId === void 0 ? void 0 : Object.freeze({
|
|
606
|
+
requestId: f.requestId,
|
|
607
|
+
operation: t,
|
|
608
|
+
fingerprint: l,
|
|
609
|
+
...u ? { projection: {
|
|
610
|
+
entity: f.entity,
|
|
611
|
+
location: f.location
|
|
612
|
+
} } : {}
|
|
613
|
+
});
|
|
614
|
+
if (x && h([...this.worldReceipts.values()].filter((e) => e.requestId !== d && e.requestId !== v && !b.has(e.requestId)).concat(x)), t === "enter" && this.assertWorldEnterVacant(f.entity.id), t === "exit") {
|
|
615
|
+
let e = Object.getOwnPropertyDescriptor(this.state, "entities"), t = e && "value" in e && e.value && typeof e.value == "object" ? Object.getOwnPropertyDescriptor(e.value, f.entity.id) : void 0;
|
|
616
|
+
if (!t || !("value" in t) || await _(c(t.value, i)) !== await _(f.entity)) throw new n({ detail: "World source entity changed since transition capture" });
|
|
617
|
+
}
|
|
618
|
+
await this.mutateMemberAsync((e) => {
|
|
619
|
+
let a = {
|
|
620
|
+
state: e,
|
|
621
|
+
phase: s ? "rollback" : "commit",
|
|
622
|
+
entity: f.entity,
|
|
623
|
+
...f.requestId === void 0 ? {} : { requestId: f.requestId }
|
|
624
|
+
};
|
|
625
|
+
if (t === "enter" && b.size > 0) {
|
|
626
|
+
let t = this, a = (e) => y.suspend(() => {
|
|
627
|
+
let t = e();
|
|
628
|
+
if (t === void 0) return y.void;
|
|
629
|
+
if (!y.isEffect(t)) throw t instanceof Promise && t.catch(() => void 0), new n({ detail: "World promotion hooks require synchronous void or Effect" });
|
|
630
|
+
return t.pipe(y.flatMap((e) => e === void 0 ? y.void : y.fail(new n({ detail: "World promotion Effects must produce void" }))));
|
|
631
|
+
});
|
|
632
|
+
return y.gen(function* () {
|
|
633
|
+
for (let n of m) b.has(n.requestId) && (yield* a(() => r.onGhost({
|
|
634
|
+
state: e,
|
|
635
|
+
phase: "rollback",
|
|
636
|
+
entity: n.entity,
|
|
637
|
+
requestId: n.requestId
|
|
638
|
+
})), t.assertWorldHookResult(e, "ghost", f.entity.id));
|
|
639
|
+
yield* a(() => t.enterWorldDraft(e, f, s ? "rollback" : "commit", i));
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
return t === "enter" ? this.enterWorldDraft(e, f, s ? "rollback" : "commit", i) : t === "exit" ? r.onExit({
|
|
643
|
+
...a,
|
|
644
|
+
to: f.location
|
|
645
|
+
}) : r.onGhost(a);
|
|
646
|
+
}, a, !0, (e) => {
|
|
647
|
+
if (this.assertWorldHookResult(e, t, f.entity.id), s) {
|
|
648
|
+
let t = Object.getOwnPropertyDescriptor(e, "entities"), r = Object.getOwnPropertyDescriptor(t.value, f.entity.id);
|
|
649
|
+
if (g(c(r.value, i)) !== g(f.entity)) throw new n({ detail: "World rollback hook must restore the exact captured source entity" });
|
|
650
|
+
}
|
|
651
|
+
}, o);
|
|
652
|
+
for (let e of b) this.worldReceipts.delete(e);
|
|
653
|
+
if (d) {
|
|
654
|
+
this.worldReceipts.delete(d);
|
|
655
|
+
for (let e of this.members.values()) e.worldDeparture === d && (delete e.worldDeparture, e.lastSeq = Math.max(e.lastAppliedSeq, e.lastDiscardedSeq));
|
|
656
|
+
for (let [e, t] of this.grace) if (t.worldDeparture === d) {
|
|
657
|
+
let { worldDeparture: n, ...r } = t;
|
|
658
|
+
this.grace.set(e, r);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
v && this.worldReceipts.delete(v), x && this.worldReceipts.set(x.requestId, x), t === "enter" && f.requestId !== void 0 && this.worldPreviews.delete(f.requestId);
|
|
662
|
+
}, s && f.requestId?.endsWith(":enter") ? `${f.requestId.slice(0, -6)}:exit` : t === "exit" && o ? f.requestId : void 0);
|
|
663
|
+
}
|
|
664
|
+
assertWorldHookResult(e, t, r) {
|
|
665
|
+
let i = Object.getOwnPropertyDescriptor(e, "entities"), a = i && "value" in i && i.value && typeof i.value == "object" ? Object.getOwnPropertyDescriptor(i.value, r) : void 0;
|
|
666
|
+
if (t === "ghost") {
|
|
667
|
+
let e = Object.getOwnPropertyDescriptor(this.state, "entities"), t = e && "value" in e && e.value && typeof e.value == "object" ? Object.getOwnPropertyDescriptor(e.value, r) : void 0;
|
|
668
|
+
if (g(t && "value" in t ? t.value : null) !== g(a && "value" in a ? a.value : null)) throw new n({ detail: "World ghost hook cannot change authoritative entity ownership or state" });
|
|
669
|
+
} else if (t === "exit" ? a !== void 0 : !a || !("value" in a) || a.value?.id !== r) throw new n({ detail: "World hook did not establish its declared entity ownership result" });
|
|
670
|
+
}
|
|
671
|
+
assertWorldEnterVacant(e) {
|
|
672
|
+
let t = Object.getOwnPropertyDescriptor(this.state, "entities");
|
|
673
|
+
if (!t || !("value" in t) || !t.value || typeof t.value != "object" || Object.hasOwn(t.value, e)) throw new n({ detail: "World target entity slot is unavailable" });
|
|
674
|
+
}
|
|
675
|
+
retireWorldReceipt(e, t) {
|
|
676
|
+
return this.serializeLifecycle(async () => {
|
|
677
|
+
this.ensureOpen();
|
|
678
|
+
let r = this.worldReceipts.get(e);
|
|
679
|
+
if (r) {
|
|
680
|
+
if (r.operation !== t) throw new n({ detail: "World receipt retirement operation conflicts" });
|
|
681
|
+
this.worldReceipts.delete(e), t === "enter" && e.endsWith(":enter") && this.worldPublishedTransitions.delete(e.slice(0, -6));
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
cancelWorldGhost(e, t, r) {
|
|
686
|
+
let i = c(e, t);
|
|
687
|
+
return i.epoch !== this.epoch || !i.requestId?.endsWith(":ghost") || typeof r != "function" ? Promise.reject(new n({ detail: "World ghost rollback requires its exact actor request" })) : this.serializeLifecycle(async () => {
|
|
688
|
+
this.ensureOpen(), await r();
|
|
689
|
+
let e = this.worldReceipts.get(i.requestId);
|
|
690
|
+
if (!e) return;
|
|
691
|
+
let t = this.descriptor.world;
|
|
692
|
+
if (!t || e.operation !== "ghost" || e.fingerprint !== await _({
|
|
693
|
+
roomId: this.id,
|
|
694
|
+
tenantId: this.tenantId,
|
|
695
|
+
operation: "ghost",
|
|
696
|
+
entity: i.entity,
|
|
697
|
+
location: i.location
|
|
698
|
+
})) throw new n({ detail: "World ghost rollback differs from its retained projection" });
|
|
699
|
+
await this.mutateMemberAsync((e) => t.onGhost({
|
|
700
|
+
state: e,
|
|
701
|
+
phase: "rollback",
|
|
702
|
+
entity: i.entity,
|
|
703
|
+
requestId: i.requestId
|
|
704
|
+
}), void 0, !0, (e) => this.assertWorldHookResult(e, "ghost", i.entity.id), r), this.worldReceipts.delete(i.requestId);
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
cancelWorldPreview(e, t) {
|
|
708
|
+
return typeof e != "string" || !e || e.length > 191 || typeof t != "function" ? Promise.reject(new n({ detail: "World preview cancellation requires a bounded request and actual authority" })) : this.serializeLifecycle(async () => {
|
|
709
|
+
this.ensureOpen(), await t(), this.worldPreviews.delete(e);
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
hasWorldReceipt(e, t) {
|
|
713
|
+
if (this.ticking || this.isLifecycleBusy()) throw new r({
|
|
714
|
+
room: this.id,
|
|
715
|
+
detail: "World receipts are observed outside actor lifecycle work"
|
|
716
|
+
});
|
|
717
|
+
let i = this.worldReceipts.get(e);
|
|
718
|
+
if (i && i.operation !== t) throw new n({ detail: "World receipt operation conflicts" });
|
|
719
|
+
return i !== void 0;
|
|
720
|
+
}
|
|
721
|
+
worldReceiptSnapshot() {
|
|
722
|
+
if (this.ticking || this.isLifecycleBusy()) throw new r({
|
|
723
|
+
room: this.id,
|
|
724
|
+
detail: "World receipts are captured outside actor lifecycle work"
|
|
725
|
+
});
|
|
726
|
+
return h([...this.worldReceipts.values()].sort((e, t) => e.requestId.localeCompare(t.requestId)), this.descriptor.world?.maxPendingTransitions ?? 128);
|
|
727
|
+
}
|
|
728
|
+
captureWorldEntity(e, t, n) {
|
|
729
|
+
return this.serializeLifecycle(async () => this.captureWorldEntityOwned(e, t, n), this.worldFinalizationHold?.requestId);
|
|
730
|
+
}
|
|
731
|
+
configureWorldCellMovement(e, t, r = 0) {
|
|
732
|
+
if (!Number.isFinite(r) || r < 0) throw new n({ detail: "World movement margin must be finite and nonnegative" });
|
|
733
|
+
let i = t === void 0 ? void 0 : u(t);
|
|
734
|
+
return this.serializeLifecycle(async () => {
|
|
735
|
+
if (this.ensureOpen(), e !== this.epoch || !this.descriptor.world?.movement) throw new n({ detail: "World movement binding lost its configured actor epoch" });
|
|
736
|
+
this.worldMovement = i, this.worldMovementMargin = r, this.worldMovementIntents = Object.freeze([]);
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
worldMovementSnapshot() {
|
|
740
|
+
return Object.freeze({
|
|
741
|
+
bound: this.worldMovement !== void 0,
|
|
742
|
+
clamped: this.worldMovementClamps
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
captureWorldMovement(e) {
|
|
746
|
+
return this.serializeLifecycle(async () => {
|
|
747
|
+
this.ensureOpen();
|
|
748
|
+
let t = this.descriptor.world?.movement;
|
|
749
|
+
if (!t || !this.worldMovement || e !== this.epoch || this.faulted || this.worldFinalizationHold) throw new n({ detail: "World movement observation requires its live bounded actor" });
|
|
750
|
+
let r = [], i = t.maxBytesPerTick;
|
|
751
|
+
for (let e of this.worldMovementIntents) {
|
|
752
|
+
let a = Object.getOwnPropertyDescriptor(this.state, "entities"), o = a && "value" in a ? a.value : void 0, s = o && typeof o == "object" ? Object.getOwnPropertyDescriptor(o, e.entityId) : void 0;
|
|
753
|
+
if (!s || !("value" in s)) continue;
|
|
754
|
+
let l = c(s.value, i);
|
|
755
|
+
i -= new TextEncoder().encode(JSON.stringify(l)).length;
|
|
756
|
+
let u = t.position(l);
|
|
757
|
+
if (u instanceof Promise) throw u.catch(() => void 0), new n({ detail: "World movement observation must be synchronous" });
|
|
758
|
+
l.id === e.entityId && g(c(u, 128)) === g(e.sourceAt) && r.push(e);
|
|
759
|
+
}
|
|
760
|
+
return Object.freeze({
|
|
761
|
+
epoch: e,
|
|
762
|
+
tick: this.lastGoodTick,
|
|
763
|
+
intents: Object.freeze(r)
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
constrainWorldMovement() {
|
|
768
|
+
let t = this.descriptor.world?.movement;
|
|
769
|
+
if (!t) return {
|
|
770
|
+
clamped: 0,
|
|
771
|
+
intents: Object.freeze([])
|
|
772
|
+
};
|
|
773
|
+
let r = this.worldMovement;
|
|
774
|
+
if (!r) throw new n({ detail: "World movement requires acquired source cell bounds" });
|
|
775
|
+
let i = t.maxBytesPerTick, a = 0, o = 0, s = [], l = (e) => {
|
|
776
|
+
let t = c(e, i);
|
|
777
|
+
return i -= new TextEncoder().encode(JSON.stringify(t)).length, t;
|
|
778
|
+
}, u = (e) => {
|
|
779
|
+
let r = t.position(e);
|
|
780
|
+
if (r instanceof Promise) throw r.catch(() => void 0), new n({ detail: "World movement position must be synchronous" });
|
|
781
|
+
return c(r, 128);
|
|
782
|
+
}, d = Object.getOwnPropertyDescriptor(this.state, "entities"), f = Object.getOwnPropertyDescriptor(this.lastGoodState, "entities"), p = d && "value" in d ? d.value : void 0, m = f && "value" in f ? f.value : void 0;
|
|
783
|
+
if (!p || typeof p != "object" || Array.isArray(p) || ![null, Object.prototype].includes(Object.getPrototypeOf(p)) || !m || typeof m != "object" || Array.isArray(m)) throw new n({ detail: "World movement requires actual entity records" });
|
|
784
|
+
for (let i in p) if (Object.hasOwn(p, i)) {
|
|
785
|
+
if (++a > t.maxEntities) throw new e({
|
|
786
|
+
budget: "worldMovementEntities",
|
|
787
|
+
limit: t.maxEntities
|
|
788
|
+
});
|
|
789
|
+
let c = Object.getOwnPropertyDescriptor(p, i), d = Object.getOwnPropertyDescriptor(m, i);
|
|
790
|
+
if (!c || !("value" in c)) throw new n({ detail: "World movement requires own entity data" });
|
|
791
|
+
let f = l(c.value);
|
|
792
|
+
if (f.id !== i) throw new n({ detail: "World movement entity differs from its state key" });
|
|
793
|
+
let h = u(f), _ = d && "value" in d ? u(l(d.value)) : h, v = r.constrain(_, h), y = r.approach(_, h, this.worldMovementMargin);
|
|
794
|
+
if (y && s.push(Object.freeze({
|
|
795
|
+
entityId: i,
|
|
796
|
+
...y
|
|
797
|
+
})), !v.clamped) continue;
|
|
798
|
+
let b = t.move(c.value, v.at);
|
|
799
|
+
if (b !== void 0) throw b instanceof Promise && b.catch(() => void 0), new n({ detail: "World movement write must return synchronous void" });
|
|
800
|
+
let x = l(c.value), S = u(x);
|
|
801
|
+
if (x.id !== i || g(S) !== g(v.at)) throw new n({ detail: "World movement writer did not establish its constrained position" });
|
|
802
|
+
o++;
|
|
803
|
+
}
|
|
804
|
+
if (this.worldMovementClamps > 2 ** 53 - 1 - o) throw new e({
|
|
805
|
+
budget: "worldMovementClamps",
|
|
806
|
+
limit: 2 ** 53 - 1
|
|
807
|
+
});
|
|
808
|
+
return {
|
|
809
|
+
clamped: o,
|
|
810
|
+
intents: Object.freeze(s)
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
listWorldEntityIds(t, r, i) {
|
|
814
|
+
return !Number.isSafeInteger(r) || r < 1 || r > 4095 || !Number.isSafeInteger(i) || i < 1 || i > 1048576 ? Promise.reject(new n({ detail: "World entity inventory requires a bounded count" })) : this.serializeLifecycle(async () => {
|
|
815
|
+
if (this.ensureOpen(), !this.descriptor.world || t !== this.epoch) throw new n({ detail: "World inventory lost its actor epoch" });
|
|
816
|
+
let a = Object.getOwnPropertyDescriptor(this.state, "entities"), o = a && "value" in a ? a.value : void 0;
|
|
817
|
+
if (!o || typeof o != "object" || Array.isArray(o) || ![null, Object.prototype].includes(Object.getPrototypeOf(o))) throw new n({ detail: "World inventory requires its actual entity record" });
|
|
818
|
+
let s = [];
|
|
819
|
+
for (let t in o) if (Object.hasOwn(o, t)) {
|
|
820
|
+
if (s.length >= r) throw new e({
|
|
821
|
+
budget: "worldGhostEntities",
|
|
822
|
+
limit: r
|
|
823
|
+
});
|
|
824
|
+
let i = Object.getOwnPropertyDescriptor(o, t), a = i && "value" in i ? i.value : void 0, c = a && typeof a == "object" ? Object.getOwnPropertyDescriptor(a, "id") : void 0;
|
|
825
|
+
if (!t || t.length > 191 || !c || !("value" in c) || c.value !== t) throw new n({ detail: "World inventory entity identity contradicts its state key" });
|
|
826
|
+
s.push(t);
|
|
827
|
+
}
|
|
828
|
+
return s.sort((e, t) => e < t ? -1 : +(e > t)), c(s, i);
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
captureWorldProjection(e, t, n) {
|
|
832
|
+
return this.serializeLifecycle(async () => Object.freeze({
|
|
833
|
+
...this.captureWorldEntityOwned(e, t, n),
|
|
834
|
+
tick: this.tick
|
|
835
|
+
}));
|
|
836
|
+
}
|
|
837
|
+
captureWorldEntityOwned(e, t, r) {
|
|
838
|
+
this.ensureOpen();
|
|
839
|
+
let i = this.descriptor.world;
|
|
840
|
+
if (!i || t !== this.epoch || typeof e != "string" || !e || e.length > 191) throw new n({ detail: "World capture requires current configured actor authority" });
|
|
841
|
+
let a = Object.getOwnPropertyDescriptor(this.state, "entities"), o = a && "value" in a && a.value && typeof a.value == "object" ? Object.getOwnPropertyDescriptor(a.value, e) : void 0;
|
|
842
|
+
if (!o || !("value" in o)) throw new n({ detail: "World entity is absent from the actual actor state" });
|
|
843
|
+
let s = c(o.value, r);
|
|
844
|
+
if (!s || s.id !== e) throw new n({ detail: "World actor entity identity contradicts its state key" });
|
|
845
|
+
let l = i.persistEntity({ entity: s });
|
|
846
|
+
if (l instanceof Promise) throw l.catch(() => void 0), new n({ detail: "World entity persistence projection must be synchronous" });
|
|
847
|
+
return Object.freeze({
|
|
848
|
+
entity: s,
|
|
849
|
+
data: c(l, r)
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
finalizeWorldExit(t, r, i, a, o, s) {
|
|
853
|
+
let l = c(t, r);
|
|
854
|
+
if (!l || l.epoch !== this.epoch || !l.requestId?.endsWith(":exit") || l.requestId.length > 191 || !l.location || Object.keys(l.location).length !== 6 || ![
|
|
855
|
+
l.location.worldId,
|
|
856
|
+
l.location.zoneId,
|
|
857
|
+
l.location.roomId
|
|
858
|
+
].every((e) => typeof e == "string" && e.length > 0 && e.length <= 191) || ![l.location.epoch, l.location.layer].every((e) => Number.isSafeInteger(e) && e > 0) || !Array.isArray(l.location.at) || l.location.at.length !== 2 || !l.location.at.every(Number.isFinite) || l.subjectId !== void 0 && (typeof l.subjectId != "string" || !l.subjectId || l.subjectId.length > 191) || l.afterglow !== void 0 && (!l.afterglow || Object.keys(l.afterglow).sort().join(",") !== "expiresAt,requestId" || !/^wghost-[0-9a-f]{64}:ghost$/.test(l.afterglow.requestId) || !Number.isSafeInteger(l.afterglow.expiresAt) || l.afterglow.expiresAt < 0) || typeof i != "function" || typeof a != "function") return Promise.reject(new n({ detail: "World finalization requires canonical exit and actual durable callbacks" }));
|
|
859
|
+
let u = o === void 0 ? void 0 : c(o, r);
|
|
860
|
+
return this.serializeLifecycle(async () => {
|
|
861
|
+
this.ensureOpen();
|
|
862
|
+
let t = this.captureWorldEntityOwned(l.entityId, l.epoch, r), o = this.worldFinalizationHold;
|
|
863
|
+
if (o && (g(o.afterglow ?? null) !== g(l.afterglow ?? null) || o.subjectId !== l.subjectId || o.entityId !== l.entityId || g(o.location) !== g(l.location) || g(o.entity) !== g(t.entity))) throw new n({ detail: "World finalization retry conflicts with retained source capture" });
|
|
864
|
+
if (u !== void 0 && g(u) !== g(t.entity)) throw new n({ detail: "World finalization requires exact retained final source entity" });
|
|
865
|
+
if (this.worldReceipts.has(l.requestId)) throw new n({ detail: "World finalization already has an exit receipt; replay its retained payload" });
|
|
866
|
+
if (this.worldReceipts.size + this.worldPreviews.size + +!!l.afterglow >= (this.descriptor.world.maxPendingTransitions ?? 128)) throw new e({
|
|
867
|
+
budget: "worldActorReceipts",
|
|
868
|
+
limit: this.descriptor.world.maxPendingTransitions ?? 128
|
|
869
|
+
});
|
|
870
|
+
let c = o ?? Object.freeze({
|
|
871
|
+
...t,
|
|
872
|
+
requestId: l.requestId,
|
|
873
|
+
entityId: l.entityId,
|
|
874
|
+
location: l.location,
|
|
875
|
+
...l.afterglow ? { afterglow: l.afterglow } : {},
|
|
876
|
+
...l.subjectId === void 0 ? {} : { subjectId: l.subjectId },
|
|
877
|
+
heldAt: this.now()
|
|
878
|
+
});
|
|
879
|
+
this.worldFinalizationHold = c;
|
|
880
|
+
let d = await _({
|
|
881
|
+
roomId: this.id,
|
|
882
|
+
tenantId: this.tenantId,
|
|
883
|
+
operation: "exit",
|
|
884
|
+
entity: c.entity,
|
|
885
|
+
location: l.location
|
|
886
|
+
}), f = c.afterglow ? Object.freeze({
|
|
887
|
+
requestId: c.afterglow.requestId,
|
|
888
|
+
operation: "ghost",
|
|
889
|
+
fingerprint: await _({
|
|
890
|
+
roomId: this.id,
|
|
891
|
+
tenantId: this.tenantId,
|
|
892
|
+
operation: "ghost",
|
|
893
|
+
entity: c.entity,
|
|
894
|
+
location: l.location
|
|
895
|
+
}),
|
|
896
|
+
projection: {
|
|
897
|
+
entity: c.entity,
|
|
898
|
+
location: l.location,
|
|
899
|
+
expiresAt: c.afterglow.expiresAt
|
|
900
|
+
}
|
|
901
|
+
}) : void 0;
|
|
902
|
+
if (f && this.worldReceipts.has(f.requestId)) throw new n({ detail: "Source afterglow conflicts with a retained projection" });
|
|
903
|
+
if (h([
|
|
904
|
+
...this.worldReceipts.values(),
|
|
905
|
+
{
|
|
906
|
+
requestId: l.requestId,
|
|
907
|
+
operation: "exit",
|
|
908
|
+
fingerprint: d
|
|
909
|
+
},
|
|
910
|
+
...f ? [f] : []
|
|
911
|
+
]), await this.mutateMemberAsync((e) => {
|
|
912
|
+
let t = this.descriptor.world, r = () => t.onExit({
|
|
913
|
+
state: e,
|
|
914
|
+
phase: "commit",
|
|
915
|
+
entity: c.entity,
|
|
916
|
+
to: l.location,
|
|
917
|
+
requestId: l.requestId
|
|
918
|
+
});
|
|
919
|
+
return f ? y.gen(function* () {
|
|
920
|
+
for (let i of [r, () => t.onGhost({
|
|
921
|
+
state: e,
|
|
922
|
+
phase: "commit",
|
|
923
|
+
entity: c.entity,
|
|
924
|
+
requestId: f.requestId
|
|
925
|
+
})]) {
|
|
926
|
+
let e = i();
|
|
927
|
+
if (e !== void 0) {
|
|
928
|
+
if (!y.isEffect(e)) throw e instanceof Promise && e.catch(() => void 0), new n({ detail: "Source afterglow requires synchronous void or Effect hooks" });
|
|
929
|
+
if ((yield* e) !== void 0) throw new n({ detail: "Source afterglow Effects must produce void" });
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}) : r();
|
|
933
|
+
}, s, !0, (e) => this.assertWorldHookResult(e, "exit", l.entityId), async () => {
|
|
934
|
+
await a(), await i(Object.freeze({
|
|
935
|
+
entity: c.entity,
|
|
936
|
+
data: c.data
|
|
937
|
+
})), await a();
|
|
938
|
+
}), f && this.worldReceipts.set(f.requestId, f), this.worldReceipts.set(l.requestId, Object.freeze({
|
|
939
|
+
requestId: l.requestId,
|
|
940
|
+
operation: "exit",
|
|
941
|
+
fingerprint: d
|
|
942
|
+
})), l.subjectId !== void 0) {
|
|
943
|
+
for (let e of this.members.values()) e.client.subjectId === l.subjectId && (e.worldDeparture = l.requestId);
|
|
944
|
+
for (let [e, t] of this.grace) t.subjectId === l.subjectId && this.grace.set(e, {
|
|
945
|
+
...t,
|
|
946
|
+
worldDeparture: l.requestId
|
|
947
|
+
});
|
|
948
|
+
for (let e = this.inputs.length - 1; e >= 0; e--) this.inputs[e].client.subjectId === l.subjectId && this.inputs.splice(e, 1);
|
|
949
|
+
}
|
|
950
|
+
}, l.requestId);
|
|
951
|
+
}
|
|
952
|
+
notifyWorldHandoff(e, t, r) {
|
|
953
|
+
let i = c(e, 2048), a = l({
|
|
954
|
+
...i,
|
|
955
|
+
tick: 0
|
|
956
|
+
});
|
|
957
|
+
return b.isFailure(a) || i.kind !== "WorldHandoff" || typeof t != "function" || typeof r != "function" ? Promise.reject(new n({ detail: "World notice requires canonical metadata and actual authority" })) : this.serializeLifecycle(async () => {
|
|
958
|
+
if (this.ensureOpen(), !this.descriptor.world || i.sourceEpoch !== this.epoch) throw new n({ detail: "World notice lost its source epoch" });
|
|
959
|
+
let e = i.phase === "departed" ? "exit" : "enter", a = this.worldReceipts.get(`${i.transitionId}:${e}`);
|
|
960
|
+
if (!a || a.operation !== e) throw new n({ detail: "World notice requires its actual actor receipt" });
|
|
961
|
+
this.assertWorldHookResult(this.state, e, i.entityId), await t(), r(Object.freeze({
|
|
962
|
+
...i,
|
|
963
|
+
tick: this.tick
|
|
964
|
+
}));
|
|
965
|
+
}, `${i.transitionId}:exit`);
|
|
966
|
+
}
|
|
967
|
+
completeWorldFinalization(e, t) {
|
|
968
|
+
return this.serializeLifecycle(async () => {
|
|
969
|
+
if (!this.worldFinalizationHold) return;
|
|
970
|
+
if (typeof t != "function") throw new n({ detail: "World completion requires actual durable authority" });
|
|
971
|
+
await t();
|
|
972
|
+
for (let [e, t] of this.deferredWorldLeaves) {
|
|
973
|
+
let n = this.prepareLeave(e, t);
|
|
974
|
+
n.pending || await this.mutateMemberAsync((e) => this.descriptor.onLeave?.(n.context(e))), n.commit(), this.deferredWorldLeaves.delete(e);
|
|
975
|
+
}
|
|
976
|
+
let e = this.now() - this.worldFinalizationHold.heldAt;
|
|
977
|
+
this.nextTickAt += e, this.emptyAt !== null && this.emptyAt <= this.worldFinalizationHold.heldAt && (this.emptyAt += e);
|
|
978
|
+
for (let t of this.schedules.values()) t.at += e;
|
|
979
|
+
this.worldFinalizationHold = null;
|
|
980
|
+
}, e);
|
|
981
|
+
}
|
|
982
|
+
worldFinalizationAfterglow() {
|
|
983
|
+
return this.worldFinalizationHold?.afterglow;
|
|
984
|
+
}
|
|
985
|
+
worldFinalizationRequest() {
|
|
986
|
+
return this.worldFinalizationHold?.requestId ?? null;
|
|
987
|
+
}
|
|
988
|
+
isWorldFinalizationHeld() {
|
|
989
|
+
return this.worldFinalizationHold !== null;
|
|
990
|
+
}
|
|
991
|
+
reconcileWorldSource(t, r, i, a) {
|
|
992
|
+
let o = c(t, r);
|
|
993
|
+
return !o || o.epoch !== this.epoch || !o.requestId?.endsWith(":enter") || o.requestId.length > 191 || !o.entity || typeof o.entity.id != "string" || !o.entity.id || o.entity.id.length > 191 || !o.location || Object.keys(o.location).length !== 6 || ![
|
|
994
|
+
o.location.worldId,
|
|
995
|
+
o.location.zoneId,
|
|
996
|
+
o.location.roomId
|
|
997
|
+
].every((e) => typeof e == "string" && e.length > 0 && e.length <= 191) || ![o.location.epoch, o.location.layer].every((e) => Number.isSafeInteger(e) && e > 0) || !Array.isArray(o.location.at) || o.location.at.length !== 2 || !o.location.at.every(Number.isFinite) || typeof i != "function" ? Promise.reject(new n({ detail: "World source recovery requires canonical current authority" })) : this.serializeLifecycle(async () => {
|
|
998
|
+
if (this.ensureOpen(), !this.descriptor.world || this.publicationHeldAt === null && !this.worldFinalizationHold) throw new n({ detail: "World source recovery requires unpublished or held configured actor" });
|
|
999
|
+
if (this.publicationHeldAt === null && g(this.captureWorldEntityOwned(o.entity.id, o.epoch, r).entity) !== g(o.entity)) throw new n({ detail: "Live World compensation cannot replace divergent source state" });
|
|
1000
|
+
let t = await _({
|
|
1001
|
+
roomId: this.id,
|
|
1002
|
+
tenantId: this.tenantId,
|
|
1003
|
+
operation: "enter",
|
|
1004
|
+
entity: o.entity,
|
|
1005
|
+
location: o.location
|
|
1006
|
+
}), s = this.worldReceipts.get(o.requestId);
|
|
1007
|
+
if (s) {
|
|
1008
|
+
if (s.operation !== "enter" || s.fingerprint !== t) throw new n({ detail: "World recovery conflicts with retained receipt" });
|
|
1009
|
+
await i();
|
|
1010
|
+
} else {
|
|
1011
|
+
if (this.worldReceipts.size + this.worldPreviews.size >= (this.descriptor.world.maxPendingTransitions ?? 128)) throw new e({
|
|
1012
|
+
budget: "worldActorReceipts",
|
|
1013
|
+
limit: this.descriptor.world.maxPendingTransitions ?? 128
|
|
1014
|
+
});
|
|
1015
|
+
await this.mutateMemberAsync((e) => {
|
|
1016
|
+
let t = Object.getOwnPropertyDescriptor(e, "entities");
|
|
1017
|
+
if (!t || !("value" in t) || !t.value || typeof t.value != "object") throw new n({ detail: "World recovery requires actual entity state" });
|
|
1018
|
+
return delete t.value[o.entity.id], this.descriptor.world.onEnter({
|
|
1019
|
+
state: e,
|
|
1020
|
+
phase: "rollback",
|
|
1021
|
+
entity: o.entity,
|
|
1022
|
+
from: o.location,
|
|
1023
|
+
requestId: o.requestId
|
|
1024
|
+
});
|
|
1025
|
+
}, a, !0, (e) => {
|
|
1026
|
+
this.assertWorldHookResult(e, "enter", o.entity.id);
|
|
1027
|
+
let t = Object.getOwnPropertyDescriptor(e, "entities");
|
|
1028
|
+
if (g(c(t.value[o.entity.id], r)) !== g(o.entity)) throw new n({ detail: "World recovery must restore exact final source entity" });
|
|
1029
|
+
}, i), this.worldReceipts.set(o.requestId, Object.freeze({
|
|
1030
|
+
requestId: o.requestId,
|
|
1031
|
+
operation: "enter",
|
|
1032
|
+
fingerprint: t
|
|
1033
|
+
}));
|
|
1034
|
+
}
|
|
1035
|
+
}, `${o.requestId.slice(0, -6)}:exit`);
|
|
1036
|
+
}
|
|
1037
|
+
assertSynchronousLifecycle() {
|
|
1038
|
+
if (this.isLifecycleBusy() || this.worldFinalizationHold) throw new r({
|
|
1039
|
+
room: this.descriptor.name,
|
|
1040
|
+
detail: "A synchronous lifecycle call cannot overlap queued Effect hooks"
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
serializeLifecycle(t, i) {
|
|
1044
|
+
if (this.ticking) return Promise.reject(new r({
|
|
1045
|
+
room: this.descriptor.name,
|
|
1046
|
+
detail: "Lifecycle work must run outside the tick"
|
|
1047
|
+
}));
|
|
1048
|
+
if (this.lifecycleRequests >= this.descriptor.budgets.maxQueuedLifecycleHooks) return Promise.reject(new e({
|
|
1049
|
+
budget: "maxQueuedLifecycleHooks",
|
|
1050
|
+
limit: this.descriptor.budgets.maxQueuedLifecycleHooks
|
|
1051
|
+
}));
|
|
1052
|
+
this.lifecycleRequests++;
|
|
1053
|
+
let a = this.lifecycleTail.then(async () => {
|
|
1054
|
+
this.lifecycleActive = !0;
|
|
1055
|
+
try {
|
|
1056
|
+
if (this.worldFinalizationHold && this.worldFinalizationHold.requestId !== (typeof i == "function" ? i() : i)) throw new n({ detail: "World finalization holds actor lifecycle until durable completion" });
|
|
1057
|
+
return await t();
|
|
1058
|
+
} finally {
|
|
1059
|
+
this.lifecycleActive = !1, this.lifecycleRequests--;
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
return this.lifecycleTail = a.then(() => void 0, () => void 0), a;
|
|
1063
|
+
}
|
|
1064
|
+
async mutateMemberAsync(e, t, n = !0, i, a) {
|
|
1065
|
+
let o = structuredClone(this.state), s = this.outbox.length, c = this.clientOperations.length, l = [...this.schedules].map(([e, t]) => [e, { ...t }]), u = () => {
|
|
1066
|
+
this.outbox.splice(s), this.clientOperations.splice(c), this.schedules.clear();
|
|
1067
|
+
for (let [e, t] of l) this.schedules.set(e, t);
|
|
1068
|
+
};
|
|
1069
|
+
try {
|
|
1070
|
+
if (t?.aborted) throw new r({
|
|
1071
|
+
room: this.descriptor.name,
|
|
1072
|
+
detail: "Lifecycle operation was cancelled"
|
|
1073
|
+
});
|
|
1074
|
+
let s = e(o);
|
|
1075
|
+
if (s !== void 0) {
|
|
1076
|
+
if (!y.isEffect(s)) throw s instanceof Promise && s.catch(() => void 0), new r({
|
|
1077
|
+
room: this.descriptor.name,
|
|
1078
|
+
detail: "Lifecycle hooks must return undefined or Effect, never Promise"
|
|
1079
|
+
});
|
|
1080
|
+
let e = s.pipe(y.timeout(this.descriptor.lifecycleTimeoutMs)), n = await y.runPromise(y.result(e), { signal: t });
|
|
1081
|
+
if (b.isFailure(n)) throw n.failure;
|
|
1082
|
+
if (n.success !== void 0) throw new r({
|
|
1083
|
+
room: this.descriptor.name,
|
|
1084
|
+
detail: "Lifecycle Effects must produce void"
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
if (t?.aborted) throw new r({
|
|
1088
|
+
room: this.descriptor.name,
|
|
1089
|
+
detail: "Lifecycle operation was cancelled"
|
|
1090
|
+
});
|
|
1091
|
+
if (x.decodeUnknownSync(x.toType(this.descriptor.state))(o), i?.(o), !n) {
|
|
1092
|
+
u();
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
let c = structuredClone(o);
|
|
1096
|
+
if (a && await a(), t?.aborted) throw new r({
|
|
1097
|
+
room: this.descriptor.name,
|
|
1098
|
+
detail: "Lifecycle operation was cancelled"
|
|
1099
|
+
});
|
|
1100
|
+
this.currentState = c, this.lastGoodState = structuredClone(c);
|
|
1101
|
+
} catch (e) {
|
|
1102
|
+
throw u(), e;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
pruneGrace(e) {
|
|
1106
|
+
for (let [t, n] of this.grace) n.expiresAt <= e && this.grace.delete(t);
|
|
1107
|
+
}
|
|
1108
|
+
mutateMember(e) {
|
|
1109
|
+
if (this.ticking) throw new r({
|
|
1110
|
+
room: this.descriptor.name,
|
|
1111
|
+
detail: "Membership hooks must run outside the tick"
|
|
1112
|
+
});
|
|
1113
|
+
let t = structuredClone(this.state), n = this.outbox.length, i = this.clientOperations.length, a = [...this.schedules].map(([e, t]) => [e, { ...t }]);
|
|
1114
|
+
try {
|
|
1115
|
+
e(), x.decodeUnknownSync(x.toType(this.descriptor.state))(this.state), this.lastGoodState = structuredClone(this.state);
|
|
1116
|
+
} catch (e) {
|
|
1117
|
+
this.currentState = t, this.outbox.splice(n), this.clientOperations.splice(i), this.schedules.clear();
|
|
1118
|
+
for (let [e, t] of a) this.schedules.set(e, t);
|
|
1119
|
+
throw e;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
input(r, i, a, o, s) {
|
|
1123
|
+
this.ensureOpen();
|
|
1124
|
+
let c = this.members.get(r);
|
|
1125
|
+
if (!c) throw new t({ sessionId: r });
|
|
1126
|
+
if (![
|
|
1127
|
+
i,
|
|
1128
|
+
a,
|
|
1129
|
+
s
|
|
1130
|
+
].every((e) => Number.isSafeInteger(e) && e >= 0)) throw new n({ detail: "Input metadata must use nonnegative safe integers" });
|
|
1131
|
+
if (i <= c.lastSeq) return !1;
|
|
1132
|
+
let l = this.now(), u = Math.max(0, l - c.replenishedAt) / 1e3, d = this.descriptor.budgets;
|
|
1133
|
+
if (c.inputTokens = Math.min(d.burst, c.inputTokens + u * d.inputsPerSecond), c.byteTokens = Math.min(d.bytesPerSecond, c.byteTokens + u * d.bytesPerSecond), c.replenishedAt = l, c.inputTokens < 1) throw new e({
|
|
1134
|
+
budget: "inputsPerSecond",
|
|
1135
|
+
limit: d.inputsPerSecond
|
|
1136
|
+
});
|
|
1137
|
+
if (c.byteTokens < s) throw new e({
|
|
1138
|
+
budget: "bytesPerSecond",
|
|
1139
|
+
limit: d.bytesPerSecond
|
|
1140
|
+
});
|
|
1141
|
+
let f = x.decodeUnknownResult(this.descriptor.input)(o);
|
|
1142
|
+
if (b.isFailure(f)) throw new n({ detail: "Input does not match the declared intention schema" });
|
|
1143
|
+
let p = f.success;
|
|
1144
|
+
if (!c.worldPending && !c.worldDeparture && this.inputs.length >= d.maxQueuedInputs) throw new e({
|
|
1145
|
+
budget: "maxQueuedInputs",
|
|
1146
|
+
limit: d.maxQueuedInputs
|
|
1147
|
+
});
|
|
1148
|
+
return --c.inputTokens, c.byteTokens -= s, c.lastSeq = i, c.worldDeparture ? !1 : c.worldPending ? (c.lastDiscardedSeq = i, !1) : (this.inputs.push(Object.freeze({
|
|
1149
|
+
client: c.client,
|
|
1150
|
+
seq: i,
|
|
1151
|
+
tickHint: a,
|
|
1152
|
+
input: structuredClone(p)
|
|
1153
|
+
})), !0);
|
|
1154
|
+
}
|
|
1155
|
+
send(e, t, n) {
|
|
1156
|
+
let i = this.descriptor.messages[t];
|
|
1157
|
+
if (!i || i.direction === "client") throw new r({
|
|
1158
|
+
room: this.descriptor.name,
|
|
1159
|
+
detail: `Channel ${t} is not server-sendable`
|
|
1160
|
+
});
|
|
1161
|
+
e.send(t, x.decodeUnknownSync(i.schema)(n));
|
|
1162
|
+
}
|
|
1163
|
+
extension(e, n, r) {
|
|
1164
|
+
if (!n) return;
|
|
1165
|
+
let i = Object.create(null);
|
|
1166
|
+
for (let t of e.fields) i[t] = structuredClone(Reflect.get(Object(this.state), t));
|
|
1167
|
+
Object.preventExtensions(i);
|
|
1168
|
+
let a = this.now();
|
|
1169
|
+
S(this.descriptor.name, n, {
|
|
1170
|
+
state: i,
|
|
1171
|
+
tick: r.tick,
|
|
1172
|
+
dt: r.dt,
|
|
1173
|
+
inputs: r.inputs.map((e) => ({
|
|
1174
|
+
...e,
|
|
1175
|
+
input: structuredClone(e.input),
|
|
1176
|
+
client: Object.freeze({
|
|
1177
|
+
sessionId: e.client.sessionId,
|
|
1178
|
+
subjectId: e.client.subjectId,
|
|
1179
|
+
tenantId: e.client.tenantId,
|
|
1180
|
+
...e.client.isBot ? {
|
|
1181
|
+
isBot: !0,
|
|
1182
|
+
countsAsPlayer: e.client.countsAsPlayer !== !1
|
|
1183
|
+
} : {}
|
|
1184
|
+
})
|
|
1185
|
+
})),
|
|
1186
|
+
clock: this.clock,
|
|
1187
|
+
...this.geometry ? { geometry: this.geometry } : {},
|
|
1188
|
+
send: (n, r) => {
|
|
1189
|
+
let i = this.members.get(n);
|
|
1190
|
+
if (!i) throw new t({ sessionId: n });
|
|
1191
|
+
this.send(i.client, e.channel, r);
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
for (let t of e.fields) Reflect.set(Object(this.state), t, structuredClone(i[t]));
|
|
1195
|
+
let o = Math.max(0, this.now() - a);
|
|
1196
|
+
this.extensionDurationMs[e.name] = (this.extensionDurationMs[e.name] ?? 0) + o, o > e.tickBudgetMs && (this.extensionOverruns[e.name] = (this.extensionOverruns[e.name] ?? 0) + 1);
|
|
1197
|
+
}
|
|
1198
|
+
holdPublication() {
|
|
1199
|
+
if (this.ensureOpen(), this.assertSynchronousLifecycle(), this.ticking) throw new r({
|
|
1200
|
+
room: this.id,
|
|
1201
|
+
detail: "Publication cannot change inside a tick"
|
|
1202
|
+
});
|
|
1203
|
+
this.publicationHeldAt ??= this.now();
|
|
1204
|
+
}
|
|
1205
|
+
releasePublication() {
|
|
1206
|
+
if (this.ensureOpen(), this.assertSynchronousLifecycle(), this.publicationHeldAt === null) throw new n({ detail: "Actor publication has no retained hold" });
|
|
1207
|
+
let e = this.now() - this.publicationHeldAt;
|
|
1208
|
+
this.nextTickAt += e, this.emptyAt !== null && (this.emptyAt += e);
|
|
1209
|
+
for (let t of this.schedules.values()) t.at += e;
|
|
1210
|
+
for (let [t, n] of this.grace) this.grace.set(t, {
|
|
1211
|
+
...n,
|
|
1212
|
+
expiresAt: n.expiresAt + e
|
|
1213
|
+
});
|
|
1214
|
+
this.publicationHeldAt = null;
|
|
1215
|
+
}
|
|
1216
|
+
advance() {
|
|
1217
|
+
if (this.drainFrozen || this.publicationHeldAt !== null || this.worldFinalizationHold || this.isLifecycleBusy() || this.descriptor.world?.movement && !this.worldMovement) return 0;
|
|
1218
|
+
if (this.ensureOpen(), this.ticking) throw new r({
|
|
1219
|
+
room: this.descriptor.name,
|
|
1220
|
+
detail: "A room cannot reenter its tick"
|
|
1221
|
+
});
|
|
1222
|
+
let e = this.now(), t = 1e3 / this.descriptor.tickRate;
|
|
1223
|
+
if (this.pruneGrace(e), this.emptyAt !== null && this.grace.size === 0 && this.worldReceipts.size === 0 && this.worldPreviews.size === 0 && e - this.emptyAt >= this.descriptor.autoDispose.afterEmptyMs) return this.retiring = !0, 0;
|
|
1224
|
+
if (e < this.nextTickAt) return 0;
|
|
1225
|
+
let n = Math.floor((e - this.nextTickAt) / t) + 1, i = Math.max(0, n - this.descriptor.maxCatchUpTicks);
|
|
1226
|
+
if (!Number.isSafeInteger(this.tick + n)) throw new r({
|
|
1227
|
+
room: this.descriptor.name,
|
|
1228
|
+
detail: "Room tick counter is exhausted"
|
|
1229
|
+
});
|
|
1230
|
+
this.droppedTicks += i, this.tick += i, this.nextTickAt += i * t;
|
|
1231
|
+
let a = 0, o = this.outbox.length, s = this.clientOperations.length;
|
|
1232
|
+
this.ticking = !0;
|
|
1233
|
+
try {
|
|
1234
|
+
for (; a < n - i; a++) {
|
|
1235
|
+
o = this.outbox.length, s = this.clientOperations.length, this.tick++;
|
|
1236
|
+
let e = Object.freeze(this.inputs.splice(0).sort((e, t) => e.client.sessionId < t.client.sessionId ? -1 : e.client.sessionId > t.client.sessionId ? 1 : e.seq - t.seq)), n = {
|
|
1237
|
+
state: this.state,
|
|
1238
|
+
tick: this.tick,
|
|
1239
|
+
dt: 1 / this.descriptor.tickRate,
|
|
1240
|
+
inputs: e,
|
|
1241
|
+
app: this.app,
|
|
1242
|
+
clock: this.clock,
|
|
1243
|
+
scheduler: this.scheduler,
|
|
1244
|
+
...this.geometry ? { geometry: this.geometry } : {},
|
|
1245
|
+
broadcast: (e, t) => {
|
|
1246
|
+
for (let n of this.members.values()) this.send(n.client, e, t);
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
for (let e of [...this.schedules.values()]) e.at > this.nextTickAt || this.schedules.get(e.label) !== e || (e.interval === null ? this.schedules.delete(e.label) : e.at += (Math.floor((this.nextTickAt - e.at) / e.interval) + 1) * e.interval, S(this.descriptor.name, this.descriptor.onSchedule, {
|
|
1250
|
+
...n,
|
|
1251
|
+
label: e.label
|
|
1252
|
+
}));
|
|
1253
|
+
for (let e of this.descriptor.extensions) this.extension(e, e.beforeTick, n);
|
|
1254
|
+
S(this.descriptor.name, this.descriptor.onTick, n);
|
|
1255
|
+
for (let e of this.descriptor.extensions) this.extension(e, e.afterTick, n);
|
|
1256
|
+
let r = this.constrainWorldMovement();
|
|
1257
|
+
x.decodeUnknownSync(x.toType(this.descriptor.state))(this.state), this.worldMovementClamps += r.clamped, this.worldMovementIntents = r.intents;
|
|
1258
|
+
for (let t of e) {
|
|
1259
|
+
let e = this.members.get(t.client.sessionId);
|
|
1260
|
+
e && (e.lastAppliedSeq = Math.max(e.lastAppliedSeq, t.seq));
|
|
1261
|
+
}
|
|
1262
|
+
this.lastGoodState = structuredClone(this.state), this.lastGoodTick = this.tick, this.nextTickAt += t;
|
|
1263
|
+
}
|
|
1264
|
+
} catch (e) {
|
|
1265
|
+
throw this.faulted = !0, this.worldMovementIntents = Object.freeze([]), this.currentState = structuredClone(this.lastGoodState), this.outbox.splice(o), this.clientOperations.splice(s), this.schedules.clear(), this.inputs.length = 0, e;
|
|
1266
|
+
} finally {
|
|
1267
|
+
this.ticking = !1;
|
|
1268
|
+
}
|
|
1269
|
+
return a;
|
|
1270
|
+
}
|
|
1271
|
+
drainOutbox() {
|
|
1272
|
+
if (this.lifecycleActive) throw new r({
|
|
1273
|
+
room: this.descriptor.name,
|
|
1274
|
+
detail: "Lifecycle commands are drained only after the hook commits"
|
|
1275
|
+
});
|
|
1276
|
+
if (this.ticking) throw new r({
|
|
1277
|
+
room: this.descriptor.name,
|
|
1278
|
+
detail: "The app outbox can only be drained outside the tick"
|
|
1279
|
+
});
|
|
1280
|
+
return this.outbox.splice(0);
|
|
1281
|
+
}
|
|
1282
|
+
stageClientOperation(t) {
|
|
1283
|
+
if (this.clientOperations.length >= this.descriptor.budgets.maxOutboxCommands) throw new e({
|
|
1284
|
+
budget: "maxClientOperations",
|
|
1285
|
+
limit: this.descriptor.budgets.maxOutboxCommands
|
|
1286
|
+
});
|
|
1287
|
+
this.clientOperations.push(t);
|
|
1288
|
+
}
|
|
1289
|
+
flushClientOperations() {
|
|
1290
|
+
if (this.lifecycleActive) throw new r({
|
|
1291
|
+
room: this.descriptor.name,
|
|
1292
|
+
detail: "Lifecycle client operations flush only after the hook commits"
|
|
1293
|
+
});
|
|
1294
|
+
if (this.ticking) throw new r({
|
|
1295
|
+
room: this.descriptor.name,
|
|
1296
|
+
detail: "Client operations must be flushed outside the tick"
|
|
1297
|
+
});
|
|
1298
|
+
let e = [];
|
|
1299
|
+
for (let t of this.clientOperations.splice(0)) try {
|
|
1300
|
+
t();
|
|
1301
|
+
} catch (t) {
|
|
1302
|
+
e.push(t);
|
|
1303
|
+
}
|
|
1304
|
+
if (e.length) throw AggregateError(e, "Room client delivery failed");
|
|
1305
|
+
}
|
|
1306
|
+
inputReceiptSnapshot() {
|
|
1307
|
+
if (this.ticking || this.isLifecycleBusy()) throw new r({
|
|
1308
|
+
room: this.id,
|
|
1309
|
+
detail: "Input receipts are captured outside committed lifecycle and tick work"
|
|
1310
|
+
});
|
|
1311
|
+
this.pruneGrace(this.drainAt ?? this.publicationHeldAt ?? this.worldFinalizationHold?.heldAt ?? this.now());
|
|
1312
|
+
let e = [];
|
|
1313
|
+
for (let [t, n] of this.members) !n.client.isBot && (n.lastAppliedSeq >= 0 || n.lastDiscardedSeq >= 0) && e.push({
|
|
1314
|
+
sessionId: t,
|
|
1315
|
+
subjectId: n.client.subjectId,
|
|
1316
|
+
lastAppliedSeq: n.lastAppliedSeq,
|
|
1317
|
+
...n.lastDiscardedSeq < 0 ? {} : { discardedThrough: n.lastDiscardedSeq }
|
|
1318
|
+
});
|
|
1319
|
+
for (let [t, n] of this.grace) (n.lastAppliedSeq >= 0 || (n.discardedThrough ?? -1) >= 0) && e.push({
|
|
1320
|
+
sessionId: t,
|
|
1321
|
+
subjectId: n.subjectId,
|
|
1322
|
+
lastAppliedSeq: n.lastAppliedSeq,
|
|
1323
|
+
...(n.discardedThrough ?? -1) < 0 ? {} : { discardedThrough: n.discardedThrough }
|
|
1324
|
+
});
|
|
1325
|
+
return p(e, Math.min(this.descriptor.maxClients, 16384));
|
|
1326
|
+
}
|
|
1327
|
+
confirmInputCheckpoint(e) {
|
|
1328
|
+
for (let t of p(e, Math.min(this.descriptor.maxClients, 16384))) {
|
|
1329
|
+
let e = this.members.get(t.sessionId);
|
|
1330
|
+
e?.worldTarget && e.client.subjectId === t.subjectId && t.discardedThrough !== void 0 && (e.checkpointedDiscardedSeq = Math.max(e.checkpointedDiscardedSeq, Math.min(e.lastDiscardedSeq, t.discardedThrough)));
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
lastAppliedInputSeq(e) {
|
|
1334
|
+
let n = this.members.get(e);
|
|
1335
|
+
if (!n) throw new t({ sessionId: e });
|
|
1336
|
+
return n.lastAppliedSeq;
|
|
1337
|
+
}
|
|
1338
|
+
lock() {
|
|
1339
|
+
this.ensureOpen(), this.locked = !0;
|
|
1340
|
+
}
|
|
1341
|
+
unlock() {
|
|
1342
|
+
this.ensureOpen(), this.locked = !1;
|
|
1343
|
+
}
|
|
1344
|
+
dispose() {
|
|
1345
|
+
if (this.assertSynchronousLifecycle(), this.disposed) return;
|
|
1346
|
+
if (this.ticking) throw new r({
|
|
1347
|
+
room: this.descriptor.name,
|
|
1348
|
+
detail: "Dispose must run outside the tick"
|
|
1349
|
+
});
|
|
1350
|
+
this.retiring = !1;
|
|
1351
|
+
let e = [];
|
|
1352
|
+
try {
|
|
1353
|
+
for (let [t, n] of this.members) try {
|
|
1354
|
+
this.faulted ? n.client.kick("Room fault") : this.leave(t, "disposed");
|
|
1355
|
+
} catch (t) {
|
|
1356
|
+
e.push(t);
|
|
1357
|
+
}
|
|
1358
|
+
if (!this.faulted) try {
|
|
1359
|
+
C(this.descriptor.name, this.descriptor.onDispose, {
|
|
1360
|
+
state: this.state,
|
|
1361
|
+
app: this.app,
|
|
1362
|
+
...this.geometry ? { geometry: this.geometry } : {}
|
|
1363
|
+
});
|
|
1364
|
+
} catch (t) {
|
|
1365
|
+
e.push(t);
|
|
1366
|
+
}
|
|
1367
|
+
} finally {
|
|
1368
|
+
this.disposed = !0, this.members.clear(), this.grace.clear(), this.schedules.clear(), this.inputs.length = 0;
|
|
1369
|
+
}
|
|
1370
|
+
if (e.length) throw AggregateError(e, "Room disposal failed");
|
|
1371
|
+
}
|
|
1372
|
+
isDisposed() {
|
|
1373
|
+
return this.disposed;
|
|
1374
|
+
}
|
|
1375
|
+
isLifecycleBusy() {
|
|
1376
|
+
return this.lifecycleRequests > 0;
|
|
1377
|
+
}
|
|
1378
|
+
async freezeForDrain() {
|
|
1379
|
+
if (this.ticking || this.disposed || this.faulted || this.retiring) throw new o({ roomId: this.id });
|
|
1380
|
+
this.drainFrozen ||= (this.drainAt = this.now(), !0), await this.lifecycleTail;
|
|
1381
|
+
}
|
|
1382
|
+
releaseTransferred() {
|
|
1383
|
+
if (this.assertSynchronousLifecycle(), !this.disposed) {
|
|
1384
|
+
if (!this.drainFrozen || this.ticking || this.outbox.length || this.clientOperations.length) throw new r({
|
|
1385
|
+
room: this.id,
|
|
1386
|
+
detail: "Transferred actor release requires its frozen committed boundary"
|
|
1387
|
+
});
|
|
1388
|
+
this.disposed = !0, this.members.clear(), this.grace.clear(), this.schedules.clear(), this.inputs.length = 0;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
disposeAsync(e) {
|
|
1392
|
+
return this.serializeLifecycle(async () => {
|
|
1393
|
+
if (this.disposed) return;
|
|
1394
|
+
this.retiring = !1;
|
|
1395
|
+
let t = [];
|
|
1396
|
+
try {
|
|
1397
|
+
for (let [n, r] of this.members) try {
|
|
1398
|
+
if (this.faulted || this.worldFinalizationHold) r.client.kick("Room fault");
|
|
1399
|
+
else {
|
|
1400
|
+
let t = this.prepareLeave(n, "disposed");
|
|
1401
|
+
t.pending || await this.mutateMemberAsync((e) => this.descriptor.onLeave?.(t.context(e)), e), t.commit();
|
|
1402
|
+
}
|
|
1403
|
+
} catch (e) {
|
|
1404
|
+
t.push(e);
|
|
1405
|
+
}
|
|
1406
|
+
if (!this.faulted && !this.worldFinalizationHold) try {
|
|
1407
|
+
await this.mutateMemberAsync((e) => this.descriptor.onDispose?.({
|
|
1408
|
+
state: e,
|
|
1409
|
+
app: this.app,
|
|
1410
|
+
...this.geometry ? { geometry: this.geometry } : {}
|
|
1411
|
+
}), e);
|
|
1412
|
+
} catch (e) {
|
|
1413
|
+
t.push(e);
|
|
1414
|
+
}
|
|
1415
|
+
} finally {
|
|
1416
|
+
this.disposed = !0, this.worldFinalizationHold = null, this.deferredWorldLeaves.clear(), this.members.clear(), this.grace.clear(), this.schedules.clear(), this.inputs.length = 0;
|
|
1417
|
+
}
|
|
1418
|
+
if (t.length) throw AggregateError(t, "Asynchronous Room disposal failed");
|
|
1419
|
+
}, () => this.worldFinalizationHold?.requestId);
|
|
1420
|
+
}
|
|
1421
|
+
isRetiring() {
|
|
1422
|
+
return this.retiring;
|
|
1423
|
+
}
|
|
1424
|
+
releaseHibernated() {
|
|
1425
|
+
if (this.assertSynchronousLifecycle(), !this.disposed) {
|
|
1426
|
+
if (this.ticking || !this.retiring || this.members.size || this.grace.size || this.outbox.length) throw new r({
|
|
1427
|
+
room: this.descriptor.name,
|
|
1428
|
+
detail: "Hibernate release requires an empty retired actor and committed outbox"
|
|
1429
|
+
});
|
|
1430
|
+
this.disposed = !0, this.retiring = !1, this.members.clear(), this.grace.clear(), this.schedules.clear(), this.inputs.length = 0, this.clientOperations.length = 0;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
isFaulted() {
|
|
1434
|
+
return this.faulted;
|
|
1435
|
+
}
|
|
1436
|
+
commandCursor() {
|
|
1437
|
+
return this.commandSeq;
|
|
1438
|
+
}
|
|
1439
|
+
scheduleSnapshot() {
|
|
1440
|
+
if (this.ticking) throw new r({
|
|
1441
|
+
room: this.descriptor.name,
|
|
1442
|
+
detail: "Schedules are checkpointed outside the tick"
|
|
1443
|
+
});
|
|
1444
|
+
let e = this.drainAt ?? this.publicationHeldAt ?? this.worldFinalizationHold?.heldAt ?? this.now();
|
|
1445
|
+
return [...this.schedules.values()].map((t) => Object.freeze({
|
|
1446
|
+
label: t.label,
|
|
1447
|
+
remainingMs: Math.max(0, t.at - e),
|
|
1448
|
+
intervalMs: t.interval
|
|
1449
|
+
}));
|
|
1450
|
+
}
|
|
1451
|
+
lastGoodSnapshot() {
|
|
1452
|
+
if (this.ticking) throw new r({
|
|
1453
|
+
room: this.descriptor.name,
|
|
1454
|
+
detail: "Fault snapshots are taken outside the tick"
|
|
1455
|
+
});
|
|
1456
|
+
return {
|
|
1457
|
+
tick: this.lastGoodTick,
|
|
1458
|
+
state: structuredClone(this.lastGoodState)
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
metrics() {
|
|
1462
|
+
return {
|
|
1463
|
+
tick: this.tick,
|
|
1464
|
+
droppedTicks: this.droppedTicks,
|
|
1465
|
+
queuedInputs: this.inputs.length,
|
|
1466
|
+
outboxCommands: this.outbox.length,
|
|
1467
|
+
clients: this.members.size,
|
|
1468
|
+
extensionDurationMs: { ...this.extensionDurationMs },
|
|
1469
|
+
extensionOverruns: { ...this.extensionOverruns }
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
export { w as RoomActor };
|