@@ -8,16 +8,30 @@ import {
88 ActorComponent ,
99 ActorTree
1010} from "../actor/index.ts" ;
11- import type { WorldDefaultContext } from "./World.ts" ;
11+ import type { World , WorldDefaultContext } from "./World.ts" ;
1212import type { Component } from "../components/types.ts" ;
13+ import type { Scene } from "./Scene.ts" ;
14+
15+ export type AppendedSceneEntry < TContext > = {
16+ scene : Scene < TContext > ;
17+ /**
18+ * All actors created during the scene's awake(),
19+ * tracked for cleanup on removeScene.
20+ **/
21+ ownedActors : ReadonlySet < Actor < TContext > > ;
22+ } ;
1323
14- export type SceneEvents = {
24+ export type SceneEvents < TContext = WorldDefaultContext > = {
1525 awake : [ ] ;
26+ sceneChanged : [ scene : Scene < TContext > ] ;
27+ sceneDestroyed : [ scene : Scene < TContext > ] ;
28+ sceneAppended : [ scene : Scene < TContext > ] ;
29+ sceneRemoved : [ scene : Scene < TContext > ] ;
1630} ;
1731
1832export class SceneManager <
1933 TContext = WorldDefaultContext
20- > extends EventEmitter < SceneEvents > {
34+ > extends EventEmitter < SceneEvents < TContext > > {
2135 default : THREE . Scene ;
2236
2337 componentsToBeStarted : Component [ ] = [ ] ;
@@ -27,6 +41,14 @@ export class SceneManager<
2741 #actorsByName: Map < string , Actor < TContext > [ ] > = new Map ( ) ;
2842 #cachedActors: Actor < TContext > [ ] = [ ] ;
2943
44+ #currentScene: Scene < TContext > | null = null ;
45+ #pendingScene: Scene < TContext > | null = null ;
46+ #sceneStartPending = false ;
47+ #world: World < any , TContext > | null = null ;
48+
49+ #appendedScenes: Map < number , AppendedSceneEntry < TContext > > = new Map ( ) ;
50+ #appendedScenesPendingStart: Set < number > = new Set ( ) ;
51+
3052 readonly tree = new ActorTree < TContext > ( {
3153 addCallback : ( actor ) => this . default . add ( actor . object3D ) ,
3254 removeCallback : ( actor ) => this . default . remove ( actor . object3D )
@@ -39,10 +61,24 @@ export class SceneManager<
3961 this . default = scene ?? new THREE . Scene ( ) ;
4062 }
4163
64+ get currentScene ( ) : Scene < TContext > | null {
65+ return this . #currentScene;
66+ }
67+
68+ get hasPendingScene ( ) : boolean {
69+ return this . #pendingScene !== null ;
70+ }
71+
4272 getSource ( ) {
4373 return this . default ;
4474 }
4575
76+ bindWorld (
77+ world : World < any , TContext >
78+ ) : void {
79+ this . #world = world ;
80+ }
81+
4682 awake ( ) {
4783 for ( const { actor } of this . tree . walk ( ) ) {
4884 if ( ! actor . awoken ) {
@@ -53,7 +89,158 @@ export class SceneManager<
5389 this . emit ( "awake" ) ;
5490 }
5591
92+ setScene (
93+ scene : Scene < TContext >
94+ ) : void {
95+ if ( this . #currentScene !== null ) {
96+ for ( const entry of this . #appendedScenes. values ( ) ) {
97+ this . emit ( "sceneRemoved" , entry . scene ) ;
98+ entry . scene . destroy ( ) ;
99+ }
100+ this . #appendedScenes. clear ( ) ;
101+ this . #appendedScenesPendingStart. clear ( ) ;
102+
103+ this . emit ( "sceneDestroyed" , this . #currentScene) ;
104+ this . #currentScene. destroy ( ) ;
105+
106+ const allActors = Array . from ( this . #registeredActors) ;
107+ for ( const actor of allActors ) {
108+ this . destroyActor ( actor ) ;
109+ }
110+
111+ this . componentsToBeStarted . length = 0 ;
112+ this . componentsToBeDestroyed . length = 0 ;
113+
114+ this . default . clear ( ) ;
115+ this . #registeredActors. clear ( ) ;
116+ this . #actorsByName. clear ( ) ;
117+ }
118+
119+ scene . world = this . #world! ;
120+ this . #currentScene = scene ;
121+
122+ scene . awake ( ) ;
123+ this . awake ( ) ;
124+
125+ this . #sceneStartPending = true ;
126+ this . emit ( "sceneChanged" , scene ) ;
127+ }
128+
129+ loadScene (
130+ scene : Scene < TContext >
131+ ) : void {
132+ this . #pendingScene = scene ;
133+ }
134+
135+ /**
136+ * Inserts a scene as a prefab into the current scene.
137+ * The scene's awake() is called immediately; start() is deferred to the next beginFrame.
138+ * All actors created during awake() are tracked and will be destroyed on removeScene().
139+ */
140+ appendScene (
141+ scene : Scene < TContext >
142+ ) : void {
143+ const snapshot = new Set ( this . #registeredActors) ;
144+
145+ scene . world = this . #world! ;
146+ scene . awake ( ) ;
147+
148+ const ownedActors = new Set < Actor < TContext > > ( ) ;
149+ for ( const actor of this . #registeredActors) {
150+ if ( ! snapshot . has ( actor ) ) {
151+ ownedActors . add ( actor ) ;
152+ }
153+ }
154+
155+ // Awaken any actors created during awake() that haven't been woken yet
156+ this . awake ( ) ;
157+
158+ this . #appendedScenes. set ( scene . id , { scene, ownedActors } ) ;
159+ this . #appendedScenesPendingStart. add ( scene . id ) ;
160+
161+ this . emit ( "sceneAppended" , scene ) ;
162+ }
163+
164+ removeScene ( scene : Scene < TContext > ) : void ;
165+ removeScene ( name : string ) : void ;
166+ removeScene (
167+ target : Scene < TContext > | string
168+ ) : void {
169+ if ( typeof target === "string" ) {
170+ for ( const [ id , entry ] of this . #appendedScenes) {
171+ if ( entry . scene . name === target ) {
172+ this . #teardownAppendedScene( id , entry ) ;
173+ }
174+ }
175+ }
176+ else {
177+ const entry = this . #appendedScenes. get ( target . id ) ;
178+ if ( entry !== undefined ) {
179+ this . #teardownAppendedScene( target . id , entry ) ;
180+ }
181+ }
182+ }
183+
184+ #teardownAppendedScene(
185+ id : number ,
186+ entry : AppendedSceneEntry < TContext >
187+ ) : void {
188+ this . emit ( "sceneRemoved" , entry . scene ) ;
189+ entry . scene . destroy ( ) ;
190+
191+ // Destroy only root-level owned actors; destroyActor cascades to children
192+ for ( const actor of entry . ownedActors ) {
193+ if ( actor . parent === null || ! entry . ownedActors . has ( actor . parent ) ) {
194+ this . destroyActor ( actor ) ;
195+ }
196+ }
197+
198+ this . #appendedScenes. delete ( id ) ;
199+ this . #appendedScenesPendingStart. delete ( id ) ;
200+ }
201+
202+ getScene ( ) : Scene < TContext > | null ;
203+ getScene ( id : number ) : Scene < TContext > | null ;
204+ getScene ( name : string ) : Scene < TContext > [ ] ;
205+ getScene (
206+ target ?: number | string
207+ ) : Scene < TContext > | null | Scene < TContext > [ ] {
208+ if ( target === undefined ) {
209+ return this . #currentScene;
210+ }
211+
212+ if ( typeof target === "number" ) {
213+ return this . #appendedScenes. get ( target ) ?. scene ?? null ;
214+ }
215+
216+ const result : Scene < TContext > [ ] = [ ] ;
217+ for ( const entry of this . #appendedScenes. values ( ) ) {
218+ if ( entry . scene . name === target ) {
219+ result . push ( entry . scene ) ;
220+ }
221+ }
222+
223+ return result ;
224+ }
225+
56226 beginFrame ( ) {
227+ if ( this . #pendingScene !== null ) {
228+ this . setScene ( this . #pendingScene) ;
229+ this . #pendingScene = null ;
230+ }
231+
232+ if ( this . #sceneStartPending) {
233+ this . #currentScene?. start ( ) ;
234+ this . #sceneStartPending = false ;
235+ }
236+
237+ if ( this . #appendedScenesPendingStart. size > 0 ) {
238+ for ( const id of this . #appendedScenesPendingStart) {
239+ this . #appendedScenes. get ( id ) ?. scene . start ( ) ;
240+ }
241+ this . #appendedScenesPendingStart. clear ( ) ;
242+ }
243+
57244 this . #cachedActors = Array . from ( this . #registeredActors) ;
58245
59246 let i = 0 ;
@@ -78,6 +265,11 @@ export class SceneManager<
78265 this . #cachedActors. forEach ( ( actor ) => {
79266 actor . fixedUpdate ( deltaTime ) ;
80267 } ) ;
268+ this . #currentScene?. fixedUpdate ( deltaTime ) ;
269+
270+ for ( const { scene } of this . #appendedScenes. values ( ) ) {
271+ scene . fixedUpdate ( deltaTime ) ;
272+ }
81273 }
82274
83275 update (
@@ -86,6 +278,11 @@ export class SceneManager<
86278 this . #cachedActors. forEach ( ( actor ) => {
87279 actor . update ( deltaTime ) ;
88280 } ) ;
281+ this . #currentScene?. update ( deltaTime ) ;
282+
283+ for ( const { scene } of this . #appendedScenes. values ( ) ) {
284+ scene . update ( deltaTime ) ;
285+ }
89286 }
90287
91288 endFrame ( ) {
@@ -117,7 +314,10 @@ export class SceneManager<
117314
118315 this . unregisterActor ( actor ) ;
119316
120- // NOTE: make sure to remove deeply into the tree
317+ // For root actors (parent === null): removes from tree.children and fires
318+ // the removeCallback that detaches actor.object3D from the THREE.Scene.
319+ // For non-root actors this is a no-op; actor.destroy() handles removal
320+ // from the parent's children list via parent.remove(this).
121321 this . tree . remove ( actor ) ;
122322 actor . destroy ( ) ;
123323 }
0 commit comments