Warning, /firebird/packages/firebird-core/src/painters/data-model-painter.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * This class is responsible in rendering Event or Frame data.
0003 * It first takes event pieces and manipulates three.js Scene
0004 * Then responsible for correct rendering at a given time
0005 */
0006
0007 import { Event } from "../model/event";
0008 import { EventPiece } from "../model/event-piece";
0009 import { Object3D, Group } from "three";
0010 import {
0011 EventPiecePainter,
0012 PainterConfigView,
0013 PiecePainterConstructor,
0014 assertPainterKnobKeys,
0015 } from "./event-piece-painter";
0016
0017 export enum DisplayMode
0018 {
0019 Timed = "timed",
0020 Timeless = "timeless"
0021 }
0022
0023 /**
0024 * The painter registry starts EMPTY. Register painters explicitly:
0025 * - Workers/scripts (no DI): call `registerDefaultPainters(painter)` from
0026 * ./default-painters, then `registerPainter()` for custom types.
0027 * - The Angular app contributes painters through the `PAINTERS` DI token
0028 * (see firebird-ng `provideFirebird()` / `withPainter()`).
0029 *
0030 * Several painters may register for one piece type. Which one paints a piece
0031 * is decided per piece by `painterSelector` (the app binds it to the config
0032 * key `painters.byPiece.<pieceName>`); without a selector the first
0033 * registered painter wins.
0034 */
0035 export class DataModelPainter {
0036 private threeParentNode: Object3D | null = null;
0037 private entry: Event | null = null;
0038 private painters: EventPiecePainter[] = [];
0039
0040 /** Registered painter classes per piece type, in registration order. */
0041 piecePainterRegistry: { [type: string]: PiecePainterConstructor[] } = {};
0042
0043 /**
0044 * Optional hook choosing which registered painter paints a piece.
0045 * Return undefined to fall back to the first registered painter.
0046 * The Angular layer binds this to the painter-selection config keys;
0047 * workers/scripts may leave it unset.
0048 */
0049 painterSelector:
0050 | ((piece: EventPiece, candidates: readonly PiecePainterConstructor[]) => PiecePainterConstructor | undefined)
0051 | null = null;
0052
0053 /**
0054 * Optional hook supplying the resolved config view for a painter instance.
0055 * The Angular layer binds this to ConfigService-declared keys; without it
0056 * painters run on their declared defaults.
0057 */
0058 configViewProvider:
0059 | ((piece: EventPiece, painterClass: PiecePainterConstructor) => PainterConfigView | undefined)
0060 | null = null;
0061
0062 public setThreeSceneParent(parentNode: Object3D) {
0063 this.threeParentNode = parentNode;
0064 }
0065
0066 public cleanupCurrentEntry() {
0067 for (let painter of this.painters) {
0068 painter.dispose();
0069 }
0070 this.painters = [];
0071 }
0072
0073 public getEntry(): Event|null {
0074 return this.entry;
0075 }
0076
0077 /** The painter drawing the piece with this name in the current entry, or null. */
0078 public painterFor(pieceName: string): EventPiecePainter | null {
0079 return this.painters.find(painter => painter.pieceName === pieceName) ?? null;
0080 }
0081
0082 /** The painters of the current entry (one per painted piece). */
0083 public getPainters(): readonly EventPiecePainter[] {
0084 return this.painters;
0085 }
0086
0087 /** The painter classes registered for a piece type, in registration order. */
0088 public paintersForType(pieceType: string): readonly PiecePainterConstructor[] {
0089 return this.piecePainterRegistry[pieceType] ?? [];
0090 }
0091
0092 public setEntry(entry: Event): void {
0093 this.cleanupCurrentEntry();
0094 this.entry = entry;
0095
0096 if (!this.threeParentNode) {
0097 throw new Error('Three.js parent node is not set.');
0098 }
0099
0100 const entryStart = performance.now();
0101 for (const piece of entry.pieces) {
0102 const candidates = this.piecePainterRegistry[piece.type] ?? [];
0103 if (candidates.length === 0) {
0104 console.warn(`No piece painter registered for piece type: ${piece.type}`);
0105 continue;
0106 }
0107 const PainterClass = this.painterSelector?.(piece, candidates) ?? candidates[0];
0108
0109 let pieceNode = new Group();
0110 pieceNode.name = piece.name;
0111 pieceNode.userData['piece'] = piece;
0112 this.threeParentNode.add(pieceNode);
0113
0114 const configView = this.configViewProvider?.(piece, PainterClass);
0115 const painterStart = performance.now();
0116 const painter = new PainterClass(pieceNode, piece, configView);
0117 const painterMs = performance.now() - painterStart;
0118 if (painterMs > 10) {
0119 console.log(`[load-timing] painter '${piece.name}' (${PainterClass.name}): ${painterMs.toFixed(1)} ms`);
0120 }
0121
0122 this.painters.push(painter);
0123 }
0124 const entryMs = performance.now() - entryStart;
0125 if (entryMs > 100) {
0126 console.log(`[load-timing] setEntry total (${entry.pieces.length} pieces): ${entryMs.toFixed(1)} ms`);
0127 }
0128 }
0129
0130 /**
0131 * Registers a custom painter class provided by the user. Registering more
0132 * classes for the same type adds alternatives (selectable per piece);
0133 * registering the same class twice is a no-op.
0134 *
0135 * @param pieceType - The type of the piece for which the painter should be used.
0136 * @param painterClass - The user's custom EventPiecePainter subclass.
0137 * @throws Error when the painter's meta declares a reserved knob key
0138 * (see `RESERVED_PAINTER_KNOB_KEYS`).
0139 */
0140 public registerPainter(pieceType: string, painterClass: PiecePainterConstructor): void {
0141 if (!pieceType || !painterClass) {
0142 throw new Error('Both pieceType and painterClass are required to register a custom painter.');
0143 }
0144 assertPainterKnobKeys(painterClass);
0145 const list = this.piecePainterRegistry[pieceType] ??= [];
0146 if (!list.includes(painterClass)) {
0147 list.push(painterClass);
0148 }
0149 }
0150
0151 /** paints scene at the current time. null - no-time mode (draws everything) */
0152 public paint(time: number | null): void {
0153 for (let painter of this.painters) {
0154 painter.paint(time);
0155 }
0156 }
0157 }