Files
strom/event/event_bus.go
T
2026-03-11 09:18:58 +01:00

154 lines
3.4 KiB
Go

// Package event is the event handler
package event
import (
"context"
"errors"
"fmt"
"reflect"
"time"
)
const (
LoopsPerSecond = 100 // i dunno seems reasonable
TimePerLoop = time.Second / LoopsPerSecond
)
type Handler interface {
OnStart() (err error)
}
type Loop struct {
handlerFunctions map[reflect.Type][]reflect.Value
Ctx context.Context
ErrChan chan error
cancel context.CancelFunc
}
type Unhandled struct {
Val any
}
type Anything struct {
Val any
}
type (
Tick struct{}
)
// HandlerDoneErr will stop the event loop and return the error if provided.
type HandlerDoneErr struct {
Return error
}
// Close is fired when the loop shuts down.
type Close struct {
Reason error
}
func (e HandlerDoneErr) Error() string {
return "HandlerDoneErr"
}
func (e HandlerDoneErr) Unwrap() error {
return e.Return
}
// ErrDontForward will stop the event from being forwarded to other handlers, including anything handlers.
var ErrDontForward = errors.New("dont forward")
var WhileClosingErr = errors.New("error while closing")
func ValidateHandler(h any) (hv reflect.Value, eventType reflect.Type) {
hv = reflect.ValueOf(h)
if hv.Kind() != reflect.Func {
panic("expected method")
}
if hv.Type().NumIn() != 1 {
panic("expected method with one argument")
}
if hv.Type().NumOut() != 1 || hv.Type().Out(0) != reflect.TypeFor[error]() {
panic("expected method that returns an error")
}
eventType = hv.Type().In(0)
return
}
func (l *Loop) Register(h any) {
hv, eventType := ValidateHandler(h)
l.RegisterDirect(eventType, hv)
}
func (l *Loop) RegisterDirect(k reflect.Type, h reflect.Value) {
l.handlerFunctions[k] = append(l.handlerFunctions[k], h)
}
// FireFound triggers all handlers registered for the specified event type and returns if handlers were found or an error occurred.
func (l *Loop) FireFound(event any) (found bool, err error) {
var handlers []reflect.Value
if handlers, found = l.handlerFunctions[reflect.TypeOf(event)]; found {
for _, handler := range handlers {
errV := handler.Call([]reflect.Value{reflect.ValueOf(event)})[0]
if !errV.IsNil() {
err = errV.Interface().(error)
err = errors.Join(fmt.Errorf("event handler failed at: %s(%T)", handler.Type().In(0), event), err)
return
}
}
}
return
}
// Fire dispatches the provided event to all appropriate handlers and invokes fallback handlers if the event is unhandled.
func (l *Loop) Fire(event any) (err error) {
found, err := l.FireFound(event)
if err != nil {
if errors.Is(err, ErrDontForward) {
err = nil
}
return
}
if !found {
_, err = l.FireFound(Unhandled{event})
if err != nil {
return
}
}
_, err = l.FireFound(Anything{event})
return
}
func (l *Loop) startLoop(handler Handler) (err error) {
_ = *l // exit early on nil loop
l.handlerFunctions = make(map[reflect.Type][]reflect.Value)
err = handler.OnStart()
for err == nil {
tickStartTime := time.Now()
err = l.Fire(Tick{})
if err != nil {
break
}
time.Sleep(TimePerLoop - time.Since(tickStartTime))
}
if errors.Is(err, HandlerDoneErr{}) {
err = errors.Unwrap(err)
}
closeErr := l.Fire(Close{err})
if closeErr != nil {
err = errors.Join(err, errors.Join(WhileClosingErr, closeErr))
}
return
}
func (l *Loop) Start(handler Handler) (err error) {
l.ErrChan = make(chan error)
l.Ctx, l.cancel = context.WithCancel(context.Background())
go func() {
l.ErrChan <- l.startLoop(handler)
}()
err = <-l.ErrChan
l.cancel()
return
}