diff options
| -rw-r--r-- | broadcast.go | 59 | ||||
| -rw-r--r-- | gui/widget/widget.go | 1 | ||||
| -rw-r--r-- | main.go | 156 |
3 files changed, 167 insertions, 49 deletions
diff --git a/broadcast.go b/broadcast.go new file mode 100644 index 0000000..7268971 --- /dev/null +++ b/broadcast.go @@ -0,0 +1,59 @@ +package main + +import "sync" + +// Broadcast sends data sent from source to all destination channels. +type Broadcast[T any] struct { + source chan T + destinations []chan<- T + + mu sync.Mutex + wg sync.WaitGroup +} + +// The caller is responsible for closing source. When source is closed, +// Broadcast will close all destinations. +func NewBroadcast[T any](source chan T) Broadcast[T] { + bc := Broadcast[T]{ + source, + make([]chan<- T, 0), + sync.Mutex{}, + sync.WaitGroup{}, + } + + go func(bc *Broadcast[T]) { + bc.wg.Add(1) + + for v := range bc.source { + bc.mu.Lock() + for _, dest := range bc.destinations { + dest <- v + } + bc.mu.Unlock() + } + + bc.mu.Lock() + for _, dest := range bc.destinations { + close(dest) + } + bc.mu.Unlock() + + bc.wg.Done() + }(&bc) + return bc +} + +func (bc *Broadcast[T]) AddDestination() <-chan T { + bc.mu.Lock() + defer bc.mu.Unlock() + + ch := make(chan T) + bc.destinations = append(bc.destinations, ch) + return ch +} + +// Wait for the Broadcast to see that source is closed and to close the +// destinations. +func (bc *Broadcast[T]) Wait() { + bc.wg.Wait() +} diff --git a/gui/widget/widget.go b/gui/widget/widget.go index 82f67c6..61ba760 100644 --- a/gui/widget/widget.go +++ b/gui/widget/widget.go @@ -43,6 +43,7 @@ func Label(text string, r image.Rectangle, env gui.Env, wg *sync.WaitGroup) { func Input(val chan<- uint, r image.Rectangle, focusChan <-chan bool, env gui.Env, wg *sync.WaitGroup) { defer wg.Done() defer close(env.Draw()) + defer close(val) redraw := func(text []byte, focus bool) func(draw.Image) image.Rectangle { return func(drw draw.Image) image.Rectangle { @@ -27,13 +27,17 @@ const ( ) func run() { - var ( - wg = new(sync.WaitGroup) + wg := new(sync.WaitGroup) + defer wg.Wait() - focus = NewFocus([]int{1, POINTS, POINTS, POINTS, POINTS}) + focus := NewFocus([]int{1, POINTS, POINTS, POINTS, POINTS}) + defer focus.Close() - displacementChan = make(chan uint) + displacementChan := make(chan uint) + displacementBroadcast := NewBroadcast(displacementChan) + defer displacementBroadcast.Wait() + var ( rpmChan [POINTS]chan uint veChan [POINTS]chan uint imapChan [POINTS]chan uint @@ -41,22 +45,8 @@ func run() { flowChan [POINTS]chan float64 ) - defer wg.Wait() - defer focus.Close() - defer close(displacementChan) - for i := 0; i < POINTS; i++ { - rpmChan[i] = make(chan uint) - veChan[i] = make(chan uint) - imapChan[i] = make(chan uint) - actChan[i] = make(chan uint) - defer close(rpmChan[i]) - defer close(veChan[i]) - defer close(imapChan[i]) - defer close(actChan[i]) - - flowChan[i] = make(chan float64) - defer close(flowChan[i]) - } + makeChans(rpmChan[:], veChan[:], imapChan[:], actChan[:]) + makeChans(flowChan[:]) w, err := win.New(win.Title("volute"), win.Size(WIDTH, HEIGHT)) if err != nil { @@ -66,6 +56,34 @@ func run() { mux, env := gui.NewMux(w) defer close(env.Draw()) + spawnWidgets( + displacementChan, + rpmChan, veChan, imapChan, actChan, + flowChan, + &focus, mux, wg, + ) + + // TODO: make these output properly on screen. + for i := 0; i < POINTS; i++ { + wg.Add(1) + go calculateFlow( + flowChan[i], + displacementBroadcast.AddDestination(), + rpmChan[i], veChan[i], actChan[i], imapChan[i], + wg, + ) + } + + focus.Focus(true) + eventLoop(env, &focus) +} + +func spawnWidgets( + displacementChan chan uint, + rpmChan, veChan, imapChan, actChan [POINTS]chan uint, + flowChan [POINTS]chan float64, + focus *Focus, mux *gui.Mux, wg *sync.WaitGroup, +) { bounds := layout.Grid{ Rows: []int{2, 7, 7, 7, 7, 7}, Background: color.Gray{255}, @@ -139,39 +157,36 @@ func run() { wg, ) } +} - focus.widgets[focus.p.Y][focus.p.X] <- true - -Loop: - for { - select { - case _ = <-displacementChan: - case _ = <-rpmChan[0]: - case _ = <-veChan[0]: - case event, ok := <-env.Events(): - if !ok { // channel closed - break Loop - } - switch event := event.(type) { - case win.WiClose: - break Loop - case win.KbType: - switch event.Rune { - case 'q': - break Loop - case 'h': - focus.Left() - case 'j': - focus.Down() - case 'k': - focus.Up() - case 'l': - focus.Right() - } +func eventLoop(env gui.Env, focus *Focus) { + for event := range env.Events() { + switch event := event.(type) { + case win.WiClose: + return + case win.KbType: + switch event.Rune { + case 'q': + return + case 'h': + focus.Left() + case 'j': + focus.Down() + case 'k': + focus.Up() + case 'l': + focus.Right() } } } - fmt.Println("Shutting down...") +} + +func makeChans[T any](chanss ...[]chan T) { + for i := range chanss { + for j := range chanss[i] { + chanss[i][j] = make(chan T) + } + } } func split(elements int, space int) []int { @@ -197,6 +212,49 @@ func splitRows(elements int, space int) []int { return bounds } +func calculateFlow( + flow chan<- float64, + displacementChan, rpmChan, veChan, actChan, imapChan <-chan uint, + wg *sync.WaitGroup, +) { + defer wg.Done() + defer close(flow) + + var ( + displacement Volume + rpm uint + ve uint + act Temperature + imap Pressure + + v uint + ok bool + ) + + for { + select { + case v, ok = <-displacementChan: + displacement = Volume(v) * CubicCentimetre + case rpm, ok = <-rpmChan: + case ve, ok = <-veChan: + case v, ok = <-actChan: + act = Temperature{float64(v), Celcius} + case v, ok = <-imapChan: + imap = Pressure(v) * Millibar + } + if !ok { + return + } + flow <- massFlow(displacement, rpm, ve, act, imap) + } +} + +func massFlow(displacement Volume, rpm, ve uint, act Temperature, imap Pressure) float64 { + density := (M / R) * float64(imap/Pascal) / act.AsUnit(Kelvin) // kg/m3 + volumeFlow := float64(displacement/CubicMetre) * float64(rpm/2) * (float64(ve) / 100.0) // m3/min + return density * volumeFlow +} + func main() { mainthread.Run(run) } |