2020-04-24 22:09:05 +00:00
|
|
|
package wsutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/diamondburned/arikawa/utils/heart"
|
2020-04-25 07:13:07 +00:00
|
|
|
"github.com/diamondburned/arikawa/utils/moreatomic"
|
2020-04-24 22:09:05 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TODO API
|
|
|
|
type EventLoop interface {
|
|
|
|
Heartbeat() error
|
|
|
|
HandleOP(*OP) error
|
|
|
|
// HandleEvent(ev Event) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// PacemakerLoop provides an event loop with a pacemaker.
|
|
|
|
type PacemakerLoop struct {
|
|
|
|
pacemaker *heart.Pacemaker // let's not copy this
|
|
|
|
pacedeath chan error
|
|
|
|
|
2020-04-25 07:13:07 +00:00
|
|
|
running moreatomic.Bool
|
|
|
|
|
2020-04-24 22:09:05 +00:00
|
|
|
events <-chan Event
|
|
|
|
handler func(*OP) error
|
|
|
|
|
|
|
|
Extras ExtraHandlers
|
|
|
|
|
|
|
|
ErrorLog func(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewLoop(heartrate time.Duration, evs <-chan Event, evl EventLoop) *PacemakerLoop {
|
|
|
|
pacemaker := heart.NewPacemaker(heartrate, evl.Heartbeat)
|
|
|
|
|
|
|
|
return &PacemakerLoop{
|
|
|
|
pacemaker: pacemaker,
|
|
|
|
events: evs,
|
|
|
|
handler: evl.HandleOP,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PacemakerLoop) errorLog(err error) {
|
|
|
|
if p.ErrorLog == nil {
|
|
|
|
WSDebug("Uncaught error:", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
p.ErrorLog(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PacemakerLoop) Pace() error {
|
|
|
|
return p.pacemaker.Pace()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PacemakerLoop) Echo() {
|
|
|
|
p.pacemaker.Echo()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PacemakerLoop) Stop() {
|
|
|
|
p.pacemaker.Stop()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PacemakerLoop) Stopped() bool {
|
2020-04-25 07:13:07 +00:00
|
|
|
return p == nil || !p.running.Get()
|
2020-04-24 22:09:05 +00:00
|
|
|
}
|
|
|
|
|
2020-04-25 07:13:07 +00:00
|
|
|
func (p *PacemakerLoop) RunAsync(exit func(error)) {
|
|
|
|
WSDebug("Starting the pacemaker loop.")
|
|
|
|
|
2020-04-24 22:09:05 +00:00
|
|
|
// callers should explicitly handle waitgroups.
|
|
|
|
p.pacedeath = p.pacemaker.StartAsync(nil)
|
2020-04-25 07:13:07 +00:00
|
|
|
p.running.Set(true)
|
2020-04-24 22:09:05 +00:00
|
|
|
|
2020-04-25 07:13:07 +00:00
|
|
|
go func() {
|
|
|
|
exit(p.startLoop())
|
2020-04-24 22:09:05 +00:00
|
|
|
}()
|
2020-04-25 07:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PacemakerLoop) startLoop() error {
|
|
|
|
defer WSDebug("Pacemaker loop has exited.")
|
|
|
|
defer p.running.Set(false)
|
2020-04-24 22:09:05 +00:00
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case err := <-p.pacedeath:
|
2020-05-11 23:57:40 +00:00
|
|
|
WSDebug("Pacedeath returned with error:", err)
|
2020-05-16 21:14:49 +00:00
|
|
|
return errors.Wrap(err, "pacemaker died, reconnecting")
|
2020-04-24 22:09:05 +00:00
|
|
|
|
|
|
|
case ev, ok := <-p.events:
|
|
|
|
if !ok {
|
2020-05-11 23:57:40 +00:00
|
|
|
WSDebug("Events channel closed, stopping pacemaker.")
|
|
|
|
defer WSDebug("Pacemaker stopped automatically.")
|
2020-04-24 22:09:05 +00:00
|
|
|
// Events channel is closed. Kill the pacemaker manually and
|
|
|
|
// die.
|
|
|
|
p.pacemaker.Stop()
|
|
|
|
return <-p.pacedeath
|
|
|
|
}
|
|
|
|
|
|
|
|
o, err := DecodeOP(ev)
|
|
|
|
if err != nil {
|
2020-05-16 21:14:49 +00:00
|
|
|
return errors.Wrap(err, "failed to decode OP")
|
2020-04-24 22:09:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check the events before handling.
|
|
|
|
p.Extras.Check(o)
|
|
|
|
|
|
|
|
// Handle the event
|
|
|
|
if err := p.handler(o); err != nil {
|
2020-05-16 21:14:49 +00:00
|
|
|
p.errorLog(errors.Wrap(err, "handler failed"))
|
2020-04-24 22:09:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|