This repository has been archived on 2024-03-03. You can view files and clone it, but cannot push or open issues or pull requests.
adlin/pkg/login-app/cmd/stream.go

139 lines
3.2 KiB
Go

package main
import (
"math/rand"
"sync"
"time"
"github.com/gdamore/tcell/v2"
)
// Stream updates a StreamDisplay with new data updates
type Stream struct {
display *StreamDisplay
speed int
length int
headPos int
tailPos int
stopCh chan bool
killCh chan bool
headDone bool
fini bool
}
func (s *Stream) run() {
blackStyle := tcell.StyleDefault.
Foreground(tcell.ColorBlack).
Background(tcell.ColorBlack)
midStyleA := blackStyle.Foreground(tcell.ColorGreen)
midStyleB := blackStyle.Foreground(tcell.ColorLime)
headStyleA := blackStyle.Foreground(tcell.ColorSilver)
headStyleB := blackStyle.Foreground(tcell.ColorWhite)
var lastRune rune
STREAM:
for {
select {
case <-s.stopCh:
s.fini = true
case <-time.After(time.Duration(s.speed) * time.Millisecond):
// add a new rune if there is space in the stream
if !s.fini && !s.headDone && s.headPos <= curSizes.height {
newRune := characters[rand.Intn(len(characters))]
// Making most of the green characters bright/bold...
if rand.Intn(100) < 66 {
screen.SetCell(s.display.column, s.headPos-1, midStyleA, lastRune)
} else {
screen.SetCell(s.display.column, s.headPos-1, midStyleB, lastRune)
}
// ...and turning about a third of the heads from gray to white
if rand.Intn(100) < 33 {
screen.SetCell(s.display.column, s.headPos, headStyleA, newRune)
} else {
screen.SetCell(s.display.column, s.headPos, headStyleB, newRune)
}
lastRune = newRune
s.headPos++
} else {
if s.fini && (s.length <= 0 || s.headPos <= 0) {
break STREAM
} else if s.fini && s.headPos < s.length {
s.length = s.headPos
}
s.headDone = true
}
// clear rune at the tail of the stream
if s.tailPos > 0 || s.headPos >= s.length {
if s.tailPos == 0 && !s.fini {
// tail is being incremented for the first time. there is space for a new stream
s.display.newStream <- true
}
if s.tailPos < curSizes.height {
screen.SetCell(s.display.column, s.tailPos, blackStyle, ' ') //'\uFF60'
s.tailPos++
} else {
break STREAM
}
}
}
}
delete(s.display.streams, s)
}
// StreamDisplay represents a vertical line in the terminal on which `Stream`s are displayed.
// StreamDisplay also creates the Streams themselves
type StreamDisplay struct {
column int
stopCh chan bool
streams map[*Stream]bool
streamsLock sync.Mutex
newStream chan bool
}
func (sd *StreamDisplay) run() {
for {
select {
case <-sd.stopCh:
// lock this SD forever
sd.streamsLock.Lock()
// stop streams for this SD
for s := range sd.streams {
s.stopCh <- true
}
// close this goroutine
return
case <-sd.newStream:
// have some wait before the first stream starts..
time.Sleep(time.Duration(rand.Intn(9000)) * time.Millisecond)
// lock map
sd.streamsLock.Lock()
// create new stream instance
s := &Stream{
display: sd,
stopCh: make(chan bool),
speed: 30 + rand.Intn(110),
length: 10 + rand.Intn(8), // length of a stream is between 10 and 18 runes
}
// store in streams map
sd.streams[s] = true
// run the stream in a goroutine
go s.run()
// unlock map
sd.streamsLock.Unlock()
}
}
}