Reset a scene to initial state

I’m trying to keep the same scene when loading “levels”. This allows me to reuse asset containers. But weird issues creep up after some time, presumably because the scene is not in the same state. Removing all assets (assetContainer.removeAllFromScene()), meshes, cameras, lights,… is not enough. How can we reset the scene to its initial state? There’s dispose(), but that makes the scene unusable afterwards.

I can’t replicate the issues in a PG… simple scenarios don’t expose the issues I’m seeing in the real game engine where there are a lot more features being used.

Unfortunately, this sounds really hard to fix without a repo.

It seems like something could be leaking in your experience but not to sure what :slight_smile:

It’s not really a bug, more of a question of how to use the same scene for multiple “levels”.

We use asset containers to avoid reloading assets between levels, but they can’t be used with changing scenes. So we use the same scene for all game levels. But that’s causing issues because the scene has been mutated in unpredictable ways by the previous level.

We need a ‘reset’ method on the scene that allows it to be reused. Scene.dispose() does reset it, but the scene is no longer usable after that. Alternatively, AssetContainer needs to be able to copy assets to a different scene.

Maybe you can use Serialize and Parse like mentioned here to reduce time of scene switching:

Just commenting relevant code as @Takemura mentioned. Though you may need to be careful to make sure your scene hierarchy remains intact, by serialising and caching the content we want to be shared between scenes, we can then parse from this instead of reloading upon scene reinitialisation (apologies for the messy code!):

let entities: AssetContainer;
let serialisedMaterials: Array<any>;
let serialisedGeometries: Array<any>;
let serialisedMeshes: Array<any>;

export const initialiseEntities = async (
  engine: Engine,
  projectDataUrl: string
) => {
  if (entities) entities.dispose();
  entities = await SceneLoader.LoadAssetContainerAsync(
    '',
    projectDataUrl,
    new Scene(engine),
    undefined,
    `.glb`
  );

  serialisedMaterials = entities.materials.map((m) => m.serialize());
  serialisedGeometries = entities.geometries.map((g) => g.serialize());
  serialisedMeshes = entities.meshes.map((m) => m.serialize());
};

export const cloneFromTemplate = async (i: number, thisScene: MyScene) => {
  // Parse content into this scene:
  for (const m of serialisedMaterials) {
    Material.Parse(m, thisScene.scene, '');
  }
  for (const g of serialisedGeometries) {
    Geometry.Parse(g, thisScene.scene, '');
  }

  // Making a root node to attach all the meshes to:
  const originalRootNode = entities.meshes.find((m) => m.name === '__root__');
  const newRootNode = new TransformNode('__root__', thisScene.scene);

  thisScene.scene.addTransformNode(newRootNode);

  // Parsing the meshes and attaching to root node:
  for (const m of serialisedMeshes) {
    const isRoot: boolean = m.name === '__root__';

    if (!isRoot) {
      const parsedMesh = Mesh.Parse(m, thisScene.scene, '');

      parsedMesh.parent = newRootNode;
      parsedMesh.material =
        thisScene.scene.materials.find(
          (material) => material.name === m.materialId
        ) ?? null;

      const assocMesh = originalRootNode?.getChildMeshes(
        false,
        (om) => om.name === parsedMesh.name
      )[0];
      parsedMesh._geometry = (assocMesh as Mesh)?._geometry ?? null;
    }
  }

  thisScene.afterProjectAdded();
};

Another approach that might work would be to “reset” scene like here, just modify to your needs like not recreating AssetContainer etc.:

As far as I can tell, Ariel is just recreating the scene. I can’t tell what createScene does, but it’s returning a new scene, so I assume the original scene is disposed. If that’s the case, then all the asset containers would be disposed as well, and all assets will have to be loaded again. That’s what I’m trying to avoid by reusing the scene.

I’m not trying to preserve the hierarchy - quite the opposite. When the player goes from “level 1” to “level 2”, I want to remove everything from the scene, but keep the asset containers intact because the next level uses a lot of the same assets.

class SceneManager {
  async loadScene(name) {
    this.clearScene()

    // Then load the new scene into this.sceneAssetContainer, and addAllToScene.
    // This takes care of static assets.
    this.sceneAssetContainer = await SceneLoader.LoadAssetContainerAsync(..., this.scene)
    this.sceneAssetContainer.addAllToScene()

    // Next, make list of entities (marked by transform nodes), and load their assets into this.assetContainers.
    // Existing asset container are just skipped.
    // Asset containers that are not used in this scene will be disposed to release memory.

    // Then we can spawn the entities, knowing their assets are available.
  }

  clearScene() {
    // remove assets, but keep asset containers around for the next scene
    this.sceneAssetContainer.removeAllFromScene()
    this.entityAssetContainers.forEach(ac => {ac.removeAllFromScene()})

    const disposeAll = (list) => {
      while (list.length > 0) {
        list[0].dispose()
      }
    }

    // dispose of dynamically created meshes (e.g. createInstance, clone, etc.)
    disposeAll(this.scene.meshes)  
    // same for transformNodes, materials, lights, cameras...
  }
}

This is just a fraction of what Scene.dispose does, and it doesn’t seem to be enough. There’s tons of internal state, caches, etc. that gets mutated while the game runs, meaning the scene is not in a predictable state when the new level loads. After a couple of level changes, weird issues start to appear. It’s impractical to replicate this in a PG without copying large chunks of the game.

What we need is a way to “retarget” an asset container to a new scene, so we can dispose of the old one and start from a known state.

This is not a supported feature but I think we discussed it several time @bghgary ???

I don’t remember exactly what we discussed. Resetting a scene back to original should be possible. If not, that seems like an issue. It also seems possible to implement something on asset container to move all the assets to another scene.