Appearance
Handle Lifecycle
Free host-side handle registry entries your plugin no longer needs — individually (release), by kind (releaseAll), or as a mint-recording region (beginScope / endScope). Releasing is a handle-lifecycle operation only: it frees host memory and quota room and makes the handle unresolvable; it never deletes engine-side geometry or scene entities. Accessed via snaptrude.core.handles.
Long-running plugins should release deterministically: every value handle (vec3/quat math intermediates, curves) and every topology enumeration you keep costs host memory until your plugin stops. The SDK also auto-releases handles your code has completely dropped (a garbage-collection backstop), but that is eventual — loops and batch jobs should use scopes or explicit release.
Types
ArenaKind
The 12 releasable handle kinds: "vec3" | "quat" | "line" | "arc" | "circle" | "profile" | "contour" | "brep" | "face" | "edge" | "halfedge" | "vertex". Entity-style handles (components, materials, underlays, terrain, import jobs, comments) have no registry entry and no lifecycle — passing one to release is a silent no-op.
ScopeToken
Opaque token returned by beginScope and consumed by endScope. Not an arena handle: it pins no memory, and release([token]) ignores it.
PluginHandlesStatsResult
| Property | Type | Description |
|---|---|---|
resources | number | Live resource-arena entries (profile/contour) |
values | number | Live value-arena entries (vec3/quat/line/arc/circle) |
topology | number | Live topology-arena entries (brep/face/edge/halfedge/vertex) |
scopes | number | Currently open handle scopes |
hostHeapUsedBytes | number | null | Host page usedJSHeapSize (null where unsupported) |
hostHeapTotalBytes | number | null | Host page totalJSHeapSize (null where unsupported) |
Functions
release(handles)
Free arena handles in bulk. Unknown, foreign, or already-released ids are silent no-ops — there is no per-id result and no count.
- Parameters:
handles:AnyArenaHandle[]— handles to release (max 10 000 per call).
- Returns: Nothing.
ts
const faces = await snaptrude.core.geom.query.brep.listFaces(brep);
// ... read what you need ...
await snaptrude.core.handles.release(faces);releaseAll(kind?)
Free every live arena handle of one kind, or ALL of your plugin's arena handles when the kind is omitted. Useful at batch boundaries.
- Parameters:
kind:ArenaKind(optional) — restrict the sweep to one kind.
- Returns: Nothing.
ts
const footprint = await snaptrude.design.query.spaces.getFootprint(space);
// ... measure ...
await snaptrude.core.handles.releaseAll("contour");beginScope()
Open a mint-recording scope: every arena handle minted after this call is freed by the matching endScope. Fresh mints only — re-enumerating existing topology (identity-deduped) inside a scope does not capture those handles. Scopes nest, up to 64 deep. Always pair beginScope/endScope in try/finally so a thrown body cannot leak the scope.
- Returns:
ScopeToken— pass it toendScope.
ts
for (const space of spaces) {
const scope = await snaptrude.core.handles.beginScope();
try {
const footprint = await snaptrude.design.query.spaces.getFootprint(space);
const area = await snaptrude.core.geom.query.contour.getArea(footprint);
} finally {
await snaptrude.core.handles.endScope(scope); // footprint + intermediates freed
}
}endScope(scope, retain?)
Close a scope by its token and free every handle it recorded, except those listed in retain. Retained handles are promoted to the enclosing scope, so nesting composes like block scopes. Closing is token-addressed: an out-of-order close (from interleaved async tasks) frees only that scope's mints and leaves other open scopes intact.
- Parameters:
scope:ScopeToken— the token from the matchingbeginScope.retain:AnyArenaHandle[](optional) — scope-minted handles that must survive.
- Returns: Nothing.
ts
const scope = await snaptrude.core.handles.beginScope();
const outline = await snaptrude.design.query.spaces.getFootprint(space);
const outer = await snaptrude.core.geom.query.contour.getOuterProfile(outline);
await snaptrude.core.handles.endScope(scope, [outer]); // outline freed, outer survivesConcurrency note: a scope captures all mints between
beginandendtemporally. If you fire RPCs withPromise.allwhile a scope is open, their mints land in that scope too. Sequentialawaitflows (the normal pattern) are exact.
stats()
Live handle-registry statistics for your plugin plus the host page's JS heap usage — the diagnostic surface for verifying that releases actually shrink host memory. Arena counts are exact and drop immediately on release; heap bytes are GC-dependent (the browser collects when it wants), so heap deltas are best-effort.
- Returns:
PluginHandlesStatsResult.
ts
const before = await snaptrude.core.handles.stats();
await snaptrude.core.handles.releaseAll("vec3");
const after = await snaptrude.core.handles.stats();
console.log(`freed ${before.values - after.values} value entries`);