mirror of
https://github.com/storytold/spark.git
synced 2026-10-22 08:16:06 +00:00
Fix effect switching race: add async disposal guards and scene cleanup to prevent stray meshes (#175)
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
Reference in New Issue
Block a user