BabylonJS/Babylon.js · error

${method}: this streaming part has been removed

Error message

${method}: this streaming part has been removed

What it means

Streaming part handles returned by reserveStreamingPart assert liveness before any mutating call. Once a part is removed, its part index/base may be reused by a surviving part, so a stale retained handle is rejected to prevent it from writing into another part's atlas region.

Source

Thrown at packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.ts:2187

        };
        this._streamingStates.push(state);
        capacity = alignedCapacity;

        // The handle's live getters need a stable reference to this compound (its atlas textures can be
        // recreated on a later rebuild), so alias it for the closures below.
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const compound = this;
        const applyBounds = () => {
            if (boundsMin.x <= boundsMax.x) {
                proxy.setBoundingInfo(new BoundingInfo(boundsMin.clone(), boundsMax.clone()));
                compound._updateBoundingInfoFromProxies();
            }
        };
        // Rejects any mutating call after the part was removed, so a retained stale handle can't touch a surviving
        // part that inherited this region's (now-reused) part index/base.
        const assertLive = (method: string) => {
            if (state.removed) {
                throw new Error(`${method}: this streaming part has been removed`);
            }
        };
        // Enforces the documented local part boundary [0, capacity) so a handle call can never address another
        // part's atlas region (the region base is added to these local coordinates before use).
        const assertLocalRange = (method: string, offset: number, count: number) => {
            if (!Number.isInteger(offset) || !Number.isInteger(count) || offset < 0 || count < 0 || offset + count > state.capacity) {
                throw new Error(`${method}: local range [${offset}, ${offset + count}) is outside the reserved region [0, ${state.capacity})`);
            }
        };

        const handle: IGaussianSplattingStreamingPart = {
            proxy,
            capacity,
            get partIndex() {
                return state.partIndex;
            },
            get base() {
                return state.base;

View on GitHub (pinned to 0592b347b8)

Solutions

  1. Discard or null the handle immediately after removing the part
  2. Check a liveness flag of your own before using cached handles
  3. Re-reserve a new part (and obtain a fresh handle) instead of reusing removed ones

Example fix

// before
part.remove();
part.writeSplats(0, data); // throws
// after
part.remove();
part = null; // or request a new part handle before writing
Defensive patterns

Strategy: type-guard

Validate before calling

if (isPartRemoved(handle)) return; // skip write
function isPartRemoved(h: IGaussianSplattingStreamingPart) { return (h as any)?.removed === true; }

Type guard

function isLive(h: IGaussianSplattingStreamingPart | null | undefined): h is IGaussianSplattingStreamingPart {
  return !!h && !(h as any).removed;
}

Try / catch

try {
  handle.writeSplats(offset, data);
} catch (e) {
  if (String(e).includes('has been removed')) { handle = null; reReserveAndWrite(); }
  else throw e;
}

Prevention

When it happens

Trigger: Keeping an IGaussianSplattingStreamingPart handle after calling its remove/dispose method (or removal via the compound mesh) and then invoking any of its write/update methods.

Common situations: Caching handles in app code or a streaming manager without clearing them on removal; event-driven pipelines where a removal callback races a pending write.

Related errors


AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30). Data as JSON: /api/errors/23780316c849340d. Report an issue: GitHub.