Warning, /firebird/packages/firebird-core/src/model/event-piece.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * One reference from an entity into another piece: the declaring column,
0003 * the target piece name (from the piece `refs` declaration) and the target
0004 * entity index (entity id ≡ array index; -1 never appears here — refs that
0005 * hold -1 are "no reference" and are not reported).
0006 */
0007 export interface EntityRefLink {
0008 column: string;
0009 targetPiece: string;
0010 targetIndex: number;
0011 }
0012
0013 /**
0014 * The EventPiece class is an abstract base class for all event components.
0015 * A "piece" is one named block of typed entity data inside an event —
0016 * a hits collection, a set of trajectories, a custom extension type.
0017 */
0018 export abstract class EventPiece {
0019
0020 /**
0021 * The name of the piece, unique within its event. Shown in UI menus and
0022 * targeted by `refs` declarations of other pieces.
0023 */
0024 name: string;
0025
0026 /**
0027 * The type of the piece, used to identify the piece class
0028 * and facilitate deserialization and factory lookup.
0029 */
0030 type: string;
0031
0032 /**
0033 * An optional value indicating the origin of the piece,
0034 * such as the original EDM4EIC/EDM4HEP/C++ data type from which it was derived.
0035 */
0036 origin?: any;
0037
0038 /**
0039 * The constructor is protected to prevent direct instantiation of the abstract class.
0040 * Only derived classes can call this constructor when they implement their own constructors.
0041 *
0042 * @param name - The name of the piece.
0043 * @param type - The type of the piece.
0044 * @param origin - Optional origin of the piece.
0045 */
0046 protected constructor(name: string, type: string, origin?: any) {
0047 this.name = name;
0048 this.type = type;
0049 this.origin = origin;
0050 }
0051
0052 /**
0053 * Serializes the piece into a JSON-compatible object following the
0054 * Data Exchange format (a DEX v1 piece object).
0055 */
0056 abstract toDexObject(): any;
0057
0058 // -------------------------------------------------------------------------
0059 // Entity introspection — powers model-oriented UI (the model tree panel)
0060 // without UI code knowing concrete piece classes. Entity id ≡ array index.
0061 // -------------------------------------------------------------------------
0062
0063 /** Number of entities in this piece. Pieces with a `count` field report it. */
0064 get entityCount(): number {
0065 const count = (this as { count?: unknown }).count;
0066 return typeof count === 'number' ? count : 0;
0067 }
0068
0069 /**
0070 * A short human-readable label for one entity. The default is the index;
0071 * pieces with meaningful columns (pdg, time, energy) override this.
0072 */
0073 entityLabel(entityIndex: number): string {
0074 return `#${entityIndex}`;
0075 }
0076
0077 /**
0078 * The outgoing references of one entity, resolved from the piece's `refs`
0079 * declaration: which column points at which entity of which piece.
0080 * Values of -1 mean "no reference" and are not reported.
0081 */
0082 entityRefs(entityIndex: number): EntityRefLink[] {
0083 return [];
0084 }
0085
0086 /** min and max time of the EventPiece
0087 * null means the piece doesn't need time and shows as is
0088 *
0089 * @returns [min, max] range of times or null if it is not available for the piece
0090 */
0091 abstract get timeRange(): [number, number] | null;
0092 }
0093
0094 /**
0095 * The EventPieceFactory interface defines the structure for factory classes
0096 * that are responsible for creating instances of EventPiece subclasses.
0097 */
0098 export interface EventPieceFactory {
0099
0100 /**
0101 * The type of the piece that this factory creates.
0102 * This should match the `type` property of the pieces it creates.
0103 */
0104 type: string;
0105
0106 /**
0107 * Method to create an instance of an EventPiece subclass from a deserialized
0108 * DEX v1 piece object (typically parsed from JSON).
0109 *
0110 * @param obj - The deserialized piece object.
0111 * @returns An instance of an EventPiece subclass.
0112 */
0113 fromDexObject(obj: any): EventPiece;
0114 }
0115
0116 /**
0117 * The pieceRegistry is a mapping from piece type strings to their corresponding factories.
0118 * It is used to look up the appropriate factory when deserializing pieces from JSON data.
0119 * This registry enables the system to support multiple piece types dynamically.
0120 */
0121 const pieceRegistry: { [type: string]: EventPieceFactory } = {};
0122
0123 /**
0124 * Registers a new piece factory in the registry.
0125 * This allows the factory to be used during deserialization to create instances
0126 * of the piece it represents.
0127 *
0128 * @param factory - The factory to register.
0129 */
0130 export function registerEventPieceFactory(factory: EventPieceFactory): void {
0131 pieceRegistry[factory.type] = factory;
0132 }
0133
0134 /**
0135 * Retrieves a piece factory from the registry based on the piece type.
0136 *
0137 * @param type - The type of the piece.
0138 * @returns The corresponding EventPieceFactory, or undefined if not found.
0139 */
0140 export function getEventPieceFactory(type: string): EventPieceFactory | undefined {
0141 return pieceRegistry[type];
0142 }
0143
0144 /**
0145 * Resets the piece registry.
0146 * This function is intended for internal use during testing.
0147 *
0148 * @internal
0149 */
0150 export function _resetEventPieceRegistry(): void {
0151 for (const key in pieceRegistry) {
0152 delete pieceRegistry[key];
0153 }
0154 }
0155
0156 // ---------------------------------------------------------------------------
0157 // Column reading helpers for piece factories.
0158 //
0159 // DEX v1 stores entity data as parallel arrays: a scalar column holds `count`
0160 // values, a fixed-width vector column (like an xyz position) holds
0161 // `stride * count` values flattened. Entity id IS the array index — malformed
0162 // files fail loudly here instead of producing silently shifted data (v1 files
0163 // are cheap to regenerate).
0164 // ---------------------------------------------------------------------------
0165
0166 /** Reads the piece `count` field; throws with the piece name on bad data. */
0167 export function readPieceCount(obj: any): number {
0168 const count = obj?.['count'];
0169 if (typeof count !== 'number' || !Number.isInteger(count) || count < 0) {
0170 throw new Error(`DEX piece '${obj?.['name']}': "count" must be a non-negative integer, got ${count}`);
0171 }
0172 return count;
0173 }
0174
0175 /**
0176 * Reads a required numeric column into a Float32Array (bulk copy, no
0177 * per-entity objects). `stride` is values per entity (1 = scalar, 3 = xyz).
0178 */
0179 export function readNumberColumn(obj: any, columnName: string, count: number, stride: number = 1): Float32Array {
0180 const column = readOptionalNumberColumn(obj, columnName, count, stride);
0181 if (column === null) {
0182 throw new Error(`DEX piece '${obj?.['name']}': required column "${columnName}" is missing`);
0183 }
0184 return column;
0185 }
0186
0187 /** Same as readNumberColumn but returns null when the writer omitted the column. */
0188 export function readOptionalNumberColumn(obj: any, columnName: string, count: number, stride: number = 1): Float32Array | null {
0189 const column = obj?.['columns']?.[columnName];
0190 if (column === undefined || column === null) {
0191 return null;
0192 }
0193 if (!Array.isArray(column)) {
0194 throw new Error(`DEX piece '${obj?.['name']}': column "${columnName}" is not an array`);
0195 }
0196 if (column.length !== stride * count) {
0197 throw new Error(`DEX piece '${obj?.['name']}': column "${columnName}" has ${column.length} values, ` +
0198 `expected ${stride}*count=${stride * count} (entity id must equal array index)`);
0199 }
0200 return Float32Array.from(column);
0201 }