667930731d
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
180 lines
3.5 KiB
Go
180 lines
3.5 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|