init: fast-test-dashboard app from fn_registry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dataforge
2026-04-06 00:56:54 +02:00
commit 667930731d
32 changed files with 4894 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
package main
import (
"context"
"math"
"sync"
"sync/atomic"
"time"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
// App struct — cada metodo publico es un binding IPC al frontend.
type App struct {
ctx context.Context
hz atomic.Int64
running atomic.Bool
mu sync.Mutex
cancelGen context.CancelFunc
stats Stats
}
type Stats struct {
TotalEmitted int64 `json:"totalEmitted"`
StartedAt int64 `json:"startedAt"` // unix ms
CurrentHz int64 `json:"currentHz"`
AvgLatency float64 `json:"avgLatency"` // ns per emit
}
type DataPoint struct {
Value float64 `json:"value"`
Timestamp int64 `json:"timestamp"` // unix ms
Sequence int64 `json:"sequence"`
}
func NewApp() *App {
a := &App{}
a.hz.Store(10)
return a
}
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
// --- Generador: Lorenz attractor (componente X) ---
// dx/dt = sigma*(y-x), dy/dt = x*(rho-z)-y, dz/dt = x*y - beta*z
// Produce valores caóticos deterministas — visualmente interesante.
type lorenzState struct {
x, y, z float64
}
func lorenzStep(s lorenzState, dt float64) lorenzState {
const sigma = 10.0
const rho = 28.0
const beta = 8.0 / 3.0
dx := sigma * (s.y - s.x)
dy := s.x*(rho-s.z) - s.y
dz := s.x*s.y - beta*s.z
return lorenzState{
x: s.x + dx*dt,
y: s.y + dy*dt,
z: s.z + dz*dt,
}
}
// Start arranca el generador de numeros.
func (a *App) Start() {
if a.running.Load() {
return
}
a.running.Store(true)
a.mu.Lock()
ctx, cancel := context.WithCancel(a.ctx)
a.cancelGen = cancel
a.stats = Stats{StartedAt: time.Now().UnixMilli()}
a.mu.Unlock()
go a.generateLoop(ctx)
}
// Stop detiene el generador.
func (a *App) Stop() {
a.running.Store(false)
a.mu.Lock()
if a.cancelGen != nil {
a.cancelGen()
a.cancelGen = nil
}
a.mu.Unlock()
}
// SetHz cambia la frecuencia del generador en tiempo real.
func (a *App) SetHz(hz int64) {
if hz < 1 {
hz = 1
}
a.hz.Store(hz)
}
// GetHz retorna la frecuencia actual.
func (a *App) GetHz() int64 {
return a.hz.Load()
}
// GetStats retorna estadisticas del generador.
func (a *App) GetStats() Stats {
a.mu.Lock()
defer a.mu.Unlock()
s := a.stats
s.CurrentHz = a.hz.Load()
return s
}
// IsRunning retorna si el generador esta activo.
func (a *App) IsRunning() bool {
return a.running.Load()
}
// Ping verifica IPC.
func (a *App) Ping() string {
return "pong"
}
func (a *App) generateLoop(ctx context.Context) {
state := lorenzState{x: 1.0, y: 1.0, z: 1.0}
dt := 0.005
var seq int64
// Usar ticker dinámico — recalculamos el intervalo en cada iteración
for {
select {
case <-ctx.Done():
return
default:
}
hz := a.hz.Load()
interval := time.Second / time.Duration(hz)
start := time.Now()
// Paso del atractor de Lorenz
state = lorenzStep(state, dt)
// Normalizar X a rango visible (~[-20, 20] -> [0, 100])
normalized := (state.x + 25.0) * (100.0 / 50.0)
normalized = math.Max(0, math.Min(100, normalized))
seq++
point := DataPoint{
Value: math.Round(normalized*1000) / 1000,
Timestamp: time.Now().UnixMilli(),
Sequence: seq,
}
runtime.EventsEmit(a.ctx, "data:point", point)
a.mu.Lock()
a.stats.TotalEmitted = seq
elapsed := time.Since(start).Nanoseconds()
if a.stats.AvgLatency == 0 {
a.stats.AvgLatency = float64(elapsed)
} else {
a.stats.AvgLatency = a.stats.AvgLatency*0.99 + float64(elapsed)*0.01
}
a.mu.Unlock()
// Sleep el tiempo restante del intervalo
sleepTime := interval - time.Since(start)
if sleepTime > 0 {
time.Sleep(sleepTime)
}
}
}