From f652782324b26e62199fcbfee2edbe1cfa726ac3 Mon Sep 17 00:00:00 2001 From: Oba Yang <195147269+zzoraemon043@users.noreply.github.com> Date: Fri, 30 Oct 2026 09:03:45 +0800 Subject: [PATCH] fix(core): don't throw on Clipper and Outliner teardown after the world is disposed --- packages/core/src/core/Clipper/clipper.test.ts | 14 ++++++++++++++ packages/core/src/core/Clipper/index.ts | 7 +++---- packages/front/src/fragments/Outliner/index.ts | 6 ++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/core/src/core/Clipper/clipper.test.ts b/packages/core/src/core/Clipper/clipper.test.ts index cced412b..dbeaa2f1 100644 --- a/packages/core/src/core/Clipper/clipper.test.ts +++ b/packages/core/src/core/Clipper/clipper.test.ts @@ -100,4 +100,18 @@ describe("Clipper delete events (#799)", () => { { plane: b, size: 0 }, ]); }); + + it("deletes a plane whose world was disposed, so its renderer is null", () => { + const plane = fakePlane(); + (plane.world as { renderer: unknown }).renderer = null; + clipper.list.set("a", plane); + + const after: SimplePlane[] = []; + clipper.onAfterDelete.add((deleted) => after.push(deleted)); + + expect(() => clipper.list.delete("a")).not.toThrow(); + expect(plane.dispose).toHaveBeenCalledTimes(1); + expect(after).toEqual([plane]); + expect(clipper.list.size).toBe(0); + }); }); diff --git a/packages/core/src/core/Clipper/index.ts b/packages/core/src/core/Clipper/index.ts index e7ea705c..983e2c64 100644 --- a/packages/core/src/core/Clipper/index.ts +++ b/packages/core/src/core/Clipper/index.ts @@ -257,11 +257,10 @@ export class Clipper // key, so the plane is carried across in `_deleting`. this.list.onBeforeDelete.add(({ key, value: plane }) => { this.onBeforeDelete.trigger(); - if (!plane.world.renderer) { - throw new Error("Renderer not found for this plane's world!"); - } this._deleting.set(key, plane); - plane.world.renderer.setPlane(false, plane.three); + // The world can be disposed on its own, which leaves its renderer + // `null` while its planes stay listed; there is nothing to unregister. + plane.world.renderer?.setPlane(false, plane.three); plane.dispose(); }); diff --git a/packages/front/src/fragments/Outliner/index.ts b/packages/front/src/fragments/Outliner/index.ts index 06846e8a..06978fb4 100644 --- a/packages/front/src/fragments/Outliner/index.ts +++ b/packages/front/src/fragments/Outliner/index.ts @@ -306,7 +306,7 @@ export class Outliner extends OBC.Component implements OBC.Disposable { this.detachAll(group, name); this._groups.delete(name); - if (this.world) { + if (this.world?.renderer) { this.getRenderer().postproduction.outlinePass.removeGroup(name); } } @@ -672,7 +672,9 @@ export class Outliner extends OBC.Component implements OBC.Disposable { private detachAll(state: OutlineGroupState, group = DEFAULT_GROUP) { state.revision++; state.update = undefined; - if (!this.world) { + // The world can be disposed on its own, which leaves its renderer `null` + // and nothing to detach from. + if (!this.world?.renderer) { state.attached = new Map(); return; }