Fix effect switching race: add async disposal guards and scene cleanup to prevent stray meshes (#175)

This commit is contained in:
kali
2025-09-23 02:26:00 -03:00
committed by GitHub
parent 2ec7d706a1
commit 6317006ef3
5 changed files with 32 additions and 6 deletions
@@ -6,6 +6,7 @@ import { getAssetFileURL } from "/examples/js/get-asset-url.js";
export async function init({ THREE: _THREE, scene, camera, renderer, spark }) { export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
const group = new THREE.Group(); const group = new THREE.Group();
scene.add(group); scene.add(group);
let disposed = false;
// Basic lights // Basic lights
const ambient = new THREE.AmbientLight(0x404040, 0.6); const ambient = new THREE.AmbientLight(0x404040, 0.6);
@@ -329,6 +330,7 @@ export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
} }
await Promise.all([loadSplats(), loadTable()]); await Promise.all([loadSplats(), loadTable()]);
if (disposed) return { group, update: () => {}, dispose, setupGUI };
// Instructional text // Instructional text
const instructionsText = textSplats({ const instructionsText = textSplats({
@@ -400,6 +402,7 @@ export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
} }
function dispose() { function dispose() {
disposed = true;
window.removeEventListener("keydown", onKeyDown); window.removeEventListener("keydown", onKeyDown);
// Disable controls to avoid interfering with other effects // Disable controls to avoid interfering with other effects
controls.fpsMovement.enable = false; controls.fpsMovement.enable = false;
+4 -2
View File
@@ -6,6 +6,7 @@ import { getAssetFileURL } from "/examples/js/get-asset-url.js";
export async function init({ THREE: _THREE, scene, camera, renderer, spark }) { export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
const group = new THREE.Group(); const group = new THREE.Group();
scene.add(group); scene.add(group);
let disposed = false;
const PARAMETERS = { const PARAMETERS = {
speedMultiplier: 0.5, speedMultiplier: 0.5,
@@ -148,7 +149,7 @@ export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
const skyFile = "dali-env.glb"; const skyFile = "dali-env.glb";
const env = await loadGLB(skyFile, true); const env = await loadGLB(skyFile, true);
group.add(env); if (!disposed) group.add(env);
const meshes = []; const meshes = [];
const period = dyno.dynoFloat(splatFiles.length); const period = dyno.dynoFloat(splatFiles.length);
@@ -164,7 +165,7 @@ export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
await m.initialized; await m.initialized;
m.position.copy(positions[i]); m.position.copy(positions[i]);
m.rotateX(Math.PI); m.rotateX(Math.PI);
group.add(m); if (!disposed) group.add(m);
meshes.push(m); meshes.push(m);
} }
@@ -299,6 +300,7 @@ export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
} }
function dispose() { function dispose() {
disposed = true;
scene.remove(group); scene.remove(group);
} }
+3 -1
View File
@@ -5,6 +5,7 @@ import { getAssetFileURL } from "/examples/js/get-asset-url.js";
export async function init({ THREE: _THREE, scene, camera, renderer, spark }) { export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
const group = new THREE.Group(); const group = new THREE.Group();
scene.add(group); scene.add(group);
let disposed = false;
// Camera baseline for Morph effect // Camera baseline for Morph effect
camera.position.set(0, 2.2, 6.5); camera.position.set(0, 2.2, 6.5);
@@ -169,7 +170,7 @@ export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
mesh.rotateX(Math.PI); mesh.rotateX(Math.PI);
mesh.position.set(0, 0, 0); mesh.position.set(0, 0, 0);
mesh.scale.set(1.5, 1.5, 1.5); mesh.scale.set(1.5, 1.5, 1.5);
group.add(mesh); if (!disposed) group.add(mesh);
meshes.push(mesh); meshes.push(mesh);
} }
@@ -219,6 +220,7 @@ export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
} }
function dispose() { function dispose() {
disposed = true;
scene.remove(group); scene.remove(group);
} }
@@ -6,6 +6,7 @@ import { getAssetFileURL } from "/examples/js/get-asset-url.js";
export async function init({ THREE: _THREE, scene, camera, renderer, spark }) { export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
const group = new THREE.Group(); const group = new THREE.Group();
scene.add(group); scene.add(group);
let disposed = false;
// Params and uniforms // Params and uniforms
const PARAMETERS = { const PARAMETERS = {
@@ -209,12 +210,12 @@ export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
// Load env and assets // Load env and assets
const sky = await loadDelitGLB(skyFile, true); const sky = await loadDelitGLB(skyFile, true);
group.add(sky); if (!disposed) group.add(sky);
const table = await loadDelitGLB(sceneFile, false); const table = await loadDelitGLB(sceneFile, false);
const sceneScale = 3.5; const sceneScale = 3.5;
table.scale.set(sceneScale, sceneScale, sceneScale); table.scale.set(sceneScale, sceneScale, sceneScale);
table.position.set(-1, 0, -0.8); table.position.set(-1, 0, -0.8);
group.add(table); if (!disposed) group.add(table);
const period = dyno.dynoFloat(splatFiles.length); const period = dyno.dynoFloat(splatFiles.length);
const spereRadiusDyno = dyno.dynoFloat(PARAMETERS.spereRadius); const spereRadiusDyno = dyno.dynoFloat(PARAMETERS.spereRadius);
@@ -233,7 +234,7 @@ export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
spereRadiusDyno, spereRadiusDyno,
sphereHeightDyno, sphereHeightDyno,
); );
group.add(mesh); if (!disposed) group.add(mesh);
meshes.push(mesh); meshes.push(mesh);
} }
@@ -267,6 +268,7 @@ export async function init({ THREE: _THREE, scene, camera, renderer, spark }) {
} }
function dispose() { function dispose() {
disposed = true;
// Remove group // Remove group
scene.remove(group); scene.remove(group);
// No global listeners here; controls are managed by main // No global listeners here; controls are managed by main
+17
View File
@@ -46,8 +46,10 @@ const effectFiles = {
let active = null; // { api, group } let active = null; // { api, group }
let last = 0; let last = 0;
let effectFolder = null; // GUI folder for current effect let effectFolder = null; // GUI folder for current effect
let switchCounter = 0; // guards concurrent effect switches
async function switchEffect(name) { async function switchEffect(name) {
const myToken = ++switchCounter;
const loading = document.getElementById("loading"); const loading = document.getElementById("loading");
loading.textContent = `Loading ${name}...`; loading.textContent = `Loading ${name}...`;
loading.style.display = "block"; loading.style.display = "block";
@@ -71,10 +73,25 @@ async function switchEffect(name) {
const loader = effectFiles[name]; const loader = effectFiles[name];
if (!loader) return; if (!loader) return;
const preChildren = new Set(scene.children);
const mod = await loader(); const mod = await loader();
if (myToken !== switchCounter) {
// A newer switch started; ignore this one
return;
}
const context = { THREE, scene, camera, renderer, spark }; const context = { THREE, scene, camera, renderer, spark };
const api = await mod.init(context); const api = await mod.init(context);
if (myToken !== switchCounter) {
try {
api.dispose?.();
} catch {}
// Remove any children added during this init
for (const child of [...scene.children]) {
if (!preChildren.has(child)) scene.remove(child);
}
return;
}
if (api.group) scene.add(api.group); if (api.group) scene.add(api.group);
active = { api, group: api.group }; active = { api, group: api.group };