init: fast-test-dashboard app from fn_registry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user