ResourceLifecycleTracker
The ResourceLifecycleTracker class monitors the creation, attachment, detachment, and disposal of Three.js resources (geometries, materials, and textures). It provides detailed lifecycle events, optional stack traces for debugging, and automatic leak detection.
Overview
import { ResourceLifecycleTracker } from '@3lens/core';
// Create with default options
const tracker = new ResourceLifecycleTracker();
// Or with custom configuration
const customTracker = new ResourceLifecycleTracker({
captureStackTraces: true, // Enable stack traces (performance impact)
maxEvents: 1000, // Keep last 1000 events
leakThresholdMs: 60000, // Consider leaked after 60s
orphanCheckIntervalMs: 10000, // Check for orphans every 10s
memoryGrowthThresholdBytes: 50 * 1024 * 1024, // 50MB growth threshold
});2
3
4
5
6
7
8
9
10
11
12
13
Constructor Options
interface ResourceTrackerOptions {
captureStackTraces?: boolean; // Default: false
maxEvents?: number; // Default: 1000
leakThresholdMs?: number; // Default: 60000 (1 minute)
orphanCheckIntervalMs?: number; // Default: 10000 (10 seconds)
memoryGrowthThresholdBytes?: number; // Default: 52428800 (50MB)
}2
3
4
5
6
7
| Option | Type | Default | Description |
|---|---|---|---|
captureStackTraces | boolean | false | Capture stack traces for each event (performance impact) |
maxEvents | number | 1000 | Maximum number of events to keep in history |
leakThresholdMs | number | 60000 | Time after which undisposed resources are flagged as leaked |
orphanCheckIntervalMs | number | 10000 | Interval for orphan detection checks |
memoryGrowthThresholdBytes | number | 52428800 | Alert threshold for memory growth |
Resource Types
The tracker monitors three types of resources:
type ResourceType = 'geometry' | 'material' | 'texture';Lifecycle Event Types
type LifecycleEventType = 'created' | 'disposed' | 'attached' | 'detached';| Event Type | Description |
|---|---|
created | Resource was instantiated |
disposed | Resource's dispose() method was called |
attached | Resource was assigned to a mesh |
detached | Resource was removed from a mesh |
Recording Events
recordCreation()
Record when a resource is created.
recordCreation(
resourceType: ResourceType,
resourceUuid: string,
options?: {
name?: string;
subtype?: string;
estimatedMemory?: number;
vertexCount?: number;
faceCount?: number;
}
): void2
3
4
5
6
7
8
9
10
11
Example:
const geometry = new THREE.BoxGeometry(1, 1, 1);
tracker.recordCreation('geometry', geometry.uuid, {
name: 'PlayerBox',
subtype: 'BoxGeometry',
estimatedMemory: 1024,
vertexCount: 24,
faceCount: 12,
});2
3
4
5
6
7
8
9
recordDisposal()
Record when a resource is disposed.
recordDisposal(resourceType: ResourceType, resourceUuid: string): voidExample:
geometry.dispose();
tracker.recordDisposal('geometry', geometry.uuid);2
recordAttachment()
Record when a resource is attached to a mesh.
recordAttachment(
resourceType: ResourceType,
resourceUuid: string,
meshUuid: string,
options?: {
meshName?: string;
textureSlot?: string;
}
): void2
3
4
5
6
7
8
9
Example:
mesh.material = material;
tracker.recordAttachment('material', material.uuid, mesh.uuid, {
meshName: mesh.name,
});
// For textures, specify the slot
tracker.recordAttachment('texture', texture.uuid, mesh.uuid, {
meshName: mesh.name,
textureSlot: 'map',
});2
3
4
5
6
7
8
9
10
11
recordDetachment()
Record when a resource is detached from a mesh.
recordDetachment(
resourceType: ResourceType,
resourceUuid: string,
meshUuid: string,
options?: {
meshName?: string;
textureSlot?: string;
}
): void2
3
4
5
6
7
8
9
Example:
// Old material being replaced
tracker.recordDetachment('material', oldMaterial.uuid, mesh.uuid);
mesh.material = newMaterial;2
3
Querying Events
getEvents()
Get all recorded events.
getEvents(): ResourceLifecycleEvent[]getEventsByType()
Filter events by resource type.
getEventsByType(resourceType: ResourceType): ResourceLifecycleEvent[]Example:
const textureEvents = tracker.getEventsByType('texture');getEventsByEventType()
Filter events by lifecycle event type.
getEventsByEventType(eventType: LifecycleEventType): ResourceLifecycleEvent[]Example:
const disposalEvents = tracker.getEventsByEventType('disposed');
console.log(`${disposalEvents.length} resources disposed`);2
getEventsInRange()
Get events within a time range.
getEventsInRange(startMs: number, endMs: number): ResourceLifecycleEvent[]Example:
const now = performance.now();
const lastMinuteEvents = tracker.getEventsInRange(now - 60000, now);2
Event Interface
interface ResourceLifecycleEvent {
id: string; // Unique event ID (e.g., "evt_123")
resourceType: ResourceType; // 'geometry' | 'material' | 'texture'
resourceUuid: string; // Three.js UUID
resourceName?: string; // Optional name
resourceSubtype?: string; // e.g., 'MeshStandardMaterial'
eventType: LifecycleEventType; // 'created' | 'disposed' | etc.
timestamp: number; // performance.now() value
stackTrace?: string; // Stack trace if enabled
metadata?: {
meshUuid?: string; // For attach/detach events
meshName?: string;
textureSlot?: string; // e.g., 'map', 'normalMap'
estimatedMemory?: number; // Memory in bytes
vertexCount?: number; // For geometries
faceCount?: number; // For geometries
};
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Event Subscriptions
onEvent()
Subscribe to lifecycle events in real-time.
onEvent(callback: LifecycleEventCallback): () => voidReturns: Unsubscribe function.
Example:
const unsubscribe = tracker.onEvent((event) => {
if (event.eventType === 'created') {
console.log(`Created ${event.resourceType}: ${event.resourceName || event.resourceUuid}`);
}
});
// Later, to unsubscribe:
unsubscribe();2
3
4
5
6
7
8
Summary Statistics
getSummary()
Get a summary of resource lifecycle statistics.
getSummary(): ResourceLifecycleSummaryReturns:
interface ResourceLifecycleSummary {
geometries: {
created: number;
disposed: number;
active: number; // created - disposed
leaked: number; // Active resources older than threshold
};
materials: { /* same structure */ };
textures: { /* same structure */ };
totalEvents: number;
oldestActiveResource?: {
type: ResourceType;
uuid: string;
name?: string;
ageMs: number;
};
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Example:
const summary = tracker.getSummary();
console.log(`Active geometries: ${summary.geometries.active}`);
console.log(`Potentially leaked: ${summary.geometries.leaked}`);
if (summary.oldestActiveResource) {
const age = summary.oldestActiveResource.ageMs / 1000;
console.log(`Oldest resource: ${summary.oldestActiveResource.name} (${age.toFixed(0)}s old)`);
}2
3
4
5
6
7
8
9
Stack Trace Control
setStackTraceCapture()
Enable or disable stack trace capture at runtime.
setStackTraceCapture(enabled: boolean): voidPerformance Impact
Capturing stack traces has a significant performance impact. Enable only during debugging sessions, not in production.
Example:
// Enable temporarily for debugging
tracker.setStackTraceCapture(true);
// Do operations you want to trace
createSomeResources();
// Disable to restore performance
tracker.setStackTraceCapture(false);2
3
4
5
6
7
8
isStackTraceCaptureEnabled()
Check if stack trace capture is currently enabled.
isStackTraceCaptureEnabled(): booleanResource Lifecycle Diagram
┌─────────────────────────────────────────────────────────────────┐
│ Resource Lifecycle │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ CREATE │─────▶│ ATTACH │◀────▶│ DETACH │ │
│ │ │ │ │ │ │ │
│ │ geometry │ │ to mesh │ │from mesh │ │
│ │ material │ │ │ │ │ │
│ │ texture │ │ │ │ │ │
│ └────┬─────┘ └──────────┘ └────┬─────┘ │
│ │ │ │
│ │ │ │
│ │ ┌──────────────┐ │ │
│ └────────▶│ DISPOSE │◀─────────┘ │
│ │ │ │
│ │ Free GPU │ │
│ │ memory │ │
│ └──────────────┘ │
│ │
│ ⚠️ Problem States: │
│ • Orphaned: Created but never attached │
│ • Leaked: Active too long without disposal │
│ • Detached-not-disposed: Removed but not freed │
│ │
└─────────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Integration with 3Lens
The ResourceLifecycleTracker is used internally by SceneObserver:
// Access through SceneObserver
const tracker = sceneObserver.getLifecycleTracker();
// Or access through probe
const probe = createProbe({ /* config */ });
probe.observeScene(scene);
// The tracker is automatically populated as resources change2
3
4
5
6
7
8
Clearing Data
clear()
Clear all events, active resources, and alerts.
clear(): voidExample:
// Clear when switching contexts
function resetSession() {
tracker.clear();
}2
3
4
Best Practices
Enable Stack Traces Only When Debugging
// Development debugging mode
if (process.env.NODE_ENV === 'development' && debugMode) {
tracker.setStackTraceCapture(true);
}2
3
4
Monitor Summary for Memory Issues
setInterval(() => {
const summary = tracker.getSummary();
// Check for accumulation
if (summary.geometries.active > 500) {
console.warn('High geometry count:', summary.geometries.active);
}
// Check for potential leaks
if (summary.materials.leaked > 10) {
console.warn('Potentially leaked materials:', summary.materials.leaked);
}
}, 10000);2
3
4
5
6
7
8
9
10
11
12
13
Log Disposal Failures
tracker.onEvent((event) => {
if (event.eventType === 'disposed' && event.stackTrace) {
// Keep track of disposal locations for debugging
console.debug(`Disposed ${event.resourceType} at:`, event.stackTrace);
}
});2
3
4
5
6
See Also
- Leak Detection System - Automatic leak detection and alerts
- Memory Tracking - GPU memory estimation
- SceneObserver - Scene graph tracking with lifecycle integration
- DevtoolProbe - Main probe class