()
| 579 | }; |
| 580 | |
| 581 | const requestRender = (): void => { |
| 582 | if (disposed) return; |
| 583 | isDirty = true; |
| 584 | if (scheduledRaf !== null) return; |
| 585 | |
| 586 | scheduledRaf = requestAnimationFrame(() => { |
| 587 | scheduledRaf = null; |
| 588 | if (disposed) return; |
| 589 | |
| 590 | // Record frame timestamp BEFORE rendering |
| 591 | const frameStartTime = performance.now(); |
| 592 | frameTimestamps[frameTimestampIndex] = frameStartTime; |
| 593 | frameTimestampIndex = (frameTimestampIndex + 1) % FRAME_BUFFER_SIZE; |
| 594 | if (frameTimestampCount < FRAME_BUFFER_SIZE) { |
| 595 | frameTimestampCount++; |
| 596 | } |
| 597 | totalFrames++; |
| 598 | |
| 599 | // Frame drop detection (only after first frame) |
| 600 | if (lastFrameTime > 0) { |
| 601 | const deltaTime = frameStartTime - lastFrameTime; |
| 602 | if (deltaTime > EXPECTED_FRAME_TIME_MS * FRAME_DROP_THRESHOLD_MULTIPLIER) { |
| 603 | totalDroppedFrames++; |
| 604 | consecutiveDroppedFrames++; |
| 605 | lastDropTimestamp = frameStartTime; |
| 606 | } else { |
| 607 | // Reset consecutive counter on successful frame |
| 608 | consecutiveDroppedFrames = 0; |
| 609 | } |
| 610 | } |
| 611 | lastFrameTime = frameStartTime; |
| 612 | |
| 613 | // Requirement: on RAF tick, call resize() first. |
| 614 | resizeInternal(false); |
| 615 | |
| 616 | if (isDirty) { |
| 617 | isDirty = false; |
| 618 | coordinator?.render(); |
| 619 | } |
| 620 | |
| 621 | const frameEndTime = performance.now(); |
| 622 | lastCPUTime = frameEndTime - frameStartTime; |
| 623 | |
| 624 | // Calculate and emit performance metrics |
| 625 | const metrics = calculatePerformanceMetrics(); |
| 626 | for (const callback of performanceUpdateCallbacks) { |
| 627 | try { |
| 628 | callback(metrics); |
| 629 | } catch (error) { |
| 630 | console.error('Error in performance update callback:', error); |
| 631 | } |
| 632 | } |
| 633 | }); |
| 634 | }; |
| 635 | |
| 636 | const unbindCoordinatorInteractionXChange = (): void => { |
| 637 | if (!unsubscribeCoordinatorInteractionXChange) return; |
no test coverage detected