UNPKG

tempus

Version:
1 lines 20.5 kB
{"version":3,"sources":["../packages/core/src/profiler.ts"],"sourcesContent":["// tempus/profiler — a live overlay that shows how a frame is composed.\n//\n// It reads Tempus.inspect() (every Tempus.add() callback + every loop absorbed\n// by Tempus.patch(), in execution order) and lays each one out on a single\n// timeline whose full width is the per-frame budget (1000 / Tempus.targetFps).\n// Each segment's width is that callback's share of the budget, drawn end-to-end\n// so you can see the frame fill up — and overflow — left to right.\n\n// Import the published package entry (not the relative source) so this module\n// shares the ONE Tempus singleton with the host app. A relative import would\n// make tsup inline a second copy whose framerates are always empty.\nimport Tempus from 'tempus'\n\nexport type ProfilerCorner =\n | 'top-left'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-right'\n\nexport type ProfilerOptions = {\n // Overlay refresh rate. Kept low so the panel itself stays cheap; the\n // measurements it shows are unaffected (they come from Tempus.inspect()).\n fps?: number\n // Where to pin the panel. Default 'top-left'.\n corner?: ProfilerCorner\n // Mount target. Defaults to document.body.\n container?: HTMLElement\n}\n\nexport type ProfilerHandle = {\n element: HTMLElement\n destroy: () => void\n}\n\nconst isClient = typeof window !== 'undefined'\n\n// Our own render callback shows up in inspect() like any other — tag it so we\n// can filter it back out of the timeline.\nconst PROFILER_LABEL = 'tempus:profiler'\n\nconst STYLE_ID = 'tempus-profiler-style'\n\nconst CORNERS: Record<ProfilerCorner, string> = {\n 'top-left': 'top:12px;left:12px;',\n 'top-right': 'top:12px;right:12px;',\n 'bottom-left': 'bottom:12px;left:12px;',\n 'bottom-right': 'bottom:12px;right:12px;',\n}\n\n// Deterministic, readable hue per label so a callback keeps the same colour\n// across frames (identity at a glance, not a rainbow that reshuffles).\nfunction hueFor(label: string) {\n let h = 0\n for (let i = 0; i < label.length; i++) {\n h = (h * 31 + label.charCodeAt(i)) | 0\n }\n return Math.abs(h) % 360\n}\n\nfunction colorFor(label: string) {\n return `hsl(${hueFor(label)} 70% 55%)`\n}\n\nconst CSS = `\n.tempus-profiler {\n position: fixed;\n z-index: 2147483647;\n width: 320px;\n padding: 10px 12px 12px;\n border-radius: 10px;\n background: rgba(12, 12, 14, 0.92);\n color: #e8e8ea;\n font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-variant-numeric: tabular-nums;\n -webkit-backdrop-filter: blur(6px);\n backdrop-filter: blur(6px);\n box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);\n user-select: none;\n}\n.tempus-profiler-head {\n display: flex;\n gap: 10px;\n align-items: baseline;\n margin-bottom: 8px;\n cursor: grab;\n touch-action: none;\n}\n.tempus-profiler.dragging { cursor: grabbing; }\n.tempus-profiler.dragging .tempus-profiler-head { cursor: grabbing; }\n.tempus-profiler-toggle {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n padding: 0;\n border: 0;\n border-radius: 4px;\n background: rgba(255, 255, 255, 0.1);\n color: #e8e8ea;\n font-size: 9px;\n line-height: 1;\n cursor: pointer;\n}\n.tempus-profiler-toggle:hover { background: rgba(255, 255, 255, 0.18); }\n.tempus-profiler-toggle.paused { background: rgba(125, 220, 125, 0.22); color: #7ddc7d; }\n.tempus-profiler-title { font-weight: 600; letter-spacing: 0.02em; }\n.tempus-profiler-head .spacer { flex: 1; }\n.tempus-profiler-head b { color: #fff; font-weight: 600; }\n.tempus-profiler-track {\n position: relative;\n height: 18px;\n border-radius: 4px;\n background: rgba(255, 255, 255, 0.06);\n overflow: hidden;\n}\n.tempus-profiler-seg {\n position: absolute;\n top: 0;\n bottom: 0;\n min-width: 1px;\n box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.35);\n}\n.tempus-profiler-seg.throttled {\n background-image: repeating-linear-gradient(\n -45deg,\n rgba(255, 255, 255, 0.18) 0 4px,\n transparent 4px 8px\n );\n}\n.tempus-profiler-danger {\n position: absolute;\n top: 0;\n bottom: 0;\n background: rgba(255, 64, 64, 0.22);\n pointer-events: none;\n}\n.tempus-profiler-budget {\n position: absolute;\n top: -2px;\n bottom: -2px;\n width: 2px;\n background: #fff;\n box-shadow: 0 0 4px rgba(255, 255, 255, 0.6);\n pointer-events: none;\n}\n.tempus-profiler-scale {\n display: flex;\n justify-content: space-between;\n margin: 3px 1px 8px;\n color: rgba(232, 232, 234, 0.5);\n}\n.tempus-profiler-list { display: flex; flex-direction: column; gap: 3px; }\n.tempus-profiler-row { display: flex; align-items: center; gap: 7px; }\n.tempus-profiler-order {\n flex: none;\n width: 22px;\n text-align: right;\n color: rgba(232, 232, 234, 0.45);\n font-size: 10px;\n}\n.tempus-profiler-dot {\n width: 8px;\n height: 8px;\n border-radius: 2px;\n flex: none;\n}\n.tempus-profiler-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.tempus-profiler-badge {\n flex: none;\n padding: 0 4px;\n border-radius: 3px;\n background: rgba(255, 255, 255, 0.1);\n color: rgba(232, 232, 234, 0.7);\n font-size: 9px;\n}\n.tempus-profiler-ms { flex: none; color: #fff; }\n.tempus-profiler-pct { flex: none; width: 40px; text-align: right; }\n.tempus-profiler.collapsed .tempus-profiler-track,\n.tempus-profiler.collapsed .tempus-profiler-scale,\n.tempus-profiler.collapsed .tempus-profiler-list { display: none; }\n`\n\nfunction injectStyle() {\n if (document.getElementById(STYLE_ID)) return\n const style = document.createElement('style')\n style.id = STYLE_ID\n style.textContent = CSS\n document.head.appendChild(style)\n}\n\n// Mean over a callback's sample window. Throttled buckets only push a sample on\n// the frames they actually run, so this is the average \"cost when it runs\".\nfunction average(samples: number[]) {\n if (!samples.length) return 0\n let sum = 0\n for (const s of samples) sum += s\n return sum / samples.length\n}\n\nfunction pctColor(pct: number) {\n if (pct > 66) return '#ff5b5b'\n if (pct > 33) return '#ffb000'\n return '#7ddc7d'\n}\n\n// Compact, readable `order` for the legend gutter. ±Infinity (used to force a\n// callback first/last) shows as ±∞ rather than a giant number.\nfunction formatOrder(order: number) {\n if (order === Number.POSITIVE_INFINITY) return '∞'\n if (order === Number.NEGATIVE_INFINITY) return '-∞'\n return String(order)\n}\n\nexport function profiler(options: ProfilerOptions = {}): ProfilerHandle {\n if (!isClient) {\n // SSR-safe no-op so callers don't have to guard.\n return { element: null as unknown as HTMLElement, destroy() {} }\n }\n\n // Refresh twice a second by default: the panel shows a rolling average, so a\n // faster cadence just makes the numbers flicker without telling you more.\n const { fps = 5, corner = 'top-left', container = document.body } = options\n\n injectStyle()\n\n const root = document.createElement('div')\n root.className = 'tempus-profiler'\n root.style.cssText = CORNERS[corner]\n root.innerHTML = `\n <div class=\"tempus-profiler-head\">\n <button class=\"tempus-profiler-toggle\" data-toggle type=\"button\"></button>\n <span class=\"tempus-profiler-title\">tempus</span>\n <span class=\"spacer\"></span>\n <span class=\"tempus-profiler-stat\" data-fps></span>\n <span class=\"tempus-profiler-stat\" data-usage></span>\n </div>\n <div class=\"tempus-profiler-track\" data-track></div>\n <div class=\"tempus-profiler-scale\">\n <span>0</span>\n <span data-budget></span>\n </div>\n <div class=\"tempus-profiler-list\" data-list></div>\n `\n container.appendChild(root)\n\n const head = root.querySelector('.tempus-profiler-head') as HTMLElement\n const toggleEl = root.querySelector('[data-toggle]') as HTMLButtonElement\n const fpsEl = root.querySelector('[data-fps]') as HTMLElement\n const usageEl = root.querySelector('[data-usage]') as HTMLElement\n const trackEl = root.querySelector('[data-track]') as HTMLElement\n const budgetEl = root.querySelector('[data-budget]') as HTMLElement\n const listEl = root.querySelector('[data-list]') as HTMLElement\n\n // Drag the panel by its header. We switch to top/left positioning on first\n // grab (the panel starts pinned to a corner via right/bottom), clamp to the\n // viewport, and treat a press that never moves past a few px as a tap —\n // which toggles collapse, preserving the old click-to-collapse behaviour.\n const DRAG_THRESHOLD = 4 // px before a press counts as a drag, not a tap\n let dragging = false\n let moved = false\n let startX = 0\n let startY = 0\n let originLeft = 0\n let originTop = 0\n\n const onPointerDown = (e: PointerEvent) => {\n if (e.button !== 0) return\n const rect = root.getBoundingClientRect()\n // Pin via left/top from here on so right/bottom corners don't fight us.\n originLeft = rect.left\n originTop = rect.top\n startX = e.clientX\n startY = e.clientY\n dragging = true\n moved = false\n head.setPointerCapture(e.pointerId)\n }\n\n const onPointerMove = (e: PointerEvent) => {\n if (!dragging) return\n const dx = e.clientX - startX\n const dy = e.clientY - startY\n if (!moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return\n if (!moved) {\n moved = true\n root.classList.add('dragging')\n root.style.right = 'auto'\n root.style.bottom = 'auto'\n }\n // Clamp so the panel can't be dragged fully off-screen.\n const maxLeft = window.innerWidth - root.offsetWidth\n const maxTop = window.innerHeight - root.offsetHeight\n const left = Math.min(Math.max(originLeft + dx, 0), Math.max(0, maxLeft))\n const top = Math.min(Math.max(originTop + dy, 0), Math.max(0, maxTop))\n root.style.left = `${left}px`\n root.style.top = `${top}px`\n }\n\n const onPointerUp = (e: PointerEvent) => {\n if (!dragging) return\n dragging = false\n root.classList.remove('dragging')\n if (head.hasPointerCapture(e.pointerId)) head.releasePointerCapture(e.pointerId)\n // A press that never crossed the threshold is a tap → toggle collapse.\n if (!moved) root.classList.toggle('collapsed')\n }\n\n head.addEventListener('pointerdown', onPointerDown)\n head.addEventListener('pointermove', onPointerMove)\n head.addEventListener('pointerup', onPointerUp)\n head.addEventListener('pointercancel', onPointerUp)\n\n // Start/stop the whole Tempus loop. Reflect the live state on every render\n // tick too, so the button stays correct if play/pause happens elsewhere.\n const syncToggle = () => {\n const playing = Tempus.isPlaying\n toggleEl.textContent = playing ? '⏸' : '▶'\n toggleEl.classList.toggle('paused', !playing)\n toggleEl.setAttribute('aria-label', playing ? 'Stop the loop' : 'Start the loop')\n }\n toggleEl.addEventListener('click', (e) => {\n e.stopPropagation()\n if (Tempus.isPlaying) Tempus.pause()\n else Tempus.play()\n // render() is part of the loop, so it won't fire while paused — update now.\n syncToggle()\n })\n // Keep button presses from starting a header drag / collapse toggle.\n toggleEl.addEventListener('pointerdown', (e) => e.stopPropagation())\n syncToggle()\n\n const render = () => {\n syncToggle()\n const budget = Tempus.frameBudget\n\n const entries = Tempus.inspect()\n .filter((entry) => entry.label !== PROFILER_LABEL)\n .map((entry) => {\n // Rolling average over the sample window (~1s at 60fps) rather than the\n // last frame, so the once-a-second readout stays stable instead of\n // latching onto a single spiky frame.\n const duration = average(entry.samples)\n return {\n label: entry.source === 'patch' ? `${entry.label} ⟳` : entry.label,\n duration,\n order: entry.order,\n throttled: entry.fps !== Number.POSITIVE_INFINITY,\n fps: entry.fps,\n color: colorFor(entry.label),\n }\n })\n\n const total = entries.reduce((acc, e) => acc + e.duration, 0)\n // The timeline spans at least one budget; if the frame blew through it,\n // grow the span so the overflow stays visible and the budget marker slides\n // left to mark where the limit was.\n const span = Math.max(budget, total) || budget\n const budgetLeft = (budget / span) * 100\n\n // Header\n fpsEl.innerHTML = `<b>${Math.round(Tempus.fps ?? 0)}</b> fps`\n const usagePct = Math.round((total / budget) * 100)\n usageEl.innerHTML = `<b style=\"color:${pctColor(usagePct)}\">${usagePct}%</b> budget`\n budgetEl.textContent = `${budget.toFixed(1)}ms`\n\n // Timeline: each callback as a segment, laid end-to-end.\n let offset = 0\n let segHTML = ''\n for (const e of entries) {\n const left = (offset / span) * 100\n const width = (e.duration / span) * 100\n offset += e.duration\n segHTML += `<div class=\"tempus-profiler-seg${\n e.throttled ? ' throttled' : ''\n }\" style=\"left:${left}%;width:${width}%;background:${e.color}\" title=\"${\n e.label\n }: ${e.duration.toFixed(2)}ms\"></div>`\n }\n // Over-budget region + budget marker.\n if (total > budget) {\n segHTML += `<div class=\"tempus-profiler-danger\" style=\"left:${budgetLeft}%;width:${\n 100 - budgetLeft\n }%\"></div>`\n }\n segHTML += `<div class=\"tempus-profiler-budget\" style=\"left:${budgetLeft}%\"></div>`\n trackEl.innerHTML = segHTML\n\n // Legend / detail list, ordered by `order` (low → high, i.e. the order\n // callbacks run). The timeline above keeps true execution order.\n listEl.innerHTML = [...entries]\n .sort((a, b) => a.order - b.order)\n .map((e) => {\n const pct = (e.duration / budget) * 100\n return `<div class=\"tempus-profiler-row\">\n <span class=\"tempus-profiler-order\" title=\"order ${formatOrder(\n e.order\n )}\">${formatOrder(e.order)}</span>\n <span class=\"tempus-profiler-dot\" style=\"background:${e.color}\"></span>\n <span class=\"tempus-profiler-label\">${e.label}</span>\n ${\n e.throttled\n ? `<span class=\"tempus-profiler-badge\">${e.fps}${\n typeof e.fps === 'number' ? 'fps' : ''\n }</span>`\n : ''\n }\n <span class=\"tempus-profiler-ms\">${e.duration.toFixed(2)}ms</span>\n <span class=\"tempus-profiler-pct\" style=\"color:${pctColor(pct)}\">${pct.toFixed(\n 0\n )}%</span>\n </div>`\n })\n .join('')\n }\n\n // Run last (order Infinity) so we snapshot after every other callback has\n // executed this frame. Throttled to keep the panel cheap.\n const unsubscribe = Tempus.add(render, {\n fps,\n order: Number.POSITIVE_INFINITY,\n label: PROFILER_LABEL,\n })\n\n return {\n element: root,\n destroy() {\n unsubscribe?.()\n root.remove()\n },\n }\n}\n"],"mappings":"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","names":[]}