-
Notifications
You must be signed in to change notification settings - Fork 68
/
stream.go
138 lines (117 loc) · 3.22 KB
/
stream.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package main
import (
"log"
"math/rand"
"sync"
"time"
"github.com/gdamore/tcell"
)
// Stream updates a StreamDisplay with new data updates
type Stream struct {
display *StreamDisplay
speed int
length int
headPos int
tailPos int
stopCh chan bool
headDone 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:
log.Printf("Stream on SD %d was stopped.\n", s.display.column)
break STREAM
case <-time.After(time.Duration(s.speed) * time.Millisecond):
// add a new rune if there is space in the stream
if !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 {
s.headDone = true
}
// clear rune at the tail of the stream
if s.tailPos > 0 || s.headPos >= s.length {
if s.tailPos == 0 {
// 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
}
}
}
}
s.display.streamsLock.Lock()
delete(s.display.streams, s)
s.display.streamsLock.Unlock()
}
// 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
}
// log that SD has closed
log.Printf("StreamDisplay on column %d stopped.\n", sd.column)
// 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()
}
}
}