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
- Discard or null the handle immediately after removing the part
- Check a liveness flag of your own before using cached handles
- 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
- Null out handles in the same code path that removes parts
- Wrap handles in a manager that tracks removal state centrally
- Avoid retaining streaming handles in long-lived collections
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
- There is no NavMesh generated.
- There is no TileCache generated.
- No volume subnode
- Disconnect failed
- Engine does not have gl rendering context.
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/23780316c849340d.
Report an issue: GitHub.