diff options
| author | grothedev <grothedev@gmail.com> | 2025-10-04 01:04:53 -0400 |
|---|---|---|
| committer | grothedev <grothedev@gmail.com> | 2025-10-04 01:04:53 -0400 |
| commit | 2c1b37a5b0c4962b405a85768b9b8cdd5c4f1097 (patch) | |
| tree | 22c8d6ea6e3cd9e9eb8e50d09e785d16b5ccb4c3 /web-timeplot/src | |
| parent | 6d94c48de530027c2d7363852924e648d7f7ccfb (diff) | |
claude
Diffstat (limited to 'web-timeplot/src')
| -rw-r--r-- | web-timeplot/src/main.js | 179 | ||||
| -rw-r--r-- | web-timeplot/src/state.js | 2 |
2 files changed, 101 insertions, 80 deletions
diff --git a/web-timeplot/src/main.js b/web-timeplot/src/main.js index b0ba032..31be4bb 100644 --- a/web-timeplot/src/main.js +++ b/web-timeplot/src/main.js @@ -1,31 +1,26 @@ import { Application } from 'pixi.js'; import { WaterfallGraph } from './waterfall.js'; import { PerformanceMetrics } from './metrics.js'; +import { StateManager } from './state.js'; // ============================================================================ // GLOBAL STATE // ============================================================================ -let env = {}; -let cfg = { - showGrid: true, - showMetrics: true, - rollingWindow: 60, - historyCapacity: 10000, -}; +// Centralized reactive state +const state = new StateManager(); +// DOM references let dom = { container: null, controls: {}, display: {}, }; +// Application instances let app = null; // PixiJS Application -let graphs = []; let metrics = null; -let time = 0; -let frameCounter = 0; -let metricsUpdateInterval = 10; // Update metrics display every N frames +let graphs = []; // Keep PixiJS graph instances outside reactive state // Vertical zoom dragging state let isDraggingVerticalZoom = false; @@ -42,9 +37,6 @@ document.addEventListener('DOMContentLoaded', async function() { log('init DOM'); await initDOM(); - log('init config'); - await initCfg(); - log('init PixiJS renderer'); await initRenderer(); @@ -57,6 +49,9 @@ document.addEventListener('DOMContentLoaded', async function() { log('setup controls'); await setupControls(); + log('setup state listeners'); + await setupStateListeners(); + log('TimePlot ready'); }); @@ -74,23 +69,6 @@ async function initDOM() { dom.display.timeScale = document.getElementById('time-scale'); } -async function initCfg() { - const localCfg = localStorage.getItem('timeplot-cfg'); - if (localCfg) { - try { - const parsed = JSON.parse(localCfg); - cfg = { ...cfg, ...parsed }; - log('Loaded config from localStorage'); - } catch (e) { - log('Failed to parse config, using defaults'); - } - } -} - -function saveCfg() { - localStorage.setItem('timeplot-cfg', JSON.stringify(cfg)); -} - async function initRenderer() { // Check WebGPU availability let preference = 'webgpu'; @@ -114,11 +92,16 @@ async function initRenderer() { dom.container.appendChild(app.canvas); - // Display actual renderer type + // Store renderer info in state const rendererType = app.renderer.type; + state.state.rendering.rendererType = rendererType; dom.display.rendererType.textContent = rendererType; log(`Using renderer: ${rendererType}`); + // Store canvas dimensions in state + state.state.uiConfig.canvasWidth = app.screen.width; + state.state.uiConfig.canvasHeight = app.screen.height; + // Handle window resize window.addEventListener('resize', handleResize); @@ -152,16 +135,22 @@ async function initGraphs() { color: 0x66ff66, }); - graphs.push(graph1, graph2); + // Store graphs locally (PixiJS objects shouldn't be proxied) + graphs = [graph1, graph2]; + + // Add to stage graphs.forEach(graph => { app.stage.addChild(graph.container); - graph.setGridVisible(cfg.showGrid); + graph.setGridVisible(state.state.userPrefs.showGrid); }); } async function initServices() { // Initialize performance metrics - metrics = new PerformanceMetrics(cfg.rollingWindow, cfg.historyCapacity); + metrics = new PerformanceMetrics( + state.state.userPrefs.rollingWindow, + state.state.userPrefs.historyCapacity + ); // Start animation loop app.ticker.add(update); @@ -170,13 +159,23 @@ async function initServices() { } function setupControls() { + // Register input actions + state.registerAction('toggleGrid', toggleGrid); + state.registerAction('toggleMetrics', toggleMetrics); + state.registerAction('exportMetrics', exportMetrics); + + // Map keyboard keys to actions + state.mapKey('KeyG', 'toggleGrid'); + state.mapKey('KeyM', 'toggleMetrics'); + state.mapKey('KeyE', 'exportMetrics'); + // Button controls - dom.controls.gridBtn.addEventListener('click', toggleGrid); - dom.controls.metricsBtn.addEventListener('click', toggleMetrics); - dom.controls.exportBtn.addEventListener('click', exportMetrics); + dom.controls.gridBtn.addEventListener('click', () => state.executeAction('toggleGrid')); + dom.controls.metricsBtn.addEventListener('click', () => state.executeAction('toggleMetrics')); + dom.controls.exportBtn.addEventListener('click', () => state.executeAction('exportMetrics')); - // Keyboard controls - window.addEventListener('keydown', handleKeyboard); + // Keyboard controls via state's input action system + window.addEventListener('keydown', (e) => state.handleKeyboardEvent(e)); // Mouse controls for time scaling dom.container.addEventListener('mousedown', handleMouseDown); @@ -188,30 +187,51 @@ function setupControls() { updateControlButtons(); } +function setupStateListeners() { + // React to showGrid changes + state.on('userPrefs.showGrid', ({ value }) => { + graphs.forEach(graph => graph.setGridVisible(value)); + updateControlButtons(); + log(`Grid: ${value ? 'ON' : 'OFF'}`); + }); + + // React to showMetrics changes + state.on('userPrefs.showMetrics', ({ value }) => { + updateControlButtons(); + log(`Metrics: ${value ? 'ON' : 'OFF'}`); + }); + + // React to vertical scale changes + state.on('time.verticalScale', ({ value }) => { + graphs.forEach(graph => graph.setVerticalScale(value)); + updateVerticalZoomDisplay(); + }); + + // React to pause state changes + state.on('time.isPaused', ({ value }) => { + log(`Time ${value ? 'PAUSED' : 'RESUMED'}`); + }); + + // React to speed changes + state.on('time.speed', ({ value }) => { + log(`Time speed: ${value.toFixed(1)}x`); + }); +} + // ============================================================================ // EVENT HANDLERS // ============================================================================ -function handleKeyboard(e) { - switch(e.key.toLowerCase()) { - case 'g': - toggleGrid(); - break; - case 'm': - toggleMetrics(); - break; - case 'e': - exportMetrics(); - break; - } -} - function handleResize() { const width = window.innerWidth; const height = window.innerHeight - 60; app.renderer.resize(width, height); + // Update state + state.state.uiConfig.canvasWidth = width; + state.state.uiConfig.canvasHeight = height; + // Update graphs if (graphs[0]) graphs[0].resize(0, 0, width / 2, height); if (graphs[1]) graphs[1].resize(width / 2, 0, width / 2, height); @@ -223,7 +243,7 @@ function handleMouseDown(e) { e.preventDefault(); isDraggingVerticalZoom = true; dragStartY = e.clientY; - dragStartZoom = graphs[0]?.getVerticalScale() || 1.0; + dragStartZoom = state.state.time.verticalScale; dom.container.style.cursor = 'ns-resize'; } } @@ -235,13 +255,8 @@ function handleMouseMove(e) { const sensitivity = 0.005; // Adjust sensitivity const newZoom = dragStartZoom + (deltaY * sensitivity); - // Apply to all graphs - graphs.forEach(graph => { - graph.setVerticalScale(newZoom); - }); - - // Update display - updateVerticalZoomDisplay(); + // Update state (which will trigger graph updates via state listener) + state.state.time.verticalScale = Math.max(0.2, Math.min(3.0, newZoom)); } function handleMouseUp(e) { @@ -259,12 +274,15 @@ function update() { metrics.beginFrame(); metrics.beginUpdate(); - time += 0.016; // ~60fps increment - frameCounter++; + // Update time using state manager + state.incrementTime(0.016); // ~60fps increment + state.updateRealElapsed(); + + state.state.rendering.frameCounter++; // Update each graph graphs.forEach((graph, idx) => { - graph.update(time, idx); + graph.update(state.state.time.current, idx); }); const updateMs = metrics.endUpdate(); @@ -279,8 +297,18 @@ function update() { metrics.endFrame(updateMs, renderMs, vertexCount, lineCount); + // Update health metrics in state (silently to avoid spamming events) + const currentHealth = state.state.health; + currentHealth.updateMs = updateMs; + currentHealth.renderMs = renderMs; + currentHealth.vertexCount = vertexCount; + currentHealth.lineCount = lineCount; + currentHealth.fps = metrics.getFPS(); + // Update UI less frequently to prevent flickering - if (cfg.showMetrics && frameCounter % metricsUpdateInterval === 0) { + const frameCounter = state.state.rendering.frameCounter; + const interval = state.state.userPrefs.metricsUpdateInterval; + if (state.state.userPrefs.showMetrics && frameCounter % interval === 0) { updateMetricsDisplay(); } } @@ -290,18 +318,11 @@ function update() { // ============================================================================ function toggleGrid() { - cfg.showGrid = !cfg.showGrid; - graphs.forEach(graph => graph.setGridVisible(cfg.showGrid)); - updateControlButtons(); - saveCfg(); - log(`Grid: ${cfg.showGrid ? 'ON' : 'OFF'}`); + state.togglePref('showGrid'); } function toggleMetrics() { - cfg.showMetrics = !cfg.showMetrics; - updateControlButtons(); - saveCfg(); - log(`Metrics: ${cfg.showMetrics ? 'ON' : 'OFF'}`); + state.togglePref('showMetrics'); } function exportMetrics() { @@ -323,13 +344,13 @@ function updateMetricsDisplay() { } function updateControlButtons() { - dom.controls.gridBtn.classList.toggle('active', cfg.showGrid); - dom.controls.metricsBtn.classList.toggle('active', cfg.showMetrics); + dom.controls.gridBtn.classList.toggle('active', state.state.userPrefs.showGrid); + dom.controls.metricsBtn.classList.toggle('active', state.state.userPrefs.showMetrics); } function updateVerticalZoomDisplay() { - if (dom.display.timeScale && graphs[0]) { - const zoom = graphs[0].getVerticalScale(); + if (dom.display.timeScale) { + const zoom = state.state.time.verticalScale; dom.display.timeScale.textContent = `${zoom.toFixed(2)}x`; // Color code: zoomed out = blue, normal = white, zoomed in = orange diff --git a/web-timeplot/src/state.js b/web-timeplot/src/state.js index a38dc98..53d8279 100644 --- a/web-timeplot/src/state.js +++ b/web-timeplot/src/state.js @@ -98,8 +98,8 @@ export class StateManager extends EventEmitter { rendering: { rendererType: 'unknown', // 'webgpu' | 'webgl' | 'canvas' - graphs: [], // Array of graph instances frameCounter: 0, + // Note: graph instances are NOT stored here to avoid proxy wrapping }, health: { |
