// Package event is the event handler package event import ( "errors" "fmt" "log/slog" "reflect" "strings" ) type Loop struct { HandlerFunctions map[reflect.Type][]reflect.Value Handlers []any HandlerIndex int } type Unhandled struct { Val any } type Anything struct { Val any } type ( OnStart struct{} Tick struct{} ) // HandlerDoneErr will stop the event loop and return the error if provided. type HandlerDoneErr struct { Return 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") // FindHandlers initializes and populates the HandlerFunctions map by reflecting over methods of registered handlers. func (l *Loop) FindHandlers() { l.HandlerFunctions = make(map[reflect.Type][]reflect.Value) for _, inst := range l.Handlers { t := reflect.TypeOf(inst) v := reflect.ValueOf(inst) for i := 0; i < t.NumMethod(); i++ { method := t.Method(i) if strings.HasPrefix(method.Name, "On") { if method.Type.NumIn() != 2 || method.Type.NumOut() != 1 || method.Type.Out(0) != reflect.TypeFor[error]() { fmt.Println("Invalid event handler signature:", t.String(), ".", method.Name) continue } } else { continue } eventType := method.Type.In(1) l.HandlerFunctions[eventType] = append(l.HandlerFunctions[eventType], v.Method(i)) slog.Debug("Registered Handler", "at", t.String()+"."+method.Name, "for", eventType.String()) } } } // 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) Start() (err error) { _ = *l // exit early on nil connection l.FindHandlers() err = l.Fire(OnStart{}) for err == nil { err = l.Fire(Tick{}) if err != nil { break } } if errors.Is(err, HandlerDoneErr{}) { err = errors.Unwrap(err) } return }