@hology/core 0.0.256 → 0.0.258
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gameplay/game-mode.d.ts +3 -2
- package/dist/gameplay/game-mode.js +1 -1
- package/dist/gameplay/game-state-context.d.ts +13 -0
- package/dist/gameplay/game-state-context.js +4 -0
- package/dist/gameplay/index.d.ts +2 -1
- package/dist/gameplay/index.js +1 -1
- package/dist/gameplay/initiate.js +1 -1
- package/dist/gameplay/net/player-session.d.ts +6 -0
- package/dist/gameplay/net/player-session.js +1 -1
- package/dist/gameplay/net/service/net-service.d.ts +3 -2
- package/dist/gameplay/net/service/net-service.js +1 -1
- package/dist/gameplay/network-travel-coordinator.js +1 -1
- package/dist/gameplay/player-lifecycle-coordinator.d.ts +2 -1
- package/dist/gameplay/player-lifecycle-coordinator.js +1 -1
- package/dist/gameplay/{players.d.ts → player-sessions.d.ts} +7 -10
- package/dist/gameplay/player-sessions.js +4 -0
- package/dist/rendering/node-material-webgpu-resolver.js +1 -1
- package/dist/rendering/webgpu-experimental-backend.d.ts +1 -0
- package/dist/rendering/webgpu-experimental-backend.js +1 -1
- package/dist/rendering.d.ts +4 -0
- package/dist/rendering.js +1 -1
- package/dist/test/game-state-context.test.d.ts +2 -0
- package/dist/test/game-state-context.test.js +4 -0
- package/dist/test/player-sessions.test.d.ts +2 -0
- package/dist/test/player-sessions.test.js +4 -0
- package/package.json +2 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/gameplay/players.js +0 -4
- package/dist/test/players.test.d.ts +0 -2
- package/dist/test/players.test.js +0 -4
|
@@ -2,7 +2,7 @@ import type { BaseActor } from './actors/actor.js';
|
|
|
2
2
|
import type { BasePlayerController, BasePlayerState } from './actors/controller/actor-controller.js';
|
|
3
3
|
import type { BaseGameState } from './actors/controller/game-state.js';
|
|
4
4
|
import type { PlayerSession } from './net/player-session.js';
|
|
5
|
-
import {
|
|
5
|
+
import { PlayerSessions } from './player-sessions.js';
|
|
6
6
|
import { World } from './services/world.js';
|
|
7
7
|
/**
|
|
8
8
|
* Authority-side policy for creating players and applying game-specific
|
|
@@ -10,7 +10,8 @@ import { World } from './services/world.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export declare abstract class GameMode {
|
|
12
12
|
protected readonly world: World;
|
|
13
|
-
protected readonly
|
|
13
|
+
protected readonly playerSessions: PlayerSessions;
|
|
14
|
+
private readonly gameStateContext;
|
|
14
15
|
/** The authoritative game state installed by the lifecycle coordinator. */
|
|
15
16
|
protected get gameState(): BaseGameState | undefined;
|
|
16
17
|
/** Create shared replicated match state, or return nothing if the game does not need it. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{GameStateContext as e}from"./game-state-context.js";import{inject as t}from"./inject.js";import{PlayerSessions as o}from"./player-sessions.js";import{World as r}from"./services/world.js";export class GameMode{constructor(){this.world=t(r),this.playerSessions=t(o),this.gameStateContext=t(e)}get gameState(){return this.gameStateContext.get()}createGameState(){}spawnDefaultPawn(e,t){}onPlayerReady(e){}onPlayerLeaving(e){const t=e.controller;if(null!=t?.pawn){const e=t.pawn;t.unpossess(),this.world.removeActor(e)}null!=t&&this.playerSessions.clearOwningConnection(t),null!=e.playerState&&(e.playerState.connected=!1)}onStart(){}onStop(){}onTravelStarted(e){}onTravelCompleted(e){}onPlayerLifecycleError(e,t){console.error(`Player lifecycle operation failed for ${JSON.stringify(e.info.id)}`,t)}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BaseGameState } from './actors/controller/game-state.js';
|
|
2
|
+
/** Access to the replicated game state currently installed in this game session. */
|
|
3
|
+
export declare class GameStateContext {
|
|
4
|
+
private current?;
|
|
5
|
+
private readonly revisionSubject;
|
|
6
|
+
readonly changed$: import("rxjs").Observable<number>;
|
|
7
|
+
private validator?;
|
|
8
|
+
get revision(): number;
|
|
9
|
+
get<T extends BaseGameState = BaseGameState>(): T | undefined;
|
|
10
|
+
require<T extends BaseGameState = BaseGameState>(): T;
|
|
11
|
+
private notifyChanged;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=game-state-context.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{__decorate as t}from"tslib";import{BehaviorSubject as i}from"rxjs";import{Service as r}from"typedi";let e=class{constructor(){this.revisionSubject=new i(0),this.changed$=this.revisionSubject.asObservable()}get revision(){return this.revisionSubject.value}get(){return this.current}require(){const t=this.get();if(null==t)throw new Error("Game state is not available");return t}set(t){this.validator?.(t),this.current!==t&&(this.current=t,this.notifyChanged())}clear(t){null==this.current||null!=t&&this.current!==t||(this.current=void 0,this.notifyChanged())}setValidator(t){this.validator=t}stop(){this.clear(),this.validator=void 0}notifyChanged(){this.revisionSubject.next(this.revisionSubject.value+1)}};e=t([r()],e);export{e as GameStateContext};/*
|
|
2
|
+
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
|
+
* See the LICENSE.md file for details.
|
|
4
|
+
*/
|
package/dist/gameplay/index.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ export { Container as DIContainer } from 'typedi';
|
|
|
3
3
|
export * from './initiate.js';
|
|
4
4
|
export * from './game-mode.js';
|
|
5
5
|
export * from './player-lifecycle-coordinator.js';
|
|
6
|
-
export * from './
|
|
6
|
+
export * from './player-sessions.js';
|
|
7
|
+
export * from './game-state-context.js';
|
|
7
8
|
export * from './network-travel-coordinator.js';
|
|
8
9
|
export * from './rendering-host.js';
|
|
9
10
|
export * from './inject.js';
|
package/dist/gameplay/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import"reflect-metadata";export{Container as DIContainer}from"typedi";export*from"./initiate.js";export*from"./game-mode.js";export*from"./player-lifecycle-coordinator.js";export*from"./
|
|
1
|
+
import"reflect-metadata";export{Container as DIContainer}from"typedi";export*from"./initiate.js";export*from"./game-mode.js";export*from"./player-lifecycle-coordinator.js";export*from"./player-sessions.js";export*from"./game-state-context.js";export*from"./network-travel-coordinator.js";export*from"./rendering-host.js";export*from"./inject.js";export{Service,Inject}from"typedi";export{ActorFactory}from"./actors/factory.js";export{Actor,ActorLifetime,BaseActor,getActorClassById,getActorClassId,getRegisteredActorClasses,getActorOptions}from"./actors/actor.js";export{Component,ActorComponent,attach,Attach,getComponentClassById,getComponentClassId,getRegisteredComponentClasses}from"./actors/component.js";export*from"./services/world.js";export*from"./services/scene-transition.js";export*from"./services/game-time-scheduler.js";export*from"./services/camera-shake.js";export*from"./services/render.js";export*from"./services/physics/physics-system.js";export*from"./animation/anim-sm.js";export*from"./animation/root-motion.js";export*from"./animation/retarget.js";export*from"./services/asset-loader.js";export*from"./services/pointer-events.js";export{VisualEffect}from"../effects/vfx/vfx-param.js";export{DataAssetRef}from"../scene/objects/data-asset.js";export{SceneRef,sceneId}from"../scene/scene-ref.js";export*from"../shader/parameter.js";export*from"./ai/index.js";export*from"../effects/vfx/index.js";export*from"./services/shader-provider.js";export{EventGraphCallable,EventGraphEventSource,EventGraphProperty,getEventGraphCallableInfo,getEventGraphCallableInfos,getEventGraphCallableMethods,getEventGraphEventSourceInfo,getEventGraphEventSourceInfos,getEventGraphEventSourceMethods,getEventGraphPropertyInfo,getEventGraphPropertyInfos,getEventGraphProperties,isEventGraphEventSource}from"./event-graph/decorators.js";export{EventGraphCompiler,validateEventGraphDocument}from"./event-graph/document-compiler.js";export{createDefaultEventGraphDocument,hasExecutableEventGraphDocument}from"./event-graph/model.js";export{EVENT_GRAPH_NODE_DEFINITIONS,getEventGraphNodePorts,getEventGraphPortDefinition}from"./event-graph/registry.js";export{EventGraphInstance}from"./event-graph/runtime.js";export{analyzeEventGraphTypes,assertEventGraphTypes,createEventGraphTypeContext,getEventGraphMembersForType}from"./event-graph/type-inference.js";export{EVENT_GRAPH_ACTOR_TYPE,EVENT_GRAPH_ACTOR_CLASS_TYPE,EVENT_GRAPH_ANY_TYPE,EVENT_GRAPH_BOOLEAN_TYPE,EVENT_GRAPH_COMPONENT_TYPE,EVENT_GRAPH_NUMBER_TYPE,EVENT_GRAPH_OBJECT_TYPE,EVENT_GRAPH_STRING_TYPE,EVENT_GRAPH_VOID_TYPE,EVENT_GRAPH_VECTOR2_TYPE,EVENT_GRAPH_VECTOR3_TYPE,eventGraphActorClassType,eventGraphActorType,eventGraphComponentType,eventGraphTypeName,isEventGraphTypeAssignable}from"./event-graph/types.js";export*from"./net/net-connection.js";export*from"./net/net-session.js";/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e from"typedi";import{BehaviorSubject as t,Subject as i}from"rxjs";import{loadScene as n}from"../scene/bootstrap.js";import{ActorFactory as s}from"./actors/factory.js";import{SceneMaterializer as o}from"../scene/materializer.js";import{SceneDataService as r}from"../scene/scene-data-service.js";import{World as a}from"./services/world.js";import{ViewController as c}from"./services/render.js";import{RenderingView as l,GraphicsRuntime as p}from"../rendering.js";import{PhysicsSystem as h}from"./services/physics/physics-system.js";import{MeshComponent as d}from"./actors/builtin/components/mesh-component.js";import{builtInComponents as m}from"./actors/builtin/components/index.js";import{InputService as u}from"./input/index.js";import{RuntimeAssetsService as g}from"../scene/runtime-asset-service.js";import{AssetResourceLoader as w}from"../scene/asset-resource-loader.js";import{AssetLoader as f}from"./services/asset-loader.js";import{RuntimeBundledBackendService as y}from"../scene/runtime-bundled-backend-service.js";import{AssetsProvider as S}from"../scene/assets-provider.js";import{ShaderProvider as v}from"./services/shader-provider.js";import{PointerEvents as b}from"./services/pointer-events.js";import{GpuVfxWorldService as I}from"../effects/vfx/gpu-vfx-world-service.js";import{NetMode as j}from"./net/net-session.js";import{Players as A}from"./players.js";import{polyfillClient as V}from"./polyfill.js";import{withInjectionContext as P}from"./inject.js";import{RenderingHost as R}from"./rendering-host.js";import{SceneTransitionService as N}from"./services/scene-transition.js";import{SceneRef as x}from"../scene/scene-ref.js";let z=0;export function initiateApplication(t,i){const n="function"==typeof t?t:void 0,s=i??t;var o;V(),"element"in s&&null!=s.element&&(o=s.element,Object.assign(o.style,{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",overflow:"hidden"}));const r=s.renderingHost??(null!=s.renderingView?new R(s.renderingView,!1,("renderingViewFactory"in s?s.renderingViewFactory:void 0)??(()=>new l(s.renderingView.container,s.renderingView.options))):function(e,t,i){const n=new p,s=()=>function(e,t,i,n){return new l(e,{graphicsRuntime:n,backend:t?.backend??"webgpu",enableXR:!0===i?.enabled,maxPixelRatio:t?.maxPixelRatio,resolutionScale:t?.resolutionScale,dynamicResolution:t?.dynamicResolution,upscaling:t?.upscaling,webgpuPostProcessing:t?.webgpuPostProcessing,webgpuDirectionalShadows:t?.webgpuDirectionalShadows,webgpuVfx:t?.webgpuVfx??"auto",msaa:t?.msaa,fpsCap:t?.fpsCap,depthPrepass:{enabled:!0===t?.depthPrepass?.enabled},bloom:{enabled:!1!==t?.bloom?.enabled},reflection:{enabled:!1!==t?.reflection?.enabled},occlusionCulling:{enabled:!1!==t?.occlusionCulling?.enabled},shadows:{enabled:t?.shadows?.enabled??!0,autoUpdate:t?.shadows?.autoUpdate??!0,cascadeUpdateIntervals:[22,40,60,120]}})}(e,t,i,n);return new R(s(),!0,s)}(s.element,s.rendering,s.xr)),a=e.of("hology-application-"+ ++z),c=new HologyApplicationRuntime(s,a,r,n??s.defaultGameClass);return c.startInitialization(),c}export function initiateGame(e,t){const{sceneName:i,detailTier:n,multi:s,localPlayer:o,...r}=t,a=initiateApplication(e,r),c=a.ready.then(()=>a.loadGame({sceneName:i,detailTier:n,multi:s,localPlayer:o}));return new HologyRuntime(a,c)}export class GameInstance{onPreload(){}onStart(){}onUnload(){return this.onShutdown()}onShutdown(){}}export class HologyApplicationRuntime{constructor(e,n,s,o){this.config=e,this.applicationContainer=n,this.renderingHost=s,this.defaultGameClass=o,this.stateSubject=new t({type:"initializing"}),this.state=this.stateSubject.asObservable(),this.shutdownSubject=new i,this.shutdownStarted=this.shutdownSubject.asObservable(),this.transitionTail=Promise.resolve(),this.initializationPromise=Promise.resolve(),this.transitionIntent=0,this.shutdownFlag=!1,this.ready=new Promise((e,t)=>{this.resolveReady=e,this.rejectReady=t})}get renderingView(){return this.renderingHost.activeView}get currentState(){return this.stateSubject.value}startInitialization(){this.initializationPromise=this.initialize()}async initialize(){try{if(this.registerApplicationServices(),await this.renderingHost.applicationView.compileAsync(),this.shutdownFlag)return void this.rejectReady(C());const e=this.getApplicationService(u);this.renderingHost.start(t=>e.update(t)),this.setState({type:"menu"}),this.resolveReady()}catch(e){this.setState({type:"error",operation:"initialize",error:e}),this.rejectReady(e)}}loadGame(e,t){const i="function"==typeof e?e:this.defaultGameClass,n=t??e;if(null==i)return Promise.reject(new Error("loadGame() requires a game class because no default was configured"));const s=++this.transitionIntent;return this.enqueue(async()=>(await this.ready,this.assertRunning(),this.checkIntent(s),null!=this.session&&await this.disposeSession(this.session,!0),this.checkIntent(s),this.performLoad(i,n,s)))}unloadGame(){const e=++this.transitionIntent;return this.enqueue(async()=>{if(await this.ready,!this.shutdownFlag&&e===this.transitionIntent){try{null!=this.session&&await this.disposeSession(this.session,!0)}catch(t){throw this.shutdownFlag||e!==this.transitionIntent||this.setState({type:"error",operation:"unload",error:t}),t}this.shutdownFlag||e!==this.transitionIntent||this.setState({type:"menu"})}})}transitionScene(e){const t="string"==typeof e?{scene:new x(e,e)}:e instanceof x?{scene:e}:e,i=this.transitionIntent;return this.enqueue(async()=>{await this.ready,this.assertRunning(),this.checkIntent(i);const e=this.session;if(null==e)throw new Error("transitionScene() requires an active game session");try{return await this.performSceneTransition(e,t,i),e}catch(e){throw k(e)||this.shutdownFlag||i!==this.transitionIntent||this.setState({type:"error",operation:"load",error:e}),e}})}async shutdown(){this.shutdownFlag||(this.shutdownFlag=!0,++this.transitionIntent,this.shutdownSubject.next(),this.shutdownSubject.complete(),await this.initializationPromise.catch(()=>{}),await this.transitionTail.catch(()=>{}),null!=this.session&&await this.disposeSession(this.session,!1),this.getApplicationService(u).stop(),this.getOptionalApplicationService(b)?.stop(),this.getApplicationService(w).disposeAll(),this.getApplicationService(g).dispose(),this.getApplicationService(c).dispose(!1),this.renderingHost.shutdown(),e.reset(this.applicationContainer.id),this.setState({type:"stopped"}),this.stateSubject.complete())}getWorld(){return this.session?.world}getGameInstance(){return this.session?.gameInstance}getApplicationService(e){return this.applicationContainer.get(e)}getSessionService(e){return this.session?.container.get(e)}getService(e){return null!=this.session?this.session.container.get(e):this.applicationContainer.get(e)}registerRenderStage(e){return this.renderingHost.registerStage(e)}registerApplicationServices(){const e=new c(this.renderingView);this.applicationContainer.set(l,this.renderingHost.applicationView),this.applicationContainer.set(c,e);const t=new y(this.config.runtimeDataUrl),i=new g(t),n=new w;n.setDataDir(this.config.dataDir),n.setDirectFileLoading(!0===this.config.directAssetFileLoading),"webgpu"===this.renderingHost.applicationView.requestedBackend?n.initKtx2ForWebGpu():n.initKtx2(this.renderingHost.applicationView.renderer);const s=Object.entries(this.config.shaders).map(([e,t])=>({name:e,type:t}));this.applicationContainer.set(y,t),this.applicationContainer.set(S,i),this.applicationContainer.set(g,i),this.applicationContainer.set(w,n),this.applicationContainer.set(f,new f(n,i,s)),this.applicationContainer.set(v,new v(s));const o=P(this.applicationContainer,e=>e.get(u));this.applicationContainer.set(u,o)}async performLoad(t,i,s){const r=this.createSession(i);this.session=r;try{this.loading(i.sceneName,"physics",.08),await r.physics.start(),this.checkIntent(s),this.loading(i.sceneName,"runtime-data",.2),await this.getApplicationService(y).preloadData(),this.checkIntent(s),this.loading(i.sceneName,"scene",.42);const a=await n(r.renderingView,i.sceneName,this.config.dataDir,this.config.shaders,this.config.actors,r.components,r.actorFactory,this.getApplicationService(y),this.getApplicationService(g),this.getApplicationService(w),{detailTier:i.detailTier,nodeMode:i.multi?.session.mode??j.none});if(r.materializer.dispose(),r.materializer=a.materializer,r.sceneRoots=a.sceneRoots,r.container.set(o,a.materializer),r.world.materializer=a.materializer,r.world.scene=a.scene,r.physics.scene=a.scene,this.checkIntent(s),null!=i.multi?.session){const{NetService:e}=await import("./net/service/net-service.js"),t=P(r.container,t=>t.get(e));t.session=i.multi.session,t.start(),r.stopPlayers=()=>t.stop()}e.import([d]);for(const e of a.actors)r.world.addActor(e);if(r.physics.addFromScene(a.scene),await r.world.startSceneEventGraphs(a.materializer),this.checkIntent(s),r.gameInstance=P(r.container,e=>(e.set({id:t,type:t}),e.get(t))),this.loading(i.sceneName,"game-preload",.58),r.gameInstance instanceof GameInstance&&await r.gameInstance.onPreload(),this.checkIntent(s),this.loading(i.sceneName,"shaders",.7),await r.renderingView.compileAsync(),this.checkIntent(s),this.loading(i.sceneName,"textures",.82),await r.renderingView.initTexturesAsync(),this.checkIntent(s),"webgpu"===r.renderingView.activeBackend){this.loading(i.sceneName,"vfx-warmup",.94);const e=r.container.get(I).beginPipelineWarmup();try{const e=performance.now();console.info("[Hology GPU VFX warmup] Rendering loading frame",i.sceneName),r.renderingView.render(0),console.info("[Hology GPU VFX warmup] Render returned; waiting for GPU queue",{sceneName:i.sceneName,renderMs:performance.now()-e}),await(r.renderingView.webGpuDevice?.queue.onSubmittedWorkDone()),console.info("[Hology GPU VFX warmup] GPU queue completed",{sceneName:i.sceneName,totalMs:performance.now()-e})}finally{e()}}return this.checkIntent(s),this.renderingHost.activate(r.renderingView),r.gameInstance instanceof GameInstance&&await r.gameInstance.onStart(),this.checkIntent(s),this.setState({type:"playing",sceneName:i.sceneName}),r}catch(e){throw await this.disposeSession(r,!1),k(e)||this.shutdownFlag||s!==this.transitionIntent||this.setState({type:"error",operation:"load",error:e}),e}}createSession(t){const i=e.of("hology-session-"+ ++z);this.copyApplicationServices(i);const n=this.renderingHost.createView(),p=new c(n);i.set(l,n),i.set(c,p);const d={...m,...this.config.components??{}},u=new s(i,{inEditor:!1});u.classes=this.config.actors,i.set(s,u);const f=new o(n.scene,new r,this.getApplicationService(g),this.getApplicationService(w),n,Object.entries(this.config.shaders).map(([e,t])=>({name:e,type:t})),Object.entries(this.config.actors).map(([e,t])=>({name:e,type:t})),u,Object.entries(d).map(([e,t])=>({name:e,type:t})));f.inEditor=!1,f.netMode=t.multi?.session.mode??j.none,i.set(o,f);const y=P(i,e=>e.get(a));y.scene=n.scene,i.set(a,y);const S=P(i,e=>e.get(h));i.set(h,S);const v=new N;v.bind(async e=>{await this.transitionScene(e)}),i.set(N,v);const b=P(i,e=>e.get(A));return i.set(A,b),null==t.multi?.session&&b.startStandalone(t.localPlayer??{id:"local-player",displayName:"Player"}),{sceneName:t.sceneName,detailTier:t.detailTier,container:i,renderingView:n,viewController:p,actorFactory:u,materializer:f,world:y,physics:S,components:d,sceneTransition:v,sceneRoots:[],netMode:t.multi?.session.mode??j.none,stopPlayers:null==t.multi?.session?()=>b.stop():void 0}}async performSceneTransition(e,t,i){if(e.netMode!==j.none)throw new Error("In-session scene transitions are not yet supported for network sessions");const s=t.scene.id.trim();if(0===s.length)throw new Error("A destination scene id is required");const r=e.sceneName;this.setState({type:"unloading",sceneName:r}),e.world.stopSceneEventGraphs();const a=e.world.removeSceneLifetimeActors(e.sceneRoots);for(const t of e.sceneRoots)e.physics.removeRemoved(t),t.removeFromParent();e.sceneRoots=[],e.materializer.dispose(a),this.checkIntent(i),this.loading(s,"scene",.42);const c=await n(e.renderingView,s,this.config.dataDir,this.config.shaders,this.config.actors,e.components,e.actorFactory,this.getApplicationService(y),this.getApplicationService(g),this.getApplicationService(w),{detailTier:e.detailTier,nodeMode:e.netMode});this.checkIntent(i),e.materializer=c.materializer,e.sceneRoots=c.sceneRoots,e.sceneName=s,e.container.set(o,c.materializer),e.world.materializer=c.materializer;for(const t of c.actors)e.world.addActor(t);e.physics.addFromScene(c.scene),await e.world.startSceneEventGraphs(c.materializer),await(t.afterLoad?.(e.world)),this.checkIntent(i),this.loading(s,"shaders",.7),await e.renderingView.compileAsync(),this.checkIntent(i),this.loading(s,"textures",.82),await e.renderingView.initTexturesAsync(),this.checkIntent(i),e.sceneTransition.publish({previousSceneName:r,sceneName:s}),this.setState({type:"playing",sceneName:s})}copyApplicationServices(e){const t=[u,S,g,y,w,f,v];for(const i of t)e.set(i,this.applicationContainer.get(i))}async disposeSession(t,i){if(t.disposed)return;let n;t.disposed=!0,i&&this.setState({type:"unloading",sceneName:t.sceneName});try{t.stopPlayers?.(),t.sceneTransition.dispose(),t.world.stopSceneEventGraphs(),t.gameInstance instanceof GameInstance&&await t.gameInstance.onUnload()}catch(e){n=e}finally{for(const e of[...t.world.actors])t.world.removeActor(e);t.physics.stop(),t.materializer.dispose(),this.renderingHost.activeView===t.renderingView&&this.renderingHost.activate(this.renderingHost.applicationView),t.viewController.dispose(!1),this.renderingHost.releaseView(t.renderingView),e.reset(t.container.id),this.session===t&&(this.session=void 0)}if(null!=n)throw n}enqueue(e){const t=this.transitionTail.then(e,e);return this.transitionTail=t.catch(()=>{}),t}checkIntent(e){if(this.assertRunning(),e!==this.transitionIntent)throw C()}assertRunning(){if(this.shutdownFlag)throw new Error("Hology application has been shut down")}loading(e,t,i){this.setState({type:"loading",sceneName:e,stage:t,progress:i})}setState(e){this.stateSubject.next(e)}getOptionalApplicationService(e){return this.applicationContainer.has(e)?this.applicationContainer.get(e):void 0}}export class HologyRuntime{constructor(e,t){this.application=e,this.ready=t.then(()=>!0),this.shutdownStarted=e.shutdownStarted}get status(){return"playing"===this.application.currentState.type?5:"stopped"===this.application.currentState.type||"error"===this.application.currentState.type?10:0}get gameInstance(){return this.application.getGameInstance()}get isShutdown(){return"stopped"===this.application.currentState.type}getWorld(){return this.application.getWorld()}getService(e){return this.application.getService(e)}shutdown(){return this.application.shutdown()}}function C(){return new DOMException("Superseded by a newer game transition","AbortError")}function k(e){return e instanceof DOMException&&"AbortError"===e.name}/*
|
|
1
|
+
import e from"typedi";import{BehaviorSubject as t,Subject as i}from"rxjs";import{loadScene as n}from"../scene/bootstrap.js";import{ActorFactory as s}from"./actors/factory.js";import{SceneMaterializer as o}from"../scene/materializer.js";import{SceneDataService as r}from"../scene/scene-data-service.js";import{World as a}from"./services/world.js";import{ViewController as c}from"./services/render.js";import{RenderingView as l,GraphicsRuntime as p}from"../rendering.js";import{PhysicsSystem as h}from"./services/physics/physics-system.js";import{MeshComponent as d}from"./actors/builtin/components/mesh-component.js";import{builtInComponents as m}from"./actors/builtin/components/index.js";import{InputService as u}from"./input/index.js";import{RuntimeAssetsService as g}from"../scene/runtime-asset-service.js";import{AssetResourceLoader as w}from"../scene/asset-resource-loader.js";import{AssetLoader as f}from"./services/asset-loader.js";import{RuntimeBundledBackendService as y}from"../scene/runtime-bundled-backend-service.js";import{AssetsProvider as S}from"../scene/assets-provider.js";import{ShaderProvider as v}from"./services/shader-provider.js";import{PointerEvents as b}from"./services/pointer-events.js";import{GpuVfxWorldService as j}from"../effects/vfx/gpu-vfx-world-service.js";import{NetMode as I}from"./net/net-session.js";import{GameStateContext as A}from"./game-state-context.js";import{PlayerSessions as V}from"./player-sessions.js";import{polyfillClient as P}from"./polyfill.js";import{withInjectionContext as R}from"./inject.js";import{RenderingHost as N}from"./rendering-host.js";import{SceneTransitionService as x}from"./services/scene-transition.js";import{SceneRef as z}from"../scene/scene-ref.js";let C=0;export function initiateApplication(t,i){const n="function"==typeof t?t:void 0,s=i??t;var o;P(),"element"in s&&null!=s.element&&(o=s.element,Object.assign(o.style,{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",overflow:"hidden"}));const r=s.renderingHost??(null!=s.renderingView?new N(s.renderingView,!1,("renderingViewFactory"in s?s.renderingViewFactory:void 0)??(()=>new l(s.renderingView.container,s.renderingView.options))):function(e,t,i){const n=new p,s=()=>function(e,t,i,n){return new l(e,{graphicsRuntime:n,backend:t?.backend??"webgpu",enableXR:!0===i?.enabled,maxPixelRatio:t?.maxPixelRatio,resolutionScale:t?.resolutionScale,dynamicResolution:t?.dynamicResolution,upscaling:t?.upscaling,webgpuPostProcessing:t?.webgpuPostProcessing,webgpuDirectionalShadows:t?.webgpuDirectionalShadows,webgpuVfx:t?.webgpuVfx??"auto",msaa:t?.msaa,fpsCap:t?.fpsCap,depthPrepass:{enabled:!0===t?.depthPrepass?.enabled},bloom:{enabled:!1!==t?.bloom?.enabled},reflection:{enabled:!1!==t?.reflection?.enabled},occlusionCulling:{enabled:!1!==t?.occlusionCulling?.enabled},shadows:{enabled:t?.shadows?.enabled??!0,autoUpdate:t?.shadows?.autoUpdate??!0,cascadeUpdateIntervals:[22,40,60,120]}})}(e,t,i,n);return new N(s(),!0,s)}(s.element,s.rendering,s.xr)),a=e.of("hology-application-"+ ++C),c=new HologyApplicationRuntime(s,a,r,n??s.defaultGameClass);return c.startInitialization(),c}export function initiateGame(e,t){const{sceneName:i,detailTier:n,multi:s,localPlayer:o,...r}=t,a=initiateApplication(e,r),c=a.ready.then(()=>a.loadGame({sceneName:i,detailTier:n,multi:s,localPlayer:o}));return new HologyRuntime(a,c)}export class GameInstance{onPreload(){}onStart(){}onUnload(){return this.onShutdown()}onShutdown(){}}export class HologyApplicationRuntime{constructor(e,n,s,o){this.config=e,this.applicationContainer=n,this.renderingHost=s,this.defaultGameClass=o,this.stateSubject=new t({type:"initializing"}),this.state=this.stateSubject.asObservable(),this.shutdownSubject=new i,this.shutdownStarted=this.shutdownSubject.asObservable(),this.transitionTail=Promise.resolve(),this.initializationPromise=Promise.resolve(),this.transitionIntent=0,this.shutdownFlag=!1,this.ready=new Promise((e,t)=>{this.resolveReady=e,this.rejectReady=t})}get renderingView(){return this.renderingHost.activeView}get currentState(){return this.stateSubject.value}startInitialization(){this.initializationPromise=this.initialize()}async initialize(){try{if(this.registerApplicationServices(),await this.renderingHost.applicationView.compileAsync(),this.shutdownFlag)return void this.rejectReady(k());const e=this.getApplicationService(u);this.renderingHost.start(t=>e.update(t)),this.setState({type:"menu"}),this.resolveReady()}catch(e){this.setState({type:"error",operation:"initialize",error:e}),this.rejectReady(e)}}loadGame(e,t){const i="function"==typeof e?e:this.defaultGameClass,n=t??e;if(null==i)return Promise.reject(new Error("loadGame() requires a game class because no default was configured"));const s=++this.transitionIntent;return this.enqueue(async()=>(await this.ready,this.assertRunning(),this.checkIntent(s),null!=this.session&&await this.disposeSession(this.session,!0),this.checkIntent(s),this.performLoad(i,n,s)))}unloadGame(){const e=++this.transitionIntent;return this.enqueue(async()=>{if(await this.ready,!this.shutdownFlag&&e===this.transitionIntent){try{null!=this.session&&await this.disposeSession(this.session,!0)}catch(t){throw this.shutdownFlag||e!==this.transitionIntent||this.setState({type:"error",operation:"unload",error:t}),t}this.shutdownFlag||e!==this.transitionIntent||this.setState({type:"menu"})}})}transitionScene(e){const t="string"==typeof e?{scene:new z(e,e)}:e instanceof z?{scene:e}:e,i=this.transitionIntent;return this.enqueue(async()=>{await this.ready,this.assertRunning(),this.checkIntent(i);const e=this.session;if(null==e)throw new Error("transitionScene() requires an active game session");try{return await this.performSceneTransition(e,t,i),e}catch(e){throw G(e)||this.shutdownFlag||i!==this.transitionIntent||this.setState({type:"error",operation:"load",error:e}),e}})}async shutdown(){this.shutdownFlag||(this.shutdownFlag=!0,++this.transitionIntent,this.shutdownSubject.next(),this.shutdownSubject.complete(),await this.initializationPromise.catch(()=>{}),await this.transitionTail.catch(()=>{}),null!=this.session&&await this.disposeSession(this.session,!1),this.getApplicationService(u).stop(),this.getOptionalApplicationService(b)?.stop(),this.getApplicationService(w).disposeAll(),this.getApplicationService(g).dispose(),this.getApplicationService(c).dispose(!1),this.renderingHost.shutdown(),e.reset(this.applicationContainer.id),this.setState({type:"stopped"}),this.stateSubject.complete())}getWorld(){return this.session?.world}getGameInstance(){return this.session?.gameInstance}getApplicationService(e){return this.applicationContainer.get(e)}getSessionService(e){return this.session?.container.get(e)}getService(e){return null!=this.session?this.session.container.get(e):this.applicationContainer.get(e)}registerRenderStage(e){return this.renderingHost.registerStage(e)}registerApplicationServices(){const e=new c(this.renderingView);this.applicationContainer.set(l,this.renderingHost.applicationView),this.applicationContainer.set(c,e);const t=new y(this.config.runtimeDataUrl),i=new g(t),n=new w;n.setDataDir(this.config.dataDir),n.setDirectFileLoading(!0===this.config.directAssetFileLoading),"webgpu"===this.renderingHost.applicationView.requestedBackend?n.initKtx2ForWebGpu():n.initKtx2(this.renderingHost.applicationView.renderer);const s=Object.entries(this.config.shaders).map(([e,t])=>({name:e,type:t}));this.applicationContainer.set(y,t),this.applicationContainer.set(S,i),this.applicationContainer.set(g,i),this.applicationContainer.set(w,n),this.applicationContainer.set(f,new f(n,i,s)),this.applicationContainer.set(v,new v(s));const o=R(this.applicationContainer,e=>e.get(u));this.applicationContainer.set(u,o)}async performLoad(t,i,s){const r=this.createSession(i);this.session=r;try{this.loading(i.sceneName,"physics",.08),await r.physics.start(),this.checkIntent(s),this.loading(i.sceneName,"runtime-data",.2),await this.getApplicationService(y).preloadData(),this.checkIntent(s),this.loading(i.sceneName,"scene",.42);const a=await n(r.renderingView,i.sceneName,this.config.dataDir,this.config.shaders,this.config.actors,r.components,r.actorFactory,this.getApplicationService(y),this.getApplicationService(g),this.getApplicationService(w),{detailTier:i.detailTier,nodeMode:i.multi?.session.mode??I.none});if(r.materializer.dispose(),r.materializer=a.materializer,r.sceneRoots=a.sceneRoots,r.container.set(o,a.materializer),r.world.materializer=a.materializer,r.world.scene=a.scene,r.physics.scene=a.scene,this.checkIntent(s),null!=i.multi?.session){const{NetService:e}=await import("./net/service/net-service.js"),t=R(r.container,t=>t.get(e));t.session=i.multi.session,t.start(),r.stopPlayers=()=>t.stop()}e.import([d]);for(const e of a.actors)r.world.addActor(e);if(r.physics.addFromScene(a.scene),await r.world.startSceneEventGraphs(a.materializer),this.checkIntent(s),r.gameInstance=R(r.container,e=>(e.set({id:t,type:t}),e.get(t))),this.loading(i.sceneName,"game-preload",.58),r.gameInstance instanceof GameInstance&&await r.gameInstance.onPreload(),this.checkIntent(s),this.loading(i.sceneName,"shaders",.7),await r.renderingView.compileAsync(),this.checkIntent(s),this.loading(i.sceneName,"textures",.82),await r.renderingView.initTexturesAsync(),this.checkIntent(s),"webgpu"===r.renderingView.activeBackend){this.loading(i.sceneName,"vfx-warmup",.94);const e=r.container.get(j).beginPipelineWarmup();try{const e=performance.now();console.info("[Hology GPU VFX warmup] Rendering loading frame",i.sceneName),r.renderingView.render(0),console.info("[Hology GPU VFX warmup] Render returned; waiting for GPU queue",{sceneName:i.sceneName,renderMs:performance.now()-e}),await(r.renderingView.webGpuDevice?.queue.onSubmittedWorkDone()),console.info("[Hology GPU VFX warmup] GPU queue completed",{sceneName:i.sceneName,totalMs:performance.now()-e})}finally{e()}}return this.checkIntent(s),this.renderingHost.activate(r.renderingView),r.gameInstance instanceof GameInstance&&await r.gameInstance.onStart(),this.checkIntent(s),this.setState({type:"playing",sceneName:i.sceneName}),r}catch(e){throw await this.disposeSession(r,!1),G(e)||this.shutdownFlag||s!==this.transitionIntent||this.setState({type:"error",operation:"load",error:e}),e}}createSession(t){const i=e.of("hology-session-"+ ++C);this.copyApplicationServices(i);const n=this.renderingHost.createView(),p=new c(n);i.set(l,n),i.set(c,p);const d={...m,...this.config.components??{}},u=new s(i,{inEditor:!1});u.classes=this.config.actors,i.set(s,u);const f=new o(n.scene,new r,this.getApplicationService(g),this.getApplicationService(w),n,Object.entries(this.config.shaders).map(([e,t])=>({name:e,type:t})),Object.entries(this.config.actors).map(([e,t])=>({name:e,type:t})),u,Object.entries(d).map(([e,t])=>({name:e,type:t})));f.inEditor=!1,f.netMode=t.multi?.session.mode??I.none,i.set(o,f);const y=R(i,e=>e.get(a));y.scene=n.scene,i.set(a,y);const S=R(i,e=>e.get(h));i.set(h,S);const v=new x;v.bind(async e=>{await this.transitionScene(e)}),i.set(x,v);const b=R(i,e=>e.get(V));i.set(V,b);const j=R(i,e=>e.get(A));return i.set(A,j),null==t.multi?.session&&b.startStandalone(t.localPlayer??{id:"local-player",displayName:"Player"}),{sceneName:t.sceneName,detailTier:t.detailTier,container:i,renderingView:n,viewController:p,actorFactory:u,materializer:f,world:y,physics:S,components:d,sceneTransition:v,sceneRoots:[],netMode:t.multi?.session.mode??I.none,stopPlayers:null==t.multi?.session?()=>{b.stop(),j.stop()}:void 0}}async performSceneTransition(e,t,i){if(e.netMode!==I.none)throw new Error("In-session scene transitions are not yet supported for network sessions");const s=t.scene.id.trim();if(0===s.length)throw new Error("A destination scene id is required");const r=e.sceneName;this.setState({type:"unloading",sceneName:r}),e.world.stopSceneEventGraphs();const a=e.world.removeSceneLifetimeActors(e.sceneRoots);for(const t of e.sceneRoots)e.physics.removeRemoved(t),t.removeFromParent();e.sceneRoots=[],e.materializer.dispose(a),this.checkIntent(i),this.loading(s,"scene",.42);const c=await n(e.renderingView,s,this.config.dataDir,this.config.shaders,this.config.actors,e.components,e.actorFactory,this.getApplicationService(y),this.getApplicationService(g),this.getApplicationService(w),{detailTier:e.detailTier,nodeMode:e.netMode});this.checkIntent(i),e.materializer=c.materializer,e.sceneRoots=c.sceneRoots,e.sceneName=s,e.container.set(o,c.materializer),e.world.materializer=c.materializer;for(const t of c.actors)e.world.addActor(t);e.physics.addFromScene(c.scene),await e.world.startSceneEventGraphs(c.materializer),await(t.afterLoad?.(e.world)),this.checkIntent(i),this.loading(s,"shaders",.7),await e.renderingView.compileAsync(),this.checkIntent(i),this.loading(s,"textures",.82),await e.renderingView.initTexturesAsync(),this.checkIntent(i),e.sceneTransition.publish({previousSceneName:r,sceneName:s}),this.setState({type:"playing",sceneName:s})}copyApplicationServices(e){const t=[u,S,g,y,w,f,v];for(const i of t)e.set(i,this.applicationContainer.get(i))}async disposeSession(t,i){if(t.disposed)return;let n;t.disposed=!0,i&&this.setState({type:"unloading",sceneName:t.sceneName});try{t.stopPlayers?.(),t.sceneTransition.dispose(),t.world.stopSceneEventGraphs(),t.gameInstance instanceof GameInstance&&await t.gameInstance.onUnload()}catch(e){n=e}finally{for(const e of[...t.world.actors])t.world.removeActor(e);t.physics.stop(),t.materializer.dispose(),this.renderingHost.activeView===t.renderingView&&this.renderingHost.activate(this.renderingHost.applicationView),t.viewController.dispose(!1),this.renderingHost.releaseView(t.renderingView),e.reset(t.container.id),this.session===t&&(this.session=void 0)}if(null!=n)throw n}enqueue(e){const t=this.transitionTail.then(e,e);return this.transitionTail=t.catch(()=>{}),t}checkIntent(e){if(this.assertRunning(),e!==this.transitionIntent)throw k()}assertRunning(){if(this.shutdownFlag)throw new Error("Hology application has been shut down")}loading(e,t,i){this.setState({type:"loading",sceneName:e,stage:t,progress:i})}setState(e){this.stateSubject.next(e)}getOptionalApplicationService(e){return this.applicationContainer.has(e)?this.applicationContainer.get(e):void 0}}export class HologyRuntime{constructor(e,t){this.application=e,this.ready=t.then(()=>!0),this.shutdownStarted=e.shutdownStarted}get status(){return"playing"===this.application.currentState.type?5:"stopped"===this.application.currentState.type||"error"===this.application.currentState.type?10:0}get gameInstance(){return this.application.getGameInstance()}get isShutdown(){return"stopped"===this.application.currentState.type}getWorld(){return this.application.getWorld()}getService(e){return this.application.getService(e)}shutdown(){return this.application.shutdown()}}function k(){return new DOMException("Superseded by a newer game transition","AbortError")}function G(e){return e instanceof DOMException&&"AbortError"===e.name}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -19,6 +19,9 @@ export interface PlayerSession {
|
|
|
19
19
|
/** Tracks stable players separately from replaceable transport connections. */
|
|
20
20
|
export declare class NetPlayerRegistry {
|
|
21
21
|
private readonly sessions;
|
|
22
|
+
private allSnapshot?;
|
|
23
|
+
private activeSnapshot?;
|
|
24
|
+
private localSnapshot?;
|
|
22
25
|
private readonly joinedSubject;
|
|
23
26
|
private readonly leftSubject;
|
|
24
27
|
private readonly changedSubject;
|
|
@@ -28,6 +31,8 @@ export declare class NetPlayerRegistry {
|
|
|
28
31
|
/** Emits whenever player membership, status, controller, or player state changes. */
|
|
29
32
|
readonly changed$: Observable<void>;
|
|
30
33
|
get values(): readonly PlayerSession[];
|
|
34
|
+
get activeValues(): readonly PlayerSession[];
|
|
35
|
+
get localValues(): readonly PlayerSession[];
|
|
31
36
|
get(playerId: NetPlayerId): PlayerSession | undefined;
|
|
32
37
|
start(session: NetSession): void;
|
|
33
38
|
startLocal(info: NetPlayerInfo): void;
|
|
@@ -39,5 +44,6 @@ export declare class NetPlayerRegistry {
|
|
|
39
44
|
private upsert;
|
|
40
45
|
private removeConnection;
|
|
41
46
|
private require;
|
|
47
|
+
private invalidateSnapshots;
|
|
42
48
|
}
|
|
43
49
|
//# sourceMappingURL=player-session.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Subject as t}from"rxjs";import{NetMode as
|
|
1
|
+
import{Subject as t}from"rxjs";import{NetMode as s}from"./net-session.js";export class NetPlayerRegistry{constructor(){this.sessions=new Map,this.joinedSubject=new t,this.leftSubject=new t,this.changedSubject=new t,this.subscriptions=[],this.playerJoined$=this.joinedSubject.asObservable(),this.playerLeft$=this.leftSubject.asObservable(),this.changed$=this.changedSubject.asObservable()}get values(){return this.allSnapshot??(this.allSnapshot=[...this.sessions.values()])}get activeValues(){return this.activeSnapshot??(this.activeSnapshot=this.values.filter(t=>"active"===t.status))}get localValues(){return this.localSnapshot??(this.localSnapshot=this.values.filter(t=>null!=t.localPlayerIndex))}get(t){return this.sessions.get(t)}start(t){if(this.stop(!1),null!=t.localPlayer&&this.upsert(t.localPlayer,void 0,0),t.mode<s.client){for(const s of t.clients)this.addConnection(s);this.subscriptions.push(t.playerJoined.subscribe(t=>this.addConnection(t)),t.playerLeft.subscribe(t=>this.removeConnection(t)))}}startLocal(t){this.stop(!1),this.upsert(t,void 0,0)}attachController(t,s){const e=this.require(t);e.controller=s,s.playerId=t,null!=e.playerState?s.playerState=e.playerState:null!=s.playerState&&(e.playerState=s.playerState),e.status="active",this.invalidateSnapshots(),this.changedSubject.next()}attachPlayerState(t,s){const e=this.require(t);e.playerState=s,s.playerId=t,null!=e.info.displayName&&(s.displayName=e.info.displayName),null!=e.controller&&(e.controller.playerState=s),this.invalidateSnapshots(),this.changedSubject.next()}setStatus(t,s){const e=this.require(t);e.status!==s&&(e.status=s,this.invalidateSnapshots(),this.changedSubject.next())}stop(t=!0){for(const t of this.subscriptions)t.unsubscribe();this.subscriptions=[],t&&this.sessions.size>0&&(this.sessions.clear(),this.invalidateSnapshots(),this.changedSubject.next())}addConnection(t){null!=t.player&&this.upsert(t.player,t)}upsert(t,s,e){const i=this.sessions.get(t.id);if(null!=i){const t="disconnected"===i.status;return i.connection=s,i.status="active",null!=e&&(i.localPlayerIndex=e),this.invalidateSnapshots(),t&&this.joinedSubject.next(i),this.changedSubject.next(),i}const n={info:t,connection:s,localPlayerIndex:e,status:"joining"};return this.sessions.set(t.id,n),this.invalidateSnapshots(),this.joinedSubject.next(n),this.changedSubject.next(),n}removeConnection(t){const s=t.player?.id;if(null==s)return;const e=this.sessions.get(s);null!=e&&e.connection?.id===t.id&&(e.connection=void 0,e.status="disconnected",this.invalidateSnapshots(),this.leftSubject.next(e),this.changedSubject.next())}require(t){const s=this.sessions.get(t);if(null==s)throw new Error(`Unknown network player ${JSON.stringify(t)}`);return s}invalidateSnapshots(){this.allSnapshot=void 0,this.activeSnapshot=void 0,this.localSnapshot=void 0}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -5,7 +5,7 @@ import { NetMode, NetSession } from '../net-session.js';
|
|
|
5
5
|
import { ReplOn } from "./replication.js";
|
|
6
6
|
import { RunsOn } from './rpc.js';
|
|
7
7
|
import { BaseGameState } from "../../actors/controller/game-state.js";
|
|
8
|
-
import {
|
|
8
|
+
import { PlayerSessions } from '../../player-sessions.js';
|
|
9
9
|
export type SceneTravelMessage = {
|
|
10
10
|
travelId: string;
|
|
11
11
|
sceneName: string;
|
|
@@ -16,7 +16,8 @@ export type SceneTravelReadyMessage = {
|
|
|
16
16
|
};
|
|
17
17
|
export declare class NetService {
|
|
18
18
|
session?: NetSession;
|
|
19
|
-
readonly
|
|
19
|
+
readonly playerSessions: PlayerSessions;
|
|
20
|
+
private readonly gameStateContext;
|
|
20
21
|
private readonly suspendedReplicationConnections;
|
|
21
22
|
private readonly sceneTravelBeginSubject;
|
|
22
23
|
private readonly sceneTravelReadySubject;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as e}from"tslib";import{delay as t,interval as n,Subject as s,takeUntil as r,tap as o}from"rxjs";import{Euler as i,Vector3 as a}from"three";import{Service as c}from"typedi";import{$actorOptions as l,BaseActor as d,getActorClassById as p}from"../../actors/actor.js";import{ActorComponent as h}from"../../actors/component.js";import{$actorComponents as u}from"../../actors/internal/component-init.js";import{inject as f}from"../../inject.js";import{World as g}from"../../services/world.js";import{NetMode as y}from"../net-session.js";import{NetRole as w}from"./net-actor-role.js";import{DecodeResult as m,NetSerializer as v}from"./net-serializer.js";import{isSignalLike as S}from"./net-utils.js";import{ReplOn as A}from"./replication.js";import{getRpcMethodId as b,getRpcMethodName as R,RunsOn as C}from"./rpc.js";import{ActorController as _,BasePlayerController as M,BasePlayerState as U}from"../../actors/controller/actor-controller.js";import{BaseGameState as P}from"../../actors/controller/game-state.js";import{makePropertyReplicated as T}from"./net-decorator.js";import{Players as I}from"../../players.js";var B;!function(e){e[e.rpc=1]="rpc",e[e.prop=2]="prop",e[e.spawn=3]="spawn",e[e.removeActor=4]="removeActor",e[e.rpcComponent=5]="rpcComponent",e[e.propComponent=6]="propComponent",e[e.rpcBinary=21]="rpcBinary",e[e.rpcComponentBinary=25]="rpcComponentBinary",e[e.actorReady=7]="actorReady",e[e.propSnapshot=8]="propSnapshot",e[e.serverTime=100]="serverTime",e[e.sceneTravelBegin=101]="sceneTravelBegin",e[e.sceneTravelReady=102]="sceneTravelReady",e[e.sceneTravelComplete=103]="sceneTravelComplete"}(B||(B={}));var $;!function(e){e[e.actor=0]="actor",e[e.component=1]="component"}($||($={}));let L=class{constructor(){this.players=f(I),this.suspendedReplicationConnections=new Set,this.sceneTravelBeginSubject=new s,this.sceneTravelReadySubject=new s,this.sceneTravelCompleteSubject=new s,this.sceneTravelBegin$=this.sceneTravelBeginSubject.asObservable(),this.sceneTravelReady$=this.sceneTravelReadySubject.asObservable(),this.sceneTravelComplete$=this.sceneTravelCompleteSubject.asObservable(),this.actorConnection=new Map,this.destroyed=new s,this.world=f(g),this.serializer=f(v),this.serverTimeOffset=0,this.actorPreSpawnMessageBuffer=new Map,this.actorRefMissingMessageQueue=new Map,this.netStartupActorSpawnQueue=new Map,this.spawningActorNetIds=new Set,this.nextNetId=1,this.shouldReplicateMap=new Map,this.replicatedActors=new Map,this.replicatedProperties=new Map,this.replicatedPropertyRegistry=new Map}get pendingSceneTravel(){return this.activeSceneTravel}get gameState(){return this.players.gameState}setGameState(e){if(null!=this.players.gameState&&this.players.gameState!==e)throw new Error(`A game state is already registered as ${this.players.gameState.constructor.name}`);this.players.setGameState(e)}get mode(){return this.session?.mode??y.none}get isClient(){return this.mode===y.client}get isServer(){return this.mode<y.client}get isDedicatedServer(){return this.mode===y.dedicatedServer}start(){if(this.stop(),this.destroyed=new s,this.players.startNetwork(this.session),this.players.setOwningConnectionHandler((e,t)=>this.setOwningConnection(e,t)),this.players.setClearOwningConnectionHandler(e=>this.clearOwningConnection(e)),this.players.setGameStateValidator(e=>{if(!this.shouldReplicate(e))throw new Error(`${e.constructor.name} must use @Actor({ replicate: true }) to be a game state`)}),this.mode!==y.none){if(this.isServer)for(const e of this.session.clients)this.sendServerTime(e);n(15).pipe(r(this.destroyed)).subscribe(()=>{this.processMessages()}),this.world.actorAdded.pipe(r(this.destroyed),o(e=>{this.configureMaterializedActorRole(e),this.isServer?this.defineNetId(e):this.processQueuedNetStartupActorSpawns(e),e instanceof M?(T(e,"playerId",this,!0,A.all),T(e,"playerState",this,!0,A.all),T(e,"pawn",this,!0,A.all)):e instanceof U?(T(e,"playerId",this,!0,A.all),T(e,"displayName",this,!0,A.all),T(e,"connected",this,!0,A.all)):e instanceof P&&T(e,"playerStates",this,!0,A.all)}),t(8)).subscribe(e=>{this.shouldReplicate(e)&&(T(e,"owner",this,!0,A.all),this.sendActorSpawn(e))}),this.world.actorRemoved.pipe(r(this.destroyed),t(10)).subscribe(e=>{this.sendActorRemove(e),e instanceof P&&this.players.clearGameState(e),this.actorConnection.delete(e.id),this.replicatedActors.delete(e),this.replicatedProperties.delete(e),this.replicatedPropertyRegistry.delete(e),this.actorPreSpawnMessageBuffer.delete(e)}),this.session.playerJoined.pipe(r(this.destroyed)).subscribe(e=>{null==this.activeSceneTravel?(this.sendServerTime(e),this.isServer&&this.replicatedActors.size>0&&this.spawnOnLateClient(e)):this.suspendAndSendSceneTravel(e,this.activeSceneTravel)})}}stop(){this.destroyed.next(null),this.destroyed.complete(),this.players.stop(),this.suspendedReplicationConnections.clear(),this.activeSceneTravel=void 0}configureMaterializedActorRole(e){null!=e.__netSceneId&&this.mode!==y.none&&(this.shouldReplicate(e)?e.netRole=this.isServer?w.authority:w.simulatedProxy:this.isServer||(e.netRole=w.none))}shouldReplicate(e){const t=this.shouldReplicateMap.get(e);if(null==t){const t=e.constructor[l],n=!0===t?.replicate;return this.shouldReplicateMap.set(e,n),n}return!0===t}setReplicate(e,t){this.shouldReplicateMap.set(e,t)}isRelevant(e,t){const n=this.getRelevancyOptions(e);return!(!0===n?.ownerOnly)||this.resolveActorConnection(e)?.id===t.id}getRelevancyOptions(e){return e.constructor[l].relevancy}spawnOnLateClient(e){const t=[e];for(const e of this.replicatedActors.keys())this.sendActorSpawn(e,t)}async processMessages(){const e=this.session.hasMessage();for(let t=0;t<e;t++){const e=this.session.readMessage();null!=e&&await this.processMessage(e)}}async processMessage(e){try{switch(new DataView(e.buffer).getUint8(0)){case B.rpc:case B.rpcComponent:case B.rpcBinary:case B.rpcComponentBinary:this.processRpc(e.from,e.buffer);break;case B.prop:case B.propComponent:this.processProp(e.from,e.buffer);break;case B.propSnapshot:this.processPropSnapshot(e.from,e.buffer);break;case B.spawn:await this.processSpawn(e.from,e.buffer);break;case B.removeActor:this.processRemoveActor(e.from,e.buffer);break;case B.serverTime:this.processServerTime(e.from,e.buffer);break;case B.actorReady:this.processActorReady(e.from,e.buffer);break;case B.sceneTravelBegin:this.processSceneTravelBegin(e.from,e.buffer);break;case B.sceneTravelReady:this.processSceneTravelReady(e.from,e.buffer);break;case B.sceneTravelComplete:this.processSceneTravelComplete(e.from,e.buffer)}}catch(e){console.error("Failed to process message",e)}}processServerTime(e,t){if(this.isServer||this.session.server?.id!==e.id)return;const n=this.parseServerTime(t);this.serverTimeOffset=Date.now()-n}getActorRefMissingMessageQueue(e){let t=this.actorRefMissingMessageQueue.get(e);return null==t&&(t=[],this.actorRefMissingMessageQueue.set(e,t)),t}processRpc(e,t){let n=0;const s=new DataView(t),r=s.getUint8(n);n+=1;const o=s.getUint32(n,!0);n+=4;const i=s.getUint8(n);n+=1;const a=r===B.rpcBinary||r===B.rpcComponentBinary;let c,l,d="";if(a){const e=s.getUint32(n,!0);n+=4,l=new Uint8Array(t,n,e),n+=e}else{const e=s.getUint16(n,!0);n+=2,d=j(t,n,e),n+=e;try{c=this.serializer.decode(d,V)}catch(e){return void console.warn("Failed to parse RPC args JSON",d,e)}}if(!a&&V.error){if(null!=V.missingActorId){const n=this.getActorRefMissingMessageQueue(V.missingActorId);console.debug(`Missing referenced actor ${V.missingActorId}. Queueing message for later`),n.push({from:e,buffer:t})}else console.log("Unknown decode error",d);return}const p=this.world.actors.find(e=>e.__netid===o);if(null==p){return this.getActorRefMissingMessageQueue(o).push({from:e,buffer:t}),void console.debug(`Missing actor ${o}. Queueing message for later`)}let h;if(r===B.rpcComponent||r===B.rpcComponentBinary){const e=s.getUint8(n),t=p[u];if(null!=t&&(h=t.find(t=>t.__netid===e)),null==h)return void console.error(`RPC failed: Failed to find component with netid ${e} on actor with netid ${o}`)}if(this.isServer){if(!this.session.clients.some(t=>t.id==e.id))return void console.warn("Received RCP from non client");const t=this.resolveActorConnection(p);if(null==t)return void console.error("Received server RPC for actor without owner which can't be processed");if(t.id!==e.id)return void console.warn(`Received RPC for actor net id ${p.__netid} from connection ${e.id}, but this connection is not the owner. Can't process.`)}else{if(!(this.session.server?.id===e.id))return void console.warn("Received RCP from non server which is not allowed")}const f=h??p,g=R(f,i);if(null!=g)if("function"==typeof f[g])if(a)f[g](l);else{if(V.requiresAssetLookups)return this.serializer.decodeAssets(c).then(e=>{f[g](...e)}).catch(e=>{console.warn(`Failed to decode asset references for RPC '${g}' on actor net id ${p.__netid}`,e)});f[g](...c)}else console.error(`RPC function '${g}' not found on actor net id ${p.__netid}`+(null!=h?` and component net id ${h.__netid}`:""));else console.error(`RPC method id ${i} not found on actor net id ${p.__netid}`+(null!=h?` and component net id ${h.__netid}`:""))}processProp(e,t){if(this.session.server?.id!==e.id)return void console.warn(`Received property replication from non-server connection (id: ${e.id}), ignoring.`);let n=0;const s=new DataView(t),r=s.getUint8(n);n+=1;const o=s.getUint32(n,!0);n+=4;const i=s.getUint8(n);n+=1;let a="";for(let e=0;e<i;e++)a+=String.fromCharCode(s.getUint8(n+e));n+=i;const c=s.getUint16(n,!0);n+=2;const l=t.slice(n,n+c);n+=c;const d=this.world.actors.find(e=>e.__netid===o);if(!d){return this.getActorRefMissingMessageQueue(o).push({from:e,buffer:t}),void console.error("Failed to process property replication. No actor found with id",o,a)}let p;if(r===B.propComponent){const e=s.getUint8(n),t=d[u];if(null!=t&&(p=t.find(t=>t.__netid===e)),null==p)return void console.error(`Failed to find component with netid ${e} on actor with netid ${o}`)}const h=p??d;try{const n=this.decodeReplicatedValue(l);if(V.error){if(null!=V.missingActorId){let n=this.getActorRefMissingMessageQueue(V.missingActorId);console.debug(`Missing referenced actor ${V.missingActorId}. Queueing message for later`),n.push({from:e,buffer:t})}return}if(V.requiresAssetLookups)return this.serializer.decodeAssets(n).then(e=>{this.applyReplicatedProperty(h,a,e),this.callRepNotify(h,a)}).catch(e=>{console.warn(`Failed to decode asset references for replicated property '${a}' on actor id ${o}`,e)});this.applyReplicatedProperty(h,a,n),this.callRepNotify(h,a)}catch(e){console.warn(`Failed to decode replicated property '${a}' on actor id ${o}`,e)}}processPropSnapshot(e,t){if(this.session.server?.id!==e.id)return void console.warn(`Received property snapshot from non-server connection (id: ${e.id}), ignoring.`);let n=0;const s=new DataView(t);n+=1;const r=s.getUint32(n,!0);n+=4;const o=this.world.actors.find(e=>e.__netid===r);if(null==o){return this.getActorRefMissingMessageQueue(r).push({from:e,buffer:t}),void console.debug(`Missing actor ${r}. Queueing property snapshot for later`)}const i=s.getUint16(n,!0);n+=2;const a=[];for(let c=0;c<i;c++){const i=s.getUint8(n);n+=1;let c=o;if(i===$.component){const e=s.getUint8(n);n+=1;const t=o[u];if(c=t?.find(t=>t.__netid===e),null==c)return void console.error(`Failed to process property snapshot. No component found with netid ${e} on actor with netid ${r}`)}else if(i!==$.actor)return void console.error(`Failed to process property snapshot. Unknown target kind ${i}`);const l=s.getUint8(n);n+=1;let d="";for(let e=0;e<l;e++)d+=String.fromCharCode(s.getUint8(n+e));n+=l;const p=s.getUint32(n,!0);n+=4;const h=t.slice(n,n+p);n+=p;try{const n=this.decodeReplicatedValue(h);if(V.error){if(null!=V.missingActorId){this.getActorRefMissingMessageQueue(V.missingActorId).push({from:e,buffer:t}),console.debug(`Missing referenced actor ${V.missingActorId}. Queueing property snapshot for later`)}else console.warn(`Unknown decode error in property snapshot for actor ${r}`);return}a.push({target:c,property:d,value:n,requiresAssetLookups:V.requiresAssetLookups})}catch(e){return void console.warn(`Failed to decode replicated property snapshot entry '${d}' on actor id ${r}`,e)}}if(a.some(e=>e.requiresAssetLookups))return Promise.all(a.map(async e=>{e.requiresAssetLookups&&(e.value=await this.serializer.decodeAssets(e.value))})).then(()=>{this.applyReplicatedProperties(a)}).catch(e=>{console.warn(`Failed to decode asset references for property snapshot on actor id ${r}`,e)});this.applyReplicatedProperties(a)}applyReplicatedProperties(e){for(const t of e)this.applyReplicatedProperty(t.target,t.property,t.value);for(const t of e)this.callRepNotify(t.target,t.property)}async processSpawn(e,t){if(this.isServer)return;const n=new DataView(t);let s=0;s+=1;const r=n.getUint32(s,!0);s+=4;const o=n.getUint8(s);s+=1;let i="";for(let e=0;e<o;e++)i+=String.fromCharCode(n.getUint8(s+e));s+=o,O.set(n.getFloat32(s,!0),n.getFloat32(s+4,!0),n.getFloat32(s+8,!0)),s+=12,Q.set(n.getFloat32(s,!0),n.getFloat32(s+4,!0),n.getFloat32(s+8,!0)),s+=12;const a=n.getUint8(s);s+=1;const c=n.getUint32(s);s+=4;const l=this.readOptionalSceneId(t,s);console.log("Spawning "+i);const d=p(i);if(!d)return void console.error(`Failed to spawn actor: unknown actor type '${i}'`);const h=this.world.actors.find(e=>e.__netid===r);if(null!=h)return this.applySpawnNetInfo(h,r,a,c),h instanceof P&&this.setGameState(h),this.processQueuedActorMessages(r),void this.sendActorReady(h);if(this.spawningActorNetIds.has(r))return void console.log("Actor was already spawned",null!=h);const u=this.findMaterializedNetStartupActor(l,i,r);if(null!=u)return this.applySpawnNetInfo(u,r,a,c),u instanceof P&&this.setGameState(u),this.processQueuedActorMessages(r),void this.sendActorReady(u);if(null==l){this.spawningActorNetIds.add(r);try{const e=await this.world.spawnActorWithNetInfo(d,r,a,c,O,Q);e instanceof P&&this.setGameState(e),this.processQueuedActorMessages(r),this.sendActorReady(e)}catch(e){return void console.error("Spawning replicated actor on client failed",e)}finally{this.spawningActorNetIds.delete(r)}}else this.getNetStartupActorSpawnQueue(l).push({from:e,buffer:t})}readOptionalSceneId(e,t){if(t+2>e.byteLength)return;const n=new DataView(e).getUint16(t,!0);return t+=2,0===n||t+n>e.byteLength?void 0:j(e,t,n)}findMaterializedNetStartupActor(e,t,n){if(null!=e)return this.world.actors.find(s=>{if(s.__netSceneId!==e)return!1;if(null!=s.__netid&&s.__netid!==n)return!1;return(s.constructor.__actorId??s.constructor.name)===t})}applySpawnNetInfo(e,t,n,s){if(e.__netid=t,e.netRole=n,0===s)e.owner=void 0;else{const t=this.world.actors.find(e=>e.__netid===s);null!=t&&(e.owner=t)}e instanceof M&&e.setLocallyControlled(n===w.autonomousProxy)}getNetStartupActorSpawnQueue(e){let t=this.netStartupActorSpawnQueue.get(e);return null==t&&(t=[],this.netStartupActorSpawnQueue.set(e,t)),t}processQueuedNetStartupActorSpawns(e){if(null==e.__netSceneId)return;const t=this.netStartupActorSpawnQueue.get(e.__netSceneId);if(null!=t){this.netStartupActorSpawnQueue.delete(e.__netSceneId);for(const e of t)this.processMessage(e)}}processQueuedActorMessages(e){const t=this.actorRefMissingMessageQueue.get(e);if(null!=t){console.log(`Processing queued messages for actor ${e}`),this.actorRefMissingMessageQueue.delete(e);for(const e of t)this.processMessage(e)}}sendActorReady(e){if(this.mode===y.none)return;if(!e||null==e.__netid)return void console.warn("sendActorReady: actor or actor.__netid is undefined");if(this.isServer)return;const t=new ArrayBuffer(5),n=new DataView(t);let s=0;n.setUint8(s,B.actorReady),s+=1,n.setUint32(s,e.__netid,!0),this.session.server&&this.session.sendMessage(this.session.server,!0,t)}processActorReady(e,t){if(!this.isServer)return;const n=new DataView(t).getUint32(1,!0),s=this.world.actors.find(e=>e.__netid===n);if(!s)return void console.warn(`processActorReady: No actor found with net id ${n}`);const r=this.actorPreSpawnMessageBuffer.get(s),o=this.getConnectionKey(e),i=r?.get(o);if(null!=i){this.sendPropertySnapshot(s,e);const t=i.filter(e=>!this.isPropertyMessage(e.buffer));if(t.length>0){console.log(`Sending ${t.length} queued messages for actor ${s.__netid}`);for(const e of t)this.sendMessage(e.connection,e.reliable,e.buffer)}r.delete(o),0===r.size&&this.actorPreSpawnMessageBuffer.delete(s)}}sendRpc(e,t,n,s,r,o){if(this.mode==y.none)return;if(!this.shouldReplicate(n))return;const i=n.__netid;if(null==i)return void console.warn("No __netid defined on actor. Actor must be repliated for RPC");const a=this.resolveActorConnection(n),c=[];if(this.isServer)switch(e){case C.all:c.push(...this.session.clients.filter(e=>this.isRelevant(n,e)));break;case C.client:null!=a&&c.push(a);break;case C.notOwner:c.push(...this.session.clients.filter(e=>e.id!==a?.id&&this.isRelevant(n,e)));case C.server:}else switch(e){case C.all:case C.client:case C.notOwner:break;case C.server:c.push(this.session.server)}if(0==c.length)return;if(null!=o&&null==o.__netid)return void console.warn("No __netid defined on component. Component must be initialized for RPC",o);const l=null!=o,d=o??n,p=b(d,s);if(null==p)return void console.warn(`RPC function '${s}' is not registered on ${d.constructor?.name??"target"}`);const h=1===r.length?function(e){if(e instanceof Uint8Array)return e;if(e instanceof ArrayBuffer)return new Uint8Array(e);if("undefined"!=typeof SharedArrayBuffer&&e instanceof SharedArrayBuffer)return new Uint8Array(e);if(ArrayBuffer.isView(e))return new Uint8Array(e.buffer,e.byteOffset,e.byteLength);return null}(r[0]):null;if(null!=h){let e=10+h.byteLength;l&&(e+=1);const s=new ArrayBuffer(e),r=new DataView(s);let a=0;r.setUint8(a,l?B.rpcComponentBinary:B.rpcBinary),a+=1,r.setUint32(a,i,!0),a+=4,r.setUint8(a,p),a+=1,r.setUint32(a,h.byteLength,!0),a+=4,k(s,a,h),a+=h.byteLength,l&&(r.setUint8(a,o.__netid),a+=1);for(const e of c)null!=e&&this.sendWithQueue(n,{connection:e,reliable:t,buffer:s});return}const u=this.serializer.encode(r),f=N.encode(u);if(f.byteLength>65535)return void console.warn("RPC arguments are too large to send with the JSON RPC format",s);let g=8+f.byteLength;l&&(g+=1);const w=new ArrayBuffer(g),m=new DataView(w);let v=0;m.setUint8(v,l?B.rpcComponent:B.rpc),v+=1,m.setUint32(v,i,!0),v+=4,m.setUint8(v,p),v+=1,m.setUint16(v,f.byteLength,!0),v+=2,k(w,v,f),v+=f.byteLength,l&&(m.setUint8(v,o.__netid),v+=1);for(const e of c)null!=e&&this.sendWithQueue(n,{connection:e,reliable:t,buffer:w})}sendWithQueue(e,t){if(this.shouldQueueMessageUntilActorReady(e,t.connection)){return void this.getPreSpawnMessageQueue(e,t.connection).push(t)}this.sendMessage(t.connection,t.reliable,t.buffer)}findQueuedActorsInData(e,t=[]){if("object"==typeof e){if(Array.isArray(e))for(const t of e)this.findQueuedActorsInData(t);if(e instanceof d){this.actorPreSpawnMessageBuffer.get(e)}}}handlePropertySet(e,t,n,s,r,o){if(this.mode==y.none)return;if(!this.isServer)return;if(!this.shouldReplicate(n))return;let i=this.replicatedProperties.get(n);null==i&&(i={},this.replicatedProperties.set(n,i));const a=this.serializer.encode(r),c=N.encode(a),l=c.buffer.slice(c.byteOffset,c.byteOffset+c.byteLength);i[this.getReplicatedPropertyKey(s,o)]={property:s,replOn:e,reliable:t,buffer:l,component:o},this.sendProperty(e,t,n,s,l,o)}sendProperty(e,t,n,s,r,o){if(!this.isServer)return;const i=[],a=this.resolveActorConnection(n);switch(e){case A.all:i.push(...this.session.clients);break;case A.owner:null!=a&&i.push(a);break;case A.notOwner:null==a?i.push(...this.session.clients):i.push(...this.session.clients.filter(e=>e.id!==a.id))}const c=null!=o&&null!=o.__netid;let l=6+s.length+2+r.byteLength;c&&(l+=1);const d=new ArrayBuffer(l),p=new DataView(d);let h=0;p.setUint8(h,c?B.propComponent:B.prop),h+=1,p.setUint32(h,n.__netid,!0),h+=4,p.setUint8(h,s.length),h+=1;for(let e=0;e<s.length;e++)p.setUint8(h++,s.charCodeAt(e));p.setUint16(h,r.byteLength,!0),h+=2,new Uint8Array(d,h,r.byteLength).set(new Uint8Array(r)),h+=r.byteLength,c&&(p.setUint8(h,o.__netid),h+=1);for(const e of i)null!=e&&this.isRelevant(n,e)&&this.sendWithQueue(n,{connection:e,reliable:t,buffer:d})}defineNetId(e){null==e.__netid&&(e.__netid=this.nextNetId++)}registerReplicatedProperty(e,t,n,s,r,o){if(null==n||null==s||null==r)return;let i=this.replicatedPropertyRegistry.get(n);null==i&&(i=[],this.replicatedPropertyRegistry.set(n,i));const a=o??(r instanceof h?r:void 0),c=i.find(e=>e.target===r&&e.property===s);if(null!=c)return c.replOn=e,c.reliable=t,void(c.component=a);i.push({property:s,replOn:e,reliable:t,target:r,component:a})}sendActorSpawn(e,t=this.session.clients){if(!this.isServer)return;const n=this.replicatedActors.get(e),s=t.filter(t=>!!this.isRelevant(e,t)&&!n?.connections.some(e=>e.id===t.id));if(0===s.length)return;this.defineNetId(e);const r=e.__netid;let o=e.constructor.__actorId??e.constructor.name;const i=N.encode(o);let a;null!=e.__netSceneId&&(a=N.encode(e.__netSceneId),a.byteLength>65535&&(console.warn(`Scene actor id is too long to replicate as a startup actor: ${e.__netSceneId}`),a=void 0));const c=a?.byteLength??0,l=6+i.length+12+12+1+4+2+c,d=new ArrayBuffer(l),p=new DataView(d);let h=0;p.setUint8(h,B.spawn),h+=1,p.setUint32(h,r,!0),h+=4,p.setUint8(h,i.length),h+=1,new Uint8Array(d).set(i,h),h+=i.length,p.setFloat32(h,e.position.x,!0),h+=4,p.setFloat32(h,e.position.y,!0),h+=4,p.setFloat32(h,e.position.z,!0),h+=4,p.setFloat32(h,e.rotation.x,!0),h+=4,p.setFloat32(h,e.rotation.y,!0),h+=4,p.setFloat32(h,e.rotation.z,!0),h+=4;let u=h;h+=1,p.setUint32(h,e.owner?.__netid??0),h+=4,p.setUint16(h,c,!0),h+=2,null!=a&&(k(d,h,a),h+=a.byteLength);let f=this.replicatedActors.get(e);null==f&&(f={spawnedAt:Date.now(),connections:[]},this.replicatedActors.set(e,f));for(const t of s){const n=d.slice(0),s=new DataView(n),r=this.resolveActorConnection(e)?.id===t.id?w.autonomousProxy:w.simulatedProxy;s.setUint8(u,r),this.getPreSpawnMessageQueue(e,t),this.sendMessage(t,!0,n),f.connections.some(e=>e.id===t.id)||f.connections.push(t)}}sendActorRemove(e){if(!this.isServer)return;const t=new ArrayBuffer(5),n=new DataView(t);let s=0;n.setUint8(s,B.removeActor),s+=1,n.setUint32(s,e.__netid,!0);for(const e of this.session.clients)this.sendMessage(e,!0,t)}processRemoveActor(e,t){if(this.isServer||this.session.server?.id!==e.id)return;const n=new DataView(t).getUint32(1,!0),s=this.world.actors.find(e=>e.__netid===n);s&&(this.world.removeActor(s),this.replicatedActors?.delete?.(s),this.replicatedProperties?.delete?.(s))}setOwningConnection(e,t){this.actorConnection.set(e.id,t),this.isServer&&this.world.actors.includes(e)&&this.shouldReplicate(e)&&!0===this.getRelevancyOptions(e)?.ownerOnly&&this.sendActorSpawn(e,[t])}clearOwningConnection(e){this.actorConnection.delete(e.id)}getOwningConnection(e){return this.actorConnection.get(e.id)}getActorsByOwningConnection(e){const t=[];return this.actorConnection.forEach((n,s)=>{if(n.id===e.id){const e=this.world.actors.find(e=>e.id===s);null!=e&&t.push(e)}}),t}resolveActorConnection(e){let t;for(;null==t&&null!=e;)t=this.actorConnection.get(e.id),null==t&&(e=e.owner);return t}isLocallyControlled(e){let t=e;for(;null!=t;){if(t.netRole===w.autonomousProxy)return!0;if(t instanceof _)return t.isLocallyControlled;t=t.owner}return!1}isLocalOwner(e){return this.isLocallyControlled(e)}beginSceneTravel(e,t){if(!this.isServer)throw new Error("Only authority can begin scene travel");const n={travelId:e,sceneName:t};this.activeSceneTravel=n;for(const e of this.session.clients)this.suspendAndSendSceneTravel(e,n)}sendSceneTravelReady(e,t){if(!this.isClient||null==this.session.server)return;const n=this.createSceneTravelMessage(B.sceneTravelReady,{travelId:e,sceneName:t});this.session.sendMessage(this.session.server,!0,n)}completeSceneTravel(e,t){if(!this.isServer)throw new Error("Only authority can complete scene travel");const n=this.createSceneTravelMessage(B.sceneTravelComplete,{travelId:e,sceneName:t});for(const e of this.session.clients)this.resetReplicationState(e),this.suspendedReplicationConnections.delete(this.getConnectionKey(e)),this.spawnOnLateClient(e),this.session.sendMessage(e,!0,n);this.activeSceneTravel=void 0}suspendAndSendSceneTravel(e,t){this.suspendedReplicationConnections.add(this.getConnectionKey(e));const n=this.createSceneTravelMessage(B.sceneTravelBegin,t);this.session.sendMessage(e,!0,n)}processSceneTravelBegin(e,t){if(!this.isClient||this.session.server?.id!==e.id)return;const n=this.parseSceneTravelMessage(t);this.activeSceneTravel=n,this.sceneTravelBeginSubject.next(n)}processSceneTravelReady(e,t){if(!this.isServer||!this.session.clients.some(t=>t.id===e.id))return;const n=this.parseSceneTravelMessage(t);this.sceneTravelReadySubject.next({travelId:n.travelId,connection:e})}processSceneTravelComplete(e,t){if(!this.isClient||this.session.server?.id!==e.id)return;const n=this.parseSceneTravelMessage(t);this.activeSceneTravel=void 0,this.sceneTravelCompleteSubject.next(n)}createSceneTravelMessage(e,t){const n=N.encode(JSON.stringify(t)),s=new ArrayBuffer(1+n.byteLength);return new DataView(s).setUint8(0,e),k(s,1,n),s}parseSceneTravelMessage(e){const t=e.slice(1),n=null!=D?D.decode(t):String.fromCharCode(...new Uint8Array(t));return JSON.parse(n)}sendMessage(e,t,n){this.isServer&&this.suspendedReplicationConnections.has(this.getConnectionKey(e))||this.session.sendMessage(e,t,n)}sendServerTime(e){const t=new ArrayBuffer(9),n=new DataView(t);n.setUint8(0,B.serverTime),n.setBigUint64(1,BigInt(Date.now()),!0),this.sendMessage(e,!0,t)}parseServerTime(e){const t=new DataView(e).getBigUint64(1,!0);return Number(t)}getConnectionKey(e){return e.id.toString()}resetReplicationState(e){const t=this.getConnectionKey(e);for(const[n,s]of this.replicatedActors){for(let t=s.connections.length-1;t>=0;t--)s.connections[t].id===e.id&&s.connections.splice(t,1);const r=this.actorPreSpawnMessageBuffer.get(n);r?.delete(t),0===r?.size&&this.actorPreSpawnMessageBuffer.delete(n)}}getPreSpawnMessageQueue(e,t){let n=this.actorPreSpawnMessageBuffer.get(e);null==n&&(n=new Map,this.actorPreSpawnMessageBuffer.set(e,n));const s=this.getConnectionKey(t);let r=n.get(s);return null==r&&(r=[],n.set(s,r)),r}shouldQueueMessageUntilActorReady(e,t){const n=this.actorPreSpawnMessageBuffer.get(e);return!!n?.has(this.getConnectionKey(t))||this.isServer&&!this.replicatedActors.has(e)&&this.shouldReplicate(e)}isPropertyMessage(e){const t=new DataView(e).getUint8(0);return t===B.prop||t===B.propComponent}getReplicatedPropertyKey(e,t){return null!=t?.__netid?`component:${t.__netid}:${e}`:`actor:${e}`}sendPropertySnapshot(e,t){const n=this.buildPropertySnapshotEntries(e,t);if(0===n.length)return;const s=this.createPropertySnapshotBuffer(e,n);this.sendMessage(t,!0,s)}buildPropertySnapshotEntries(e,t){if(!this.isRelevant(e,t))return[];const n=this.replicatedPropertyRegistry.get(e);if(null==n)return[];const s=[];for(const r of n){if(!this.shouldReplicatePropertyToConnection(e,r.replOn,t))continue;if(null!=r.component&&null==r.component.__netid)continue;const n=this.readReplicatedPropertyValue(r),o=this.serializer.encode(n),i=N.encode(o),a=i.buffer.slice(i.byteOffset,i.byteOffset+i.byteLength);s.push({property:r.property,component:r.component,buffer:a})}return s}shouldReplicatePropertyToConnection(e,t,n){const s=this.resolveActorConnection(e);switch(t){case A.all:return!0;case A.owner:return s?.id===n.id;case A.notOwner:return null==s||s.id!==n.id}}readReplicatedPropertyValue(e){const t=e.target[e.property];return S(t)?t.value:t}createPropertySnapshotBuffer(e,t){let n=7;const s=t.map(e=>N.encode(e.property));for(let e=0;e<t.length;e++){const r=t[e];n+=1,null!=r.component&&(n+=1),n+=1+s[e].byteLength+4+r.buffer.byteLength}const r=new ArrayBuffer(n),o=new DataView(r);let i=0;o.setUint8(i,B.propSnapshot),i+=1,o.setUint32(i,e.__netid,!0),i+=4,o.setUint16(i,t.length,!0),i+=2;for(let e=0;e<t.length;e++){const n=t[e],a=s[e],c=null!=n.component;o.setUint8(i,c?$.component:$.actor),i+=1,c&&(o.setUint8(i,n.component.__netid),i+=1),o.setUint8(i,a.byteLength),i+=1,new Uint8Array(r,i,a.byteLength).set(a),i+=a.byteLength,o.setUint32(i,n.buffer.byteLength,!0),i+=4,new Uint8Array(r,i,n.buffer.byteLength).set(new Uint8Array(n.buffer)),i+=n.buffer.byteLength}return r}decodeReplicatedValue(e){let t;if(null!=D)t=D.decode(e);else{t="";const n=new Uint8Array(e);for(let e=0;e<n.length;e++)t+=String.fromCharCode(n[e])}return this.serializer.decode(t,V)}applyReplicatedProperty(e,t,n){S(e[t])?e[t].value=n:e[t]=n,this.synchronizeReplicatedPlayerActor(e)}synchronizeReplicatedPlayerActor(e){if(e instanceof M&&null!=e.playerId){const t=this.players.get(e.playerId);if(null==t)return;return this.players.attachController(e.playerId,e),void e.setLocallyControlled(null!=t.localPlayerIndex)}if(e instanceof U&&null!=e.playerId){null!=this.players.get(e.playerId)&&this.players.attachPlayerState(e.playerId,e)}}callRepNotify(e,t){const n=e[`onRep_${t}`];"function"==typeof n&&n.call(e)}};L=e([c()],L);export{L as NetService};const O=new a,Q=new i,N=new TextEncoder,D="undefined"!=typeof TextDecoder?new TextDecoder:void 0;function j(e,t,n){const s=new Uint8Array(e,t,n);if(null!=D)return D.decode(s);let r="";for(let e=0;e<s.length;e++)r+=String.fromCharCode(s[e]);return r}function k(e,t,n){new Uint8Array(e,t,n.byteLength).set(n)}const V=new m;/*
|
|
1
|
+
import{__decorate as e}from"tslib";import{delay as t,interval as n,Subject as s,takeUntil as r,tap as o}from"rxjs";import{Euler as i,Vector3 as a}from"three";import{Service as c}from"typedi";import{$actorOptions as l,BaseActor as d,getActorClassById as p}from"../../actors/actor.js";import{ActorComponent as h}from"../../actors/component.js";import{$actorComponents as u}from"../../actors/internal/component-init.js";import{inject as f}from"../../inject.js";import{World as g}from"../../services/world.js";import{NetMode as y}from"../net-session.js";import{NetRole as w}from"./net-actor-role.js";import{DecodeResult as m,NetSerializer as v}from"./net-serializer.js";import{isSignalLike as S}from"./net-utils.js";import{ReplOn as A}from"./replication.js";import{getRpcMethodId as b,getRpcMethodName as R,RunsOn as C}from"./rpc.js";import{ActorController as _,BasePlayerController as M,BasePlayerState as U}from"../../actors/controller/actor-controller.js";import{BaseGameState as P}from"../../actors/controller/game-state.js";import{makePropertyReplicated as T}from"./net-decorator.js";import{GameStateContext as I}from"../../game-state-context.js";import{PlayerSessions as B}from"../../player-sessions.js";var $;!function(e){e[e.rpc=1]="rpc",e[e.prop=2]="prop",e[e.spawn=3]="spawn",e[e.removeActor=4]="removeActor",e[e.rpcComponent=5]="rpcComponent",e[e.propComponent=6]="propComponent",e[e.rpcBinary=21]="rpcBinary",e[e.rpcComponentBinary=25]="rpcComponentBinary",e[e.actorReady=7]="actorReady",e[e.propSnapshot=8]="propSnapshot",e[e.serverTime=100]="serverTime",e[e.sceneTravelBegin=101]="sceneTravelBegin",e[e.sceneTravelReady=102]="sceneTravelReady",e[e.sceneTravelComplete=103]="sceneTravelComplete"}($||($={}));var L;!function(e){e[e.actor=0]="actor",e[e.component=1]="component"}(L||(L={}));let O=class{constructor(){this.playerSessions=f(B),this.gameStateContext=f(I),this.suspendedReplicationConnections=new Set,this.sceneTravelBeginSubject=new s,this.sceneTravelReadySubject=new s,this.sceneTravelCompleteSubject=new s,this.sceneTravelBegin$=this.sceneTravelBeginSubject.asObservable(),this.sceneTravelReady$=this.sceneTravelReadySubject.asObservable(),this.sceneTravelComplete$=this.sceneTravelCompleteSubject.asObservable(),this.actorConnection=new Map,this.destroyed=new s,this.world=f(g),this.serializer=f(v),this.serverTimeOffset=0,this.actorPreSpawnMessageBuffer=new Map,this.actorRefMissingMessageQueue=new Map,this.netStartupActorSpawnQueue=new Map,this.spawningActorNetIds=new Set,this.nextNetId=1,this.shouldReplicateMap=new Map,this.replicatedActors=new Map,this.replicatedProperties=new Map,this.replicatedPropertyRegistry=new Map}get pendingSceneTravel(){return this.activeSceneTravel}get gameState(){return this.gameStateContext.get()}setGameState(e){const t=this.gameStateContext.get();if(null!=t&&t!==e)throw new Error(`A game state is already registered as ${t.constructor.name}`);this.gameStateContext.set(e)}get mode(){return this.session?.mode??y.none}get isClient(){return this.mode===y.client}get isServer(){return this.mode<y.client}get isDedicatedServer(){return this.mode===y.dedicatedServer}start(){if(this.stop(),this.destroyed=new s,this.playerSessions.startNetwork(this.session),this.playerSessions.setOwningConnectionHandler((e,t)=>this.setOwningConnection(e,t)),this.playerSessions.setClearOwningConnectionHandler(e=>this.clearOwningConnection(e)),this.gameStateContext.setValidator(e=>{if(!this.shouldReplicate(e))throw new Error(`${e.constructor.name} must use @Actor({ replicate: true }) to be a game state`)}),this.mode!==y.none){if(this.isServer)for(const e of this.session.clients)this.sendServerTime(e);n(15).pipe(r(this.destroyed)).subscribe(()=>{this.processMessages()}),this.world.actorAdded.pipe(r(this.destroyed),o(e=>{this.configureMaterializedActorRole(e),this.isServer?this.defineNetId(e):this.processQueuedNetStartupActorSpawns(e),e instanceof M?(T(e,"playerId",this,!0,A.all),T(e,"playerState",this,!0,A.all),T(e,"pawn",this,!0,A.all)):e instanceof U?(T(e,"playerId",this,!0,A.all),T(e,"displayName",this,!0,A.all),T(e,"connected",this,!0,A.all)):e instanceof P&&T(e,"playerStates",this,!0,A.all)}),t(8)).subscribe(e=>{this.shouldReplicate(e)&&(T(e,"owner",this,!0,A.all),this.sendActorSpawn(e))}),this.world.actorRemoved.pipe(r(this.destroyed),t(10)).subscribe(e=>{this.sendActorRemove(e),e instanceof P&&this.gameStateContext.clear(e),this.actorConnection.delete(e.id),this.replicatedActors.delete(e),this.replicatedProperties.delete(e),this.replicatedPropertyRegistry.delete(e),this.actorPreSpawnMessageBuffer.delete(e)}),this.session.playerJoined.pipe(r(this.destroyed)).subscribe(e=>{null==this.activeSceneTravel?(this.sendServerTime(e),this.isServer&&this.replicatedActors.size>0&&this.spawnOnLateClient(e)):this.suspendAndSendSceneTravel(e,this.activeSceneTravel)})}}stop(){this.destroyed.next(null),this.destroyed.complete(),this.playerSessions.stop(),this.gameStateContext.stop(),this.suspendedReplicationConnections.clear(),this.activeSceneTravel=void 0}configureMaterializedActorRole(e){null!=e.__netSceneId&&this.mode!==y.none&&(this.shouldReplicate(e)?e.netRole=this.isServer?w.authority:w.simulatedProxy:this.isServer||(e.netRole=w.none))}shouldReplicate(e){const t=this.shouldReplicateMap.get(e);if(null==t){const t=e.constructor[l],n=!0===t?.replicate;return this.shouldReplicateMap.set(e,n),n}return!0===t}setReplicate(e,t){this.shouldReplicateMap.set(e,t)}isRelevant(e,t){const n=this.getRelevancyOptions(e);return!(!0===n?.ownerOnly)||this.resolveActorConnection(e)?.id===t.id}getRelevancyOptions(e){return e.constructor[l].relevancy}spawnOnLateClient(e){const t=[e];for(const e of this.replicatedActors.keys())this.sendActorSpawn(e,t)}async processMessages(){const e=this.session.hasMessage();for(let t=0;t<e;t++){const e=this.session.readMessage();null!=e&&await this.processMessage(e)}}async processMessage(e){try{switch(new DataView(e.buffer).getUint8(0)){case $.rpc:case $.rpcComponent:case $.rpcBinary:case $.rpcComponentBinary:this.processRpc(e.from,e.buffer);break;case $.prop:case $.propComponent:this.processProp(e.from,e.buffer);break;case $.propSnapshot:this.processPropSnapshot(e.from,e.buffer);break;case $.spawn:await this.processSpawn(e.from,e.buffer);break;case $.removeActor:this.processRemoveActor(e.from,e.buffer);break;case $.serverTime:this.processServerTime(e.from,e.buffer);break;case $.actorReady:this.processActorReady(e.from,e.buffer);break;case $.sceneTravelBegin:this.processSceneTravelBegin(e.from,e.buffer);break;case $.sceneTravelReady:this.processSceneTravelReady(e.from,e.buffer);break;case $.sceneTravelComplete:this.processSceneTravelComplete(e.from,e.buffer)}}catch(e){console.error("Failed to process message",e)}}processServerTime(e,t){if(this.isServer||this.session.server?.id!==e.id)return;const n=this.parseServerTime(t);this.serverTimeOffset=Date.now()-n}getActorRefMissingMessageQueue(e){let t=this.actorRefMissingMessageQueue.get(e);return null==t&&(t=[],this.actorRefMissingMessageQueue.set(e,t)),t}processRpc(e,t){let n=0;const s=new DataView(t),r=s.getUint8(n);n+=1;const o=s.getUint32(n,!0);n+=4;const i=s.getUint8(n);n+=1;const a=r===$.rpcBinary||r===$.rpcComponentBinary;let c,l,d="";if(a){const e=s.getUint32(n,!0);n+=4,l=new Uint8Array(t,n,e),n+=e}else{const e=s.getUint16(n,!0);n+=2,d=k(t,n,e),n+=e;try{c=this.serializer.decode(d,V)}catch(e){return void console.warn("Failed to parse RPC args JSON",d,e)}}if(!a&&V.error){if(null!=V.missingActorId){const n=this.getActorRefMissingMessageQueue(V.missingActorId);console.debug(`Missing referenced actor ${V.missingActorId}. Queueing message for later`),n.push({from:e,buffer:t})}else console.log("Unknown decode error",d);return}const p=this.world.actors.find(e=>e.__netid===o);if(null==p){return this.getActorRefMissingMessageQueue(o).push({from:e,buffer:t}),void console.debug(`Missing actor ${o}. Queueing message for later`)}let h;if(r===$.rpcComponent||r===$.rpcComponentBinary){const e=s.getUint8(n),t=p[u];if(null!=t&&(h=t.find(t=>t.__netid===e)),null==h)return void console.error(`RPC failed: Failed to find component with netid ${e} on actor with netid ${o}`)}if(this.isServer){if(!this.session.clients.some(t=>t.id==e.id))return void console.warn("Received RCP from non client");const t=this.resolveActorConnection(p);if(null==t)return void console.error("Received server RPC for actor without owner which can't be processed");if(t.id!==e.id)return void console.warn(`Received RPC for actor net id ${p.__netid} from connection ${e.id}, but this connection is not the owner. Can't process.`)}else{if(!(this.session.server?.id===e.id))return void console.warn("Received RCP from non server which is not allowed")}const f=h??p,g=R(f,i);if(null!=g)if("function"==typeof f[g])if(a)f[g](l);else{if(V.requiresAssetLookups)return this.serializer.decodeAssets(c).then(e=>{f[g](...e)}).catch(e=>{console.warn(`Failed to decode asset references for RPC '${g}' on actor net id ${p.__netid}`,e)});f[g](...c)}else console.error(`RPC function '${g}' not found on actor net id ${p.__netid}`+(null!=h?` and component net id ${h.__netid}`:""));else console.error(`RPC method id ${i} not found on actor net id ${p.__netid}`+(null!=h?` and component net id ${h.__netid}`:""))}processProp(e,t){if(this.session.server?.id!==e.id)return void console.warn(`Received property replication from non-server connection (id: ${e.id}), ignoring.`);let n=0;const s=new DataView(t),r=s.getUint8(n);n+=1;const o=s.getUint32(n,!0);n+=4;const i=s.getUint8(n);n+=1;let a="";for(let e=0;e<i;e++)a+=String.fromCharCode(s.getUint8(n+e));n+=i;const c=s.getUint16(n,!0);n+=2;const l=t.slice(n,n+c);n+=c;const d=this.world.actors.find(e=>e.__netid===o);if(!d){return this.getActorRefMissingMessageQueue(o).push({from:e,buffer:t}),void console.error("Failed to process property replication. No actor found with id",o,a)}let p;if(r===$.propComponent){const e=s.getUint8(n),t=d[u];if(null!=t&&(p=t.find(t=>t.__netid===e)),null==p)return void console.error(`Failed to find component with netid ${e} on actor with netid ${o}`)}const h=p??d;try{const n=this.decodeReplicatedValue(l);if(V.error){if(null!=V.missingActorId){let n=this.getActorRefMissingMessageQueue(V.missingActorId);console.debug(`Missing referenced actor ${V.missingActorId}. Queueing message for later`),n.push({from:e,buffer:t})}return}if(V.requiresAssetLookups)return this.serializer.decodeAssets(n).then(e=>{this.applyReplicatedProperty(h,a,e),this.callRepNotify(h,a)}).catch(e=>{console.warn(`Failed to decode asset references for replicated property '${a}' on actor id ${o}`,e)});this.applyReplicatedProperty(h,a,n),this.callRepNotify(h,a)}catch(e){console.warn(`Failed to decode replicated property '${a}' on actor id ${o}`,e)}}processPropSnapshot(e,t){if(this.session.server?.id!==e.id)return void console.warn(`Received property snapshot from non-server connection (id: ${e.id}), ignoring.`);let n=0;const s=new DataView(t);n+=1;const r=s.getUint32(n,!0);n+=4;const o=this.world.actors.find(e=>e.__netid===r);if(null==o){return this.getActorRefMissingMessageQueue(r).push({from:e,buffer:t}),void console.debug(`Missing actor ${r}. Queueing property snapshot for later`)}const i=s.getUint16(n,!0);n+=2;const a=[];for(let c=0;c<i;c++){const i=s.getUint8(n);n+=1;let c=o;if(i===L.component){const e=s.getUint8(n);n+=1;const t=o[u];if(c=t?.find(t=>t.__netid===e),null==c)return void console.error(`Failed to process property snapshot. No component found with netid ${e} on actor with netid ${r}`)}else if(i!==L.actor)return void console.error(`Failed to process property snapshot. Unknown target kind ${i}`);const l=s.getUint8(n);n+=1;let d="";for(let e=0;e<l;e++)d+=String.fromCharCode(s.getUint8(n+e));n+=l;const p=s.getUint32(n,!0);n+=4;const h=t.slice(n,n+p);n+=p;try{const n=this.decodeReplicatedValue(h);if(V.error){if(null!=V.missingActorId){this.getActorRefMissingMessageQueue(V.missingActorId).push({from:e,buffer:t}),console.debug(`Missing referenced actor ${V.missingActorId}. Queueing property snapshot for later`)}else console.warn(`Unknown decode error in property snapshot for actor ${r}`);return}a.push({target:c,property:d,value:n,requiresAssetLookups:V.requiresAssetLookups})}catch(e){return void console.warn(`Failed to decode replicated property snapshot entry '${d}' on actor id ${r}`,e)}}if(a.some(e=>e.requiresAssetLookups))return Promise.all(a.map(async e=>{e.requiresAssetLookups&&(e.value=await this.serializer.decodeAssets(e.value))})).then(()=>{this.applyReplicatedProperties(a)}).catch(e=>{console.warn(`Failed to decode asset references for property snapshot on actor id ${r}`,e)});this.applyReplicatedProperties(a)}applyReplicatedProperties(e){for(const t of e)this.applyReplicatedProperty(t.target,t.property,t.value);for(const t of e)this.callRepNotify(t.target,t.property)}async processSpawn(e,t){if(this.isServer)return;const n=new DataView(t);let s=0;s+=1;const r=n.getUint32(s,!0);s+=4;const o=n.getUint8(s);s+=1;let i="";for(let e=0;e<o;e++)i+=String.fromCharCode(n.getUint8(s+e));s+=o,Q.set(n.getFloat32(s,!0),n.getFloat32(s+4,!0),n.getFloat32(s+8,!0)),s+=12,N.set(n.getFloat32(s,!0),n.getFloat32(s+4,!0),n.getFloat32(s+8,!0)),s+=12;const a=n.getUint8(s);s+=1;const c=n.getUint32(s);s+=4;const l=this.readOptionalSceneId(t,s);console.log("Spawning "+i);const d=p(i);if(!d)return void console.error(`Failed to spawn actor: unknown actor type '${i}'`);const h=this.world.actors.find(e=>e.__netid===r);if(null!=h)return this.applySpawnNetInfo(h,r,a,c),h instanceof P&&this.setGameState(h),this.processQueuedActorMessages(r),void this.sendActorReady(h);if(this.spawningActorNetIds.has(r))return void console.log("Actor was already spawned",null!=h);const u=this.findMaterializedNetStartupActor(l,i,r);if(null!=u)return this.applySpawnNetInfo(u,r,a,c),u instanceof P&&this.setGameState(u),this.processQueuedActorMessages(r),void this.sendActorReady(u);if(null==l){this.spawningActorNetIds.add(r);try{const e=await this.world.spawnActorWithNetInfo(d,r,a,c,Q,N);e instanceof P&&this.setGameState(e),this.processQueuedActorMessages(r),this.sendActorReady(e)}catch(e){return void console.error("Spawning replicated actor on client failed",e)}finally{this.spawningActorNetIds.delete(r)}}else this.getNetStartupActorSpawnQueue(l).push({from:e,buffer:t})}readOptionalSceneId(e,t){if(t+2>e.byteLength)return;const n=new DataView(e).getUint16(t,!0);return t+=2,0===n||t+n>e.byteLength?void 0:k(e,t,n)}findMaterializedNetStartupActor(e,t,n){if(null!=e)return this.world.actors.find(s=>{if(s.__netSceneId!==e)return!1;if(null!=s.__netid&&s.__netid!==n)return!1;return(s.constructor.__actorId??s.constructor.name)===t})}applySpawnNetInfo(e,t,n,s){if(e.__netid=t,e.netRole=n,0===s)e.owner=void 0;else{const t=this.world.actors.find(e=>e.__netid===s);null!=t&&(e.owner=t)}e instanceof M&&e.setLocallyControlled(n===w.autonomousProxy)}getNetStartupActorSpawnQueue(e){let t=this.netStartupActorSpawnQueue.get(e);return null==t&&(t=[],this.netStartupActorSpawnQueue.set(e,t)),t}processQueuedNetStartupActorSpawns(e){if(null==e.__netSceneId)return;const t=this.netStartupActorSpawnQueue.get(e.__netSceneId);if(null!=t){this.netStartupActorSpawnQueue.delete(e.__netSceneId);for(const e of t)this.processMessage(e)}}processQueuedActorMessages(e){const t=this.actorRefMissingMessageQueue.get(e);if(null!=t){console.log(`Processing queued messages for actor ${e}`),this.actorRefMissingMessageQueue.delete(e);for(const e of t)this.processMessage(e)}}sendActorReady(e){if(this.mode===y.none)return;if(!e||null==e.__netid)return void console.warn("sendActorReady: actor or actor.__netid is undefined");if(this.isServer)return;const t=new ArrayBuffer(5),n=new DataView(t);let s=0;n.setUint8(s,$.actorReady),s+=1,n.setUint32(s,e.__netid,!0),this.session.server&&this.session.sendMessage(this.session.server,!0,t)}processActorReady(e,t){if(!this.isServer)return;const n=new DataView(t).getUint32(1,!0),s=this.world.actors.find(e=>e.__netid===n);if(!s)return void console.warn(`processActorReady: No actor found with net id ${n}`);const r=this.actorPreSpawnMessageBuffer.get(s),o=this.getConnectionKey(e),i=r?.get(o);if(null!=i){this.sendPropertySnapshot(s,e);const t=i.filter(e=>!this.isPropertyMessage(e.buffer));if(t.length>0){console.log(`Sending ${t.length} queued messages for actor ${s.__netid}`);for(const e of t)this.sendMessage(e.connection,e.reliable,e.buffer)}r.delete(o),0===r.size&&this.actorPreSpawnMessageBuffer.delete(s)}}sendRpc(e,t,n,s,r,o){if(this.mode==y.none)return;if(!this.shouldReplicate(n))return;const i=n.__netid;if(null==i)return void console.warn("No __netid defined on actor. Actor must be repliated for RPC");const a=this.resolveActorConnection(n),c=[];if(this.isServer)switch(e){case C.all:c.push(...this.session.clients.filter(e=>this.isRelevant(n,e)));break;case C.client:null!=a&&c.push(a);break;case C.notOwner:c.push(...this.session.clients.filter(e=>e.id!==a?.id&&this.isRelevant(n,e)));case C.server:}else switch(e){case C.all:case C.client:case C.notOwner:break;case C.server:c.push(this.session.server)}if(0==c.length)return;if(null!=o&&null==o.__netid)return void console.warn("No __netid defined on component. Component must be initialized for RPC",o);const l=null!=o,d=o??n,p=b(d,s);if(null==p)return void console.warn(`RPC function '${s}' is not registered on ${d.constructor?.name??"target"}`);const h=1===r.length?function(e){if(e instanceof Uint8Array)return e;if(e instanceof ArrayBuffer)return new Uint8Array(e);if("undefined"!=typeof SharedArrayBuffer&&e instanceof SharedArrayBuffer)return new Uint8Array(e);if(ArrayBuffer.isView(e))return new Uint8Array(e.buffer,e.byteOffset,e.byteLength);return null}(r[0]):null;if(null!=h){let e=10+h.byteLength;l&&(e+=1);const s=new ArrayBuffer(e),r=new DataView(s);let a=0;r.setUint8(a,l?$.rpcComponentBinary:$.rpcBinary),a+=1,r.setUint32(a,i,!0),a+=4,r.setUint8(a,p),a+=1,r.setUint32(a,h.byteLength,!0),a+=4,x(s,a,h),a+=h.byteLength,l&&(r.setUint8(a,o.__netid),a+=1);for(const e of c)null!=e&&this.sendWithQueue(n,{connection:e,reliable:t,buffer:s});return}const u=this.serializer.encode(r),f=j.encode(u);if(f.byteLength>65535)return void console.warn("RPC arguments are too large to send with the JSON RPC format",s);let g=8+f.byteLength;l&&(g+=1);const w=new ArrayBuffer(g),m=new DataView(w);let v=0;m.setUint8(v,l?$.rpcComponent:$.rpc),v+=1,m.setUint32(v,i,!0),v+=4,m.setUint8(v,p),v+=1,m.setUint16(v,f.byteLength,!0),v+=2,x(w,v,f),v+=f.byteLength,l&&(m.setUint8(v,o.__netid),v+=1);for(const e of c)null!=e&&this.sendWithQueue(n,{connection:e,reliable:t,buffer:w})}sendWithQueue(e,t){if(this.shouldQueueMessageUntilActorReady(e,t.connection)){return void this.getPreSpawnMessageQueue(e,t.connection).push(t)}this.sendMessage(t.connection,t.reliable,t.buffer)}findQueuedActorsInData(e,t=[]){if("object"==typeof e){if(Array.isArray(e))for(const t of e)this.findQueuedActorsInData(t);if(e instanceof d){this.actorPreSpawnMessageBuffer.get(e)}}}handlePropertySet(e,t,n,s,r,o){if(this.mode==y.none)return;if(!this.isServer)return;if(!this.shouldReplicate(n))return;let i=this.replicatedProperties.get(n);null==i&&(i={},this.replicatedProperties.set(n,i));const a=this.serializer.encode(r),c=j.encode(a),l=c.buffer.slice(c.byteOffset,c.byteOffset+c.byteLength);i[this.getReplicatedPropertyKey(s,o)]={property:s,replOn:e,reliable:t,buffer:l,component:o},this.sendProperty(e,t,n,s,l,o)}sendProperty(e,t,n,s,r,o){if(!this.isServer)return;const i=[],a=this.resolveActorConnection(n);switch(e){case A.all:i.push(...this.session.clients);break;case A.owner:null!=a&&i.push(a);break;case A.notOwner:null==a?i.push(...this.session.clients):i.push(...this.session.clients.filter(e=>e.id!==a.id))}const c=null!=o&&null!=o.__netid;let l=6+s.length+2+r.byteLength;c&&(l+=1);const d=new ArrayBuffer(l),p=new DataView(d);let h=0;p.setUint8(h,c?$.propComponent:$.prop),h+=1,p.setUint32(h,n.__netid,!0),h+=4,p.setUint8(h,s.length),h+=1;for(let e=0;e<s.length;e++)p.setUint8(h++,s.charCodeAt(e));p.setUint16(h,r.byteLength,!0),h+=2,new Uint8Array(d,h,r.byteLength).set(new Uint8Array(r)),h+=r.byteLength,c&&(p.setUint8(h,o.__netid),h+=1);for(const e of i)null!=e&&this.isRelevant(n,e)&&this.sendWithQueue(n,{connection:e,reliable:t,buffer:d})}defineNetId(e){null==e.__netid&&(e.__netid=this.nextNetId++)}registerReplicatedProperty(e,t,n,s,r,o){if(null==n||null==s||null==r)return;let i=this.replicatedPropertyRegistry.get(n);null==i&&(i=[],this.replicatedPropertyRegistry.set(n,i));const a=o??(r instanceof h?r:void 0),c=i.find(e=>e.target===r&&e.property===s);if(null!=c)return c.replOn=e,c.reliable=t,void(c.component=a);i.push({property:s,replOn:e,reliable:t,target:r,component:a})}sendActorSpawn(e,t=this.session.clients){if(!this.isServer)return;const n=this.replicatedActors.get(e),s=t.filter(t=>!!this.isRelevant(e,t)&&!n?.connections.some(e=>e.id===t.id));if(0===s.length)return;this.defineNetId(e);const r=e.__netid;let o=e.constructor.__actorId??e.constructor.name;const i=j.encode(o);let a;null!=e.__netSceneId&&(a=j.encode(e.__netSceneId),a.byteLength>65535&&(console.warn(`Scene actor id is too long to replicate as a startup actor: ${e.__netSceneId}`),a=void 0));const c=a?.byteLength??0,l=6+i.length+12+12+1+4+2+c,d=new ArrayBuffer(l),p=new DataView(d);let h=0;p.setUint8(h,$.spawn),h+=1,p.setUint32(h,r,!0),h+=4,p.setUint8(h,i.length),h+=1,new Uint8Array(d).set(i,h),h+=i.length,p.setFloat32(h,e.position.x,!0),h+=4,p.setFloat32(h,e.position.y,!0),h+=4,p.setFloat32(h,e.position.z,!0),h+=4,p.setFloat32(h,e.rotation.x,!0),h+=4,p.setFloat32(h,e.rotation.y,!0),h+=4,p.setFloat32(h,e.rotation.z,!0),h+=4;let u=h;h+=1,p.setUint32(h,e.owner?.__netid??0),h+=4,p.setUint16(h,c,!0),h+=2,null!=a&&(x(d,h,a),h+=a.byteLength);let f=this.replicatedActors.get(e);null==f&&(f={spawnedAt:Date.now(),connections:[]},this.replicatedActors.set(e,f));for(const t of s){const n=d.slice(0),s=new DataView(n),r=this.resolveActorConnection(e)?.id===t.id?w.autonomousProxy:w.simulatedProxy;s.setUint8(u,r),this.getPreSpawnMessageQueue(e,t),this.sendMessage(t,!0,n),f.connections.some(e=>e.id===t.id)||f.connections.push(t)}}sendActorRemove(e){if(!this.isServer)return;const t=new ArrayBuffer(5),n=new DataView(t);let s=0;n.setUint8(s,$.removeActor),s+=1,n.setUint32(s,e.__netid,!0);for(const e of this.session.clients)this.sendMessage(e,!0,t)}processRemoveActor(e,t){if(this.isServer||this.session.server?.id!==e.id)return;const n=new DataView(t).getUint32(1,!0),s=this.world.actors.find(e=>e.__netid===n);s&&(this.world.removeActor(s),this.replicatedActors?.delete?.(s),this.replicatedProperties?.delete?.(s))}setOwningConnection(e,t){this.actorConnection.set(e.id,t),this.isServer&&this.world.actors.includes(e)&&this.shouldReplicate(e)&&!0===this.getRelevancyOptions(e)?.ownerOnly&&this.sendActorSpawn(e,[t])}clearOwningConnection(e){this.actorConnection.delete(e.id)}getOwningConnection(e){return this.actorConnection.get(e.id)}getActorsByOwningConnection(e){const t=[];return this.actorConnection.forEach((n,s)=>{if(n.id===e.id){const e=this.world.actors.find(e=>e.id===s);null!=e&&t.push(e)}}),t}resolveActorConnection(e){let t;for(;null==t&&null!=e;)t=this.actorConnection.get(e.id),null==t&&(e=e.owner);return t}isLocallyControlled(e){let t=e;for(;null!=t;){if(t.netRole===w.autonomousProxy)return!0;if(t instanceof _)return t.isLocallyControlled;t=t.owner}return!1}isLocalOwner(e){return this.isLocallyControlled(e)}beginSceneTravel(e,t){if(!this.isServer)throw new Error("Only authority can begin scene travel");const n={travelId:e,sceneName:t};this.activeSceneTravel=n;for(const e of this.session.clients)this.suspendAndSendSceneTravel(e,n)}sendSceneTravelReady(e,t){if(!this.isClient||null==this.session.server)return;const n=this.createSceneTravelMessage($.sceneTravelReady,{travelId:e,sceneName:t});this.session.sendMessage(this.session.server,!0,n)}completeSceneTravel(e,t){if(!this.isServer)throw new Error("Only authority can complete scene travel");const n=this.createSceneTravelMessage($.sceneTravelComplete,{travelId:e,sceneName:t});for(const e of this.session.clients)this.resetReplicationState(e),this.suspendedReplicationConnections.delete(this.getConnectionKey(e)),this.spawnOnLateClient(e),this.session.sendMessage(e,!0,n);this.activeSceneTravel=void 0}suspendAndSendSceneTravel(e,t){this.suspendedReplicationConnections.add(this.getConnectionKey(e));const n=this.createSceneTravelMessage($.sceneTravelBegin,t);this.session.sendMessage(e,!0,n)}processSceneTravelBegin(e,t){if(!this.isClient||this.session.server?.id!==e.id)return;const n=this.parseSceneTravelMessage(t);this.activeSceneTravel=n,this.sceneTravelBeginSubject.next(n)}processSceneTravelReady(e,t){if(!this.isServer||!this.session.clients.some(t=>t.id===e.id))return;const n=this.parseSceneTravelMessage(t);this.sceneTravelReadySubject.next({travelId:n.travelId,connection:e})}processSceneTravelComplete(e,t){if(!this.isClient||this.session.server?.id!==e.id)return;const n=this.parseSceneTravelMessage(t);this.activeSceneTravel=void 0,this.sceneTravelCompleteSubject.next(n)}createSceneTravelMessage(e,t){const n=j.encode(JSON.stringify(t)),s=new ArrayBuffer(1+n.byteLength);return new DataView(s).setUint8(0,e),x(s,1,n),s}parseSceneTravelMessage(e){const t=e.slice(1),n=null!=D?D.decode(t):String.fromCharCode(...new Uint8Array(t));return JSON.parse(n)}sendMessage(e,t,n){this.isServer&&this.suspendedReplicationConnections.has(this.getConnectionKey(e))||this.session.sendMessage(e,t,n)}sendServerTime(e){const t=new ArrayBuffer(9),n=new DataView(t);n.setUint8(0,$.serverTime),n.setBigUint64(1,BigInt(Date.now()),!0),this.sendMessage(e,!0,t)}parseServerTime(e){const t=new DataView(e).getBigUint64(1,!0);return Number(t)}getConnectionKey(e){return e.id.toString()}resetReplicationState(e){const t=this.getConnectionKey(e);for(const[n,s]of this.replicatedActors){for(let t=s.connections.length-1;t>=0;t--)s.connections[t].id===e.id&&s.connections.splice(t,1);const r=this.actorPreSpawnMessageBuffer.get(n);r?.delete(t),0===r?.size&&this.actorPreSpawnMessageBuffer.delete(n)}}getPreSpawnMessageQueue(e,t){let n=this.actorPreSpawnMessageBuffer.get(e);null==n&&(n=new Map,this.actorPreSpawnMessageBuffer.set(e,n));const s=this.getConnectionKey(t);let r=n.get(s);return null==r&&(r=[],n.set(s,r)),r}shouldQueueMessageUntilActorReady(e,t){const n=this.actorPreSpawnMessageBuffer.get(e);return!!n?.has(this.getConnectionKey(t))||this.isServer&&!this.replicatedActors.has(e)&&this.shouldReplicate(e)}isPropertyMessage(e){const t=new DataView(e).getUint8(0);return t===$.prop||t===$.propComponent}getReplicatedPropertyKey(e,t){return null!=t?.__netid?`component:${t.__netid}:${e}`:`actor:${e}`}sendPropertySnapshot(e,t){const n=this.buildPropertySnapshotEntries(e,t);if(0===n.length)return;const s=this.createPropertySnapshotBuffer(e,n);this.sendMessage(t,!0,s)}buildPropertySnapshotEntries(e,t){if(!this.isRelevant(e,t))return[];const n=this.replicatedPropertyRegistry.get(e);if(null==n)return[];const s=[];for(const r of n){if(!this.shouldReplicatePropertyToConnection(e,r.replOn,t))continue;if(null!=r.component&&null==r.component.__netid)continue;const n=this.readReplicatedPropertyValue(r),o=this.serializer.encode(n),i=j.encode(o),a=i.buffer.slice(i.byteOffset,i.byteOffset+i.byteLength);s.push({property:r.property,component:r.component,buffer:a})}return s}shouldReplicatePropertyToConnection(e,t,n){const s=this.resolveActorConnection(e);switch(t){case A.all:return!0;case A.owner:return s?.id===n.id;case A.notOwner:return null==s||s.id!==n.id}}readReplicatedPropertyValue(e){const t=e.target[e.property];return S(t)?t.value:t}createPropertySnapshotBuffer(e,t){let n=7;const s=t.map(e=>j.encode(e.property));for(let e=0;e<t.length;e++){const r=t[e];n+=1,null!=r.component&&(n+=1),n+=1+s[e].byteLength+4+r.buffer.byteLength}const r=new ArrayBuffer(n),o=new DataView(r);let i=0;o.setUint8(i,$.propSnapshot),i+=1,o.setUint32(i,e.__netid,!0),i+=4,o.setUint16(i,t.length,!0),i+=2;for(let e=0;e<t.length;e++){const n=t[e],a=s[e],c=null!=n.component;o.setUint8(i,c?L.component:L.actor),i+=1,c&&(o.setUint8(i,n.component.__netid),i+=1),o.setUint8(i,a.byteLength),i+=1,new Uint8Array(r,i,a.byteLength).set(a),i+=a.byteLength,o.setUint32(i,n.buffer.byteLength,!0),i+=4,new Uint8Array(r,i,n.buffer.byteLength).set(new Uint8Array(n.buffer)),i+=n.buffer.byteLength}return r}decodeReplicatedValue(e){let t;if(null!=D)t=D.decode(e);else{t="";const n=new Uint8Array(e);for(let e=0;e<n.length;e++)t+=String.fromCharCode(n[e])}return this.serializer.decode(t,V)}applyReplicatedProperty(e,t,n){S(e[t])?e[t].value=n:e[t]=n,this.synchronizeReplicatedPlayerActor(e)}synchronizeReplicatedPlayerActor(e){if(e instanceof M&&null!=e.playerId){const t=this.playerSessions.get(e.playerId);if(null==t)return;return this.playerSessions.attachController(e.playerId,e),void e.setLocallyControlled(null!=t.localPlayerIndex)}if(e instanceof U&&null!=e.playerId){null!=this.playerSessions.get(e.playerId)&&this.playerSessions.attachPlayerState(e.playerId,e)}}callRepNotify(e,t){const n=e[`onRep_${t}`];"function"==typeof n&&n.call(e)}};O=e([c()],O);export{O as NetService};const Q=new a,N=new i,j=new TextEncoder,D="undefined"!=typeof TextDecoder?new TextDecoder:void 0;function k(e,t,n){const s=new Uint8Array(e,t,n);if(null!=D)return D.decode(s);let r="";for(let e=0;e<s.length;e++)r+=String.fromCharCode(s[e]);return r}function x(e,t,n){new Uint8Array(e,t,n.byteLength).set(n)}const V=new m;/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as e}from"tslib";import{Service as t}from"typedi";import{randomUUID as s}from"../utils/uuid.js";import{BasePlayerController as n}from"./actors/controller/actor-controller.js";import{inject as i}from"./inject.js";import{NetMode as o}from"./net/net-session.js";import{NetService as r}from"./net/service/net-service.js";import{SceneTransitionService as a}from"./services/scene-transition.js";import{World as l}from"./services/world.js";let c=class{constructor(){this.net=i(r),this.scenes=i(a),this.world=i(l),this.subscriptions=[]}start(e){if(this.stop(),this.gameMode=e,this.subscriptions.push(this.net.sceneTravelBegin$.subscribe(e=>{this.clientTravel=this.handleClientTravel(e).catch(t=>{console.error(`Failed to load scene ${JSON.stringify(e.sceneName)} for network travel`,t)})}),this.net.sceneTravelReady$.subscribe(e=>this.markClientReady(e.travelId,e.connection.id.toString())),this.net.sceneTravelComplete$.subscribe(e=>this.handleClientTravelComplete(e))),null!=this.net.session&&this.subscriptions.push(this.net.session.playerLeft.subscribe(e=>this.markClientReady(this.pending?.travelId,e.id.toString())),this.net.session.playerJoined.subscribe(e=>this.addTravellingClient(e.id.toString()))),this.net.isClient&&null!=this.net.pendingSceneTravel){const e=this.net.pendingSceneTravel;this.clientTravel=this.handleClientTravel(e).catch(t=>{console.error(`Failed to load scene ${JSON.stringify(e.sceneName)} for network travel`,t)})}}stop(){for(const e of this.subscriptions)e.unsubscribe();this.subscriptions.length=0,this.gameMode=void 0,this.pending=void 0}async travelAll(e){if(!this.net.isServer)throw new Error("Only authority can initiate full-session scene travel");if(null!=this.pending)throw new Error("A network scene transition is already in progress");const t=this.requireGameMode(),n=e.trim();if(0===n.length)throw new Error("A destination scene name is required");const i=s();for(const e of this.net.
|
|
1
|
+
import{__decorate as e}from"tslib";import{Service as t}from"typedi";import{randomUUID as s}from"../utils/uuid.js";import{BasePlayerController as n}from"./actors/controller/actor-controller.js";import{inject as i}from"./inject.js";import{NetMode as o}from"./net/net-session.js";import{NetService as r}from"./net/service/net-service.js";import{SceneTransitionService as a}from"./services/scene-transition.js";import{World as l}from"./services/world.js";let c=class{constructor(){this.net=i(r),this.scenes=i(a),this.world=i(l),this.subscriptions=[]}start(e){if(this.stop(),this.gameMode=e,this.subscriptions.push(this.net.sceneTravelBegin$.subscribe(e=>{this.clientTravel=this.handleClientTravel(e).catch(t=>{console.error(`Failed to load scene ${JSON.stringify(e.sceneName)} for network travel`,t)})}),this.net.sceneTravelReady$.subscribe(e=>this.markClientReady(e.travelId,e.connection.id.toString())),this.net.sceneTravelComplete$.subscribe(e=>this.handleClientTravelComplete(e))),null!=this.net.session&&this.subscriptions.push(this.net.session.playerLeft.subscribe(e=>this.markClientReady(this.pending?.travelId,e.id.toString())),this.net.session.playerJoined.subscribe(e=>this.addTravellingClient(e.id.toString()))),this.net.isClient&&null!=this.net.pendingSceneTravel){const e=this.net.pendingSceneTravel;this.clientTravel=this.handleClientTravel(e).catch(t=>{console.error(`Failed to load scene ${JSON.stringify(e.sceneName)} for network travel`,t)})}}stop(){for(const e of this.subscriptions)e.unsubscribe();this.subscriptions.length=0,this.gameMode=void 0,this.pending=void 0}async travelAll(e){if(!this.net.isServer)throw new Error("Only authority can initiate full-session scene travel");if(null!=this.pending)throw new Error("A network scene transition is already in progress");const t=this.requireGameMode(),n=e.trim();if(0===n.length)throw new Error("A destination scene name is required");const i=s();for(const e of this.net.playerSessions.all)"disconnected"!==e.status&&this.net.playerSessions.setStatus(e.info.id,"travelling");if(await t.onTravelStarted(n),this.net.mode!==o.none){const e=this.waitForClients(i,n);this.net.beginSceneTravel(i,n),await e}this.releaseControlledActors(),await this.scenes.transitionTo(n),await this.waitForPendingClients(),await this.restartPlayers(t),this.net.mode!==o.none&&this.net.completeSceneTravel(i,n),this.pending=void 0,await t.onTravelCompleted(n)}waitForClients(e,t){const s=new Set(this.net.session?.clients.map(e=>e.id.toString())??[]);return this.pending={travelId:e,sceneName:t,expectedConnections:s,readyConnections:new Set},0===s.size?Promise.resolve():new Promise(e=>{this.pending.resolve=e})}waitForPendingClients(){const e=this.pending;return null==e||e.readyConnections.size===e.expectedConnections.size?Promise.resolve():new Promise(t=>{e.resolve=t})}markClientReady(e,t){const s=this.pending;if(null!=s&&e===s.travelId&&s.expectedConnections.has(t)&&(s.readyConnections.add(t),s.readyConnections.size===s.expectedConnections.size)){const e=s.resolve;s.resolve=void 0,e?.()}}addTravellingClient(e){this.pending?.expectedConnections.add(e)}async handleClientTravel(e){if(this.net.isClient){for(const e of this.net.playerSessions.all)this.net.playerSessions.setStatus(e.info.id,"travelling");await(this.gameMode?.onTravelStarted(e.sceneName)),this.releaseControlledActors(),await this.scenes.transitionTo(e.sceneName),this.net.sendSceneTravelReady(e.travelId,e.sceneName)}}handleClientTravelComplete(e){if(this.net.isClient){for(const e of this.net.playerSessions.all)"disconnected"!==e.status&&this.net.playerSessions.setStatus(e.info.id,"active");this.gameMode?.onTravelCompleted(e.sceneName)}}releaseControlledActors(){for(const e of[...this.world.actors]){if(!(e instanceof n)||null==e.pawn)continue;const t=e.pawn;e.unpossess(),this.world.actors.includes(t)&&this.world.removeActor(t)}}async restartPlayers(e){for(const t of this.net.playerSessions.all){if("disconnected"===t.status||null==t.controller)continue;const s=await e.spawnDefaultPawn(t,t.controller);null!=s&&t.controller.possess(s),this.net.playerSessions.setStatus(t.info.id,"active")}}requireGameMode(){if(null==this.gameMode)throw new Error("NetworkTravelCoordinator must be started with a GameMode");return this.gameMode}};c=e([t()],c);export{c as NetworkTravelCoordinator};/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { GameMode } from './game-mode.js';
|
|
2
2
|
/** Runs an optional GameMode against the authoritative network player registry. */
|
|
3
3
|
export declare class PlayerLifecycleCoordinator {
|
|
4
|
-
private readonly
|
|
4
|
+
private readonly playerSessions;
|
|
5
|
+
private readonly gameState;
|
|
5
6
|
private readonly subscriptions;
|
|
6
7
|
private readonly playerOperations;
|
|
7
8
|
private gameMode?;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as t}from"tslib";import{Service as e}from"typedi";import{inject as a}from"./inject.js";import{
|
|
1
|
+
import{__decorate as t}from"tslib";import{Service as e}from"typedi";import{GameStateContext as s}from"./game-state-context.js";import{inject as a}from"./inject.js";import{PlayerSessions as i}from"./player-sessions.js";let r=class{constructor(){this.playerSessions=a(i),this.gameState=a(s),this.subscriptions=[],this.playerOperations=new Map}get activeGameMode(){return this.gameMode}async start(t){if(await this.stop(),!this.playerSessions.isAuthority)return;this.gameMode=t;const e=this.playerSessions.all;this.subscriptions.push(this.playerSessions.playerJoined$.subscribe(e=>{this.schedulePlayerOperation(t,e,()=>this.activatePlayer(t,e))}),this.playerSessions.playerLeft$.subscribe(e=>{this.schedulePlayerOperation(t,e,()=>this.deactivatePlayer(t,e))}));try{if(null==this.gameState.get()){const e=await t.createGameState();null!=e&&this.gameState.set(e)}await t.onStart();for(const s of e)await this.runPlayerOperation(s,()=>this.activatePlayer(t,s))}catch(t){throw await this.stop(),t}}async stop(){for(const t of this.subscriptions)t.unsubscribe();this.subscriptions.length=0,await Promise.allSettled(this.playerOperations.values());const t=this.gameMode;this.gameMode=void 0,null!=t&&await t.onStop()}async activatePlayer(t,e){if("disconnected"===e.status)return;let s=e.playerState;if(null==s){s=await t.createPlayerState(e),this.playerSessions.attachPlayerState(e.info.id,s);const a=this.gameState.get();null==a||a.playerStates.includes(s)||(a.playerStates=[...a.playerStates,s])}s.connected=!0;let a=e.controller;if(null==a?(a=await t.createPlayerController(e),this.playerSessions.attachController(e.info.id,a)):this.playerSessions.setStatus(e.info.id,"active"),null!=e.connection&&this.playerSessions.setOwningConnection(a,e.connection),null==a.pawn){const s=await t.spawnDefaultPawn(e,a);null!=s&&a.possess(s)}await t.onPlayerReady(e)}async deactivatePlayer(t,e){"disconnected"===e.status&&await t.onPlayerLeaving(e)}schedulePlayerOperation(t,e,s){this.runPlayerOperation(e,s).catch(s=>t.onPlayerLifecycleError(e,s))}runPlayerOperation(t,e){const s=(this.playerOperations.get(t.info.id)??Promise.resolve()).catch(()=>{}).then(e);return this.playerOperations.set(t.info.id,s),s.finally(()=>{this.playerOperations.get(t.info.id)===s&&this.playerOperations.delete(t.info.id)}).catch(()=>{}),s}};r=t([e()],r);export{r as PlayerLifecycleCoordinator};/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
import type { BasePlayerController, BasePlayerState } from './actors/controller/actor-controller.js';
|
|
2
|
-
import type { BaseGameState } from './actors/controller/game-state.js';
|
|
3
2
|
import type { NetPlayerId } from './net/net-connection.js';
|
|
4
3
|
import { type PlayerSession } from './net/player-session.js';
|
|
5
|
-
/**
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Process-local player sessions. Authority normally knows every player session;
|
|
6
|
+
* remote clients normally know only sessions controlled by that process.
|
|
7
|
+
*/
|
|
8
|
+
export declare class PlayerSessions {
|
|
7
9
|
private readonly registry;
|
|
8
|
-
private currentGameState?;
|
|
9
10
|
private readonly revisionSubject;
|
|
10
|
-
/** Emits after
|
|
11
|
+
/** Emits after membership, status, controller, or player-state associations change. */
|
|
11
12
|
readonly changed$: import("rxjs").Observable<number>;
|
|
12
13
|
readonly playerJoined$: import("rxjs").Observable<PlayerSession>;
|
|
13
14
|
readonly playerLeft$: import("rxjs").Observable<PlayerSession>;
|
|
14
15
|
isAuthority: boolean;
|
|
15
16
|
private clearOwningConnectionHandler?;
|
|
16
17
|
private owningConnectionHandler?;
|
|
17
|
-
private validateGameStateHandler?;
|
|
18
18
|
constructor();
|
|
19
19
|
get revision(): number;
|
|
20
|
-
get gameState(): BaseGameState | undefined;
|
|
21
20
|
get all(): readonly PlayerSession[];
|
|
22
21
|
get active(): readonly PlayerSession[];
|
|
23
22
|
get local(): readonly PlayerSession[];
|
|
24
23
|
get(playerId: NetPlayerId): PlayerSession | undefined;
|
|
25
|
-
getGameState<T extends BaseGameState = BaseGameState>(): T | undefined;
|
|
26
24
|
getLocalPlayer(localPlayerIndex?: number): PlayerSession | undefined;
|
|
27
25
|
getLocalController<T extends BasePlayerController = BasePlayerController>(localPlayerIndex?: number): T | undefined;
|
|
28
26
|
getLocalPlayerState<T extends BasePlayerState = BasePlayerState>(localPlayerIndex?: number): T | undefined;
|
|
29
|
-
requireGameState<T extends BaseGameState = BaseGameState>(): T;
|
|
30
27
|
requireLocalPlayer(localPlayerIndex?: number): PlayerSession;
|
|
31
28
|
requireLocalController<T extends BasePlayerController = BasePlayerController>(localPlayerIndex?: number): T;
|
|
32
29
|
requireLocalPlayerState<T extends BasePlayerState = BasePlayerState>(localPlayerIndex?: number): T;
|
|
33
30
|
private notifyChanged;
|
|
34
31
|
}
|
|
35
|
-
//# sourceMappingURL=
|
|
32
|
+
//# sourceMappingURL=player-sessions.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{__decorate as t,__metadata as e}from"tslib";import{BehaviorSubject as r}from"rxjs";import{Service as i}from"typedi";import{NetMode as n}from"./net/net-session.js";import{NetPlayerRegistry as s}from"./net/player-session.js";let o=class{constructor(){this.registry=new s,this.revisionSubject=new r(0),this.changed$=this.revisionSubject.asObservable(),this.playerJoined$=this.registry.playerJoined$,this.playerLeft$=this.registry.playerLeft$,this.isAuthority=!0,this.registry.changed$.subscribe(()=>this.notifyChanged())}get revision(){return this.revisionSubject.value}get all(){return this.registry.values}get active(){return this.registry.activeValues}get local(){return this.registry.localValues}get(t){return this.registry.get(t)}getLocalPlayer(t=0){return this.registry.values.find(e=>e.localPlayerIndex===t)}getLocalController(t=0){return this.getLocalPlayer(t)?.controller}getLocalPlayerState(t=0){return this.getLocalPlayer(t)?.playerState}requireLocalPlayer(t=0){const e=this.getLocalPlayer(t);if(null==e)throw new Error(`Local player ${t} is not available`);return e}requireLocalController(t=0){const e=this.getLocalController(t);if(null==e)throw new Error(`Controller for local player ${t} is not available`);return e}requireLocalPlayerState(t=0){const e=this.getLocalPlayerState(t);if(null==e)throw new Error(`Player state for local player ${t} is not available`);return e}startStandalone(t){this.stop(),this.isAuthority=!0,this.registry.startLocal(t)}startNetwork(t){this.stop(),this.isAuthority=t.mode<n.client,this.registry.start(t)}attachController(t,e){this.registry.attachController(t,e)}attachPlayerState(t,e){this.registry.attachPlayerState(t,e)}setStatus(t,e){this.registry.setStatus(t,e)}setClearOwningConnectionHandler(t){this.clearOwningConnectionHandler=t}setOwningConnectionHandler(t){this.owningConnectionHandler=t}setOwningConnection(t,e){this.owningConnectionHandler?.(t,e)}clearOwningConnection(t){this.clearOwningConnectionHandler?.(t)}stop(){this.registry.stop(),this.clearOwningConnectionHandler=void 0,this.owningConnectionHandler=void 0}notifyChanged(){this.revisionSubject.next(this.revisionSubject.value+1)}};o=t([i(),e("design:paramtypes",[])],o);export{o as PlayerSessions};/*
|
|
2
|
+
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
|
+
* See the LICENSE.md file for details.
|
|
4
|
+
*/
|