Restarting a scene using TypeScript

I used BabylonJS to build a small demo https://knutsoned.itch.io/brackeys-2024-1 and posted the code

The code was made by following the Using Vite tutorial and then migrating GitHub - RaananW/babylonjs-webpack-es6: Babylon.js basic scene with typescript, webpack, es6 modules, editorconfig, eslint, hot loading and more. Will even make coffee if you ask nicely. (thank you RaananW, your comment on another post also helped me figure out how to get Ammo to work with Vite) from a Webpack to a Vite structure.

I have a few questions so I’ll start with the biggest one. (Should I make a post for each question or put them all together?) When the player presses the R key, I set a gameOver flag. I’m checking for the flag in the main render loop, calling scene.dispose(), then running createScene again.

In a PG context, especially with no physics, it seems ok to just make a scene, dispose, then replace the value of the old scene variable with a reference to a new one, so I tried it like this:

The main problem is the scene seems to “fall away” from the viewport. I can’t figure out exactly why this is happening, although I suspect it might be leftover physics bodies in Ammo. So even though I am disposing of the scene, and would expect the impostors to remove the corresponding bodies from the physics engine on disposal, that doesn’t seem to be the case.

I am repeating the process to initialize Ammo with every call to createScene DoorQuest/src/scenes/mainScene.ts at c970c2a9a57a0582273abf40862e192cef07d4d2 · knutsoned/DoorQuest · GitHub (this is the only program code in the babylonjs-webpack-es6 template that needed to be changed as mentioned above)

Looking at Babylon.js docs the suggestion is to not just call createScene over and over, but to recycle the scene by removing everything and adding it again, and perhaps that is the piece I am missing. Is there a way to empty the scene? What I really want to do is remove everything from the scene and just recreate and add fresh copies of everything back into it.

I’ve seen suggestions that an empty mesh can be used as an invisible parent container to group objects, and by removing the parent, a similar effect to “resetting” the scene can be achieved. Does this sound like the best way to go about what I’m trying to accomplish? I’m trying to support procedurally generated scenes (like a dungeon crawler) so I don’t want the thing to crash because of leaking objects and the like.

So, main question 1: what is happening to the new scene when I press the R button? Is the ghost of the old scene pushing it away, or is something else going on?

Main question 2: can I use empty meshes as a “main” container and just remove the container when I want to reset the scene, or is there a better way?

Update: I think all I am trying to do here is apply a new wall texture and return the ball to the middle, assigning a new random velocity. I can do that by tracking an array of walls in the Start class. However, if I want to load a completely different scene, the question remains: can I just set a point in my scene creation class where I set up a main parent container, and just dispose of that and make another one, or do I need to do something manual to dispose of the physics impostors?

Update: I did make an “again” method that changes the wall textures and resets the ball. So I have something that works for doing a reset, but still not sure about questions 1 and 2 above if I need to move to a completely different scene

Disposing and recreating a scene should work, as long as you don’t try to call render on the disposed scene and you recreate all the meshes in the new scene (your code snipped looks ok to me).

Would you be able to setup a small repro in the Playground? It would be easier for us to help that way.

Also, cc @Cedric as it seems the problems only appear when the scene uses Ammo (can you confirm this)?

1 Like

I would not be surprised to see static datas in ammo wasm memory space that stay independently of babylon.js scene.
I don’t know if it’s possible to offload/reload ammo.
In theory, removing everything (ie, physics) from a scene, then disposing and creating a new one should be no problem. With ammo.js or Havok. So, I’m curious to see if you can repro in a PG.
I’m not very familiar with modules in general so maybe I’m missing something.
cc @RaananW

I made the project in question in a hurry during a game jam and my repo has some cruft in it. To get that far, I started with a blank project, did the vite tutorial, and then copypasted code familiar to @RaananW to it. I could not get Havok to work, even after adding the magic words to vite.config.ts

I tried again, this time starting with babylonjs-webpack-es6 and converting to vite. Much better luck this time. I tried fiddling with the chunking but I get errors in the build sometimes. It looks like a race condition where everything works in dev, but if the chunks load out of order from the build, something tries to initialize before it is supposed to. It seems like as long as I limit it to BabylonJS in one file and Ammo in the other, that works well enough.

I understand there are issues with recast-detour that are in progress. Also that there is a major release coming soon, so I’ll be content for now that recast-detour works in dev but throws this error in my build:

Uncaught (in promise) TypeError: Cannot set properties of undefined (setting ‘Recast’)
at index-CStjOrX4.js:2:40115
at LC.createScene (index-CStjOrX4.js:2:40215)
at HC (index-CStjOrX4.js:2:43286)

And this time I can get Havok to work, so my next step is to see if I can start with the newer template and recreate with Ammo, then see if Havok makes a difference. At that point I should have enough info to set up a simple PG, or at least report back something like “when using Ammo and swapping between scenes, move the top of the old scene a couple units below where the new one will go.”

For now, here is the base code GitHub - knutsoned/babylonjs-vite-es6: Babylon.js basic scenes with typescript, vite, es6 modules, editorconfig, eslint, hot loading and more. Will even make coffee if you ask nicely. Pour over only.

First, I love the vite template! this will be very helpful :slight_smile:

Regarding recast - can i see it in the template? what is the best way to see the error in order to debug this locally?

About Ammo and Havok - Babylon should dispose everything and reset the stored state in the engines when a scene is disposed. Anyhow, when creating a new scene you create a new physics engine class, which in turn generates a new world, which is the main container of the entire state of the physics engine. If something “leaks” it needs to be investigated, because this shouldn’t happen, unless you use the same physics plugin.

About the scene-swapping issue, as @Evgeni_Popov said, this should work. It might be related to the fact that you added an async function to the render loop, which technically supports it, but doesn’t await on babylon’s side. So technically, it is possible that this will run twice or more, depending on how long it takes the scene to initialize. In general, i would suggest not to add async functions to the render loop, which runs using requestAnimationFrame and synchronously.
But it is hard to know without debugging. So - same question as above - what is the best way to reproduce this?

I updated the modules to the latest version and otherwise made the minimum changes to your webpack template to get it to work with vite. So, if you go to babylonjs-vite-es6/src/createScene.ts at master · knutsoned/babylonjs-vite-es6 · GitHub and use the NavigationMeshRecast scene, it should work fine in dev mode, but the build will throw an error. I believe all other scenes work fine in dev and prod.

I’m initializing physics during createScene like:

const ammo = await Ammo.call(this);
scene.enablePhysics(null, new AmmoJSPlugin(true, ammo));

I’m doing this because of the way vite handles ammojs-typed. I wonder if making a 2nd call to Ammo() is problematic and just returns the same underlying instance if it’s already been called.

Regarding async functions in the render loop, I agree that is weird. However, I’m not sure how else to do it since the createScene function returns a promise babylonjs-webpack-es6/src/index.ts at ecdb4c5e4c595163a4392c866d82f4536723f4ab · RaananW/babylonjs-webpack-es6 · GitHub

I wouldn’t worry about it too much yet since I still have a few more things to try. I think what I want to do in the real world is:

  1. Stop rendering the original scene
  2. Display a loading screen that has no physics
  3. Dispose the original scene with corresponding physics to free memory etc
  4. Start loading the new scene
  5. Switch to rendering the new scene when its ready event fires

Maybe instead of calling await in the render loop (which just feels cringe), I could assign a new value to the scene var when it resolves? Also not sure if I really want to use a fresh physics engine since that seems expensive. If there is a way to just empty out the engine object and reset that seems better.

At any rate, I am going to work on something along those lines and see if it helps. I mainly wanted to find out if I am doing something that has a well known solution that I just don’t know yet. Will post my findings shortly.

@RaananW I had a chance to look at the issues I was having with recast-detour and I found a solution. I had to add a plugin to the vite config:

{
    name: "fix-recast",
    transform(code, id) {
        if (id.includes("recast-detour")) {
            return code.replace(
                `this["Recast"]`,
                'window.this ||= window; window["Recast"]'
            );
        }
    },
},

I had to add the “window.this ||= window” part because of how Vite handles these older style modules. In this case, because Vite has no globalThis, to get it to work in dev, this["Recast"] must be changed to window["Recast"]

In prod, this is not enough because somehow this["Recast"] becomes this.Recast but it does not work to replace this.Recast instead. Assigning window to window.this as above seems to do the trick. Also had to change the URL for navmeshWorker.js from /public/navmeshWorker.js to just /navmeshWorker.js

Also, adding:

optimizeDeps: {
    exclude: ["@babylonjs/havok"],
},

to vite.config.ts makes Havok work in dev and prod. With those two items addressed, now all of the scenes in my Vite template (which are originally from your webpack template) work in dev and prod.

1 Like

I had a thought about the optimize deps issue and the way vite solves things. I tried implementing it, but there is a caveat with vite here as well.

Simply put - I thought of loading the wasm as a arraybuffer and injecting it to the wasm initializing function using the wasmBinary property. That does work, but vite has this interesting issue - it does not allow you to import a binary feil from an npm package. The general solution is to copy the asset yourself to your project, and import it from there. While implementing it and playing around with it, i thought - there is no difference between adding a step to copy the file, to add the exclude havok from optimizedDeps.

A small note about Recast - I will see what we can do to not force you doing this code replace.

Congratulations on 7.0! I am impressed by the new features, and also that I was able to bump the versions of everything in the vite template, and it just works. All the scenes load and run, no build or runtime issues. I feel like the fresnel shader demo looks different and might need a glance, but at any rate: I have never in my life upgraded a full major version of anything and just ran even the demos without having to hack it all to pieces. That in itself is amazing! I checked my updated package.json into the repo above.

This is our promise: Backward compatibility is a must!

1 Like