forked from GeertJohan/gomatrix
-
Notifications
You must be signed in to change notification settings - Fork 4
/
stream.go
137 lines (117 loc) · 3.21 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
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)
headStyle := blackStyle.Foreground(tcell.ColorSilver)
tailStyle := blackStyle.Foreground(tcell.ColorGreen)
midStyle := blackStyle.Foreground(tcell.ColorGreen)
if screen.Colors() >= 16 {
midStyle = headStyle.Foreground(tcell.ColorLime)
// 33% of streams (arbitrary) get a bright white head
if rand.Intn(100) < 33 {
headStyle = headStyle.Foreground(tcell.ColorWhite)
} else {
headStyle = midStyle
}
}
var lastRune rune
var llastRune rune
for {
select {
case <-s.stopCh:
log.Printf("Stream on SD %d was stopped.\n", s.display.column)
goto done
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))]
screen.SetCell(s.display.column, s.headPos-3, tailStyle, llastRune)
screen.SetCell(s.display.column, s.headPos-1, midStyle, lastRune)
screen.SetCell(s.display.column, s.headPos, headStyle, newRune)
llastRune = lastRune
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 {
goto done
}
}
}
}
done:
delete(s.display.streams, s)
}
// StreamDisplay represents a horizontal 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.After(time.Duration(rand.Intn(9000)) * time.Millisecond) //++ TODO: .After or .Sleep??
time.Sleep(time.Duration(rand.Intn(9000)) * time.Millisecond)
// lock map
sd.streamsLock.Lock()
// crekate new stream instance
s := &Stream{
display: sd,
stopCh: make(chan bool),
speed: 30 + rand.Intn(110),
length: 6 + rand.Intn(6), // length of a stream is between 6 and 12 runes
}
// store in streams map
sd.streams[s] = true
// run the stream in a goroutine
go s.run()
// unlock map
sd.streamsLock.Unlock()
}
}
}