Skip to content

Commit b417973

Browse files
authored
Merge pull request #6 from ethanv2/event
Merge new event system, which drastically reduces CPU load, increases percentage time idle (preserving my battery life) and removes a few hacky pieces of redundant waiter code.
2 parents 737bcba + d0df94a commit b417973

File tree

14 files changed

+158
-116
lines changed

14 files changed

+158
-116
lines changed

data/cache.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/dhowden/tag"
1515
"github.com/ethanv2/podbit/data/escape"
16+
ev "github.com/ethanv2/podbit/event"
1617
)
1718

1819
// Possible cache errors.
@@ -35,6 +36,8 @@ type Cache struct {
3536
downloadsMutex sync.RWMutex // Protects the below two variables
3637
downloads []*Download
3738
ongoing int
39+
40+
hndl ev.Handler
3841
}
3942

4043
// Episode represents the data extracted from a single cached episode
@@ -85,10 +88,11 @@ func (c *Cache) guessDir() string {
8588
// Open opens and initialises the cache.
8689
// Should be called once and once only - further modifications
8790
// and cache mutations happen exclusively through other methods.
88-
func (c *Cache) Open() error {
91+
func (c *Cache) Open(hndl ev.Handler) error {
8992
home, _ := os.UserHomeDir()
9093
c.dir = strings.ReplaceAll(c.guessDir(), "~", home)
9194
files, err := ioutil.ReadDir(c.dir)
95+
c.hndl = hndl
9296

9397
if err != nil {
9498
cerr := os.MkdirAll(c.dir, os.ModeDir|os.ModePerm)
@@ -175,9 +179,9 @@ func (c *Cache) Download(item *QueueItem) (id int, err error) {
175179
}
176180

177181
if item.Youtube {
178-
go dl.DownloadYoutube()
182+
go dl.DownloadYoutube(c.hndl)
179183
} else {
180-
go dl.DownloadHTTP()
184+
go dl.DownloadHTTP(c.hndl)
181185
}
182186

183187
c.downloadsMutex.Lock()

data/data.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import (
1111
"net/url"
1212
"os"
1313
"time"
14+
15+
ev "github.com/ethanv2/podbit/event"
1416
)
1517

1618
const (
1719
// QueueReloadInterval is how often the queue will be reloaded.
18-
QueueReloadInterval = time.Duration(15) * time.Second
20+
QueueReloadInterval = time.Minute
1921
// EpisodeCacheTime is how long an episode is allowed to stay in cache in seconds.
2022
// Default value is three days (3 * 24 * 60 * 60).
2123
EpisodeCacheTime = 259200
@@ -30,7 +32,7 @@ var (
3032

3133
// InitData initialises all dependent data structures.
3234
// The only returned errors *will* be fatal to the program.
33-
func InitData() error {
35+
func InitData(hndl ev.Handler) error {
3436
fmt.Print("Reading queue...")
3537
err := Q.Open()
3638
if err != nil {
@@ -46,7 +48,7 @@ func InitData() error {
4648
fmt.Println("done")
4749

4850
fmt.Print("Initialising cache...")
49-
err = Downloads.Open()
51+
err = Downloads.Open(hndl)
5052
if err != nil {
5153
return err
5254
}

data/download.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"strings"
1111
"sync"
1212
"time"
13+
14+
ev "github.com/ethanv2/podbit/event"
1315
)
1416

1517
// YouTube downloading constants.
@@ -65,7 +67,7 @@ type Download struct {
6567
// (synchronously).
6668
//
6769
// Used internally by cache; avoid calling directly.
68-
func (d *Download) DownloadYoutube() {
70+
func (d *Download) DownloadYoutube(hndl ev.Handler) {
6971
if !d.Elem.Youtube {
7072
panic("download: downloading non-youtube with youtube-dl")
7173
}
@@ -133,6 +135,8 @@ func (d *Download) DownloadYoutube() {
133135
d.Percentage /= 100
134136
d.mut.Unlock()
135137

138+
hndl.Post(ev.DownloadChanged)
139+
136140
select {
137141
case <-d.Stop:
138142
d.mut.Lock()
@@ -185,14 +189,16 @@ func (d *Download) DownloadYoutube() {
185189
Downloads.downloadsMutex.Unlock()
186190

187191
d.mut.Unlock()
192+
193+
hndl.Post(ev.DownloadChanged)
188194
}
189195

190196
// DownloadHTTP connects to the URL of the specified download
191197
// and downloads to download path on the calling thread
192198
// (synchronously)
193199
//
194200
// Used internally by cache; avoid calling directly.
195-
func (d *Download) DownloadHTTP() {
201+
func (d *Download) DownloadHTTP(hndl ev.Handler) {
196202
resp, err := http.Get(d.Elem.URL)
197203
if err != nil || resp.StatusCode != http.StatusOK {
198204
d.mut.Lock()
@@ -234,6 +240,7 @@ outer:
234240
d.Percentage = float64(d.Done) / float64(d.Size)
235241
d.mut.Unlock()
236242

243+
hndl.Post(ev.DownloadChanged)
237244
if Downloads.Ongoing() > 1 {
238245
runtime.Gosched() // Give the other threads a turn
239246
}

event/event.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package ev
2+
3+
// Event classes.
4+
// ev.Handler passes events as integers between goroutines. These integers are
5+
// not arbitrary and are selected by the caller. This allows the receiving
6+
// goroutine to select which events are relevant to it using simple arithmetic
7+
// and/or a switch case.
8+
const (
9+
Keystroke = iota
10+
Resize
11+
TrayMessage
12+
PlayerChanged
13+
DownloadChanged
14+
)

event/handle.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package ev
2+
3+
// Handler is a multiplexer over multiple subsystems waiting for events in
4+
// parallel. When an event is delivered from one subsystem, handler bounces the
5+
// message to all other listening handlers.
6+
//
7+
// Handler is thread safe, provided that handlers are only added in sequence
8+
// and it is only ever passed by value after initialisation completes.
9+
type Handler struct {
10+
msg chan int
11+
hndl []chan int
12+
}
13+
14+
func NewHandler() *Handler {
15+
return &Handler{
16+
make(chan int, 1),
17+
make([]chan int, 0, 3),
18+
}
19+
}
20+
21+
func (h Handler) Run() {
22+
for msg := range h.msg {
23+
for _, elem := range h.hndl {
24+
elem <- msg
25+
}
26+
}
27+
}
28+
29+
func (h *Handler) Register() chan int {
30+
hndl := make(chan int, 1)
31+
h.hndl = append(h.hndl, hndl)
32+
return hndl
33+
}
34+
35+
func (h Handler) Post(ev int) {
36+
h.msg <- ev
37+
}

main.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/ethanv2/podbit/colors"
1010
"github.com/ethanv2/podbit/data"
11+
ev "github.com/ethanv2/podbit/event"
1112
"github.com/ethanv2/podbit/sound"
1213
"github.com/ethanv2/podbit/ui"
1314

@@ -24,10 +25,10 @@ var (
2425
homedir string
2526
confdir string
2627

27-
redraw = make(chan int)
28+
events *ev.Handler
2829
keystroke = make(chan rune)
2930
newMen = make(chan ui.Menu)
30-
exit = make(chan int)
31+
exit = make(chan struct{})
3132
)
3233

3334
func banner() {
@@ -91,8 +92,9 @@ func main() {
9192
}
9293
defer lock.Unlock()
9394

95+
events = ev.NewHandler()
9496
now := time.Now()
95-
err := data.InitData()
97+
err := data.InitData(*events)
9698
if err != nil {
9799
fmt.Println("\n" + err.Error())
98100
return
@@ -101,7 +103,7 @@ func main() {
101103
go data.ReloadLoop()
102104

103105
fmt.Print("Initialising sound system...")
104-
sound.Plr, err = sound.NewPlayer()
106+
sound.Plr, err = sound.NewPlayer(events)
105107
if err != nil {
106108
fmt.Printf("\nError: Failed to initialise sound system: %s\n", err.Error())
107109
os.Exit(1)
@@ -120,7 +122,7 @@ func main() {
120122
initColors()
121123
defer goncurses.End()
122124

123-
ui.InitUI(scr, ui.LibraryMenu, redraw, keystroke, newMen)
125+
ui.InitUI(scr, ui.LibraryMenu, events, keystroke, newMen)
124126
go ui.RenderLoop()
125127

126128
// Welcome message
@@ -132,8 +134,9 @@ func main() {
132134
len(data.Q.Items), len(data.Q.GetPodcasts()),
133135
startup.Seconds()))
134136

135-
// Initial UI draw
136-
ui.Redraw(ui.RedrawAll)
137+
// Run events handler and kickstart listeners
138+
go events.Run()
139+
events.Post(ev.Keystroke)
137140

138141
// Initialisation is done; use this thread as the input loop
139142
ui.InputLoop(exit)

sound/sound.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"time"
2020

2121
"github.com/ethanv2/podbit/data"
22+
ev "github.com/ethanv2/podbit/event"
2223

2324
"github.com/blang/mpv"
2425
)
@@ -34,7 +35,7 @@ var (
3435
// to idle mpv ready to receive instructions.
3536
PlayerArgs = []string{"--idle", "--no-video", "--input-ipc-server=" + PlayerRPC}
3637
// UpdateTime is the time between queue checks and supervision updates.
37-
UpdateTime = 200 * time.Millisecond
38+
UpdateTime = 500 * time.Millisecond
3839
)
3940

4041
// Internal: Types of actions.
@@ -61,6 +62,10 @@ type WaitFunc func(u chan int)
6162
type Player struct {
6263
proc *exec.Cmd
6364

65+
hndl ev.Handler
66+
event chan int
67+
dlchan chan struct{}
68+
6469
act chan int
6570
dat chan interface{}
6671

@@ -111,7 +116,7 @@ func endWait(u chan int) {
111116
func downloadWait(u chan int) {
112117
var id int
113118
for y := true; y && DownloadAtHead(Plr.download); y, id = data.Downloads.IsDownloading(Plr.download.Path) {
114-
time.Sleep(UpdateTime)
119+
<-Plr.dlchan
115120
}
116121

117122
Plr.waiting = false
@@ -125,23 +130,19 @@ func downloadWait(u chan int) {
125130
head--
126131
}
127132

128-
u <- 1
129-
}
130-
131-
func newWait(u chan int) {
132-
for head >= len(queue) {
133-
time.Sleep(UpdateTime)
134-
}
135-
136-
Plr.waiting = false
133+
Plr.hndl.Post(ev.PlayerChanged)
137134
u <- 1
138135
}
139136

140137
// NewPlayer constructs a new player. This does not yet
141138
// launch any processes or play any media.
142-
func NewPlayer() (p Player, err error) {
139+
func NewPlayer(events *ev.Handler) (p Player, err error) {
143140
p.act = make(chan int)
144141
p.dat = make(chan interface{})
142+
p.dlchan = make(chan struct{}, 1)
143+
144+
p.hndl = *events
145+
p.event = events.Register()
145146

146147
return
147148
}
@@ -377,10 +378,23 @@ func (p *Player) Wait() {
377378

378379
now, _ := p.ctrl.Filename()
379380
for filename := now; filename == now; filename, _ = p.ctrl.Filename() {
381+
if !p.isPaused() {
382+
p.hndl.Post(ev.PlayerChanged)
383+
}
380384
time.Sleep(UpdateTime)
381385
}
382386
}
383387

388+
func (p *Player) Event(e int) {
389+
switch e {
390+
case ev.DownloadChanged:
391+
select {
392+
case p.dlchan <- struct{}{}:
393+
default:
394+
}
395+
}
396+
}
397+
384398
// Mainloop - Main sound handling thread loop
385399
//
386400
// Loops infinitely waiting for sound events, managing the queue, mpv
@@ -434,7 +448,9 @@ func Mainloop() {
434448
select {
435449
case <-u:
436450
keepWaiting = false
437-
451+
Plr.hndl.Post(ev.PlayerChanged)
452+
case e := <-Plr.event:
453+
Plr.Event(e)
438454
case action := <-Plr.act:
439455
switch action {
440456
case actTerm:
@@ -472,5 +488,7 @@ func Mainloop() {
472488
}
473489
}
474490
}
491+
492+
Plr.hndl.Post(ev.PlayerChanged)
475493
}
476494
}

ui/download.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/ethanv2/podbit/data"
99
"github.com/ethanv2/podbit/sound"
1010
"github.com/ethanv2/podbit/ui/components"
11+
ev "github.com/ethanv2/podbit/event"
1112
)
1213

1314
var downloadHeadings []components.Column = []components.Column{
@@ -76,6 +77,10 @@ func (q *Downloads) Render(x, y int) {
7677
q.tbl.Render()
7778
}
7879

80+
func (q *Downloads) Should(event int) bool {
81+
return event == ev.Keystroke || event == ev.DownloadChanged || event == ev.PlayerChanged
82+
}
83+
7984
func (q *Downloads) Input(c rune) {
8085
switch c {
8186
case 'j':

0 commit comments

Comments
 (0)