Disable one of multiple scenes

I organize my scenes using a ScreenManager. (Named “screen” to distinguish from scene.)
Interface Screen brings lifecycle methods activate and deactivate.

export interface Screen {
    activate(): void;
    deactivate(): void;
    render(): void;
    dispose(): void;
}

export class ScreenManager {
    private readonly screens: Screen[] = [];

    constructor() {
    }

    dispose() {
        this.clearScreens();
    }

    getTopScreen() {
        return this.screens.length > 0 ? this.screens[this.screens.length - 1] : null;
    }

    pushScreen(screen: Screen) {
        const topScreen = this.getTopScreen();
        if (topScreen !== null) {
            topScreen.deactivate();
        }
        this.screens.push(screen);
        screen.activate();
    }

    popScreen(numberOfScreens = 1) {
        if (numberOfScreens < 1) {
            return;
        }
        let topScreen = this.getTopScreen();
        if (topScreen === null) {
            return;
        }
        topScreen.deactivate();

        while (numberOfScreens > 0) {
            topScreen = this.getTopScreen();
            if (topScreen === null) {
                return;
            }
            topScreen.dispose();
            this.screens.pop();
            numberOfScreens--;
        }

        topScreen = this.getTopScreen();
        if (topScreen === null) {
            return;
        }
        topScreen.activate();
    }

    private clearScreens() {
        this.popScreen(this.screens.length);
    }
}

The main class App utilizes the ScreenManager:

export class App {
    readonly engine: Engine;
    readonly screenManager = new ScreenManager();
    private readonly resizeHandler = () => this.resize();
    private readonly renderHandler = () => this.render();
    private disposed = false;

    constructor(
        readonly canvasElement: HTMLCanvasElement
    ) {
        this.engine = new Engine(canvasElement, true);
        this.load();
    }

    dispose() {
        this.disposed = true;
        this.stop();
        this.screenManager.dispose();
        this.engine.dispose();
    }

    private async load() {
        // Load stuff...

        this.screenManager.pushScreen(new MyFirstSceen(this));
    }

    run() {
        window.addEventListener('resize', this.resizeHandler);
        this.engine.runRenderLoop(this.renderHandler);
    }

    stop() {
        window.removeEventListener('resize', this.resizeHandler);
        this.engine.stopRenderLoop(this.renderHandler);
    }

    private resize() {
        this.engine.resize();
    }

    private render() {
        const topScreen = this.screenManager.getTopScreen();
        if (topScreen === null) {
            return;
        }
        topScreen.render();
    }

}

You then can implement your “screens”:

export class MyFirstScreen implements Screen {
    readonly engine: Engine;
    readonly scene: Scene;

    constructor(
        readonly app: App
    ) {
        this.engine = this.app.engine;
        this.scene = new Scene(this.engine);
        
        // Setup scene...
    }

    activate() {
        this.scene.attachControl();

        this.engine.onResizeObservable.add(this.resize, undefined, undefined, this);
        this.resize();
    }

    deactivate() {
        // this.debugLayerManager.hide();
        
        this.engine.onResizeObservable.removeCallback(this.resize, this);

        this.scene.detachControl();
    }

    resize() {
    }

    render() {
        this.scene.render();
    }

    dispose() {
        this.scene.dispose();
    }

    private pushSecondScreen() {
        this.app.screenManager.pushScreen(new MySecondScreen(this.app));
    }
}