Back to home page

EIC code displayed by LXR

 
 

    


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

0001 /// <reference lib="webworker" />
0002 
0003 /**
0004  * Web Worker for loading and processing ROOT geometry files.
0005  *
0006  * This worker performs heavy geometry loading operations off the main thread:
0007  * - Fetches ROOT files via jsroot
0008  * - Parses TGeoManager
0009  * - Prunes and processes geometry
0010  * - Builds Three.js geometry
0011  * - Serializes the result for transfer to main thread
0012  *
0013  * Supports cancellation via requestId tracking.
0014  */
0015 
0016 import {openFile} from 'jsroot';
0017 import {build} from 'jsroot/geom';
0018 import {
0019   analyzeGeoNodes,
0020   findGeoManager,
0021   getGeoNodesByLevel
0022 } from '../../lib-root-geometry/root-geo-navigation';
0023 import {pruneTopLevelDetectors, RootGeometryProcessor} from '../data-pipelines/root-geometry.processor';
0024 
0025 // Message types for communication with main thread
0026 export interface GeometryLoadRequest {
0027   type: 'load';
0028   requestId: string;
0029   url: string;
0030   options: GeometryLoadOptions;
0031 }
0032 
0033 export interface GeometryLoadOptions {
0034   cutListName: string;       // "central" or other - controls pruning
0035   rootFilterName: string;    // "default" or other - controls pre-processing
0036 }
0037 
0038 export interface GeometryCancelRequest {
0039   type: 'cancel';
0040   requestId: string;
0041 }
0042 
0043 export type WorkerRequest = GeometryLoadRequest | GeometryCancelRequest;
0044 
0045 export interface GeometryLoadSuccess {
0046   type: 'success';
0047   requestId: string;
0048   geometryJson: any;           // Serialized Object3D via toJSON()
0049   subdetectorInfos: SubdetectorInfo[];  // Metadata about subdetectors
0050 }
0051 
0052 export interface SubdetectorInfo {
0053   name: string;
0054   originalName: string;
0055   groupName: string;
0056 }
0057 
0058 export interface GeometryLoadError {
0059   type: 'error';
0060   requestId: string;
0061   error: string;
0062 }
0063 
0064 export interface GeometryLoadCancelled {
0065   type: 'cancelled';
0066   requestId: string;
0067 }
0068 
0069 export interface GeometryLoadProgress {
0070   type: 'progress';
0071   requestId: string;
0072   stage: string;
0073   progress: number;  // 0-100
0074 }
0075 
0076 export type WorkerResponse = GeometryLoadSuccess | GeometryLoadError | GeometryLoadCancelled | GeometryLoadProgress;
0077 
0078 // Detectors to remove when cutListName is "central"
0079 const removeDetectorNames: string[] = [
0080   "Lumi",
0081   "B1",
0082   "B2",
0083   "Q2",
0084   "ForwardOffM",
0085   "Forward",
0086   "Backward",
0087   "Vacuum",
0088   "SweeperMag",
0089   "AnalyzerMag",
0090   "ZDC",
0091   "HcalFarForward",
0092   "InnerTrackingSupport"
0093 ];
0094 
0095 // Map detector names to groups (same as in geometry.service.ts)
0096 const groupsByDetName = new Map<string, string>([
0097   ["SolenoidBarrel_assembly_0", "Magnets"],
0098   ["SolenoidEndcapP_1", "Magnets"],
0099   ["SolenoidEndcapN_2", "Magnets"],
0100   ["VertexBarrelSubAssembly_3", "Tracking"],
0101   ["InnerSiTrackerSubAssembly_4", "Tracking"],
0102   ["MiddleSiTrackerSubAssembly_5", "Tracking"],
0103   ["OuterSiTrackerSubAssembly_6", "Tracking"],
0104   ["EndcapMPGDSubAssembly_7", "Tracking"],
0105   ["InnerMPGDBarrelSubAssembly_8", "Tracking"],
0106   ["EndcapTOFSubAssembly_9", "PID"],
0107   ["BarrelTOFSubAssembly_10", "PID"],
0108   ["OuterBarrelMPGDSubAssembly_11", "Tracking"],
0109   ["B0TrackerSubAssembly_12", "Tracking"],
0110   ["InnerTrackerSupport_assembly_13", "Beam pipe and support"],
0111   ["DIRC_14", "PID"],
0112   ["RICHEndcapN_Vol_15", "PID"],
0113   ["DRICH_16", "PID"],
0114   ["EcalEndcapP_17", "Calorimeters"],
0115   ["EcalEndcapPInsert_18", "Calorimeters"],
0116   ["EcalBarrelImaging_19", "Calorimeters"],
0117   ["EcalBarrelScFi_20", "Calorimeters"],
0118   ["EcalEndcapN_21", "Calorimeters"],
0119   ["LFHCAL_env_22", "Calorimeters"],
0120   ["HcalEndcapPInsert_23", "Calorimeters"],
0121   ["HcalBarrel_24", "Calorimeters"],
0122   ["FluxBarrel_env_25", "Beam pipe and support"],
0123   ["FluxEndcapP_26", "Beam pipe and support"],
0124   ["HcalEndcapN_27", "Calorimeters"],
0125   ["FluxEndcapN_28", "Beam pipe and support"],
0126   ["BeamPipe_assembly_29", "Beam pipe and support"],
0127   ["B0PF_BeamlineMagnet_assembly_30", "Magnets"],
0128   ["B0APF_BeamlineMagnet_assembly_31", "Magnets"],
0129   ["Q1APF_BeamlineMagnet_assembly_32", "Magnets"],
0130   ["Q1BPF_BeamlineMagnet_assembly_33", "Magnets"],
0131   ["BeamPipeB0_assembly_38", "Beam pipe and support"],
0132   ["Pipe_cen_to_pos_assembly_39", "Beam pipe and support"],
0133   ["Q0EF_assembly_40", "Magnets"],
0134   ["Q0EF_vac_41", "Magnets"],
0135   ["Q1EF_assembly_42", "Magnets"],
0136   ["Q1EF_vac_43", "Magnets"],
0137   ["B0ECal_44", "Calorimeters"],
0138   ["Pipe_Q1eR_to_B2BeR_assembly_54", "Beam pipe and support"],
0139   ["Magnet_Q1eR_assembly_55", "Magnets"],
0140   ["Magnet_Q2eR_assembly_56", "Magnets"],
0141   ["Magnet_B2AeR_assembly_57", "Magnets"],
0142   ["Magnet_B2BeR_assembly_58", "Magnets"],
0143   ["Magnets_Q3eR_assembly_59", "Magnets"],
0144 ]);
0145 
0146 // Track active requests for cancellation
0147 let activeRequestId: string | null = null;
0148 let cancellationRequested = false;
0149 let isProcessing = false;  // Prevents concurrent processing
0150 
0151 const rootGeometryProcessor = new RootGeometryProcessor();
0152 
0153 function stripIdFromName(name: string): string {
0154   return name.replace(/_\d+$/, '');
0155 }
0156 
0157 function sendProgress(requestId: string, stage: string, progress: number) {
0158   const response: GeometryLoadProgress = {
0159     type: 'progress',
0160     requestId,
0161     stage,
0162     progress
0163   };
0164   postMessage(response);
0165 }
0166 
0167 async function loadGeometry(request: GeometryLoadRequest): Promise<void> {
0168   const {requestId, url, options} = request;
0169 
0170   // If already processing, mark for cancellation and wait for it to finish
0171   if (isProcessing) {
0172     console.log(`[GeometryWorker]: Already processing ${activeRequestId}, marking for cancellation`);
0173     cancellationRequested = true;
0174 
0175     // Wait for current processing to finish before starting new one
0176     while (isProcessing) {
0177       await new Promise(resolve => setTimeout(resolve, 50));
0178     }
0179   }
0180 
0181   isProcessing = true;
0182   activeRequestId = requestId;
0183   cancellationRequested = false;
0184 
0185   try {
0186     // Check for cancellation at key points
0187     const checkCancellation = () => {
0188       if (cancellationRequested) {
0189         throw new Error('CANCELLED');
0190       }
0191     };
0192 
0193     sendProgress(requestId, 'Opening ROOT file', 10);
0194 
0195     console.time('[GeometryWorker]: Open root file');
0196     const file = await openFile(url);
0197     console.timeEnd('[GeometryWorker]: Open root file');
0198 
0199     checkCancellation();
0200     sendProgress(requestId, 'Reading geometry', 20);
0201 
0202     console.time('[GeometryWorker]: Reading geometry from file');
0203     const rootGeometry = await findGeoManager(file);
0204     console.timeEnd('[GeometryWorker]: Reading geometry from file');
0205 
0206     if (!rootGeometry) {
0207       throw new Error('No TGeoManager found in ROOT file');
0208     }
0209 
0210     checkCancellation();
0211     sendProgress(requestId, 'Pruning geometry', 30);
0212 
0213     // Prune top-level detectors if configured
0214     if (options.cutListName === "central") {
0215       const result = pruneTopLevelDetectors(rootGeometry, removeDetectorNames);
0216       console.log(`[GeometryWorker]: Pruned geometry. Nodes left: ${result.nodes.length}, Removed: ${result.removedNodes.length}`);
0217     }
0218 
0219     checkCancellation();
0220     sendProgress(requestId, 'Pre-processing geometry', 40);
0221 
0222     // Apply ROOT geometry processing
0223     if (options.rootFilterName === "default") {
0224       console.time('[GeometryWorker]: Root geometry pre-processing');
0225       rootGeometryProcessor.process(rootGeometry);
0226       console.timeEnd('[GeometryWorker]: Root geometry pre-processing');
0227     }
0228 
0229     checkCancellation();
0230     sendProgress(requestId, 'Analyzing geometry', 50);
0231 
0232     console.log("[GeometryWorker]: Number of tree elements analysis (after root geometry prune):");
0233     analyzeGeoNodes(rootGeometry, 1);
0234 
0235     checkCancellation();
0236     sendProgress(requestId, 'Building 3D geometry', 60);
0237 
0238     // Build Three.js geometry - this is the most expensive operation
0239     console.time('[GeometryWorker]: Build geometry');
0240     const geometry = build(rootGeometry, {
0241       numfaces: 5000000000,
0242       numnodes: 5000000000,
0243       instancing: -1,
0244       dflt_colors: false,
0245       vislevel: 200,
0246       doubleside: true,
0247       transparency: true
0248     });
0249     console.timeEnd('[GeometryWorker]: Build geometry');
0250 
0251     checkCancellation();
0252 
0253     // Validate the geometry
0254     if (!geometry) {
0255       throw new Error("Geometry is null or undefined after build");
0256     }
0257 
0258     if (!geometry.children.length) {
0259       throw new Error("Geometry is converted but empty. Expected 'world_volume' but got nothing");
0260     }
0261 
0262     if (!geometry.children[0].children.length) {
0263       throw new Error("Geometry is converted but empty. Expected array of top level nodes but got nothing");
0264     }
0265 
0266     sendProgress(requestId, 'Extracting subdetector info', 80);
0267 
0268     // Extract subdetector information
0269     const topDetectorNodes = geometry.children[0].children;
0270     const rootGeoNodes = getGeoNodesByLevel(rootGeometry, 1).map((obj: any) => obj.geoNode);
0271 
0272     const subdetectorInfos: SubdetectorInfo[] = [];
0273     for (const topNode of topDetectorNodes) {
0274       const originalName = topNode.name;
0275       const name = stripIdFromName(originalName);
0276       const groupName = groupsByDetName.get(originalName) || "";
0277 
0278       subdetectorInfos.push({
0279         name,
0280         originalName,
0281         groupName
0282       });
0283     }
0284 
0285     checkCancellation();
0286     sendProgress(requestId, 'Serializing geometry', 90);
0287 
0288     // Serialize the geometry to JSON for transfer
0289     console.time('[GeometryWorker]: Serialize geometry to JSON');
0290     const geometryJson = geometry.toJSON();
0291     console.timeEnd('[GeometryWorker]: Serialize geometry to JSON');
0292 
0293     sendProgress(requestId, 'Complete', 100);
0294 
0295     const response: GeometryLoadSuccess = {
0296       type: 'success',
0297       requestId,
0298       geometryJson,
0299       subdetectorInfos
0300     };
0301 
0302     postMessage(response);
0303 
0304   } catch (error: any) {
0305     if (error.message === 'CANCELLED') {
0306       const response: GeometryLoadCancelled = {
0307         type: 'cancelled',
0308         requestId
0309       };
0310       postMessage(response);
0311     } else {
0312       console.error('[GeometryWorker]: Error loading geometry:', error);
0313       const response: GeometryLoadError = {
0314         type: 'error',
0315         requestId,
0316         error: error.message || String(error)
0317       };
0318       postMessage(response);
0319     }
0320   } finally {
0321     activeRequestId = null;
0322     cancellationRequested = false;
0323     isProcessing = false;
0324   }
0325 }
0326 
0327 // Handle messages from main thread
0328 addEventListener('message', ({data}: MessageEvent<WorkerRequest>) => {
0329   if (data.type === 'load') {
0330     loadGeometry(data);
0331   } else if (data.type === 'cancel') {
0332     if (activeRequestId === data.requestId) {
0333       console.log(`[GeometryWorker]: Cancellation requested for ${data.requestId}`);
0334       cancellationRequested = true;
0335     }
0336   }
0337 });