// Package pipes provide helpers to pipe states from one machine to another.
package pipes // TODO register disposal handlers, detach from source machines // TODO implement removal of pipes via: // - binding-struct // - tagging of handler structs import ( am ss ) // Add adds a pipe for an Add mutation between [source] and [target] machines, // for a single target state. // // targetState: defaults to sourceState func ( , am.Api, string, string, ) am.HandlerFinal { return add(false, , , , ) } // AddFlat is like [Add], but skips unnecessary mutations. func ( , am.Api, string, string, ) am.HandlerFinal { return add(true, , , , ) } func add( bool, , am.Api, string, string, ) am.HandlerFinal { if == "" { // TODO log, dont panic? panic(am.ErrStateMissing) } if == "" { = } // graph info := .SemLogger() .AddPipeOut(true, , .Id()) .AddPipeIn(true, , .Id()) // TODO optimize .OnDispose(gcHandler()) .OnDispose(gcHandler()) := am.S{} // include Exception when adding errors if strings.HasPrefix(, am.PrefixErr) { = am.S{am.StateException, } } return func( *am.Event) { // flat skips unnecessary mutations if && .Is() { return } else if { .Add(, nil) } else { .EvAdd(, , .Args) } } } // Remove adds a pipe for a Remove mutation between source and target // machines, for a single target state. // // targetState: defaults to sourceState func ( , am.Api, string, string, ) am.HandlerFinal { return remove(false, , , , ) } func ( , am.Api, string, string, ) am.HandlerFinal { return remove(true, , , , ) } func remove( bool, , am.Api, string, string, ) am.HandlerFinal { if == "" { panic(am.ErrStateMissing) } if == "" { = } // graph info := .SemLogger() .AddPipeOut(false, , .Id()) .AddPipeIn(false, , .Id()) // TODO optimize .OnDispose(gcHandler()) .OnDispose(gcHandler()) return func( *am.Event) { // flat skips unnecessary mutations if && .Not1() { return } else if { .Remove1(, nil) } else { .EvRemove1(, , .Args) } } } // ///// ///// ///// // ///// BINDS // ///// ///// ///// // BindAny binds a whole machine via the global AnyState handler and Set // mutation. The target mutates the same number of times as the source. func (, am.Api) error { // TODO assert target has all the source states := func( *am.Event) { := .Transition() // set if not set := .TargetStates() if .Is() { return } .Set(, .Args) } := &struct { am.HandlerFinal }{ : , } // graph info TODO? optimize in bulk := .SemLogger() for , := range .StateNames() { .AddPipeOut(true, , .Id()) .AddPipeIn(true, , .Id()) } // TODO optimize .OnDispose(gcHandler()) .OnDispose(gcHandler()) return .BindHandlers() } // BindConnected binds a [ss.ConnectedSchema] machine to 4 custom states. Each // one is optional and bound with Add/Remove. func ( , am.Api, , , , string, ) error { := &struct { am.HandlerFinal am.HandlerFinal am.HandlerFinal am.HandlerFinal am.HandlerFinal am.HandlerFinal am.HandlerFinal am.HandlerFinal }{} := ss.ConnectedStates if != "" { . = Add(, , .Disconnected, ) . = Remove(, , .Disconnected, ) } if != "" { . = Add(, , .Connecting, ) . = Remove(, , .Connecting, ) } if != "" { . = Add(, , .Connected, ) . = Remove(, , .Connected, ) } if != "" { . = Add(, , .Disconnecting, ) . = Remove(, , .Disconnecting, ) } return .BindHandlers() } // BindErr binds Exception to a custom state using Add. Empty state defaults to // [am.StateException], and a custom state will also add [am.StateException]. func (, am.Api, string) error { if == "" { = am.StateException } := &struct { am.HandlerFinal }{ : Add(, , am.StateException, ), } return .BindHandlers() } // BindStart binds Start to custom states using Add/Remove. Empty state // defaults to Start. func ( , am.Api, , string, ) error { := &struct { am.HandlerFinal am.HandlerFinal }{ : Add(, , ss.BasicStates.Start, ), : Remove(, , ss.BasicStates.Start, ), } return .BindHandlers() } // BindReady binds Ready to custom states using Add/. Empty state // defaults to Ready. func ( , am.Api, , string, ) error { := &struct { am.HandlerFinal am.HandlerFinal }{ : Add(, , ss.BasicStates.Ready, ), : Remove(, , ss.BasicStates.Ready, ), } return .BindHandlers() } // Bind binds an arbitrary state to custom states using Add and Remove. // Empty [activeState] and [inactiveState] defaults to [source]. Each binding // creates a separate handler struct, unlike in BindMany. func ( , am.Api, string, , string, ) error { if == "" { = } if == "" { = } // TODO assert source has state // TODO assert target has activeState and inactiveState // define handlers for each mutation var []reflect.StructField := Add(, , , ) := Remove(, , , ) = append(, reflect.StructField{ Name: + am.SuffixState, Type: reflect.TypeOf(), }) = append(, reflect.StructField{ Name: + am.SuffixEnd, Type: reflect.TypeOf(), }) // define a struct with handlers := reflect.StructOf() := reflect.New().Elem() // set handlers .Field(0).Set(reflect.ValueOf()) .Field(1).Set(reflect.ValueOf()) // bind handlers := .Addr().Interface() return .BindHandlers() } // TODO godoc func ( , am.Api, , am.S, ) error { if len() != len() { return fmt.Errorf("%w: source and target states len mismatch", am.ErrStateMissing) } var []reflect.StructField var []am.HandlerFinal // define handlers for each state for , := range { := Add(, , , []) := Remove(, , , []) = append(, reflect.StructField{ Name: + am.SuffixState, Type: reflect.TypeOf(), }) = append(, reflect.StructField{ Name: + am.SuffixEnd, Type: reflect.TypeOf(), }) = append(, , ) } // define a struct with handlers := reflect.StructOf() := reflect.New().Elem() // set handlers for , := range { .Field().Set(reflect.ValueOf()) } // bind handlers := .Addr().Interface() return .BindHandlers() } // ///// ///// ///// // ///// INTERNAL // ///// ///// ///// func gcHandler( am.Api) am.HandlerDispose { return func( string, context.Context) { .SemLogger().RemovePipes() } }