MCP 3D Printer Server

by DMontgomery40
Verified
import { warnOnce } from '../../../utils.js'; import TimestampQueryPool from '../../common/TimestampQueryPool.js'; /** * Manages a pool of WebGL timestamp queries for performance measurement. * Handles creation, execution, and resolution of timer queries using WebGL extensions. * @extends TimestampQueryPool */ class WebGLTimestampQueryPool extends TimestampQueryPool { /** * Creates a new WebGL timestamp query pool. * @param {WebGLRenderingContext|WebGL2RenderingContext} gl - The WebGL context. * @param {string} type - The type identifier for this query pool. * @param {number} [maxQueries=2048] - Maximum number of queries this pool can hold. */ constructor( gl, type, maxQueries = 2048 ) { super( maxQueries ); this.gl = gl; this.type = type; // Check for timer query extensions this.ext = gl.getExtension( 'EXT_disjoint_timer_query_webgl2' ) || gl.getExtension( 'EXT_disjoint_timer_query' ); if ( ! this.ext ) { console.warn( 'EXT_disjoint_timer_query not supported; timestamps will be disabled.' ); this.trackTimestamp = false; return; } // Create query objects this.queries = []; for ( let i = 0; i < this.maxQueries; i ++ ) { this.queries.push( gl.createQuery() ); } this.activeQuery = null; this.queryStates = new Map(); // Track state of each query: 'inactive', 'started', 'ended' } /** * Allocates a pair of queries for a given render context. * @param {Object} renderContext - The render context to allocate queries for. * @returns {?number} The base offset for the allocated queries, or null if allocation failed. */ allocateQueriesForContext( renderContext ) { if ( ! this.trackTimestamp ) return null; // Check if we have enough space for a new query pair if ( this.currentQueryIndex + 2 > this.maxQueries ) { warnOnce( `WebGPUTimestampQueryPool [${ this.type }]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${ this.type.toUpperCase() } ).` ); return null; } const baseOffset = this.currentQueryIndex; this.currentQueryIndex += 2; // Initialize query states this.queryStates.set( baseOffset, 'inactive' ); this.queryOffsets.set( renderContext.id, baseOffset ); return baseOffset; } /** * Begins a timestamp query for the specified render context. * @param {Object} renderContext - The render context to begin timing for. */ beginQuery( renderContext ) { if ( ! this.trackTimestamp || this.isDisposed ) { return; } const baseOffset = this.queryOffsets.get( renderContext.id ); if ( baseOffset == null ) { return; } // Don't start a new query if there's an active one if ( this.activeQuery !== null ) { return; } const query = this.queries[ baseOffset ]; if ( ! query ) { return; } try { // Only begin if query is inactive if ( this.queryStates.get( baseOffset ) === 'inactive' ) { this.gl.beginQuery( this.ext.TIME_ELAPSED_EXT, query ); this.activeQuery = baseOffset; this.queryStates.set( baseOffset, 'started' ); } } catch ( error ) { console.error( 'Error in beginQuery:', error ); this.activeQuery = null; this.queryStates.set( baseOffset, 'inactive' ); } } /** * Ends the active timestamp query for the specified render context. * @param {Object} renderContext - The render context to end timing for. * @param {string} renderContext.id - Unique identifier for the render context. */ endQuery( renderContext ) { if ( ! this.trackTimestamp || this.isDisposed ) { return; } const baseOffset = this.queryOffsets.get( renderContext.id ); if ( baseOffset == null ) { return; } // Only end if this is the active query if ( this.activeQuery !== baseOffset ) { return; } try { this.gl.endQuery( this.ext.TIME_ELAPSED_EXT ); this.queryStates.set( baseOffset, 'ended' ); this.activeQuery = null; } catch ( error ) { console.error( 'Error in endQuery:', error ); // Reset state on error this.queryStates.set( baseOffset, 'inactive' ); this.activeQuery = null; } } /** * Asynchronously resolves all completed queries and returns the total duration. * @returns {Promise<number>} The total duration in milliseconds, or the last valid value if resolution fails. */ async resolveQueriesAsync() { if ( ! this.trackTimestamp || this.pendingResolve ) { return this.lastValue; } this.pendingResolve = true; try { // Wait for all ended queries to complete const resolvePromises = []; for ( const [ baseOffset, state ] of this.queryStates ) { if ( state === 'ended' ) { const query = this.queries[ baseOffset ]; resolvePromises.push( this.resolveQuery( query ) ); } } if ( resolvePromises.length === 0 ) { return this.lastValue; } const results = await Promise.all( resolvePromises ); const totalDuration = results.reduce( ( acc, val ) => acc + val, 0 ); // Store the last valid result this.lastValue = totalDuration; // Reset states this.currentQueryIndex = 0; this.queryOffsets.clear(); this.queryStates.clear(); this.activeQuery = null; return totalDuration; } catch ( error ) { console.error( 'Error resolving queries:', error ); return this.lastValue; } finally { this.pendingResolve = false; } } /** * Resolves a single query, checking for completion and disjoint operation. * @private * @param {WebGLQuery} query - The query object to resolve. * @returns {Promise<number>} The elapsed time in milliseconds. */ async resolveQuery( query ) { return new Promise( ( resolve ) => { if ( this.isDisposed ) { resolve( this.lastValue ); return; } let timeoutId; let isResolved = false; const cleanup = () => { if ( timeoutId ) { clearTimeout( timeoutId ); timeoutId = null; } }; const finalizeResolution = ( value ) => { if ( ! isResolved ) { isResolved = true; cleanup(); resolve( value ); } }; const checkQuery = () => { if ( this.isDisposed ) { finalizeResolution( this.lastValue ); return; } try { // Check if the GPU timer was disjoint (i.e., timing was unreliable) const disjoint = this.gl.getParameter( this.ext.GPU_DISJOINT_EXT ); if ( disjoint ) { finalizeResolution( this.lastValue ); return; } const available = this.gl.getQueryParameter( query, this.gl.QUERY_RESULT_AVAILABLE ); if ( ! available ) { timeoutId = setTimeout( checkQuery, 1 ); return; } const elapsed = this.gl.getQueryParameter( query, this.gl.QUERY_RESULT ); resolve( Number( elapsed ) / 1e6 ); // Convert nanoseconds to milliseconds } catch ( error ) { console.error( 'Error checking query:', error ); resolve( this.lastValue ); } }; checkQuery(); } ); } /** * Releases all resources held by this query pool. * This includes deleting all query objects and clearing internal state. */ dispose() { if ( this.isDisposed ) { return; } this.isDisposed = true; if ( ! this.trackTimestamp ) return; for ( const query of this.queries ) { this.gl.deleteQuery( query ); } this.queries = []; this.queryStates.clear(); this.queryOffsets.clear(); this.lastValue = 0; this.activeQuery = null; } } export default WebGLTimestampQueryPool;