Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-ng/workers/geometry/src/lib/geometry-worker.ts is written in an unsupported language. File is not indexed.

0001 /**
0002  * The ROOT geometry worker: `runGeometryWorker(self)` in a worker entry
0003  * module of the application (see `withWorkers()`).
0004  *
0005  * This worker performs heavy geometry loading operations off the main thread:
0006  * - Fetches ROOT files via jsroot
0007  * - Parses TGeoManager
0008  * - Prunes and processes geometry
0009  * - Builds Three.js geometry
0010  * - Serializes the result for transfer to main thread
0011  *
0012  * Supports cancellation via requestId tracking.
0013  */
0014 
0015 // jsroot declares its subpath modules (jsroot/io, jsroot/geom) in the types
0016 // of its main entry; the reference loads them without importing the main
0017 // entry, whose module graph is several times the size of the io entry.
0018 /// <reference types="jsroot" />
0019 import {openFile} from 'jsroot/io';
0020 import {build} from 'jsroot/geom';
0021 import {
0022   analyzeGeoNodes,
0023   findGeoManager,
0024 } from '@dexvis/root-geo-tree-editor';
0025 import {applyDetectorEditRules, pruneTopLevelDetectors} from './root-geometry.processor';
0026 import type {RootGeometryLoadRules} from '@dexvis/firebird-ng/api';
0027 import type {WorkerScope} from '@dexvis/firebird-ng/api';
0028 
0029 // Message types for communication with main thread
0030 export interface GeometryLoadRequest {
0031   type: 'load';
0032   requestId: string;
0033   /**
0034    * Where to read the geometry from: a URL, or a file the user picked or
0035    * dropped. A File is structured-cloneable, so it crosses to the worker
0036    * without being read - jsroot slices it the same way it byte-ranges a URL.
0037    */
0038   url: string | File;
0039   /**
0040    * What to prune and edit before the build, as plain data: this worker is
0041    * a prebuilt module and cannot import the packs that define the rules.
0042    */
0043   rules: RootGeometryLoadRules;
0044 }
0045 
0046 export interface GeometryCancelRequest {
0047   type: 'cancel';
0048   requestId: string;
0049 }
0050 
0051 export type WorkerRequest = GeometryLoadRequest | GeometryCancelRequest;
0052 
0053 export interface GeometryLoadSuccess {
0054   type: 'success';
0055   requestId: string;
0056   geometryJson: any;           // Serialized Object3D via toJSON()
0057   subdetectorInfos: SubdetectorInfo[];  // Metadata about subdetectors
0058 }
0059 
0060 export interface SubdetectorInfo {
0061   name: string;
0062   originalName: string;
0063 }
0064 
0065 export interface GeometryLoadError {
0066   type: 'error';
0067   requestId: string;
0068   error: string;
0069 }
0070 
0071 export interface GeometryLoadCancelled {
0072   type: 'cancelled';
0073   requestId: string;
0074 }
0075 
0076 export interface GeometryLoadProgress {
0077   type: 'progress';
0078   requestId: string;
0079   stage: string;
0080   progress: number;  // 0-100
0081 }
0082 
0083 export type WorkerResponse = GeometryLoadSuccess | GeometryLoadError | GeometryLoadCancelled | GeometryLoadProgress;
0084 
0085 // Track active requests for cancellation
0086 let activeRequestId: string | null = null;
0087 let cancellationRequested = false;
0088 let isProcessing = false;  // Prevents concurrent processing
0089 
0090 function stripIdFromName(name: string): string {
0091   return name.replace(/_\d+$/, '');
0092 }
0093 
0094 function sendProgress(post: (message: WorkerResponse) => void, requestId: string, stage: string, progress: number) {
0095   const response: GeometryLoadProgress = {
0096     type: 'progress',
0097     requestId,
0098     stage,
0099     progress
0100   };
0101   post(response);
0102 }
0103 
0104 async function loadGeometry(request: GeometryLoadRequest, post: (message: WorkerResponse) => void): Promise<void> {
0105   const {requestId, url, rules} = request;
0106 
0107   // If already processing, mark for cancellation and wait for it to finish
0108   if (isProcessing) {
0109     console.log(`[GeometryWorker]: Already processing ${activeRequestId}, marking for cancellation`);
0110     cancellationRequested = true;
0111 
0112     // Wait for current processing to finish before starting new one
0113     while (isProcessing) {
0114       await new Promise(resolve => setTimeout(resolve, 50));
0115     }
0116   }
0117 
0118   isProcessing = true;
0119   activeRequestId = requestId;
0120   cancellationRequested = false;
0121 
0122   try {
0123     // Check for cancellation at key points
0124     const checkCancellation = () => {
0125       if (cancellationRequested) {
0126         throw new Error('CANCELLED');
0127       }
0128     };
0129 
0130     sendProgress(post, requestId, 'Opening ROOT file', 10);
0131 
0132     console.time('[GeometryWorker]: Open root file');
0133     const file = await openFile(url);
0134     console.timeEnd('[GeometryWorker]: Open root file');
0135 
0136     checkCancellation();
0137     sendProgress(post, requestId, 'Reading geometry', 20);
0138 
0139     console.time('[GeometryWorker]: Reading geometry from file');
0140     const rootGeometry = await findGeoManager(file);
0141     console.timeEnd('[GeometryWorker]: Reading geometry from file');
0142 
0143     if (!rootGeometry) {
0144       throw new Error('No TGeoManager found in ROOT file');
0145     }
0146 
0147     checkCancellation();
0148     sendProgress(post, requestId, 'Pruning geometry', 30);
0149 
0150     // Remove the top-level detectors of the selected cut list
0151     if (rules.cutList.length > 0) {
0152       const result = pruneTopLevelDetectors(rootGeometry, rules.cutList);
0153       console.log(`[GeometryWorker]: Pruned geometry. Nodes left: ${result.nodes.length}, Removed: ${result.removedNodes.length}`);
0154     }
0155 
0156     checkCancellation();
0157     sendProgress(post, requestId, 'Pre-processing geometry', 40);
0158 
0159     // Apply the selected detector edit rules
0160     if (rules.editRules.length > 0) {
0161       console.time('[GeometryWorker]: Root geometry pre-processing');
0162       applyDetectorEditRules(rootGeometry, rules.editRules);
0163       console.timeEnd('[GeometryWorker]: Root geometry pre-processing');
0164     }
0165 
0166     checkCancellation();
0167     sendProgress(post, requestId, 'Analyzing geometry', 50);
0168 
0169     console.log("[GeometryWorker]: Number of tree elements analysis (after root geometry prune):");
0170     analyzeGeoNodes(rootGeometry, 1);
0171 
0172     checkCancellation();
0173     sendProgress(post, requestId, 'Building 3D geometry', 60);
0174 
0175     // Build Three.js geometry - this is the most expensive operation
0176     console.time('[GeometryWorker]: Build geometry');
0177     const geometry = build(rootGeometry, {
0178       numfaces: 5000000000,
0179       numnodes: 5000000000,
0180       instancing: -1,
0181       dflt_colors: false,
0182       vislevel: 200,
0183       doubleside: true,
0184       transparency: true
0185     });
0186     console.timeEnd('[GeometryWorker]: Build geometry');
0187 
0188     checkCancellation();
0189 
0190     // Validate the geometry
0191     if (!geometry) {
0192       throw new Error("Geometry is null or undefined after build");
0193     }
0194 
0195     if (!geometry.children.length) {
0196       throw new Error("Geometry is converted but empty. Expected 'world_volume' but got nothing");
0197     }
0198 
0199     if (!geometry.children[0].children.length) {
0200       throw new Error("Geometry is converted but empty. Expected array of top level nodes but got nothing");
0201     }
0202 
0203     sendProgress(post, requestId, 'Extracting subdetector info', 80);
0204 
0205     // Extract subdetector information
0206     const topDetectorNodes = geometry.children[0].children;
0207 
0208     const subdetectorInfos: SubdetectorInfo[] = topDetectorNodes.map((topNode: any) => ({
0209       name: stripIdFromName(topNode.name),
0210       originalName: topNode.name,
0211     }));
0212 
0213     checkCancellation();
0214     sendProgress(post, requestId, 'Serializing geometry', 90);
0215 
0216     // Serialize the geometry to JSON for transfer
0217     console.time('[GeometryWorker]: Serialize geometry to JSON');
0218     const geometryJson = geometry.toJSON();
0219     console.timeEnd('[GeometryWorker]: Serialize geometry to JSON');
0220 
0221     sendProgress(post, requestId, 'Complete', 100);
0222 
0223     const response: GeometryLoadSuccess = {
0224       type: 'success',
0225       requestId,
0226       geometryJson,
0227       subdetectorInfos
0228     };
0229 
0230     post(response);
0231 
0232   } catch (error: any) {
0233     if (error.message === 'CANCELLED') {
0234       const response: GeometryLoadCancelled = {
0235         type: 'cancelled',
0236         requestId
0237       };
0238       post(response);
0239     } else {
0240       console.error('[GeometryWorker]: Error loading geometry:', error);
0241       const response: GeometryLoadError = {
0242         type: 'error',
0243         requestId,
0244         error: error.message || String(error)
0245       };
0246       post(response);
0247     }
0248   } finally {
0249     activeRequestId = null;
0250     cancellationRequested = false;
0251     isProcessing = false;
0252   }
0253 }
0254 
0255 /**
0256  * Runs the geometry worker in `scope`, the global scope of a worker entry
0257  * module (`self`): answers the load and cancel requests of GeometryService.
0258  *
0259  * ```ts
0260  * // geometry.worker.ts, the module withWorkers({ geometry }) starts
0261  * import { runGeometryWorker } from '@dexvis/firebird-ng/workers/geometry';
0262  * runGeometryWorker(self);
0263  * ```
0264  */
0265 export function runGeometryWorker(scope: WorkerScope): void {
0266   const post = (message: WorkerResponse) => scope.postMessage(message);
0267   scope.addEventListener('message', ({data}: MessageEvent<WorkerRequest>) => {
0268     if (data.type === 'load') {
0269       void loadGeometry(data, post);
0270     } else if (data.type === 'cancel') {
0271       if (activeRequestId === data.requestId) {
0272         console.log(`[GeometryWorker]: Cancellation requested for ${data.requestId}`);
0273         cancellationRequested = true;
0274       }
0275     }
0276   });
0277 }