Skip to content

Commit 0f538a2

Browse files
Makio64sunag
andauthored
Inspector: Add export functionality to Inspector Timeline tab and enhance call detail logging (mrdoob#33192)
Co-authored-by: sunag <sunagbrasil@gmail.com>
1 parent 9b054a2 commit 0f538a2

1 file changed

Lines changed: 125 additions & 7 deletions

File tree

examples/jsm/inspector/tabs/Timeline.js

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,22 @@ class Timeline extends Tab {
102102

103103
} );
104104

105+
this.exportButton = document.createElement( 'button' );
106+
this.exportButton.className = 'console-copy-button';
107+
this.exportButton.title = 'Export';
108+
this.exportButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>';
109+
this.exportButton.style.padding = '0 10px';
110+
this.exportButton.style.lineHeight = '24px';
111+
this.exportButton.style.display = 'flex';
112+
this.exportButton.style.alignItems = 'center';
113+
this.exportButton.addEventListener( 'click', () => this.exportData() );
114+
105115
const buttonsGroup = document.createElement( 'div' );
106116
buttonsGroup.className = 'console-buttons-group';
107117
buttonsGroup.appendChild( this.viewModeButton );
108118
buttonsGroup.appendChild( this.recordButton );
109119
buttonsGroup.appendChild( this.recordRefreshButton );
120+
buttonsGroup.appendChild( this.exportButton );
110121
buttonsGroup.appendChild( clearButton );
111122

112123
header.style.display = 'flex';
@@ -597,8 +608,12 @@ class Timeline extends Tab {
597608

598609
}
599610

600-
// Only record method name as requested, skipping detail arguments
601-
this.currentFrame.calls.push( { method: methodLabel } );
611+
const call = { method: methodLabel };
612+
const details = this.getCallDetail( prop, args );
613+
614+
if ( details ) call.details = details;
615+
616+
this.currentFrame.calls.push( call );
602617

603618
return originalFunc.apply( backend, args );
604619

@@ -647,6 +662,108 @@ class Timeline extends Tab {
647662

648663
}
649664

665+
exportData() {
666+
667+
if ( this.frames.length === 0 ) return;
668+
669+
const data = JSON.stringify( this.frames, null, '\t' );
670+
const blob = new Blob( [ data ], { type: 'application/json' } );
671+
const url = URL.createObjectURL( blob );
672+
const a = document.createElement( 'a' );
673+
a.href = url;
674+
a.download = 'threejs-timeline.json';
675+
a.click();
676+
URL.revokeObjectURL( url );
677+
678+
}
679+
680+
getCallDetail( method, args ) {
681+
682+
switch ( method ) {
683+
684+
case 'draw': {
685+
686+
const renderObject = args[ 0 ];
687+
if ( ! renderObject ) return null;
688+
689+
const details = {};
690+
if ( renderObject.object ) {
691+
692+
details.object = renderObject.object.name || renderObject.object.type;
693+
694+
if ( renderObject.object.count > 1 ) {
695+
696+
details.instance = renderObject.object.count;
697+
698+
}
699+
700+
}
701+
702+
if ( renderObject.material ) details.material = renderObject.material.name || renderObject.material.type;
703+
if ( renderObject.geometry ) details.geometry = renderObject.geometry.name || undefined;
704+
705+
if ( renderObject.camera ) details.camera = renderObject.camera.name || renderObject.camera.type;
706+
707+
return details;
708+
709+
}
710+
711+
case 'updateBinding': {
712+
713+
const binding = args[ 0 ];
714+
return binding?.name ? { binding: binding.name } : null;
715+
716+
}
717+
718+
case 'createProgram': {
719+
720+
const program = args[ 0 ];
721+
if ( ! program ) return null;
722+
return { stage: program.stage, name: program.name || undefined };
723+
724+
}
725+
726+
case 'createBindings': {
727+
728+
const bindGroup = args[ 0 ];
729+
return bindGroup?.name ? { group: bindGroup.name } : null;
730+
731+
}
732+
733+
case 'createTexture':
734+
case 'destroyTexture': {
735+
736+
const texture = args[ 0 ];
737+
return texture?.name ? { texture: texture.name } : null;
738+
739+
}
740+
741+
}
742+
743+
return null;
744+
745+
}
746+
747+
formatDetails( details ) {
748+
749+
const parts = [];
750+
751+
for ( const key in details ) {
752+
753+
if ( details[ key ] !== undefined ) {
754+
755+
parts.push( `<span style="opacity: 0.5">${key}:</span> <span style="color: var(--text-secondary); opacity: 1">${details[ key ]}</span>` );
756+
757+
}
758+
759+
}
760+
761+
if ( parts.length === 0 ) return '';
762+
763+
return `<span style="font-size: 11px; margin-left: 8px; color: var(--text-secondary); opacity: 1;">{ ${parts.join( '<span style="opacity: 0.5">, </span>' )} }</span>`;
764+
765+
}
766+
650767
renderSlider() {
651768

652769
if ( this.frames.length === 0 ) {
@@ -768,14 +885,15 @@ class Timeline extends Tab {
768885

769886
const call = frame.calls[ i ];
770887
const isStructural = call.method.startsWith( 'begin' ) || call.method.startsWith( 'finish' );
888+
const formatedDetails = call.details ? this.formatDetails( call.details ) : '';
771889

772-
if ( currentGroup && currentGroup.method === call.method && ! isStructural ) {
890+
if ( currentGroup && currentGroup.method === call.method && currentGroup.formatedDetails === formatedDetails && ! isStructural ) {
773891

774892
currentGroup.count ++;
775893

776894
} else {
777895

778-
currentGroup = { method: call.method, count: 1 };
896+
currentGroup = { method: call.method, count: 1, formatedDetails };
779897
groupedCalls.push( currentGroup );
780898

781899
}
@@ -836,7 +954,7 @@ class Timeline extends Tab {
836954

837955
// Title
838956
const title = document.createElement( 'span' );
839-
title.textContent = call.method + ( call.count > 1 ? ` ( ${call.count} )` : '' );
957+
title.innerHTML = call.method + ( call.formatedDetails ? call.formatedDetails : '' ) + ( call.count > 1 ? ` <span style="opacity: 0.5">( ${call.count} )</span>` : '' );
840958
block.appendChild( title );
841959

842960
block.addEventListener( 'click', ( e ) => {
@@ -862,14 +980,14 @@ class Timeline extends Tab {
862980

863981
} else if ( call.method.startsWith( 'finish' ) ) {
864982

865-
block.textContent = call.method + ( call.count > 1 ? ` ( ${call.count} )` : '' );
983+
block.innerHTML = call.method + ( call.formatedDetails ? call.formatedDetails : '' ) + ( call.count > 1 ? ` <span style="opacity: 0.5">( ${call.count} )</span>` : '' );
866984

867985
currentIndent = Math.max( 0, currentIndent - 1 );
868986
elementStack.pop();
869987

870988
} else {
871989

872-
block.textContent = call.method + ( call.count > 1 ? ` ( ${call.count} )` : '' );
990+
block.innerHTML = call.method + ( call.formatedDetails ? call.formatedDetails : '' ) + ( call.count > 1 ? ` <span style="opacity: 0.5">( ${call.count} )</span>` : '' );
873991

874992
}
875993

0 commit comments

Comments
 (0)