Code Examples
This page provides ready-to-use code examples for common 3Lens patterns. All examples are available in TypeScript and JavaScript, with ESM and CommonJS module formats.
Table of Contents
Quick Start Examples
Basic Setup
The simplest way to get started with 3Lens.
typescript
import * as THREE from 'three';
import { createProbe } from '@3lens/core';
import { bootstrapOverlay } from '@3lens/overlay';
// Create the probe
const probe = createProbe({
appName: 'My Three.js App',
});
// Create renderer and scene
const renderer = new THREE.WebGLRenderer({ antialias: true });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// Connect 3Lens
probe.observeRenderer(renderer);
probe.observeScene(scene);
// Add the overlay UI
bootstrapOverlay(probe, renderer.domElement);
// Your render loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();1
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
27
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
27
javascript
import * as THREE from 'three';
import { createProbe } from '@3lens/core';
import { bootstrapOverlay } from '@3lens/overlay';
// Create the probe
const probe = createProbe({
appName: 'My Three.js App',
});
// Create renderer and scene
const renderer = new THREE.WebGLRenderer({ antialias: true });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// Connect 3Lens
probe.observeRenderer(renderer);
probe.observeScene(scene);
// Add the overlay UI
bootstrapOverlay(probe, renderer.domElement);
// Your render loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();1
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
27
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
27
typescript
import * as THREE from 'three';
const { createProbe } = require('@3lens/core');
const { bootstrapOverlay } = require('@3lens/overlay');
// Create the probe
const probe = createProbe({
appName: 'My Three.js App',
});
// Create renderer and scene
const renderer = new THREE.WebGLRenderer({ antialias: true });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// Connect 3Lens
probe.observeRenderer(renderer);
probe.observeScene(scene);
// Add the overlay UI
bootstrapOverlay(probe, renderer.domElement);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
javascript
const THREE = require('three');
const { createProbe } = require('@3lens/core');
const { bootstrapOverlay } = require('@3lens/overlay');
// Create the probe
const probe = createProbe({
appName: 'My Three.js App',
});
// Create renderer and scene
const renderer = new THREE.WebGLRenderer({ antialias: true });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// Connect 3Lens
probe.observeRenderer(renderer);
probe.observeScene(scene);
// Add the overlay UI
bootstrapOverlay(probe, renderer.domElement);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Development-Only Setup
Only load 3Lens in development mode to avoid production overhead.
typescript
import * as THREE from 'three';
import type { DevtoolProbe } from '@3lens/core';
let probe: DevtoolProbe | null = null;
async function initDevtools(renderer: THREE.WebGLRenderer, scene: THREE.Scene) {
// Only load in development
if (import.meta.env.DEV) {
const { createProbe } = await import('@3lens/core');
const { bootstrapOverlay } = await import('@3lens/overlay');
probe = createProbe({
appName: 'My App',
env: 'development',
debug: true,
});
probe.observeRenderer(renderer);
probe.observeScene(scene);
bootstrapOverlay(probe, renderer.domElement);
}
}
// Usage
const renderer = new THREE.WebGLRenderer();
const scene = new THREE.Scene();
await initDevtools(renderer, scene);1
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
27
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
27
javascript
import * as THREE from 'three';
let probe = null;
async function initDevtools(renderer, scene) {
// Only load in development
if (import.meta.env.DEV) {
const { createProbe } = await import('@3lens/core');
const { bootstrapOverlay } = await import('@3lens/overlay');
probe = createProbe({
appName: 'My App',
env: 'development',
debug: true,
});
probe.observeRenderer(renderer);
probe.observeScene(scene);
bootstrapOverlay(probe, renderer.domElement);
}
}
// Usage
const renderer = new THREE.WebGLRenderer();
const scene = new THREE.Scene();
await initDevtools(renderer, scene);1
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
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
typescript
import * as THREE from 'three';
import type { DevtoolProbe } from '@3lens/core';
let probe: DevtoolProbe | null = null;
async function initDevtools(renderer: THREE.WebGLRenderer, scene: THREE.Scene) {
// Only load in development
if (process.env.NODE_ENV === 'development') {
const { createProbe } = require('@3lens/core');
const { bootstrapOverlay } = require('@3lens/overlay');
probe = createProbe({
appName: 'My App',
env: 'development',
debug: true,
});
probe.observeRenderer(renderer);
probe.observeScene(scene);
bootstrapOverlay(probe, renderer.domElement);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
javascript
const THREE = require('three');
let probe = null;
async function initDevtools(renderer, scene) {
// Only load in development
if (process.env.NODE_ENV === 'development') {
const { createProbe } = require('@3lens/core');
const { bootstrapOverlay } = require('@3lens/overlay');
probe = createProbe({
appName: 'My App',
env: 'development',
debug: true,
});
probe.observeRenderer(renderer);
probe.observeScene(scene);
bootstrapOverlay(probe, renderer.domElement);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Common Patterns
Monitoring Frame Stats
Subscribe to real-time performance metrics.
typescript
import { createProbe, type FrameStats } from '@3lens/core';
const probe = createProbe({ appName: 'Stats Monitor' });
// Subscribe to frame stats
const unsubscribe = probe.onFrameStats((stats: FrameStats) => {
const fps = stats.performance?.fps ?? 0;
const drawCalls = stats.drawCalls;
const triangles = stats.triangles;
const memory = stats.memory?.totalGpuMemory ?? 0;
console.log(`FPS: ${fps.toFixed(1)} | Draw Calls: ${drawCalls} | Triangles: ${triangles}`);
// Warn on low FPS
if (fps < 30) {
console.warn('Low FPS detected!');
}
});
// Cleanup when done
// unsubscribe();1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
javascript
import { createProbe } from '@3lens/core';
const probe = createProbe({ appName: 'Stats Monitor' });
// Subscribe to frame stats
const unsubscribe = probe.onFrameStats((stats) => {
const fps = stats.performance?.fps ?? 0;
const drawCalls = stats.drawCalls;
const triangles = stats.triangles;
const memory = stats.memory?.totalGpuMemory ?? 0;
console.log(`FPS: ${fps.toFixed(1)} | Draw Calls: ${drawCalls} | Triangles: ${triangles}`);
// Warn on low FPS
if (fps < 30) {
console.warn('Low FPS detected!');
}
});
// Cleanup when done
// unsubscribe();1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Object Selection Handling
Respond to object selection changes.
typescript
import { createProbe, type ObjectMeta } from '@3lens/core';
import * as THREE from 'three';
const probe = createProbe({ appName: 'Selection Demo' });
// Subscribe to selection changes
probe.onSelectionChanged((object: THREE.Object3D | null, meta: ObjectMeta | null) => {
if (object && meta) {
console.log('Selected:', meta.name);
console.log('Type:', meta.type);
console.log('UUID:', meta.uuid);
// Highlight selected object
if (object instanceof THREE.Mesh) {
const material = object.material as THREE.MeshStandardMaterial;
material.emissive.setHex(0x333333);
}
} else {
console.log('Selection cleared');
}
});
// Programmatically select an object
function selectMesh(mesh: THREE.Mesh) {
probe.selectObject(mesh);
}
// Clear selection
function clearSelection() {
probe.selectObject(null);
}1
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
27
28
29
30
31
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
27
28
29
30
31
javascript
import { createProbe } from '@3lens/core';
import * as THREE from 'three';
const probe = createProbe({ appName: 'Selection Demo' });
// Subscribe to selection changes
probe.onSelectionChanged((object, meta) => {
if (object && meta) {
console.log('Selected:', meta.name);
console.log('Type:', meta.type);
console.log('UUID:', meta.uuid);
// Highlight selected object
if (object instanceof THREE.Mesh) {
object.material.emissive.setHex(0x333333);
}
} else {
console.log('Selection cleared');
}
});
// Programmatically select an object
function selectMesh(mesh) {
probe.selectObject(mesh);
}
// Clear selection
function clearSelection() {
probe.selectObject(null);
}1
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
27
28
29
30
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
27
28
29
30
Custom Performance Rules
Define custom rules to catch performance issues.
typescript
import { createProbe, type CustomRule, type RuleContext, type RuleResult } from '@3lens/core';
// Define custom rules
const customRules: CustomRule[] = [
{
id: 'min-fps',
name: 'Minimum FPS',
description: 'Warn when FPS drops below 30',
evaluate: (ctx: RuleContext): RuleResult => {
const fps = ctx.frameStats.performance?.fps ?? 60;
return {
passed: fps >= 30,
value: fps,
threshold: 30,
message: fps < 30 ? `FPS dropped to ${fps.toFixed(1)}` : undefined,
severity: fps < 20 ? 'critical' : 'warning',
};
},
},
{
id: 'max-draw-calls-per-object',
name: 'Draw Calls per Object',
description: 'Check average draw calls per visible object',
evaluate: (ctx: RuleContext): RuleResult => {
const { drawCalls } = ctx.frameStats;
const objectCount = ctx.sceneStats.visibleObjects;
const ratio = objectCount > 0 ? drawCalls / objectCount : 0;
return {
passed: ratio <= 2,
value: ratio,
threshold: 2,
message: ratio > 2 ? `High draw call ratio: ${ratio.toFixed(2)} per object` : undefined,
};
},
},
];
// Create probe with custom rules
const probe = createProbe({
appName: 'Custom Rules Demo',
rules: {
maxDrawCalls: 500,
maxTriangles: 200000,
customRules,
},
});
// Listen for violations
probe.onRuleViolation((violation) => {
console.warn(`Rule violated: ${violation.ruleId}`);
console.warn(`Message: ${violation.message}`);
console.warn(`Severity: ${violation.severity}`);
});1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
javascript
import { createProbe } from '@3lens/core';
// Define custom rules
const customRules = [
{
id: 'min-fps',
name: 'Minimum FPS',
description: 'Warn when FPS drops below 30',
evaluate: (ctx) => {
const fps = ctx.frameStats.performance?.fps ?? 60;
return {
passed: fps >= 30,
value: fps,
threshold: 30,
message: fps < 30 ? `FPS dropped to ${fps.toFixed(1)}` : undefined,
severity: fps < 20 ? 'critical' : 'warning',
};
},
},
{
id: 'max-draw-calls-per-object',
name: 'Draw Calls per Object',
description: 'Check average draw calls per visible object',
evaluate: (ctx) => {
const { drawCalls } = ctx.frameStats;
const objectCount = ctx.sceneStats.visibleObjects;
const ratio = objectCount > 0 ? drawCalls / objectCount : 0;
return {
passed: ratio <= 2,
value: ratio,
threshold: 2,
message: ratio > 2 ? `High draw call ratio: ${ratio.toFixed(2)} per object` : undefined,
};
},
},
];
// Create probe with custom rules
const probe = createProbe({
appName: 'Custom Rules Demo',
rules: {
maxDrawCalls: 500,
maxTriangles: 200000,
customRules,
},
});
// Listen for violations
probe.onRuleViolation((violation) => {
console.warn(`Rule violated: ${violation.ruleId}`);
console.warn(`Message: ${violation.message}`);
console.warn(`Severity: ${violation.severity}`);
});1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Transform Gizmo Setup
Enable object manipulation with transform gizmo.
typescript
import * as THREE from 'three';
import { createProbe } from '@3lens/core';
const probe = createProbe({ appName: 'Transform Demo' });
// Setup scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
probe.observeRenderer(renderer);
probe.observeScene(scene);
// Initialize transform gizmo
probe.initializeTransformGizmo(scene, camera, renderer.domElement, THREE);
// Enable transform gizmo
await probe.enableTransformGizmo();
// Set transform mode
probe.setTransformMode('translate'); // 'translate' | 'rotate' | 'scale'
// Set coordinate space
probe.setTransformSpace('world'); // 'world' | 'local'
// Enable snapping
probe.setTransformSnapEnabled(true);
probe.setTransformSnapValues(1, 15, 0.1); // translation, rotation (degrees), scale
// Listen for transform changes
probe.onTransformChange((event) => {
console.log('Transform changed:', event.mode);
console.log('Before:', event.before);
console.log('After:', event.after);
});
// Undo/Redo
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'z') {
if (e.shiftKey) {
probe.redoTransform();
} else {
probe.undoTransform();
}
}
});1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
javascript
import * as THREE from 'three';
import { createProbe } from '@3lens/core';
const probe = createProbe({ appName: 'Transform Demo' });
// Setup scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
probe.observeRenderer(renderer);
probe.observeScene(scene);
// Initialize transform gizmo
probe.initializeTransformGizmo(scene, camera, renderer.domElement, THREE);
// Enable transform gizmo
await probe.enableTransformGizmo();
// Set transform mode
probe.setTransformMode('translate'); // 'translate' | 'rotate' | 'scale'
// Set coordinate space
probe.setTransformSpace('world'); // 'world' | 'local'
// Enable snapping
probe.setTransformSnapEnabled(true);
probe.setTransformSnapValues(1, 15, 0.1); // translation, rotation (degrees), scale
// Listen for transform changes
probe.onTransformChange((event) => {
console.log('Transform changed:', event.mode);
console.log('Before:', event.before);
console.log('After:', event.after);
});1
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
27
28
29
30
31
32
33
34
35
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
27
28
29
30
31
32
33
34
35
Camera Controls
Focus and fly-to camera animations.
typescript
import * as THREE from 'three';
import { createProbe } from '@3lens/core';
const probe = createProbe({ appName: 'Camera Demo' });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// Initialize camera controller
probe.initializeCameraController(camera, THREE, { x: 0, y: 0, z: 0 });
// Focus on an object instantly
function focusOn(object: THREE.Object3D) {
probe.focusOnObject(object, 1.5); // padding multiplier
}
// Fly to an object with animation
function flyTo(object: THREE.Object3D) {
probe.flyToObject(object, {
duration: 1000,
easing: 'easeInOutCubic',
padding: 1.5,
onComplete: () => console.log('Camera animation complete'),
});
}
// Focus on selected object
function focusOnSelected() {
probe.focusOnSelected(1.5);
}
// Save current camera position as home
probe.saveCurrentCameraAsHome();
// Return to home position
function goHome() {
probe.flyHome({ duration: 800 });
}1
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
27
28
29
30
31
32
33
34
35
36
37
38
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
27
28
29
30
31
32
33
34
35
36
37
38
javascript
import * as THREE from 'three';
import { createProbe } from '@3lens/core';
const probe = createProbe({ appName: 'Camera Demo' });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// Initialize camera controller
probe.initializeCameraController(camera, THREE, { x: 0, y: 0, z: 0 });
// Focus on an object instantly
function focusOn(object) {
probe.focusOnObject(object, 1.5); // padding multiplier
}
// Fly to an object with animation
function flyTo(object) {
probe.flyToObject(object, {
duration: 1000,
easing: 'easeInOutCubic',
padding: 1.5,
onComplete: () => console.log('Camera animation complete'),
});
}
// Focus on selected object
function focusOnSelected() {
probe.focusOnSelected(1.5);
}
// Save current camera position as home
probe.saveCurrentCameraAsHome();
// Return to home position
function goHome() {
probe.flyHome({ duration: 800 });
}1
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
27
28
29
30
31
32
33
34
35
36
37
38
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
27
28
29
30
31
32
33
34
35
36
37
38
Logical Entities
Group related objects under logical entities.
typescript
import * as THREE from 'three';
import { createProbe, type EntityId } from '@3lens/core';
const probe = createProbe({ appName: 'Entity Demo' });
// Register a logical entity
const playerId: EntityId = probe.registerLogicalEntity({
name: 'Player',
module: '@game/player',
componentType: 'PlayerComponent',
tags: ['controllable', 'saveable'],
metadata: {
health: 100,
level: 5,
},
});
// Create player meshes
const playerBody = new THREE.Mesh(
new THREE.CapsuleGeometry(0.5, 1.5),
new THREE.MeshStandardMaterial({ color: 0x4488ff })
);
playerBody.name = 'PlayerBody';
const playerHead = new THREE.Mesh(
new THREE.SphereGeometry(0.3),
new THREE.MeshStandardMaterial({ color: 0xffcc88 })
);
playerHead.name = 'PlayerHead';
playerHead.position.y = 1.2;
// Add objects to entity
probe.addObjectToEntity(playerId, playerBody);
probe.addObjectToEntity(playerId, playerHead);
// Query entities
const controllables = probe.filterEntities({ tags: ['controllable'] });
console.log('Controllable entities:', controllables.length);
const gameEntities = probe.filterEntities({ modulePrefix: '@game/' });
console.log('Game entities:', gameEntities.length);
// Listen for entity events
probe.onEntityEvent((event) => {
switch (event.type) {
case 'entity-registered':
console.log('New entity:', event.entity?.name);
break;
case 'object-added':
console.log('Object added to:', event.entityId);
break;
}
});1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
javascript
import * as THREE from 'three';
import { createProbe } from '@3lens/core';
const probe = createProbe({ appName: 'Entity Demo' });
// Register a logical entity
const playerId = probe.registerLogicalEntity({
name: 'Player',
module: '@game/player',
componentType: 'PlayerComponent',
tags: ['controllable', 'saveable'],
metadata: {
health: 100,
level: 5,
},
});
// Create player meshes
const playerBody = new THREE.Mesh(
new THREE.CapsuleGeometry(0.5, 1.5),
new THREE.MeshStandardMaterial({ color: 0x4488ff })
);
playerBody.name = 'PlayerBody';
const playerHead = new THREE.Mesh(
new THREE.SphereGeometry(0.3),
new THREE.MeshStandardMaterial({ color: 0xffcc88 })
);
playerHead.name = 'PlayerHead';
playerHead.position.y = 1.2;
// Add objects to entity
probe.addObjectToEntity(playerId, playerBody);
probe.addObjectToEntity(playerId, playerHead);
// Query entities
const controllables = probe.filterEntities({ tags: ['controllable'] });
console.log('Controllable entities:', controllables.length);
const gameEntities = probe.filterEntities({ modulePrefix: '@game/' });
console.log('Game entities:', gameEntities.length);1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Resource Tracking & Leak Detection
Monitor resource lifecycle and detect memory leaks.
typescript
import { createProbe, type ResourceLifecycleEvent } from '@3lens/core';
const probe = createProbe({
appName: 'Resource Tracker',
sampling: {
resourceTracking: true,
},
});
// Subscribe to resource events
probe.onResourceEvent((event: ResourceLifecycleEvent) => {
const emoji = {
created: '✨',
disposed: '🗑️',
updated: '🔄',
orphaned: '⚠️',
}[event.eventType];
console.log(`${emoji} ${event.eventType}: ${event.resourceType} (${event.uuid})`);
if (event.eventType === 'orphaned') {
console.warn(`Potential leak: ${event.name ?? event.uuid} is ${event.ageMs}ms old`);
}
});
// Get lifecycle summary
function printResourceSummary() {
const summary = probe.getResourceLifecycleSummary();
console.log('=== Resource Summary ===');
console.log(`Geometries: ${summary.geometries.active} active, ${summary.geometries.leaked} potential leaks`);
console.log(`Materials: ${summary.materials.active} active, ${summary.materials.leaked} potential leaks`);
console.log(`Textures: ${summary.textures.active} active, ${summary.textures.leaked} potential leaks`);
console.log(`Total events: ${summary.totalEvents}`);
}
// Check for potential leaks
function checkForLeaks() {
const leaks = probe.getPotentialResourceLeaks();
if (leaks.length > 0) {
console.warn(`Found ${leaks.length} potential leaks:`);
for (const leak of leaks) {
console.warn(` - ${leak.type}: ${leak.name ?? leak.uuid} (${(leak.ageMs / 1000).toFixed(1)}s old)`);
}
}
}
// Run leak check periodically
setInterval(checkForLeaks, 30000);1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
javascript
import { createProbe } from '@3lens/core';
const probe = createProbe({
appName: 'Resource Tracker',
sampling: {
resourceTracking: true,
},
});
// Subscribe to resource events
probe.onResourceEvent((event) => {
const emoji = {
created: '✨',
disposed: '🗑️',
updated: '🔄',
orphaned: '⚠️',
}[event.eventType];
console.log(`${emoji} ${event.eventType}: ${event.resourceType} (${event.uuid})`);
if (event.eventType === 'orphaned') {
console.warn(`Potential leak: ${event.name ?? event.uuid} is ${event.ageMs}ms old`);
}
});
// Get lifecycle summary
function printResourceSummary() {
const summary = probe.getResourceLifecycleSummary();
console.log('=== Resource Summary ===');
console.log(`Geometries: ${summary.geometries.active} active, ${summary.geometries.leaked} potential leaks`);
console.log(`Materials: ${summary.materials.active} active, ${summary.materials.leaked} potential leaks`);
console.log(`Textures: ${summary.textures.active} active, ${summary.textures.leaked} potential leaks`);
console.log(`Total events: ${summary.totalEvents}`);
}
// Check for potential leaks
function checkForLeaks() {
const leaks = probe.getPotentialResourceLeaks();
if (leaks.length > 0) {
console.warn(`Found ${leaks.length} potential leaks:`);
for (const leak of leaks) {
console.warn(` - ${leak.type}: ${leak.name ?? leak.uuid} (${(leak.ageMs / 1000).toFixed(1)}s old)`);
}
}
}1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Framework Examples
React / React Three Fiber
tsx
import { Canvas } from '@react-three/fiber';
import { ThreeLensProvider, useDevtoolEntity, useFPS, useDrawCalls } from '@3lens/react-bridge';
import type { ProbeConfig } from '@3lens/core';
const config: ProbeConfig = {
appName: 'R3F App',
rules: {
maxDrawCalls: 500,
},
};
function App() {
return (
<ThreeLensProvider config={config} overlay>
<Canvas>
<Scene />
</Canvas>
<StatsOverlay />
</ThreeLensProvider>
);
}
function Scene() {
return (
<>
<ambientLight intensity={0.5} />
<Player />
<Environment />
</>
);
}
function Player() {
const ref = useDevtoolEntity<THREE.Group>({
name: 'Player',
tags: ['controllable'],
});
return (
<group ref={ref}>
<mesh position={[0, 1, 0]}>
<capsuleGeometry args={[0.5, 1.5]} />
<meshStandardMaterial color="blue" />
</mesh>
</group>
);
}
function StatsOverlay() {
const fps = useFPS();
const drawCalls = useDrawCalls();
return (
<div style={{ position: 'absolute', top: 10, left: 10, color: 'white' }}>
<div>FPS: {fps.toFixed(1)}</div>
<div>Draw Calls: {drawCalls}</div>
</div>
);
}
export default App;1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
jsx
import { Canvas } from '@react-three/fiber';
import { ThreeLensProvider, useDevtoolEntity, useFPS, useDrawCalls } from '@3lens/react-bridge';
const config = {
appName: 'R3F App',
rules: {
maxDrawCalls: 500,
},
};
function App() {
return (
<ThreeLensProvider config={config} overlay>
<Canvas>
<Scene />
</Canvas>
<StatsOverlay />
</ThreeLensProvider>
);
}
function Scene() {
return (
<>
<ambientLight intensity={0.5} />
<Player />
<Environment />
</>
);
}
function Player() {
const ref = useDevtoolEntity({
name: 'Player',
tags: ['controllable'],
});
return (
<group ref={ref}>
<mesh position={[0, 1, 0]}>
<capsuleGeometry args={[0.5, 1.5]} />
<meshStandardMaterial color="blue" />
</mesh>
</group>
);
}
function StatsOverlay() {
const fps = useFPS();
const drawCalls = useDrawCalls();
return (
<div style={{ position: 'absolute', top: 10, left: 10, color: 'white' }}>
<div>FPS: {fps.toFixed(1)}</div>
<div>Draw Calls: {drawCalls}</div>
</div>
);
}
export default App;1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Vue / TresJS
vue
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core';
import { useDevtoolEntity, useThreeLens } from '@3lens/vue-bridge';
import type { ProbeConfig } from '@3lens/core';
import { ref } from 'vue';
const config: ProbeConfig = {
appName: 'TresJS App',
rules: {
maxDrawCalls: 500,
},
};
const { probe, fps, drawCalls } = useThreeLens(config);
// Player entity
const playerRef = ref<THREE.Group | null>(null);
useDevtoolEntity(playerRef, {
name: 'Player',
tags: ['controllable'],
});
</script>
<template>
<div class="container">
<TresCanvas>
<TresAmbientLight :intensity="0.5" />
<TresGroup ref="playerRef">
<TresMesh :position="[0, 1, 0]">
<TresCapsuleGeometry :args="[0.5, 1.5]" />
<TresMeshStandardMaterial color="blue" />
</TresMesh>
</TresGroup>
</TresCanvas>
<div class="stats">
<div>FPS: {{ fps.toFixed(1) }}</div>
<div>Draw Calls: {{ drawCalls }}</div>
</div>
</div>
</template>
<style scoped>
.container {
position: relative;
width: 100%;
height: 100vh;
}
.stats {
position: absolute;
top: 10px;
left: 10px;
color: white;
}
</style>1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
vue
<script setup>
import { TresCanvas } from '@tresjs/core';
import { useDevtoolEntity, useThreeLens } from '@3lens/vue-bridge';
import { ref } from 'vue';
const config = {
appName: 'TresJS App',
rules: {
maxDrawCalls: 500,
},
};
const { probe, fps, drawCalls } = useThreeLens(config);
// Player entity
const playerRef = ref(null);
useDevtoolEntity(playerRef, {
name: 'Player',
tags: ['controllable'],
});
</script>
<template>
<div class="container">
<TresCanvas>
<TresAmbientLight :intensity="0.5" />
<TresGroup ref="playerRef">
<TresMesh :position="[0, 1, 0]">
<TresCapsuleGeometry :args="[0.5, 1.5]" />
<TresMeshStandardMaterial color="blue" />
</TresMesh>
</TresGroup>
</TresCanvas>
<div class="stats">
<div>FPS: {{ fps.toFixed(1) }}</div>
<div>Draw Calls: {{ drawCalls }}</div>
</div>
</div>
</template>1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Angular
typescript
// app.module.ts
import { NgModule } from '@angular/core';
import { ThreeLensModule, THREELENS_CONFIG } from '@3lens/angular-bridge';
import type { ProbeConfig } from '@3lens/core';
const config: ProbeConfig = {
appName: 'Angular Three.js App',
rules: {
maxDrawCalls: 500,
},
};
@NgModule({
imports: [
ThreeLensModule,
],
providers: [
{ provide: THREELENS_CONFIG, useValue: config },
],
})
export class AppModule {}
// scene.component.ts
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { ThreeLensService } from '@3lens/angular-bridge';
import * as THREE from 'three';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-scene',
template: `
<canvas #canvas></canvas>
<div class="stats">
<div>FPS: {{ fps | number:'1.1-1' }}</div>
<div>Draw Calls: {{ drawCalls }}</div>
</div>
`,
})
export class SceneComponent implements OnInit, OnDestroy {
@ViewChild('canvas', { static: true }) canvasRef!: ElementRef<HTMLCanvasElement>;
fps = 0;
drawCalls = 0;
private subscription = new Subscription();
constructor(private threeLens: ThreeLensService) {}
ngOnInit() {
// Setup Three.js
const canvas = this.canvasRef.nativeElement;
const renderer = new THREE.WebGLRenderer({ canvas });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, canvas.width / canvas.height, 0.1, 1000);
// Connect 3Lens
this.threeLens.observeRenderer(renderer);
this.threeLens.observeScene(scene);
// Subscribe to stats
this.subscription.add(
this.threeLens.frameStats$.subscribe(stats => {
this.fps = stats.performance?.fps ?? 0;
this.drawCalls = stats.drawCalls;
})
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
Advanced Patterns
Plugin Development
Create a custom devtool plugin.
typescript
import type { DevtoolPlugin, DevtoolContext, PanelRenderContext } from '@3lens/core';
const statsHistoryPlugin: DevtoolPlugin = {
metadata: {
id: 'com.example.stats-history',
name: 'Stats History',
version: '1.0.0',
description: 'Track FPS history with a chart',
},
activate(context: DevtoolContext) {
context.log('Stats History plugin activated');
// Initialize storage
context.setStorage('fpsHistory', []);
// Subscribe to frame stats
context.onFrameStats((stats) => {
const history = context.getStorage<number[]>('fpsHistory') ?? [];
history.push(stats.performance?.fps ?? 0);
// Keep last 100 samples
if (history.length > 100) {
history.shift();
}
context.setStorage('fpsHistory', history);
});
},
deactivate(context: DevtoolContext) {
context.log('Stats History plugin deactivated');
},
panels: [{
id: 'fps-history',
name: 'FPS History',
icon: '📈',
render(ctx: PanelRenderContext): string {
const history = ctx.context.getStorage<number[]>('fpsHistory') ?? [];
const max = Math.max(...history, 60);
const min = Math.min(...history, 0);
const avg = history.length > 0
? history.reduce((a, b) => a + b, 0) / history.length
: 0;
return `
<div style="padding: 8px;">
<h3>FPS History</h3>
<div>Max: ${max.toFixed(0)} | Avg: ${avg.toFixed(1)} | Min: ${min.toFixed(0)}</div>
<div style="display: flex; align-items: flex-end; height: 60px; gap: 1px;">
${history.map(fps => {
const height = (fps / max) * 100;
const color = fps >= 55 ? '#22c55e' : fps >= 30 ? '#eab308' : '#ef4444';
return `<div style="flex: 1; height: ${height}%; background: ${color};"></div>`;
}).join('')}
</div>
</div>
`;
},
onMount(container, ctx) {
// Request updates every 100ms
const intervalId = setInterval(() => ctx.requestUpdate(), 100);
(container as any).__intervalId = intervalId;
},
onUnmount(container) {
clearInterval((container as any).__intervalId);
},
}],
settings: {
fields: [
{
key: 'maxSamples',
label: 'Max Samples',
type: 'number',
defaultValue: 100,
min: 10,
max: 500,
},
],
},
};
export default statsHistoryPlugin;
// Usage
import { createProbe } from '@3lens/core';
import statsHistoryPlugin from './stats-history-plugin';
const probe = createProbe({ appName: 'Plugin Demo' });
probe.registerPlugin(statsHistoryPlugin);
await probe.activatePlugin('com.example.stats-history');1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
javascript
const statsHistoryPlugin = {
metadata: {
id: 'com.example.stats-history',
name: 'Stats History',
version: '1.0.0',
description: 'Track FPS history with a chart',
},
activate(context) {
context.log('Stats History plugin activated');
// Initialize storage
context.setStorage('fpsHistory', []);
// Subscribe to frame stats
context.onFrameStats((stats) => {
const history = context.getStorage('fpsHistory') ?? [];
history.push(stats.performance?.fps ?? 0);
// Keep last 100 samples
if (history.length > 100) {
history.shift();
}
context.setStorage('fpsHistory', history);
});
},
deactivate(context) {
context.log('Stats History plugin deactivated');
},
panels: [{
id: 'fps-history',
name: 'FPS History',
icon: '📈',
render(ctx) {
const history = ctx.context.getStorage('fpsHistory') ?? [];
const max = Math.max(...history, 60);
const avg = history.length > 0
? history.reduce((a, b) => a + b, 0) / history.length
: 0;
return `
<div style="padding: 8px;">
<h3>FPS History</h3>
<div>Max: ${max.toFixed(0)} | Avg: ${avg.toFixed(1)}</div>
</div>
`;
},
}],
};
export default statsHistoryPlugin;1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
WebGPU Support
Using 3Lens with WebGPU renderer.
typescript
import * as THREE from 'three/webgpu';
import { createProbe } from '@3lens/core';
import { bootstrapOverlay } from '@3lens/overlay';
async function initWebGPU() {
// Check WebGPU support
if (!navigator.gpu) {
console.error('WebGPU not supported');
return;
}
// Create WebGPU renderer
const renderer = new THREE.WebGPURenderer({ antialias: true });
await renderer.init();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// Create probe with WebGPU-optimized settings
const probe = createProbe({
appName: 'WebGPU App',
sampling: {
gpuTiming: true, // WebGPU has better timing support
resourceTracking: true,
},
rules: {
maxDrawCalls: 2000,
maxTriangles: 1000000,
},
});
// Connect 3Lens - auto-detects WebGPU
probe.observeRenderer(renderer);
probe.observeScene(scene);
// Verify WebGPU detection
console.log('Renderer kind:', probe.getRendererKind()); // 'webgpu'
console.log('Is WebGPU:', probe.isWebGPU()); // true
// Add overlay
bootstrapOverlay(probe, renderer.domElement);
// Animation loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
}
initWebGPU();1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
javascript
import * as THREE from 'three/webgpu';
import { createProbe } from '@3lens/core';
import { bootstrapOverlay } from '@3lens/overlay';
async function initWebGPU() {
// Check WebGPU support
if (!navigator.gpu) {
console.error('WebGPU not supported');
return;
}
// Create WebGPU renderer
const renderer = new THREE.WebGPURenderer({ antialias: true });
await renderer.init();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// Create probe with WebGPU-optimized settings
const probe = createProbe({
appName: 'WebGPU App',
sampling: {
gpuTiming: true,
resourceTracking: true,
},
});
// Connect 3Lens
probe.observeRenderer(renderer);
probe.observeScene(scene);
console.log('Is WebGPU:', probe.isWebGPU()); // true
// Add overlay
bootstrapOverlay(probe, renderer.domElement);
}
initWebGPU();1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Runnable Examples
Try these examples directly in your browser:
StackBlitz Examples
CodeSandbox Examples
| Example | Description | Link |
|---|---|---|
| Getting Started | Step-by-step tutorial | |
| Transform Gizmo | Object manipulation | |
| Memory Profiling | Leak detection example |
Copy-Paste Snippets
Quick Initialization
typescript
// Minimal setup - copy and paste
import { createProbe } from '@3lens/core';
import { bootstrapOverlay } from '@3lens/overlay';
const probe = createProbe({ appName: 'My App' });
probe.observeRenderer(renderer);
probe.observeScene(scene);
bootstrapOverlay(probe, renderer.domElement);1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Development-Only Import
typescript
// Development-only - copy and paste
if (import.meta.env.DEV) {
const { createProbe } = await import('@3lens/core');
const { bootstrapOverlay } = await import('@3lens/overlay');
const probe = createProbe({ appName: 'My App' });
probe.observeRenderer(renderer);
probe.observeScene(scene);
bootstrapOverlay(probe, renderer.domElement);
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
FPS Monitor
typescript
// FPS monitoring - copy and paste
const unsubscribe = probe.onFrameStats((stats) => {
const fps = stats.performance?.fps ?? 0;
if (fps < 30) console.warn('Low FPS:', fps.toFixed(1));
});1
2
3
4
5
2
3
4
5
Selection Sync
typescript
// Selection sync - copy and paste
probe.onSelectionChanged((obj, meta) => {
if (obj) {
myUI.showInspector(obj, meta);
} else {
myUI.hideInspector();
}
});1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Cleanup
typescript
// Proper cleanup - copy and paste
function cleanup() {
probe.dispose();
}
// Or in React
useEffect(() => {
return () => probe.dispose();
}, [probe]);1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
See Also
- Probe API - Complete probe reference
- Configuration API - All configuration options
- Events API - Event subscriptions
- Plugin API - Plugin development
- Getting Started Guide - Step-by-step tutorial