DevtoolContext Interface
The DevtoolContext interface provides plugins with access to 3Lens functionality. It's passed to plugin lifecycle methods (activate, deactivate) and various callbacks.
Overview
activate(context: DevtoolContext) {
// Access frame stats
const stats = context.getFrameStats();
// Access scene snapshot
const snapshot = context.getSnapshot();
// Manage plugin state
context.setState('counter', 0);
const counter = context.getState<number>('counter');
// Show notifications
context.showToast('Plugin ready!', 'success');
// Inter-plugin communication
context.sendMessage('other-plugin', 'event-type', { data: 123 });
// Request UI update
context.requestRender();
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Interface Definition
interface DevtoolContext {
/** The probe instance */
probe: DevtoolProbe;
/** Get current frame stats */
getFrameStats(): FrameStats | null;
/** Get current scene snapshot */
getSnapshot(): SceneSnapshot | null;
/** Get currently selected node */
getSelectedNode(): SceneNode | null;
/** Select an object by UUID */
selectObject(uuid: string): void;
/** Clear selection */
clearSelection(): void;
/** Get all logical entities */
getEntities(): LogicalEntity[];
/** Get module info */
getModuleInfo(moduleId: string): ModuleInfo | undefined;
/** Log a message */
log(message: string, data?: Record<string, unknown>): void;
/** Show a toast notification */
showToast(message: string, type?: 'info' | 'success' | 'warning' | 'error'): void;
/** Get plugin state */
getState<T = unknown>(key: string): T | undefined;
/** Set plugin state */
setState<T = unknown>(key: string, value: T): void;
/** Get all plugin state */
getAllState(): Record<string, unknown>;
/** Clear plugin state */
clearState(): void;
/** Send a message to another plugin */
sendMessage(target: PluginId | '*', type: string, payload: unknown): void;
/** Subscribe to messages */
onMessage(handler: PluginMessageHandler): () => void;
/** Request a panel re-render */
requestRender(): void;
/** Get the plugin's container element */
getContainer(): HTMLElement | null;
}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
Data Access Methods
getFrameStats
Returns the current frame statistics, or null if not yet available.
getFrameStats(): FrameStats | nullExample:
const stats = context.getFrameStats();
if (stats) {
console.log(`FPS: ${stats.fps}`);
console.log(`Draw Calls: ${stats.drawCalls}`);
console.log(`Triangles: ${stats.triangles}`);
console.log(`Frame Time: ${stats.frameTime}ms`);
}2
3
4
5
6
7
getSnapshot
Returns the current scene snapshot, or null if not yet available.
getSnapshot(): SceneSnapshot | nullExample:
const snapshot = context.getSnapshot();
if (snapshot) {
console.log(`Objects: ${snapshot.objectCount}`);
console.log(`Materials: ${snapshot.materialCount}`);
console.log(`Textures: ${snapshot.textureCount}`);
}2
3
4
5
6
getSelectedNode
Returns the currently selected scene node, or null if nothing is selected.
getSelectedNode(): SceneNode | nullExample:
const node = context.getSelectedNode();
if (node) {
console.log(`Selected: ${node.name}`);
console.log(`Type: ${node.type}`);
console.log(`UUID: ${node.uuid}`);
console.log(`Position: ${node.position.join(', ')}`);
}2
3
4
5
6
7
getEntities
Returns all registered logical entities.
getEntities(): LogicalEntity[]Example:
const entities = context.getEntities();
const enemies = entities.filter(e => e.tags.includes('enemy'));
console.log(`Enemy count: ${enemies.length}`);2
3
getModuleInfo
Returns module information by module ID.
getModuleInfo(moduleId: string): ModuleInfo | undefinedExample:
const playerModule = context.getModuleInfo('@game/player');
if (playerModule) {
console.log(`Player triangles: ${playerModule.metrics.triangles}`);
console.log(`Player draw calls: ${playerModule.metrics.drawCalls}`);
}2
3
4
5
Selection Methods
selectObject
Selects a Three.js object by its UUID.
selectObject(uuid: string): voidExample:
// Select an object from entity
const entity = context.getEntities().find(e => e.name === 'Player');
if (entity && entity.objectUuids.length > 0) {
context.selectObject(entity.objectUuids[0]);
}2
3
4
5
clearSelection
Clears the current selection.
clearSelection(): voidExample:
context.clearSelection();
context.showToast('Selection cleared', 'info');2
State Management
Plugins can store persistent state that survives panel re-renders.
setState
Sets a state value.
setState<T = unknown>(key: string, value: T): voidExample:
context.setState('analysisResults', results);
context.setState('lastRunTime', Date.now());
context.setState('settings', { threshold: 100, enabled: true });2
3
getState
Gets a state value.
getState<T = unknown>(key: string): T | undefinedExample:
const results = context.getState<AnalysisResult[]>('analysisResults');
const lastRun = context.getState<number>('lastRunTime');
const settings = context.getState<Settings>('settings');
if (results && lastRun) {
const age = Date.now() - lastRun;
console.log(`Results from ${age}ms ago`);
}2
3
4
5
6
7
8
getAllState
Gets all state as an object.
getAllState(): Record<string, unknown>Example:
const allState = context.getAllState();
console.log('Plugin state:', allState);2
clearState
Clears all plugin state.
clearState(): voidExample:
deactivate(context) {
// Clean up all state on deactivation
context.clearState();
}2
3
4
Logging & Notifications
log
Logs a message to the 3Lens console.
log(message: string, data?: Record<string, unknown>): voidExample:
context.log('Analysis started');
context.log('Found issues', { count: 5, severity: 'warning' });
context.log('Object analyzed', {
name: 'Player',
triangles: 50000,
recommendation: 'Consider LOD'
});2
3
4
5
6
7
showToast
Shows a toast notification to the user.
showToast(message: string, type?: 'info' | 'success' | 'warning' | 'error'): voidExample:
context.showToast('Analysis complete!', 'success');
context.showToast('No objects selected', 'warning');
context.showToast('Failed to export', 'error');
context.showToast('Processing...', 'info');2
3
4
Inter-Plugin Communication
sendMessage
Sends a message to another plugin or broadcasts to all plugins.
sendMessage(target: PluginId | '*', type: string, payload: unknown): voidParameters:
| Parameter | Type | Description |
|---|---|---|
target | PluginId | '*' | Target plugin ID, or '*' to broadcast |
type | string | Message type identifier |
payload | unknown | Message data |
Example:
// Send to specific plugin
context.sendMessage('3lens.shadow-debugger', 'highlight-light', {
uuid: light.uuid,
});
// Broadcast to all plugins
context.sendMessage('*', 'selection-changed', {
uuid: selectedObject.uuid,
name: selectedObject.name,
});2
3
4
5
6
7
8
9
10
onMessage
Subscribes to messages from other plugins.
onMessage(handler: PluginMessageHandler): () => voidReturns: Unsubscribe function.
Example:
activate(context) {
const unsubscribe = context.onMessage((message) => {
console.log(`Message from ${message.source}: ${message.type}`);
if (message.type === 'highlight-request') {
const { uuid } = message.payload as { uuid: string };
highlightObject(uuid);
}
});
// Store unsubscribe function for cleanup
context.setState('messageUnsubscribe', unsubscribe);
}
deactivate(context) {
const unsubscribe = context.getState<() => void>('messageUnsubscribe');
if (unsubscribe) unsubscribe();
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PluginMessage
Message structure:
interface PluginMessage {
/** Source plugin ID */
source: PluginId;
/** Target plugin ID (or '*' for broadcast) */
target: PluginId | '*';
/** Message type */
type: string;
/** Message payload */
payload: unknown;
/** Timestamp */
timestamp: number;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
UI Methods
requestRender
Requests the plugin's panel to re-render. Use this after state changes that should update the UI.
requestRender(): voidExample:
toolbarActions: [{
id: 'refresh-analysis',
name: 'Refresh',
icon: '🔄',
onClick(context) {
const results = runAnalysis(context.probe);
context.setState('results', results);
context.requestRender(); // Trigger panel update
context.showToast('Analysis refreshed', 'success');
},
}],2
3
4
5
6
7
8
9
10
11
getContainer
Gets the plugin's panel container element, if the panel is currently mounted.
getContainer(): HTMLElement | nullExample:
const container = context.getContainer();
if (container) {
// Direct DOM manipulation
const chart = container.querySelector('.chart');
if (chart) {
updateChart(chart, newData);
}
}2
3
4
5
6
7
8
Probe Access
probe
Direct access to the DevtoolProbe instance for advanced operations.
probe: DevtoolProbeExample:
// Access probe directly for advanced operations
const probe = context.probe;
// Get renderer info
const renderer = probe.getObservedRenderer();
// Get scene
const scene = probe.getObservedScene();
// Filter entities
const enemies = probe.filterEntities({ tags: ['enemy'] });
// Get performance tracker
const perfStats = probe.getPerformanceStats();2
3
4
5
6
7
8
9
10
11
12
13
14
Complete Example
const AnalyzerPlugin: DevtoolPlugin = {
metadata: {
id: 'com.example.analyzer',
name: 'Scene Analyzer',
version: '1.0.0',
},
activate(context) {
context.log('Analyzer plugin activated');
// Initialize state
context.setState('analysisResults', []);
context.setState('isAnalyzing', false);
// Subscribe to messages
const unsubscribe = context.onMessage((msg) => {
if (msg.type === 'request-analysis') {
runAnalysis(context);
}
});
context.setState('unsubscribe', unsubscribe);
// Run initial analysis
runAnalysis(context);
},
deactivate(context) {
// Clean up
const unsubscribe = context.getState<() => void>('unsubscribe');
if (unsubscribe) unsubscribe();
context.clearState();
context.log('Analyzer plugin deactivated');
},
panels: [{
id: 'results',
name: 'Analysis Results',
render(ctx) {
const results = ctx.state.analysisResults as AnalysisResult[];
const isAnalyzing = ctx.state.isAnalyzing as boolean;
if (isAnalyzing) {
return '<div class="loading">Analyzing...</div>';
}
if (!results.length) {
return '<div class="empty">No results yet</div>';
}
return `
<div class="results">
<h3>Found ${results.length} issues</h3>
<ul>
${results.map(r => `
<li class="${r.severity}">
<strong>${r.object}</strong>: ${r.message}
</li>
`).join('')}
</ul>
</div>
`;
},
}],
toolbarActions: [{
id: 'run-analysis',
name: 'Run Analysis',
icon: '🔍',
onClick(context) {
runAnalysis(context);
},
isEnabled(context) {
return !context.getState<boolean>('isAnalyzing');
},
}],
};
async function runAnalysis(context: DevtoolContext) {
context.setState('isAnalyzing', true);
context.requestRender();
const snapshot = context.getSnapshot();
if (!snapshot) {
context.showToast('No scene data available', 'warning');
context.setState('isAnalyzing', false);
context.requestRender();
return;
}
// Perform analysis...
const results = analyzeScene(snapshot);
context.setState('analysisResults', results);
context.setState('isAnalyzing', false);
context.requestRender();
context.log('Analysis complete', { issueCount: results.length });
context.showToast(`Found ${results.length} issues`, 'success');
// Notify other plugins
context.sendMessage('*', 'analysis-complete', { results });
}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
97
98
99
100
101
102
See Also
- DevtoolPlugin - Plugin interface
- PluginManager - Plugin lifecycle management
- DevtoolProbe - Probe API
- FrameStats - Frame statistics type
- SceneSnapshot - Scene snapshot type