machine

package
v0.19.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 23, 2026 License: MIT Imports: 20 Imported by: 29

README

🦾 /pkg/machine

cd /

[!NOTE] asyncmachine-go is a pathless control-flow graph with a consensus (AOP, actor model, state-machine).

/pkg/machine is a nondeterministic, multi-state, clock-based, relational, optionally accepting, and non-blocking state machine. It's a form of a rules engine that can orchestrate blocking APIs into fully controllable async state-machines. Write ops are state mutations, read ops are state checking, and subscriptions are state waiting. It's dependency-free and a building block of a larger project.

Installation

import am "github.com/pancsta/asyncmachine-go/pkg/machine"

Features

Features are explained using Mermaid flow diagrams, and the headers link to relevant sections of the manual.

Multi-state

Many states can be active at the same time.

Clock and state contexts

States have clocks that produce contexts (odd = active; even = inactive).

Queue

Queue of mutations enable lock-free actor model.

AOP handlers

States are Aspects with Enter, State, Exit, and End handlers.

Negotiation

Transitions are cancellable (during the negotiation phase).

Relations

States are connected via Require, Remove, and Add relations.

Subscriptions

Channel-broadcast waiting on clock values.

Error handling

Error is a state, handled just like any other mutation.

val, err := someOp()
if err != nil {
    mach.AddErr(err, nil)
    return // no err needed
}
Tracers

Synchronous tracers for internal events.

TransitionInit TransitionStart TransitionEnd HandlerStart HandlerEnd
MachineInit MachineDispose NewSubmachine QueueEnd SchemaChange VerifyStates

Usage

Raw Strings
// ProcessingFile to FileProcessed
// 1 async and 1 sync state
package main

import am "github.com/pancsta/asyncmachine-go/pkg/machine"

func main() {
    // init the state machine
    mach := am.New(nil, am.Schema{
        "ProcessingFile": { // async
            Remove: am.S{"FileProcessed"},
        },
        "FileProcessed": { // async
            Remove: am.S{"ProcessingFile"},
        },
        "InProgress": { // sync
            Auto:    true,
            Require: am.S{"ProcessingFile"},
        },
    }, nil)
    mach.HandlersBind(&Handlers{
        Filename: "README.md",
    })
    // change the state
    mach.Add1("ProcessingFile", nil)
    // wait for completed
    select {
    case <-time.After(5 * time.Second):
        println("timeout")
    case <-mach.WhenErr(nil):
        println("err:", mach.Err())
    case <-mach.When1("FileProcessed", nil):
        println("done")
    }
}

type Handlers struct {
    Filename string
}

// negotiation handler
func (h *Handlers) ProcessingFileEnter(e *am.Event) bool {
    // read-only ops
    // decide if moving fwd is ok
    // no blocking
    // lock-free critical section
    return true
}

// final handler
func (h *Handlers) ProcessingFileState(e *am.Event) {
    // read & write ops
    // no blocking
    // lock-free critical section
    mach := e.Machine
    // clock-based expiration context
    stateCtx := mach.NewStateCtx("ProcessingFile")
    // unblock
    go func() {
        // re-check the state ctx
        if stateCtx.Err() != nil {
            return // expired
        }
        // blocking call
        err := processFile(h.Filename, stateCtx)
        // re-check the state ctx after a blocking call
        if stateCtx.Err() != nil {
            return // expired
        }
        if err != nil {
            mach.AddErr(err, nil)
            return
        }
        // move to the next state in the flow
        mach.Add1("FileProcessed", nil)
    }()
}
Waiting

Subscriptions do not allocate goroutines and channels are reused.

// wait until Foo becomes active
<-mach.When1("Foo", nil)

// wait until Foo becomes inactive
<-mach.WhenNot1("Foo", nil)

// wait for Foo to be activated with an arg ID=123
<-mach.WhenArgs("Foo", am.A{"ID": 123}, nil)

// wait for Foo to have a tick >= 6
<-mach.WhenTime1("Foo", 6, nil)

// wait for Foo to have a tick >= 6 and Bar tick >= 10
<-mach.WhenTime(am.S{"Foo", "Bar"}, am.Time{6, 10}, nil)

// wait for Foo to have a tick increased by 2
<-mach.WhenTicks("Foo", 2, nil)

// wait for next time Foo is active (even if currently active)
<-mach.WhenNextActive("Foo", nil)

// wait for a mutation to execute
<-mach.WhenQueue(mach.Add1("Foo", nil))

// wait for an error
<-mach.WhenErr(nil)

// wait on a time query
<-mach.WhenQuery(func(c am.Clock) bool {
    // Foo activated >5 times and Bar activated twice as much
    return c["Foo"] >= 10 && c["Bar"] >= 2*c["Foo"]
}, nil)
State Targeting

Transition is available within transition handlers.

var (
    tx am.Transition
    t1 am.Time
    t2 am.Time
)

// was Foo added and Bar removed?
added, removed := tx.TimeIndexDiff()
added.Is1("Foo") && removed.Is1("Bar")

// was Foo called?
tx.TimeIndexCalled().Is1("Foo")

// was Foo active before?
tx.TimeIndexBefore().Is1("Foo")

// will Foo be active after?
tx.TimeIndexAfter().Is1("Foo")

// is Foo queued?
mach.WillBe1("Foo")

// is Foo queued at the end?
mach.WillBe1("Foo", am.PositionLast)

// number of states which ticked
len(tx.TimeIndexTimeDiff().ActiveStates())

// did Foo tick between t1 and t2?
t2.DiffSince(t1).NonZeroStates().Is1("Foo")
Schema File
// BasicStatesDef contains all the states of the Basic state machine.
type BasicStatesDef struct {
    *am.StatesBase

    // ErrNetwork indicates a generic network error.
    ErrNetwork string
    // ErrHandlerTimeout indicates one of state machine handlers has timed out.
    ErrHandlerTimeout string

    // Start indicates the machine should be working. Removing start can force
    // stop the machine.
    Start string
    // Ready indicates the machine meets criteria to perform work, and requires
    // Start.
    Ready string
    // Healthcheck is a periodic request making sure that the machine is still
    // alive.
    Healthcheck string
}

var BasicSchema = am.Schema{

    // Errors

    ssB.ErrNetwork:        {Require: S{Exception}},
    ssB.ErrHandlerTimeout: {Require: S{Exception}},

    // Basics

    ssB.Start:       {},
    ssB.Ready:       {Require: S{ssB.Start}},
    ssB.Healthcheck: {},
}
Passing Args
// Example with typed state names (ss) and typed arguments (A).
mach.Add1(ss.KillingWorker, Pass(&A{
    ConnAddr:   ":5555",
    WorkerAddr: ":5556",
}))
Mutations and Relations

While mutations are the heartbeat of asyncmachine, it's the relations which define the rules of the flow. Check out the relations playground and quiz yourself (maybe a fancier playground).

mach := newMach("DryWaterWet", am.Schema{
    "Wet": {
        Require: am.S{"Water"},
    },
    "Dry": {
        Remove: am.S{"Water"},
    },
    "Water": {
        Add:    am.S{"Wet"},
        Remove: am.S{"Dry"},
    },
})
mach.Add1("Dry", nil)
mach.Add1("Water", nil)
// TODO quiz: is Wet active?

Demos

go run github.com/pancsta/asyncmachine-go/tools/cmd/am-dbg@latest \
  --import-data https://pancsta.github.io/assets/asyncmachine-go/am-dbg-exports/secai-cook.gob.br \
  mach://cook

Examples

All examples and benchmarks can be found in /examples.

Devtools

Apps

asyncmachine-go synchronizes state for the following projects:

Documentation

API

The common API methods are listed below. There's more for local state machines, but all of these are also implemented in the transparent RPC layer.

// TODO update
// A (arguments) is a map of named arguments for a Mutation.
type A map[string]any
// S (state names) is a string list of state names.
type S []string
type Time []uint64
type Clock map[string]uint64
type Result int
type Schema = map[string]State

// Api is a subset of Machine for alternative implementations.
type Api interface {
  // ///// REMOTE

  // Mutations (remote)

  Add1(state string, args A) Result
  Add(states S, args A) Result
  Remove1(state string, args A) Result
  Remove(states S, args A) Result
  AddErr(err error, args A) Result
  AddErrState(state string, err error, args A) Result
  Toggle(states S, args A) Result
  Toggle1(state string, args A) Result
  Set(states S, args A) Result

  // Traced mutations (remote)

  EvAdd1(event *Event, state string, args A) Result
  EvAdd(event *Event, states S, args A) Result
  EvRemove1(event *Event, state string, args A) Result
  EvRemove(event *Event, states S, args A) Result
  EvAddErr(event *Event, err error, args A) Result
  EvAddErrState(event *Event, state string, err error, args A) Result
  EvToggle(event *Event, states S, args A) Result
  EvToggle1(event *Event, state string, args A) Result

  // Waiting (remote)

  WhenArgs(state string, args A, ctx context.Context) <-chan struct{}

  // ///// LOCAL

  // Checking (local)

  Err() error
  IsErr() bool
  Is(states S) bool
  Is1(state string) bool
  Any(states ...S) bool
  Any1(state ...string) bool
  Not(states S) bool
  Not1(state string) bool
  IsTime(time Time, states S) bool
  WasTime(time Time, states S) bool
  IsClock(clock Clock) bool
  WasClock(clock Clock) bool
  Has(states S) bool
  Has1(state string) bool
  CanAdd(states S, args A) Result
  CanAdd1(state string, args A) Result
  CanRemove(states S, args A) Result
  CanRemove1(state string, args A) Result
  Transition() *Transition
  IsLocal() bool
  ErrInternal() <-chan error

  // Waiting (local)

  When(states S, ctx context.Context) <-chan struct{}
  When1(state string, ctx context.Context) <-chan struct{}
  WhenNot(states S, ctx context.Context) <-chan struct{}
  WhenNot1(state string, ctx context.Context) <-chan struct{}
  WhenTime(states S, times Time, ctx context.Context) <-chan struct{}
  WhenTime1(state string, tick uint64, ctx context.Context) <-chan struct{}
  WhenTicks(state string, ticks int, ctx context.Context) <-chan struct{}
  WhenNextActive(state string, ctx context.Context) <-chan struct{}
  WhenQuery(query func(clock Clock) bool, ctx context.Context) <-chan struct{}
  WhenErr(ctx context.Context) <-chan struct{}
  WhenQueue(tick Result) <-chan struct{}

  // Getters (local)

  StateNames() S
  ActiveStates(states S) S
  Tick(state string) uint64
  Clock(states S) Clock
  Time(states S) Time
  QueueTick() uint64
  MachineTick() uint32
  QueueLen() uint16
  NewStateCtx(state string, e ...*Event) context.Context
  Export() (*Serialized, Schema, error)
  Schema() Schema
  Switch(groups ...S) string
  Groups() (map[string][]int, []string)
  Index(states S) []int
  Index1(state string) int

  // Misc (local)

  Id() string
  ParentId() string
  ParseStates(states S) S
  Tags() []string
  Context() context.Context
  ContextParent() context.Context
  String() string
  StringAll() string
  Log(msg string, args ...any)
  SemLogger() SemLogger
  Inspect(states S) string
  HandlersBind(handlers any, opts ...BindOpts) (string, error)
  HandlersBindMaps(negotiations map[string]HandlerNegotiation,
    finals map[string]HandlerFinal, opts ...BindOpts) (string, error)
  HandlersDetach(bindingId string) error
  Handlers() []string
  StatesVerified() bool
  Tracers() []Tracer
  TracerDetach(id string) error
  TracerBind(tracer Tracer) (string, error)
  AddBreakpoint(added S, removed S, strict bool)
  AddBreakpoint1(added string, removed string, strict bool)
  Dispose()
  WhenDisposed() <-chan struct{}
  IsDisposed() bool
  OnDispose(fn HandlerDispose)
}

Tests

It's very easy to get a grasp of how asyncmachine works by reading the idiomatic test suite. Consider the example below of a method used to wait for certain arguments passing via a state activation:

func TestWhenArgs(t *testing.T) {
    // init
    m := NewRels(t, nil)

    // bind
    whenCh := m.WhenArgs("B", A{"foo": "bar"}, nil)

    // incorrect args
    m.Add1("B", A{"foo": "foo"})
    select {
    case <-whenCh:
        t.Fatal("whenCh shouldnt be selected")
    default:
        // pass
    }

    // correct args
    m.Add1("B", A{"foo": "bar"})
    select {
    case <-whenCh:
        // pass
    default:
        t.Fatal("whenCh should be selected")
    }

    // dispose
    m.Dispose()
    <-m.WhenDisposed()
}

Status

Release Candidate, semantically versioned, partially optimized.

Concepts

asyncmachine is loosely based on the following concepts:

State-Oriented Programming

This is a new term which could possibly encapsulate the unique way of modeling the flow using asyncmachine. Unlike common state machines, there are no transition paths between states, and the activation / deactivation is decided by the state consensus. The consensus is calculated from a mutation, active states, relations between states, and negotiating methods. Just like object-oriented programming solves domain complexity, state-oriented programming tries to solve the unpredictability of the flow.

monorepo

Go back to the monorepo root to continue reading.

Documentation

Overview

Package machine is a nondeterministic, multi-state, clock-based, relational, optionally-accepting, and non-blocking state machine.

Index

Examples

Constants

View Source
const (
	// EnvAmDebug enables a simple debugging mode (eg long timeouts).
	// "2" logs to stdout (where applicable)
	// "1" | "2" | "" (default)
	EnvAmDebug = "AM_DEBUG"
	// EnvAmTestRunner indicates a CI test runner.
	EnvAmTestRunner = "AM_TEST_RUNNER"
	// EnvAmLog sets the log level. TODO add separate AM_DBG_LOG
	// "1" | "2" | "3" | "4" | "5" | "" (default)
	EnvAmLog = "AM_LOG"
	// EnvAmDetectEval detects evals directly in handlers (use in tests) and also
	// logs panicked funcs.
	// TODO rename EnvAmDiagnose, log names of mach.Go sources
	EnvAmDetectEval = "AM_DETECT_EVAL"
	// EnvAmTraceFilter will remove its contents from stack traces, shortening
	// them.
	EnvAmTraceFilter = "AM_TRACE_FILTER"
	// EnvAmTestDebug activates debugging in tests.
	EnvAmTestDebug = "AM_TEST_DEBUG"
	// EnvAmTestDbgAddr enables test loop integration (no parallel and dbg relay)
	EnvAmTestDbgAddr = "AM_TEST_DBG_ADDR"
	SuffixEnter      = "Enter"
	SuffixExit       = "Exit"
	SuffixState      = "State"
	SuffixEnd        = "End"
	PrefixErr        = "Err"
	// StateAny is a name of a meta-state used in catch-all global handlers.
	StateAny = "Any"
	// StateException is the name of the predefined Exception state.
	StateException = "Exception"
	// StateHeartbeat is the name of the predefined Heartbeat state.
	StateHeartbeat = "Heartbeat"
	// StateHealthcheck is the name of the predefined Healthcheck state.
	StateHealthcheck = "Healthcheck"
	// StateStart is the name of the predefined Start state.
	StateStart = "Start"
	// StateDisposing is the name of the predefined Disposing state.
	StateDisposing = "Disposing"
	// StateReady is the name of the predefined Ready state.
	StateReady = "Ready"
	// StateMachineRestored is the name of the predefined MachineRestored state.
	StateMachineRestored = "MachineRestored"
	// TagArgsReq marks a state which can only be added with relevant args.
	TagArgsReq = "ArgsReq"
)
View Source
const APrefix = "_am"

Variables

View Source
var (
	// ErrStateUnknown indicates that the state is unknown.
	ErrStateUnknown = errors.New("state unknown")
	// ErrStateInactive indicates that a necessary state isn't active.
	ErrStateInactive = errors.New("state not active")
	// ErrCanceled indicates that a transition was canceled.
	ErrCanceled = errors.New("transition canceled")
	// ErrQueued indicates that a mutation was queued. Queuing is a feature, not
	// an error, but it may be considered as such in certain cases.
	ErrQueued = errors.New("transition queued")
	// ErrInvalidArgs indicates that arguments are invalid.
	ErrInvalidArgs = errors.New("invalid arguments")
	// ErrHandlerTimeout indicates that a mutation timed out.
	ErrHandlerTimeout = errors.New("handler timeout")
	// ErrEvalTimeout indicates that an eval func timed out.
	ErrEvalTimeout = errors.New("eval timeout")
	// ErrTimeout indicates that a generic timeout occurred.
	ErrTimeout = errors.New("timeout")
	// ErrStateMissing indicates that states are missing.
	ErrStateMissing = errors.New("missing states")
	// ErrRelation indicates that a relation definition is invalid.
	ErrRelation = errors.New("relation error")
	// ErrDisposed indicates that the machine has been disposed.
	ErrDisposed = errors.New("machine disposed")
	// ErrSchema indicates an issue with the machine schema.
	ErrSchema = errors.New("schema error")
	// ErrNestedEval indicated an eval called in another eval or directly inside a
	// transition handler's body.
	ErrNestedEval = errors.New("eval nested in handler or eval")
)
View Source
var CtxKey = &CtxKeyName{}
View Source
var LogArgs = []string{
	"name", "id", "port", "addr", "err", "path", "url",
	"uri",
}

LogArgs is a list of common argument names to be logged. Useful for debugging.

View Source
var LogArgsMaxLen = 20

LogArgsMaxLen is the default maximum length of the arg's string representation.

Functions

func AMerge added in v0.9.0

func AMerge[K comparable, V any](maps ...map[K]V) map[K]V

AMerge merges 2 or more maps into 1. Useful for passing args from many packages.

func ArgIndex added in v0.19.0

func ArgIndex(arg ArgsApi) string

ArgIndex return an index of [arg] inside A.

func Capitalize added in v0.18.4

func Capitalize(s string) string

func EvToCtx added in v0.18.8

func EvToCtx(ctx context.Context, e *Event) context.Context

EvToCtx will inject *Event into [ctx] under CtxKey. Useful for passing events for tracing, does not work with Machine.Fork.

func Hash added in v0.19.0

func Hash(in string, l int) string

Hash is a general hashing function.

func IsActiveTick added in v0.5.0

func IsActiveTick(tick uint64) bool

IsActiveTick returns true if the tick represents an active state (odd number).

func IsHandler added in v0.8.0

func IsHandler(states S, method string) (string, string)

IsHandler checks if a method name is a handler method, by returning a state name.

func IsQueued added in v0.15.0

func IsQueued(result Result) bool

IsQueued returns true if the mutation has been queued, and the result represents the queue time it will be processed.

func MutationFormatArgs added in v0.15.0

func MutationFormatArgs(matched map[string]string) string

func NewLogArgsMapper added in v0.18.0

func NewLogArgsMapper(
	maxLen int, names []string,
) func(args A) map[string]string

NewLogArgsMapper returns a mapper function for Opts.LogArgs. Useful for debugging untyped argument maps. Usually [names] extend defaults from LogArgs.

maxLen: maximum length of the arg's string representation). Defaults to LogArgsMaxLen,

func NewStateGroups added in v0.8.0

func NewStateGroups[G any](groups G, mixins ...any) G

NewStateGroups accepts the target group with values (FooGroupsDef) and inherited GroupsDefs (optionally).

func NewStates added in v0.8.0

func NewStates[G States](states G) G

func NextActive added in v0.18.8

func NextActive(tick uint64) uint64

NextActive returns the absolute tick when the state will be active again (excluding the currently active tick).

func NextActiveIn added in v0.18.8

func NextActiveIn(tick uint64) int

NextActiveIn returns the number of ticks until the state will be active again (excluding the currently active tick).

func NextInactive added in v0.18.8

func NextInactive(tick uint64) uint64

NextInactive returns the absolute tick when the state will be inactive again (excluding the currently inactive tick).

func NextInactiveIn added in v0.18.8

func NextInactiveIn(tick uint64) int

NextInactiveIn returns the number of ticks until the state will be inactive again (excluding the currently inactive tick).

func OptCtx added in v0.18.8

func OptCtx(ctxs []context.Context) context.Context

OptCtx will return the first context.Context from a list.

func ParseArgs added in v0.8.0

func ParseArgs[G ArgsApi](args A) *G

ParseArgs is ParseArgsCheck but without the check.

func ParseArgsCheck added in v0.19.0

func ParseArgsCheck[G ArgsApi](args A) (*G, bool)

ParseArgsCheck parses A into typed arguments described by the passed generic type. Returns

func StatesEqual added in v0.8.0

func StatesEqual(states1 S, states2 S) bool

StatesEqual is deprecated, use S.Equal.

func StatesToIndex added in v0.12.0

func StatesToIndex(index S, states S) []int

StatesToIndex is deprecated, use S.Index.

func TestMockClock added in v0.19.0

func TestMockClock(mach *Machine, clock Clock)

TestMockClock mocks the internal clock of the machine. Only for testing.

Types

type A

type A map[string]any

A (arguments) is a map of named arguments for a Mutation.

func OptArgs added in v0.18.6

func OptArgs(args []A) A

OptArgs will return the first A from a list.

func Pass added in v0.8.0

func Pass(args ...ArgsApi) A

Pass accepts pointers of ArgsBase to pass to the machine as A.

func PassMerge added in v0.8.0

func PassMerge(args ...A) A

PassMerge merged one or more A into A. Technically a `map[string]any` merge.

type ACheck added in v0.19.0

type ACheck struct {
	Args
	// TODO close these on dispose and deadline
	CheckDone chan struct{}
	// Was the mutation canceled?
	Canceled bool
}

ACheck with a CheckDone chan which gets closed by the machine once it's processed. Can cause chan leaks when misused. Only for Can* checks.

type AException added in v0.19.0

type AException struct {
	Args
	Err      error `log:"err"`
	ErrTrace string
	Panic    *ExceptionArgsPanic

	// TODO handle
	Fn string `log:"fn"`

	TargetStates S
	CalledStates S
	TimeBefore   Time
	TimeAfter    Time
	Event        *Event
}

func (AException) ArgsState added in v0.19.0

func (AException) ArgsState() string

type Api added in v0.8.0

type Api interface {

	// Add1 is [Machine.Add1].
	Add1(state string, args A) Result
	// Add is [Machine.Add].
	Add(states S, args A) Result
	// Remove1 is [Machine.Remove1].
	Remove1(state string, args A) Result
	// Remove is [Machine.Remove].
	Remove(states S, args A) Result
	// AddErr is [Machine.AddErr].
	AddErr(err error, args A) Result
	// AddErrState is [Machine.AddErrState].
	AddErrState(state string, err error, args A) Result
	// Toggle is [Machine.Toggle].
	Toggle(states S, args A) Result
	// Toggle1 is [Machine.Toggle1].
	Toggle1(state string, args A) Result
	// Set is [Machine.Set].
	Set(states S, args A) Result

	// EvAdd1 is [Machine.EvAdd1].
	EvAdd1(event *Event, state string, args A) Result
	// EvAdd is [Machine.EvAdd].
	EvAdd(event *Event, states S, args A) Result
	// EvRemove1 is [Machine.EvRemove1].
	EvRemove1(event *Event, state string, args A) Result
	// EvRemove is [Machine.EvRemove].
	EvRemove(event *Event, states S, args A) Result
	// EvAddErr is [Machine.EvAddErr].
	EvAddErr(event *Event, err error, args A) Result
	// EvAddErrState is [Machine.EvAddErrState].
	EvAddErrState(event *Event, state string, err error, args A) Result
	// EvToggle is [Machine.Toggle].
	EvToggle(event *Event, states S, args A) Result
	// EvToggle1 is [Machine.Toggle1].
	EvToggle1(event *Event, state string, args A) Result

	// WhenArgs is [Machine.WhenArgs].
	WhenArgs(state string, args A, ctx context.Context) <-chan struct{}

	// Err is [Machine.Err].
	Err() error
	// IsErr is [Machine.IsErr].
	IsErr() bool
	// Is is [Machine.Is].
	Is(states S) bool
	// Is1 is [Machine.Is1].
	Is1(state string) bool
	// Any is [Machine.Any].
	Any(states ...S) bool
	// Any1 is [Machine.Any1].
	Any1(state ...string) bool
	// Not is [Machine.Not].
	Not(states S) bool
	// Not1 is [Machine.Not1].
	Not1(state string) bool
	// IsTime is [Machine.IsTime].
	IsTime(time Time, states S) bool
	// WasTime is [Machine.WasTime].
	WasTime(time Time, states S) bool
	// IsClock is [Machine.IsClock].
	IsClock(clock Clock) bool
	// WasClock is [Machine.WasClock].
	WasClock(clock Clock) bool
	// Has is [Machine.Has].
	Has(states S) bool
	// Has1 is [Machine.Has1].
	Has1(state string) bool
	// CanAdd is [Machine.CanAdd].
	CanAdd(states S, args A) Result
	// CanAdd1 is [Machine.CanAdd1].
	CanAdd1(state string, args A) Result
	// CanRemove is [Machine.CanRemove].
	CanRemove(states S, args A) Result
	// CanRemove1 is [Machine.CanRemove1].
	CanRemove1(state string, args A) Result
	// Transition is [Machine.Transition].
	Transition() *Transition
	// IsLocal is [Machine.IsLocal].
	IsLocal() bool
	// ErrInternal is [Machine.ErrInternal].
	ErrInternal() <-chan error

	// When is [Machine.When].
	When(states S, ctx context.Context) <-chan struct{}
	// When1 is [Machine.When1].
	When1(state string, ctx context.Context) <-chan struct{}
	// WhenNot is [Machine.WhenNot].
	WhenNot(states S, ctx context.Context) <-chan struct{}
	// WhenNot1 is [Machine.WhenNot1].
	WhenNot1(state string, ctx context.Context) <-chan struct{}
	// WhenTime is [Machine.WhenTime].
	WhenTime(states S, times Time, ctx context.Context) <-chan struct{}
	// WhenTime1 is [Machine.WhenTime1].
	WhenTime1(state string, tick uint64, ctx context.Context) <-chan struct{}
	// WhenTicks is [Machine.WhenTicks].
	WhenTicks(state string, ticks int, ctx context.Context) <-chan struct{}
	// WhenNextActive is [Machine.WhenNextActive].
	WhenNextActive(state string, ctx context.Context) <-chan struct{}
	// WhenQuery is [Machine.WhenQuery].
	WhenQuery(query func(clock Clock) bool, ctx context.Context) <-chan struct{}
	// WhenErr is [Machine.WhenErr].
	WhenErr(ctx context.Context) <-chan struct{}
	// WhenQueue is [Machine.WhenQueue].
	WhenQueue(tick Result) <-chan struct{}

	// StateNames is [Machine.StateNames].
	StateNames() S
	// ActiveStates is [Machine.ActiveStates].
	ActiveStates(states S) S
	// Tick is [Machine.Tick].
	Tick(state string) uint64
	// Clock is [Machine.Clock].
	Clock(states S) Clock
	// Time is [Machine.Time].
	Time(states S) Time
	// QueueTick is [Machine.QueueTick].
	QueueTick() uint64
	// MachineTick is [Machine.MachineTick].
	MachineTick() uint32
	// QueueLen is [Machine.QueueLen].
	QueueLen() uint16
	// NewStateCtx is [Machine.NewStateCtx].
	NewStateCtx(state string, e ...*Event) context.Context
	// Export is [Machine.Export].
	Export() (*Serialized, Schema, error)
	// Schema is [Machine.Schema].
	Schema() Schema
	// Switch is [Machine.Switch].
	Switch(groups ...S) string
	// Groups is [Machine.Groups].
	Groups() (map[string][]int, []string)
	// Index is [Machine.Index].
	Index(states S) []int
	// Index1 is [Machine.Index1].
	Index1(state string) int

	// Id is [Machine.Id].
	Id() string
	// ParentId is [Machine.ParentId].
	ParentId() string
	// ParseStates is [Machine.ParseStates].
	ParseStates(states S) S
	// Tags is [Machine.Tags].
	Tags() []string
	// Context is [Machine.Ctx].
	Context() context.Context
	// ContextParent is [Machine.ContextParent].
	ContextParent() context.Context
	// String is [Machine.String].
	String() string
	// StringAll is [Machine.StringAll].
	StringAll() string
	// Log is [Machine.Log].
	Log(msg string, args ...any)
	// SemLogger is [Machine.SemLogger].
	SemLogger() SemLogger
	// Inspect is [Machine.Inspect].
	Inspect(states S) string
	// HandlersBind is [Machine.HandlersBind].
	HandlersBind(handlers any, opts ...BindOpts) (string, error)
	// HandlersBindMaps is [Machine.HandlersBindMaps].
	HandlersBindMaps(negotiations map[string]HandlerNegotiation,
		finals map[string]HandlerFinal, opts ...BindOpts) (string, error)
	// HandlersDetach is [Machine.HandlersDetach].
	HandlersDetach(bindingId string) error
	// Handlers is [Machine.Handlers].
	Handlers() []string
	// StatesVerified is [Machine.StatesVerified].
	StatesVerified() bool
	// Tracers is [Machine.Tracers].
	Tracers() []Tracer
	// TracerDetach is [Machine.TracerDetach].
	TracerDetach(id string) error
	// TracerBind is [Machine.TracerBind].
	TracerBind(tracer Tracer) (string, error)
	// AddBreakpoint is [Machine.AddBreakpoint].
	AddBreakpoint(added S, removed S, strict bool)
	// AddBreakpoint1 is [Machine.AddBreakpoint1].
	AddBreakpoint1(added string, removed string, strict bool)
	// Dispose is [Machine.Dispose].
	Dispose()
	// WhenDisposed is [Machine.WhenDisposed].
	WhenDisposed() <-chan struct{}
	// IsDisposed is [Machine.IsDisposed].
	IsDisposed() bool
	// OnDispose is [Machine.OnDispose].
	OnDispose(fn HandlerDispose)

	// BindHandlers is deprecated, use [Api.HandlersBind].
	BindHandlers(handlers any, opts ...BindOpts) (string, error)
	// DetachHandlers is deprecated, use [Api.HandlersDetach].
	DetachHandlers(bindingId string) error
	// DetachTracer is deprecated, use [Api.TracerDetach].
	DetachTracer(id string) error
	// BindTracer is deprecated, use [Api.TracerBind].
	BindTracer(tracer Tracer) (string, error)
}

Api is a subset of Machine for alternative implementations.

type Args added in v0.19.0

type Args struct {
	ArgsBase
}

Args for this pkg. Do not reuse.

func (Args) ArgsPrefix added in v0.19.0

func (Args) ArgsPrefix() string

type ArgsApi added in v0.19.0

type ArgsApi interface {
	// ArgsPrefix returns the argument prefix inside the [A] map. Should be
	// provided by the implementation.
	ArgsPrefix() string
	// ArgsState represents the state this argument struct belongs to.
	// Defaults to [StateAny].
	ArgsState() string
}

ArgsApi is the base interface for typed arguments.

type ArgsBase added in v0.19.0

type ArgsBase struct {
	ArgsApi
}

ArgsBase is the base implementation of ArgsApi interface.

func (ArgsBase) ArgsPrefix added in v0.19.0

func (a ArgsBase) ArgsPrefix() string

ArgsPrefix is a fallback to a stack trace hash of the implementation symbol. Each pkg should overwrite this method.

func (ArgsBase) ArgsState added in v0.19.0

func (a ArgsBase) ArgsState() string

State is the default Any state.

func (ArgsBase) Clone added in v0.19.0

func (a ArgsBase) Clone() ArgsApi

Clone for deep cloning.

type BindOpts added in v0.19.0

type BindOpts struct {
	// Id of this binding, optional.
	Id string
	// method name will have this prefix removed
	MethodPrefixTrim string
	// only states with this prefix
	StatePrefix string
}

BindOpts define how the methods are bound to the state machine. Optional.

type CallSignature added in v0.18.8

type CallSignature struct {
	// states to mutate (required)
	States S

	// true if not an Add mutation (optional)
	IsRemove bool
	// call name (optional)
	Name string
	// call description (optional)
	Desc string
	// required args (optional)
	Needed []string
	// optional args (optional)
	Optional []string
	// value enums per arg name (optional)
	Values map[string][]string
	// JSON schemas per arg name (optional)
	Schemas map[string][]string
}

CallSignature binds states to args and optionally to enum values.

func (*CallSignature) Args added in v0.18.8

func (c *CallSignature) Args() []string

func (*CallSignature) String added in v0.18.9

func (c *CallSignature) String() string

type Clock added in v0.7.0

type Clock map[string]uint64

Clock is a map of state names to their tick values. It's like Time but indexed by string, instead of int. See TimeIndex for a more advanced data structure.

type CtxBinding added in v0.8.0

type CtxBinding struct {
	Ctx    context.Context
	Cancel context.CancelFunc
}

type CtxKeyName added in v0.8.0

type CtxKeyName struct{}

type CtxValue added in v0.8.0

type CtxValue struct {
	Id    string
	State string
	Tick  uint64
	Event *Event
}

type DefaultRelationsResolver

type DefaultRelationsResolver struct {
	// TODO replace with TimeIndex, log(), Schema
	//   - for netmach mut filtering
	//   - for alternative impls
	Machine    *Machine
	Transition *Transition
	Index      S
	// contains filtered or unexported fields
}

DefaultRelationsResolver is the default implementation of the RelationsResolver with Add, Remove, Require and After. It can be overridden using Opts.Resolver. TODO refac RelationsDefault

func (*DefaultRelationsResolver) InboundRelationsOf added in v0.12.0

func (rr *DefaultRelationsResolver) InboundRelationsOf(toState string) (
	S, error,
)

InboundRelationsOf returns a list of states pointing to [toState]. Not thread safe.

func (*DefaultRelationsResolver) NewAutoMutation added in v0.12.0

func (rr *DefaultRelationsResolver) NewAutoMutation() (*Mutation, S)

NewAutoMutation implements RelationsResolver.GetAutoMutation.

func (*DefaultRelationsResolver) NewSchema added in v0.12.0

func (rr *DefaultRelationsResolver) NewSchema(schema Schema, states S)

func (*DefaultRelationsResolver) RelationsBetween added in v0.12.0

func (rr *DefaultRelationsResolver) RelationsBetween(
	fromState, toState string,
) ([]Relation, error)

RelationsBetween returns a list of outbound relations between fromState -> toState. Not thread safe.

func (*DefaultRelationsResolver) RelationsOf added in v0.12.0

func (rr *DefaultRelationsResolver) RelationsOf(fromState string) (
	[]Relation, error,
)

RelationsOf returns a list of relation types of the given state. Not thread safe.

func (*DefaultRelationsResolver) SortStates

func (rr *DefaultRelationsResolver) SortStates(states S)

SortStates implements RelationsResolver.SortStates.

func (*DefaultRelationsResolver) TargetStates added in v0.12.0

func (rr *DefaultRelationsResolver) TargetStates(
	t *Transition, statesToSet, index S,
) S

TargetStates implements RelationsResolver.TargetStates.

type Event

type Event struct {
	// Ctx is an optional context this event is constrained by.
	Ctx context.Context
	// Name of the handler method (eg FooState).
	Name string
	// MachineId is the ID of the parent machine.
	MachineId string
	// TransitionId is the ID of the parent transition.
	TransitionId string
	// Args is a map of named arguments for a Mutation.
	Args A
	// IsCheck is true if this event is a check event, fired by one of Can*()
	// methods. Useful for avoiding flooding the log with errors.
	IsCheck bool
	// contains filtered or unexported fields
}

Event struct represents a single event of a Mutation within a Transition. One event can have 0-n handlers.

func CtxToEv added in v0.18.8

func CtxToEv(ctx context.Context) *Event

func NewEvent added in v0.17.0

func NewEvent(mach *Machine, machApi Api) *Event

NewEvent creates a new Event struct with private fields initialized.

func OptEv added in v0.19.0

func OptEv(events []*Event) *Event

OptEv will return the first *Event from a list.

func (*Event) Clone added in v0.9.0

func (e *Event) Clone() *Event

Clone clones the event struct, making it writable.

func (*Event) Export added in v0.15.0

func (e *Event) Export() *Event

Export clones only the essential data of the Event. Useful for tracing vs GC.

func (*Event) IsValid added in v0.10.1

func (e *Event) IsValid() bool

IsValid confirm this event should still be processed. Useful for negotiation handlers, which can't use state context.

func (*Event) Machine

func (e *Event) Machine() *Machine

func (*Event) Mutation added in v0.5.0

func (e *Event) Mutation() *Mutation

Mutation returns the Mutation of an Event.

func (*Event) String added in v0.15.0

func (e *Event) String() string

func (*Event) SwapArgs added in v0.15.0

func (e *Event) SwapArgs(args A) *Event

SwapArgs clone the event and assign new args.

func (*Event) Transition added in v0.5.0

func (e *Event) Transition() *Transition

Transition returns the Transition of an Event.

type ExceptionArgsPanic

type ExceptionArgsPanic struct {
	CalledStates S
	StatesBefore S
	Transition   *Transition
	LastStep     *Step
	StackTrace   string
}

ExceptionArgsPanic is an optional argument ["panic"] for the StateException state which describes a panic within a Transition handler.

type ExceptionHandler

type ExceptionHandler struct{}

ExceptionHandler provide a basic StateException state support, as should be embedded into handler structs in most of the cases. Because ExceptionState will be called after Machine.HandlerDeadline, it should handle locks on its own (to not race with itself).

func (*ExceptionHandler) ExceptionState

func (eh *ExceptionHandler) ExceptionState(e *Event)

ExceptionState is a final entry handler for the StateException state. ArgsBase: - err error: The error that caused the StateException state. - panic *ExceptionArgsPanic: Optional details about the panic.

type HandlerChange added in v0.15.0

type HandlerChange func(mach *Machine, before, after Time)

type HandlerDispose added in v0.9.0

type HandlerDispose func(id string, ctx context.Context)

HandlerDispose is a machine disposal handler func signature.

type HandlerError added in v0.13.0

type HandlerError func(mach *Machine, err error)

type HandlerFinal added in v0.8.0

type HandlerFinal func(e *Event)

HandlerFinal is a final transition handler func signature.

type HandlerNegotiation added in v0.8.0

type HandlerNegotiation func(e *Event) bool

HandlerNegotiation is a negotiation transition handler func signature.

type IndexStateCtx added in v0.7.0

type IndexStateCtx map[string]*CtxBinding

IndexStateCtx is a map of (single) state names to a context cancel function

type IndexWhen added in v0.7.0

type IndexWhen map[string][]*WhenBinding

IndexWhen is a map of (single) state names to a list of activation or de-activation bindings

type IndexWhenArgs added in v0.7.0

type IndexWhenArgs map[string][]*WhenArgsBinding

IndexWhenArgs is a map of (single) state names to a list of args value bindings

type IndexWhenTime added in v0.7.0

type IndexWhenTime map[string][]*WhenTimeBinding

IndexWhenTime is a map of (single) state names to a list of time bindings

type InternalCheckFunc added in v0.15.1

type InternalCheckFunc func(states S) bool

type InternalLogFunc added in v0.15.1

type InternalLogFunc func(level LogLevel, msg string, args ...any)

type LastTxTracer added in v0.12.0

type LastTxTracer struct {
	*TracerNoOp
	// contains filtered or unexported fields
}

TODO add TTL, ctx

func NewLastTxTracer added in v0.12.0

func NewLastTxTracer() *LastTxTracer

NewLastTxTracer returns a Tracer that logs the last transition.

func (*LastTxTracer) Load added in v0.12.0

func (t *LastTxTracer) Load() *Transition

Load returns the last transition.

func (*LastTxTracer) String added in v0.12.0

func (t *LastTxTracer) String() string

func (*LastTxTracer) TracerId added in v0.19.0

func (t *LastTxTracer) TracerId() string

func (*LastTxTracer) TransitionEnd added in v0.12.0

func (t *LastTxTracer) TransitionEnd(transition *Transition)

type LogArgsMapperFn added in v0.15.0

type LogArgsMapperFn func(args A) map[string]string

LogArgsMapperFn is a function that maps arguments to be logged. Useful for debugging. See NewLogArgsMapper.

func LogArgsMapperMerge added in v0.18.2

func LogArgsMapperMerge(mappers ...LogArgsMapperFn) LogArgsMapperFn

LogArgsMapperMerge returns a single log args mapper from many log arg mappers.

type LogEntry added in v0.6.0

type LogEntry struct {
	Level LogLevel
	Text  string
}

type LogLevel

type LogLevel int

LogLevel defines the level of details in the produced log (0-5).

const (
	// LogNothing means no logging, including external msgs.
	LogNothing LogLevel = iota
	// LogExternal will show ony external user msgs.
	LogExternal
	// LogChanges means logging state changes and external msgs.
	LogChanges
	// LogOps means LogChanges + logging all the operations.
	LogOps
	// LogDecisions means LogOps + logging all the decisions behind them.
	LogDecisions
	// LogEverything means LogDecisions + all event and handler names, and more.
	LogEverything
)

func EnvLogLevel added in v0.7.0

func EnvLogLevel(name string) LogLevel

EnvLogLevel returns a log level from an environment variable, AM_LOG by default.

func (LogLevel) Level added in v0.18.0

func (l LogLevel) Level() string

Level returns a number form of a log level.

func (LogLevel) String added in v0.3.0

func (l LogLevel) String() string

String returns a human form of a log level.

type LoggerFn added in v0.15.0

type LoggerFn func(level LogLevel, msg string, args ...any)

LoggerFn is a logging function for the machine.

type Machine

type Machine struct {
	// Maximum number of mutations that can be queued. Default: 1000.
	QueueLimit uint16
	// HandlerTimeout defined the time for a handler to execute before it causes
	// StateException. Default: 1s. See also [Opts.HandlerTimeout].
	// Using HandlerTimeout can cause race conditions, unless paired with
	// [Event.IsValid].
	HandlerTimeout time.Duration
	// HandlerDeadline is a grace period after a handler timeout, before the
	// machine moves on.
	HandlerDeadline time.Duration
	// LastHandlerDeadline stores when the last HandlerDeadline was hit.
	LastHandlerDeadline atomic.Pointer[time.Time]
	// HandlerBackoff is the time after a [HandlerDeadline], during which the
	// machine will return [Canceled] to any mutation.
	HandlerBackoff time.Duration
	// EvalTimeout is the time the machine will try to execute an eval func.
	// Like any other handler, eval func also has [HandlerTimeout]. Default: 1s.
	EvalTimeout time.Duration
	// If true, the machine will log stack traces of errors. Default: true.
	// Requires an ExceptionHandler binding and [Machine.PanicToException] set.
	LogStackTrace bool
	// If true, the machine will catch panic and trigger the [StateException]
	// state. Default: true. Can be disabled via AM_DEBUG=1.
	PanicToException bool
	// DisposeTimeout specifies the duration to wait for the queue to drain during
	// disposal. Default 1s.
	DisposeTimeout time.Duration
	// contains filtered or unexported fields
}

Machine provides a state machine API with mutations, state schema, handlers, subscriptions, tracers, and helpers methods. It also holds a queue of mutations to execute.

func New

func New(ctx context.Context, schema Schema, opts *Opts) *Machine

New creates a new Machine instance, bound to context and modified with optional Opts.

Example
ctx := context.TODO()
mach := New(ctx, Schema{
	"Foo": {Require: S{"Bar"}},
	"Bar": {},
}, nil)
mach.Add1("Foo", nil)
mach.Is1("Foo") // false

func NewCommon added in v0.5.0

func NewCommon(
	ctx context.Context, id string, stateSchema Schema, stateNames S,
	handlers any, parent Api, opts *Opts,
) (*Machine, error)

NewCommon creates a new Machine instance with all the common options set.

Example
// define (tip: use am-gen instead)
stateStruct := Schema{
	"Foo": {Require: S{"Bar"}},
	"Bar": {},
}
stateNames := []string{"Foo", "Bar"}
type Handlers struct{}
// func (h *Handlers) FooState(e *Event) {
// 	args := e.ArgsBase
// 	mach := e.Machine()
// 	ctx := mach.NewStateCtx("Foo")
// 	// unblock
// 	go func() {
// 		if ctx.Err() != nil {
// 			return // expired
// 		}
// 		// blocking calls...
// 		if ctx.Err() != nil {
// 			return // expired
// 		}
// 		mach.Add1("Bar", nil)
// 	}()
// }

// init
ctx := context.TODO()
mach, err := NewCommon(ctx, "mach-id",
	stateStruct, stateNames,
	&Handlers{}, nil, nil)
if err != nil {
	panic(err)
}
mach.SemLogger().SetLevel(LogOps)

func (*Machine) ActiveStates

func (m *Machine) ActiveStates(states S) S

ActiveStates returns a copy of the currently active states when states is nil, optionally limiting the results to a subset of states.

func (*Machine) Add

func (m *Machine) Add(states S, args A) Result

Add activates a list of states in the machine, returning the result of the transition (Executed, Queued, Canceled). Like every mutation method, it will resolve relations and trigger handlers.

func (*Machine) Add1 added in v0.5.0

func (m *Machine) Add1(state string, args A) Result

Add1 is a shorthand method to add a single state with the passed args. See Add().

func (*Machine) AddBreakpoint added in v0.8.0

func (m *Machine) AddBreakpoint(added S, removed S, strict bool)

AddBreakpoint adds a breakpoint for an outcome of mutation (added and removed states) checked by mutation equality. Once such a mutation happens, a log message will be printed out. We can set an IDE's breakpoint on this line and see the mutation's sync stack trace. If Machine.LogStackTrace is set, the stack trace will be printed out as well. Many breakpoints can be added, but none removed.

Breakpoints are useful to find the caller of a mutation, but don't work with Machine.Set.

strict: strict skips already active / inactive (for strict of diff equality).

func (*Machine) AddBreakpoint1 added in v0.15.0

func (m *Machine) AddBreakpoint1(added string, removed string, strict bool)

func (*Machine) AddErr

func (m *Machine) AddErr(err error, args A) Result

AddErr is a dedicated method to add the StateException state with the passed error and optional arguments. Like every mutation method, it will resolve relations and trigger handlers. AddErr produces a stack trace of the error, if LogStackTrace is enabled.

func (*Machine) AddErrState added in v0.7.0

func (m *Machine) AddErrState(state string, err error, args A) Result

AddErrState adds a dedicated error state, along with the build in StateException state. Like every mutation method, it will resolve relations and trigger handlers. AddErrState produces a stack trace of the error, if LogStackTrace is enabled.

func (*Machine) Any

func (m *Machine) Any(states ...S) bool

Any is a group call to Is, returns true if any of the params return true from Is.

machine.StringAll() // ()[Foo:0 Bar:0 Baz:0]
machine.Add(S{"Foo"})
// is(Foo, Bar) or is(Bar)
machine.Any(S{"Foo", "Bar"}, S{"Bar"}) // false
// is(Foo) or is(Bar)
machine.Any(S{"Foo"}, S{"Bar"}) // true

func (*Machine) Any1 added in v0.5.0

func (m *Machine) Any1(states ...string) bool

Any1 is group call to Machine.Is1, returns true if any of the params return true from Machine.Is1.

func (*Machine) Backoff added in v0.13.0

func (m *Machine) Backoff() bool

Backoff is true in case the machine had a recent HandlerDeadline. During a backoff, all mutations will be Canceled.

func (*Machine) BindHandlers

func (m *Machine) BindHandlers(handlers any, opts ...BindOpts) (string, error)

BindHandlers is deprecated, use Api.HandlersBind.

func (*Machine) BindTracer added in v0.8.0

func (m *Machine) BindTracer(tracer Tracer) (string, error)

BindTracer is deprecated, use Machine.TracerBind.

func (*Machine) CanAdd added in v0.5.0

func (m *Machine) CanAdd(states S, args A) Result

CanAdd checks if [states] can be added and returns Executed or [AT.CheckDone] if a dry run mutation passes. Useful for reducing failed negotiations.

func (*Machine) CanAdd1 added in v0.15.0

func (m *Machine) CanAdd1(state string, args A) Result

CanAdd1 is Machine.CanAdd for a single state.

func (*Machine) CanRemove added in v0.5.0

func (m *Machine) CanRemove(states S, args A) Result

CanRemove checks if [states] can be removed and returns Executed or [AT.CheckDone] if a dry run mutation passes. Useful for reducing failed negotiations.

func (*Machine) CanRemove1 added in v0.15.0

func (m *Machine) CanRemove1(state string, args A) Result

CanRemove1 is Machine.CanRemove for a single state.

func (*Machine) Clock

func (m *Machine) Clock(states S) Clock

Clock returns current machine's clock, a state-keyed map of ticks. If states are passed, only the ticks of the passed states are returned.

func (*Machine) Context added in v0.18.0

func (m *Machine) Context() context.Context

Context returns the machine's context. This context lives longer than the parent context and allows for graceful shutdown. This is the context which should be used to create submachines. See also Machine.DisposeTimeout.

func (*Machine) ContextParent added in v0.19.0

func (m *Machine) ContextParent() context.Context

ContextParent returns the original context passed to the constructor.

func (*Machine) DetachHandlers added in v0.8.0

func (m *Machine) DetachHandlers(bindingId string) error

DetachHandlers is deprecated, use Api.HandlersDetach.

func (*Machine) DetachTracer added in v0.8.0

func (m *Machine) DetachTracer(id string) error

DetachTracer is deprecated, use Machine.TracerDetach.

func (*Machine) Dispose

func (m *Machine) Dispose()

Dispose disposes the machine and all its emitters. You can wait for the completion of the disposal with `<-mach.WhenDisposed()`. It's advised to use Dispose() func from pkg/helpers instead, which handles Disposed state mixing.

func (*Machine) DisposeForce added in v0.5.0

func (m *Machine) DisposeForce()

DisposeForce disposes the machine and all its emitters, without waiting for the queue to drain. Will cause panics.

func (*Machine) Err

func (m *Machine) Err() error

Err returns the last error.

func (*Machine) ErrInternal added in v0.19.0

func (m *Machine) ErrInternal() <-chan error

ErrInternal returns a channel which receives handler and eval timeout errors, so they can be handled accordingly without passing through mutations. This method always returns a closed channel for Api.IsLocal == false.

func (*Machine) EvAdd added in v0.9.0

func (m *Machine) EvAdd(event *Event, states S, args A) Result

EvAdd is like Add, but passed the source event as the 1st param, which results in traceable transitions.

func (*Machine) EvAdd1 added in v0.9.0

func (m *Machine) EvAdd1(event *Event, state string, args A) Result

EvAdd1 is like Add1 but passed the source event as the 1st param, which results in traceable transitions.

func (*Machine) EvAddErr added in v0.9.0

func (m *Machine) EvAddErr(event *Event, err error, args A) Result

EvAddErr is like AddErr, but passed the source event as the 1st param, which results in traceable transitions.

func (*Machine) EvAddErrState added in v0.9.0

func (m *Machine) EvAddErrState(
	event *Event, state string, err error, args A,
) Result

EvAddErrState is like AddErrState, but passed the source event as the 1st param, which results in traceable transitions.

func (*Machine) EvRemove added in v0.9.0

func (m *Machine) EvRemove(event *Event, states S, args A) Result

EvRemove is like Remove but passed the source event as the 1st param, which results in traceable transitions.

func (*Machine) EvRemove1 added in v0.9.0

func (m *Machine) EvRemove1(event *Event, state string, args A) Result

EvRemove1 is like Remove1, but passed the source event as the 1st param, which results in traceable transitions.

func (*Machine) EvSource added in v0.18.4

func (m *Machine) EvSource(transitionId string) *Event

EvSource is sugar for creating an event for Ev* methods without having an event. TODO add to Api

func (*Machine) EvToggle added in v0.15.0

func (m *Machine) EvToggle(e *Event, states S, args A) Result

EvToggle is a traced version of Machine.Toggle.

func (*Machine) EvToggle1 added in v0.15.0

func (m *Machine) EvToggle1(e *Event, state string, args A) Result

EvToggle1 is a traced version of Machine.Toggle1.

func (*Machine) Eval added in v0.5.0

func (m *Machine) Eval(source string, fn func(), ctx context.Context) bool

Eval executes a function on the machine's queue, allowing to avoid using locks for non-handler code. Blocking code should NOT be scheduled here. Eval cannot be called within a handler's critical zone, as both are using the same serial queue and will deadlock. Eval has a timeout of HandlerTimeout/2 and will return false in case it happens. Evals do not trigger consensus, thus are much faster than state mutations.

ctx: nil context defaults to machine's context.

Note: usage of Eval is discouraged. But if you have to, use AM_DETECT_EVAL in tests for deadlock detection. Most usages of eval can be replaced with atomics or returning from mutation via channels. Evals can be better then Multi states in some cases.

func (*Machine) Export added in v0.6.4

func (m *Machine) Export() (*Serialized, Schema, error)

Export exports the machine state as Serialized: ID, machine time, and state names.

func (*Machine) Fork added in v0.18.0

func (m *Machine) Fork(ctx context.Context, e *Event, fn func())

Fork is a syntax sugar method for a handler unblocking boilerplate. Fork can only by used in the handler's body (while the event is valid). See Machine.Go for nested forking.

Fork does NOT work with events exported via EvToCtx and CtxToEv.

func (*Machine) Go added in v0.18.0

func (m *Machine) Go(ctx context.Context, fn func())

Go is a syntax sugar method for a nested handler unblocking boilerplate.

func (*Machine) GoAfter added in v0.18.9

func (m *Machine) GoAfter(ctx context.Context, delay time.Duration, fn func())

GoAfter is like [Go], but with a delay.

func (*Machine) Groups added in v0.15.0

func (m *Machine) Groups() (map[string][]int, []string)

func (*Machine) Handlers added in v0.19.0

func (m *Machine) Handlers() []string

Handlers returns the IDs of bound handlers.

func (*Machine) HandlersBind added in v0.19.0

func (m *Machine) HandlersBind(handlers any, opts ...BindOpts) (string, error)

HandlersBind binds a struct of handler methods to machine's states, based on the naming convention, eg `FooState(e *Event)`. Negotiation handlers can optionally return bool.

func (*Machine) HandlersBindMaps added in v0.19.0

func (m *Machine) HandlersBindMaps(
	negotiations map[string]HandlerNegotiation, finals map[string]HandlerFinal,
	opts ...BindOpts,
) (string, error)

HandlersBindMaps is like Machine.BindHandlers but accepts maps of handlers to bypass reflecting structs. It may lead to higher performance. Returns a binding ID.

func (*Machine) HandlersDetach added in v0.19.0

func (m *Machine) HandlersDetach(bindingId string) error

HandlersDetach detaches previously bound machine handlers.

func (*Machine) Has

func (m *Machine) Has(states S) bool

Has return true is passed states are registered in the machine. Useful for checking if a machine implements a specific state set.

func (*Machine) Has1 added in v0.5.0

func (m *Machine) Has1(state string) bool

Has1 is shorthand for Has. It returns true if the passed state is registered in the machine.

func (*Machine) Id added in v0.8.0

func (m *Machine) Id() string

Id returns the machine's id.

func (*Machine) Import added in v0.6.4

func (m *Machine) Import(data *Serialized) error

Import imports the machine state from Serialized. It's not safe to import into a machine which has already produces transitions and/or has telemetry connected (use Machine.SetSchema instead).

func (*Machine) Index added in v0.7.0

func (m *Machine) Index(states S) []int

Index returns a list of state indexes in the machine's StateNames() list, with -1s for missing ones.

func (*Machine) Index1 added in v0.12.0

func (m *Machine) Index1(state string) int

Index1 returns the index of a state in the machine's StateNames() list, or -1 when not found or machine has been disposed.

func (*Machine) Inspect

func (m *Machine) Inspect(states S) string

Inspect returns a multi-line string representation of the machine (states, relations, clocks). states: param for ordered or partial results.

func (*Machine) Is

func (m *Machine) Is(states S) bool

Is checks if all the passed states are currently active.

machine.StringAll() // ()[Foo:0 Bar:0 Baz:0]
machine.Add(S{"Foo"})
machine.Is(S{"Foo"}) // true
machine.Is(S{"Foo", "Bar"}) // false

func (*Machine) Is1 added in v0.5.0

func (m *Machine) Is1(state string) bool

Is1 is Machine.Is for a single state.

func (*Machine) IsClock

func (m *Machine) IsClock(clock Clock) bool

IsClock checks if the machine has changed since the passed clock. Returns true if at least one state has changed.

func (*Machine) IsDisposed added in v0.8.0

func (m *Machine) IsDisposed() bool

IsDisposed returns true if the machine has been disposed.

func (*Machine) IsErr

func (m *Machine) IsErr() bool

IsErr checks if the machine has the StateException state currently active.

func (*Machine) IsLocal added in v0.19.0

func (m *Machine) IsLocal() bool

IsLocal returns true for *am.Machine and false for *arpc.NetworkMachine.

func (*Machine) IsQueued

func (m *Machine) IsQueued(mutType MutationType, states S,
	withoutArgsOnly bool, statesStrictEqual bool, minQueueTick uint64,
	isCheck bool, position Position,
) (found bool, idx uint16, qTick uint64)

IsQueued checks if a particular mutation has been queued. Returns an index (idx, true), or (0, false), if not found.

mutType: add, remove, set

states: list of states used in the mutation

withoutArgsOnly: matches only mutation without the arguments object

statesStrictEqual: states of the mutation have to be exactly like `states` and not a superset.

minQueueTick: minimal queue tick assigned to the matched mutation

isCheck: the mutation has to be a Mutation.IsCheck

position: position in the queue, after applying the [startIndex]

func (*Machine) IsQueuedAbove added in v0.13.0

func (m *Machine) IsQueuedAbove(threshold int, mutType MutationType,
	states S, withoutArgsOnly bool, statesStrictEqual bool, minQueueTick uint64,
) bool

IsQueuedAbove ... N times. This method allows for rate-limiting of mutations for specific states and a threshold.

func (*Machine) IsTime added in v0.7.0

func (m *Machine) IsTime(t Time, states S) bool

IsTime checks if the machine has changed since the passed time (list of ticks). Returns true if at least one state has changed. The states param is optional and can be used to check only a subset of states.

func (*Machine) Log

func (m *Machine) Log(msg string, args ...any)

Log logs an [extern] message unless LogNothing is set. Optionally redirects to a custom logger from SemLogger.SetLogger.

func (*Machine) LogCtx added in v0.19.0

func (m *Machine) LogCtx(ctx context.Context, msg string, args ...any)

TODO api

func (*Machine) LogEv added in v0.19.0

func (m *Machine) LogEv(e *Event, msg string, args ...any)

TODO api

func (*Machine) MachineTick added in v0.16.0

func (m *Machine) MachineTick() uint32

MachineTick is the number of times the machine has been started. Each start means an empty queue. Only set by Machine.Import.

func (*Machine) NewStateCtx added in v0.5.0

func (m *Machine) NewStateCtx(state string, event ...*Event) context.Context

NewStateCtx returns a new sub-context, bound to the current clock's tick of the passed state.

Context cancels when the state has been deactivated, or right away, if it isn't currently active.

State contexts are used to check state expirations and should be checked often inside goroutines.

[e]: optional Event to bind to this context.

func (*Machine) Not

func (m *Machine) Not(states S) bool

Not checks if **none** of the passed states are currently active.

machine.StringAll()
// -> ()[A:0 B:0 C:0 D:0]
machine.Add(S{"A", "B"})

// not(A) and not(C)
machine.Not(S{"A", "C"})
// -> false

// not(C) and not(D)
machine.Not(S{"C", "D"})
// -> true

func (*Machine) Not1 added in v0.5.0

func (m *Machine) Not1(state string) bool

Not1 is Machine.Not for a single state.

func (*Machine) OnChange added in v0.13.0

func (m *Machine) OnChange(fn HandlerChange)

OnChange is the most basic state-change handler, useful for machines without any handlers.

func (*Machine) OnDispose added in v0.16.0

func (m *Machine) OnDispose(fn HandlerDispose)

OnDispose adds a function to be called after the machine gets disposed. These functions will run synchronously just before WhenDisposed() channel gets closed. Considering it's a low-level feature, it's advised to handle disposal via dedicated states.

func (*Machine) OnError added in v0.13.0

func (m *Machine) OnError(fn HandlerError)

OnError is the most basic error handler, useful for machines without any handlers.

func (*Machine) PanicToErr added in v0.7.0

func (m *Machine) PanicToErr(args A)

PanicToErr will catch a panic and add the StateException state. Needs to be called in a defer statement, just like a recover() call.

func (*Machine) PanicToErrState added in v0.7.0

func (m *Machine) PanicToErrState(state string, args A)

PanicToErrState will catch a panic and add the StateException state, along with the passed state. Needs to be called in a defer statement, just like a recover() call.

func (*Machine) ParentId added in v0.8.0

func (m *Machine) ParentId() string

ParentId returns the ID of the parent machine (if any).

func (*Machine) ParseStates added in v0.16.0

func (m *Machine) ParseStates(states S) S

ParseStates parses a list of states, removing unknown ones and duplicates. Use Machine.Has and Machine.Has1 to check if a state is defined for the machine.

func (*Machine) PoolFork added in v0.18.6

func (m *Machine) PoolFork(ctx context.Context, e *Event, fn func()) bool

PoolFork will try to fork [fn] within a pool limit. Calls Machine.Go if the event is valid and within the limit (or no limit set), then returns true. Otherwise [fn] is no-op.

func (*Machine) PoolSetLimit added in v0.18.6

func (m *Machine) PoolSetLimit(handlerName string, limit int32)

PoolSetLimit sets a limit per handler (eg "FooState") for PoolFork calls.

func (*Machine) PoolSetLimitGlobal added in v0.18.6

func (m *Machine) PoolSetLimitGlobal(limit int32)

PoolSetLimitGlobal sets a global limit for all PoolFork calls.

func (*Machine) PrependMut added in v0.15.0

func (m *Machine) PrependMut(mut *Mutation) Result

PrependMut prepends the mutation to the front of the queue. This is a special cases only method and should be used with caution, as it can create an infinite loop. It's useful for postponing mutations inside a negotiation handler. The returned Result can't be waited on, as prepended mutations don't create a queue tick.

func (*Machine) Queue

func (m *Machine) Queue() []*Mutation

Queue returns a copy of the currently active states.

func (*Machine) QueueLen added in v0.13.0

func (m *Machine) QueueLen() uint16

func (*Machine) QueueTick added in v0.15.0

func (m *Machine) QueueTick() uint64

QueueTick is the number of times the queue has processed a mutation. Starts from [1], for easy comparison with Result.

func (*Machine) Remove

func (m *Machine) Remove(states S, args A) Result

Remove deactivates a list of states in the machine, returning the result of the transition (Executed, Queued, Canceled). Like every mutation method, it will resolve relations and trigger handlers.

func (*Machine) Remove1 added in v0.5.0

func (m *Machine) Remove1(state string, args A) Result

Remove1 is Machine.Remove1 for a single state.

func (*Machine) Resolver

func (m *Machine) Resolver() RelationsResolver

Resolver returns the relation resolver, used to produce target states of transitions.

func (*Machine) Schema added in v0.11.0

func (m *Machine) Schema() Schema

Schema returns a copy of machine's schema.

func (*Machine) SchemaVer added in v0.10.1

func (m *Machine) SchemaVer() int

SchemaVer return the current version of the schema.

func (*Machine) SemLogger added in v0.15.0

func (m *Machine) SemLogger() SemLogger

SemLogger returns the semantic logger of the machine

func (*Machine) Set

func (m *Machine) Set(states S, args A) Result

Set deactivates a list of states in the machine, returning the result of the transition (Executed, Canceled, Queued). Like every mutation method, it will resolve relations and trigger handlers.

func (*Machine) SetGroups added in v0.15.0

func (m *Machine) SetGroups(groups any, optStates States)

SetGroups organizes the schema into a tree using schema-v2 structs.

func (*Machine) SetGroupsString added in v0.15.0

func (m *Machine) SetGroupsString(groups map[string]S, order []string)

SetGroupsString is like SetGroups, but work with the schema-v1 format.

func (*Machine) SetSchema added in v0.11.0

func (m *Machine) SetSchema(newSchema Schema, names S) error

SetSchema sets the machine's schema. It will automatically call VerifyStates with the names param and handle EventSchemaChange if successful. The new schema has to be longer than the previous one (no relations-only changes). The length of the schema is also the version of the schema.

func (*Machine) SetTags added in v0.10.1

func (m *Machine) SetTags(tags []string)

SetTags updates the machine's tags with the provided slice of strings.

func (*Machine) StateNames

func (m *Machine) StateNames() S

StateNames returns a SHARED copy of all the state names.

func (*Machine) StatesVerified added in v0.5.0

func (m *Machine) StatesVerified() bool

StatesVerified returns true if the state names have been ordered using VerifyStates.

func (*Machine) String

func (m *Machine) String() string

String returns a one line representation of the currently active states, with their clock values. Inactive states are omitted. Eg: (Foo:1 Bar:3)

func (*Machine) StringAll

func (m *Machine) StringAll() string

StringAll returns a one line representation of all the states, with their clock values. Inactive states are in square brackets.

(Foo:1 Bar:3) [Baz:2]

func (*Machine) Switch added in v0.3.0

func (m *Machine) Switch(groups ...S) string

Switch returns the first state from the passed list that is currently active, making it handy for switch statements. Useful for state groups.

switch mach.Switch(ss.GroupPlaying) {
case "Playing":
case "Paused":
case "Stopped":
}

func (*Machine) Tags added in v0.5.0

func (m *Machine) Tags() []string

Tags returns machine's tags, a list of unstructured strings without spaces.

func (*Machine) Tick added in v0.7.0

func (m *Machine) Tick(state string) uint64

Tick return the current tick for a given state.

func (*Machine) Time

func (m *Machine) Time(states S) Time

Time returns machine's time, a list of ticks per state. Returned value includes the specified states, or all the states if nil.

func (*Machine) Toggle added in v0.10.1

func (m *Machine) Toggle(states S, args A) Result

Toggle deactivates a list of states in case all are active, or activates them otherwise. Returns the result of the transition (Executed, Queued, Canceled).

func (*Machine) Toggle1 added in v0.8.0

func (m *Machine) Toggle1(state string, args A) Result

Toggle1 activates or deactivates a single state, depending on its current state. Returns the result of the transition (Executed, Queued, Canceled).

func (*Machine) TracerBind added in v0.19.0

func (m *Machine) TracerBind(tracer Tracer) (string, error)

BindTracer binds a Tracer to the machine. Tracers can cause StateException in submachines, before any handlers are bound. Use the Err() getter to examine such errors.

func (*Machine) TracerDetach added in v0.19.0

func (m *Machine) TracerDetach(id string) error

DetachTracer tries to remove a tracer from the machine. Returns an error in case the tracer wasn't bound.

func (*Machine) Tracers added in v0.5.0

func (m *Machine) Tracers() []Tracer

Tracers return a copy of currently attached tracers.

func (*Machine) Transition

func (m *Machine) Transition() *Transition

Transition returns the current transition, if any.

func (*Machine) VerifyStates added in v0.3.0

func (m *Machine) VerifyStates(states S) error

VerifyStates verifies an array of state names and returns an error in case at least one isn't defined. It also retains the order and uses it for StateNames. Verification can be checked via Machine.StatesVerified.

func (*Machine) WasClock added in v0.12.0

func (m *Machine) WasClock(clock Clock) bool

WasClock checks if the passed time has happened (or happening right now). Returns false if at least one state is too early.

func (*Machine) WasTime added in v0.12.0

func (m *Machine) WasTime(t Time, states S) bool

WasTime checks if the passed time has happened (or happening right now). Returns false if at least one state is too early. The states param is optional and can be used to check only a subset of states.

func (*Machine) When

func (m *Machine) When(states S, ctx context.Context) <-chan struct{}

When returns a channel that will be closed when all the passed states become active or the machine gets disposed.

ctx: optional context that will close the channel early.

func (*Machine) When1 added in v0.5.0

func (m *Machine) When1(state string, ctx context.Context) <-chan struct{}

When1 is an alias to When() for a single state. See When.

func (*Machine) WhenArgs added in v0.5.0

func (m *Machine) WhenArgs(
	state string, args A, ctx context.Context,
) <-chan struct{}

WhenArgs returns a channel that will be closed when the passed state becomes active with all the passed args. ArgsBase are compared using the native '=='. It's meant to be used with async Multi states, to filter out a specific call.

ctx: optional context that will close the channel when handler loop ends.

func (*Machine) WhenDisposed added in v0.5.0

func (m *Machine) WhenDisposed() <-chan struct{}

WhenDisposed returns a channel that will be closed when the machine is disposed.

func (*Machine) WhenErr

func (m *Machine) WhenErr(disposeCtx context.Context) <-chan struct{}

WhenErr returns a channel that will be closed when the machine is in the StateException state.

ctx: optional context that will close the channel early.

func (*Machine) WhenNextActive added in v0.18.9

func (m *Machine) WhenNextActive(
	state string, ctx context.Context,
) <-chan struct{}

WhenNextActive waits until the state becomes active, excluding the current activity.

ctx: optional context that will close the channel early.

func (*Machine) WhenNot

func (m *Machine) WhenNot(states S, ctx context.Context) <-chan struct{}

WhenNot returns a channel that will be closed when all the passed states become inactive or the machine gets disposed.

ctx: optional context that will close the channel early.

func (*Machine) WhenNot1 added in v0.5.0

func (m *Machine) WhenNot1(state string, ctx context.Context) <-chan struct{}

WhenNot1 is an alias to WhenNot() for a single state. See WhenNot.

func (*Machine) WhenQuery added in v0.16.0

func (m *Machine) WhenQuery(
	clockCheck func(clock Clock) bool, ctx context.Context,
) <-chan struct{}

WhenQuery returns a channel that will be closed when the passed [clockCheck] function returns true. [clockCheck] should be a pure function and non-blocking.`

ctx: optional context that will close the channel early.

func (*Machine) WhenQueue added in v0.15.0

func (m *Machine) WhenQueue(tick Result) <-chan struct{}

WhenQueue waits until the passed queueTick gets processed. TODO example

func (*Machine) WhenQueueEnds added in v0.5.0

func (m *Machine) WhenQueueEnds() <-chan struct{}

WhenQueueEnds closes every time the queue ends, or the optional ctx expires.

ctx: optional context that will close the channel early.

func (*Machine) WhenTicks added in v0.5.0

func (m *Machine) WhenTicks(
	state string, ticks int, ctx context.Context,
) <-chan struct{}

WhenTicks waits N ticks of a single state (relative to now). Uses WhenTime underneath.

ctx: optional context that will close the channel early.

func (*Machine) WhenTime added in v0.5.0

func (m *Machine) WhenTime(
	states S, times Time, ctx context.Context,
) <-chan struct{}

WhenTime returns a channel that will be closed when all the passed states have passed the specified time. The time is a logical clock of the state. Machine time can be sourced from Machine.Time(), or Machine.Clock().

ctx: optional context that will close the channel early.

func (*Machine) WhenTime1 added in v0.11.0

func (m *Machine) WhenTime1(
	state string, ticks uint64, ctx context.Context,
) <-chan struct{}

WhenTime1 waits till ticks for a single state equal the given value (or more).

ctx: optional context that will close the channel early.

func (*Machine) WillBe added in v0.8.0

func (m *Machine) WillBe(states S, position ...Position) bool

WillBe returns true if the passed states are scheduled to be activated within a single mutation. Does not cover implied states, only called ones. See Machine.IsQueued to perform more detailed queries.

position: optional position assertion

func (*Machine) WillBe1 added in v0.8.0

func (m *Machine) WillBe1(state string, position ...Position) bool

WillBe1 returns true if the passed state is scheduled to be activated. See IsQueued to perform more detailed queries.

func (*Machine) WillBeAny added in v0.18.9

func (m *Machine) WillBeAny(states S) bool

WillBeAny returns true if any of the passed states is scheduled to be activated. See Machine.IsQueued to perform more detailed queries.

position: optional position assertion

func (*Machine) WillBeRemoved added in v0.8.0

func (m *Machine) WillBeRemoved(states S, position ...Position) bool

WillBeRemoved returns true if the passed states are scheduled to be deactivated. Does not cover implied states, only called ones. See Machine.IsQueued to perform more detailed queries.

position: optional position assertion

func (*Machine) WillBeRemoved1 added in v0.8.0

func (m *Machine) WillBeRemoved1(state string, position ...Position) bool

WillBeRemoved1 returns true if the passed state is scheduled to be deactivated. See IsQueued to perform more detailed queries.

type MutSource added in v0.9.0

type MutSource struct {
	MachId string
	TxId   string
	// Machine time of the source machine BEFORE the event.
	MachTime uint64
}

type Mutation

type Mutation struct {
	// add, set, remove
	Type MutationType
	// States explicitly passed to the mutation method, as their indexes. Use
	// Transition.CalledStates or IndexToStates to get the actual state names.
	Called []int
	// argument map passed to the mutation method (if any).
	Args A
	// this mutation has been triggered by an auto state
	// TODO rename to IsAuto
	IsAuto bool
	// Source is the source event for this mutation.
	Source *MutSource
	// IsCheck indicates that this mutation is a check, see [Machine.CanAdd].
	IsCheck bool

	// QueueTickNow is the queue tick during which this mutation was scheduled.
	QueueTickNow uint64
	// QueueLen is the length of the queue at the time when the mutation was
	// queued.
	QueueLen int32
	// QueueTokensLen is the amount of unexecuted queue tokens (priority queue).
	// TODO impl
	QueueTokensLen int32
	// QueueTick is the assigned queue tick at the time when the mutation was
	// queued. 0 for prepended mutations.
	QueueTick uint64
	// QueueToken is a unique ID, which is given to prepended mutations.
	// Tokens are assigned in a series but executed in random order.
	QueueToken uint64
	// contains filtered or unexported fields
}

Mutation represents an atomic change (or an attempt) of machine's active states. Mutation causes a Transition.

func (*Mutation) CalledIndex added in v0.12.0

func (m *Mutation) CalledIndex(index S) *TimeIndex

func (*Mutation) Clone added in v0.18.9

func (m *Mutation) Clone() *Mutation

Clone clones the mutation for further re-processing. Queue related fields are removed.

func (*Mutation) IsCalled added in v0.12.0

func (m *Mutation) IsCalled(idx int) bool

func (*Mutation) LogArgs added in v0.15.0

func (m *Mutation) LogArgs(mapper LogArgsMapperFn) string

LogArgs returns a text snippet with arguments which should be logged for this Mutation.

func (*Mutation) MapArgs added in v0.15.0

func (m *Mutation) MapArgs(mapper LogArgsMapperFn) (ret map[string]string)

MapArgs returns arguments of this Mutation which match the passed [mapper]. The returned map is never nil.

func (*Mutation) String added in v0.11.0

func (m *Mutation) String() string

func (*Mutation) StringFromIndex added in v0.12.0

func (m *Mutation) StringFromIndex(index S) string

type MutationType

type MutationType int

MutationType enum

const (
	MutationAdd MutationType = iota
	MutationRemove
	MutationSet
)

func (MutationType) String

func (m MutationType) String() string

func (MutationType) StringShort added in v0.18.8

func (m MutationType) StringShort() string

type Opts

type Opts struct {
	// Unique ID of this machine. Default: random ID.
	Id string
	// Time for a handler to execute. Default: time.Second
	HandlerTimeout time.Duration
	// TODO docs
	HandlerDeadline time.Duration
	// If true, the machine will NOT print all exceptions to stdout.
	DontLogStackTrace bool
	// If true, the machine will die on panics.
	DontPanicToException bool
	// If true, the machine will NOT prefix its logs with its ID.
	DontLogId bool
	// Custom relations resolver. Default: *[DefaultRelationsResolver].
	Resolver RelationsResolver
	// Log level of the machine. Default: [LogNothing].
	LogLevel LogLevel
	// Tracer for the machine. Default: nil.
	Tracers []Tracer
	// LogArgs matching function for the machine. Default: nil.
	LogArgs LogArgsMapperFn
	// Parent machine, used to inherit certain properties, e.g. tracers.
	// Overrides ParentID. Default: nil.
	Parent Api
	// ParentID is the ID of the parent machine. Default: "".
	ParentId string
	// Tags is a list of tags for the machine. Default: nil.
	Tags []string
	// QueueLimit is the maximum number of mutations that can be queued.
	// Default: 1000.
	QueueLimit uint16
	// DetectEval will detect Eval calls directly in handlers, which causes a
	// deadlock. It works in similar way as -race flag in Go and can also be
	// triggered by setting either env var: AM_DEBUG=1 or AM_DETECT_EVAL=1.
	// Default: false.
	DetectEval     bool
	HandlerBackoff time.Duration
}

Opts struct is used to configure a new Machine.

func OptsWithDebug added in v0.5.0

func OptsWithDebug(opts *Opts) *Opts

OptsWithDebug returns Opts with debug settings (DontPanicToException, long HandlerTimeout).

func OptsWithTracers added in v0.5.0

func OptsWithTracers(opts *Opts, tracers ...Tracer) *Opts

OptsWithTracers returns Opts with the given tracers. Tracers are inherited by submachines (via Opts.Parent) when env.AM_DEBUG is set.

type Pipe added in v0.18.6

type Pipe struct {
	// Add or Remove mutation
	IsAdd bool

	// non-empty for outbound pipes
	SourceState string
	// non-empty for outbound pipes
	TargetMach string

	// non-empty for inbound pipes
	TargetState string
	// non-empty for inbound pipes
	SourceMach string
}

type Position added in v0.15.0

type Position uint8

Position describes the item's position in a list. Used mostly for the queue in Machine.IsQueued.

const (
	PositionAny Position = iota
	PositionFirst
	PositionLast
)

type Relation

type Relation int8

Relation enum

const (
	// TODO refac, 0 should be RelationNone, start from 1, udpate with dbg proto
	RelationAfter Relation = iota
	RelationAdd
	RelationRequire
	RelationRemove
)

func (Relation) String

func (r Relation) String() string

type RelationsResolver

type RelationsResolver interface {
	// TargetStates returns the target states after parsing the relations.
	TargetStates(t *Transition, calledStates, index S) S
	// NewAutoMutation returns an (optional) auto mutation which is appended to
	// the queue after the transition is executed. It also returns the names of
	// the called states.
	NewAutoMutation() (*Mutation, S)
	// SortStates sorts the states in the order their handlers should be
	// executed.
	SortStates(states S)
	// RelationsOf returns a list of relation types of the given state.
	RelationsOf(fromState string) ([]Relation, error)
	// RelationsBetween returns a list of relation types between the given
	// states.
	RelationsBetween(fromState, toState string) ([]Relation, error)
	InboundRelationsOf(toState string) (S, error)

	// NewSchema runs when Machine receives a new struct.
	NewSchema(schema Schema, states S)
}

RelationsResolver is an interface for parsing relations between states. Not thread-safe. TODO support custom relation types and additional state properties. TODO refac Relations

type Result

type Result uint64

Result enum is the result of a state Transition. Queued is a virtual value and everything >= Queued (2) represents a queue tick on which the mutation will be processed. It's useful for queued negotiations.

const (
	// Executed means that the transition was executed immediately and not
	// canceled.
	Executed Result = 0
	// Canceled means that the transition was canceled, by either relations or a
	// negotiation handler.
	Canceled Result = 1
	// Queued means that the transition was queued for later execution. Everything
	// above 2 also means Queued. The following methods can be used to wait for
	// the results:
	// - Machine.WhenQueue
	// - Machine.When
	// - Machine.WhenNot
	// - Machine.WhenArgs
	// - Machine.WhenTime
	// - Machine.WhenTime1
	// - Machine.WhenTicks
	// - Machine.WhenQueueEnds
	// See [Machine.QueueTick].
	Queued Result = 2
)

func (Result) String

func (r Result) String() string

type S

type S []string

S (state names) is a string list of state names.

func IndexToStates added in v0.12.0

func IndexToStates(index S, states []int) S

IndexToStates is deprecated, use S.FilterIndex.

func SAdd added in v0.7.0

func SAdd(states ...S) S

SAdd is deprecated, use S.Add.

func SRem added in v0.12.0

func SRem(src S, states ...S) S

SRem is deprecated, use S.Delete.

func StatesByTag added in v0.18.9

func StatesByTag(schema Schema, tag string) S

TODO refac to Schema.StatesByTag

func StatesDiff added in v0.17.0

func StatesDiff(states1 S, states2 S) S

StatesDiff is deprecated, use S.Sub.

func StatesPrefix added in v0.18.4

func StatesPrefix(prefix string, states S) S

StatesPrefix is deprecated, use S.Prefix.

func StatesShared added in v0.17.0

func StatesShared(states1 S, states2 S) S

StatesShared is deprecated, use S.Shared.

func StatesWithPrefix added in v0.18.9

func StatesWithPrefix(prefix string, states S) S

StatesWithPrefix is deprecated, use S.FilterPrefix.

func (S) Add added in v0.19.0

func (s S) Add(states ...S) S

Add concatenates multiple state lists into one, removing duplicates. Useful for merging lists of states, eg a state group with other states involved in a relation.

func (S) Add1 added in v0.19.0

func (s S) Add1(state ...string) S

Add1 is like S.Add, but for single state names.

func (S) Delete added in v0.19.0

func (s S) Delete(states ...S) S

Delete removes states from all passed state groups.

func (S) Delete1 added in v0.19.0

func (s S) Delete1(states ...string) S

Delete1 is like S.Delete, but for single state names.

func (S) Equal added in v0.19.0

func (s S) Equal(other S) bool

Equal returns true if states1 and states2 are equal, regardless of order.

func (S) EqualOrder added in v0.19.0

func (s S) EqualOrder(other S) bool

EqualOrder is like S.Equal, but also checks the order of states.

func (S) FilterIndex added in v0.19.0

func (s S) FilterIndex(idxs []int) S

FilterIndex returns a subset of S from specified indexes.

func (S) FilterMatch added in v0.19.0

func (s S) FilterMatch(re *regexp.Regexp) S

FilterMatch returns all the states matching a regexp.

func (S) FilterPrefix added in v0.19.0

func (s S) FilterPrefix(prefix ...string) S

FilterPrefix returns all the states with a prefix.

func (S) Has added in v0.19.1

func (s S) Has(state string) bool

Has returns true is [state] is in this state set.

func (S) Hash added in v0.19.0

func (s S) Hash() string

Hash returns a short hash of this state set.

func (S) Index added in v0.19.0

func (s S) Index(states S) []int

Index returns an index of passed [states]. Unknown states are represented by -1.

func (S) Prefix added in v0.19.0

func (s S) Prefix(prefix string) S

Prefix lists all states with a prefix.

func (S) Shared added in v0.19.0

func (s S) Shared(other S) S

Shared returns states present in both S and other (overlap).

func (S) Sub added in v0.19.0

func (s S) Sub(other S) S

Sub returns the states from [s] that are missing in [other] (subtract).

func (S) Unique added in v0.19.1

func (s S) Unique() S

Unique returns a new slice with unique state names.

type Schema added in v0.10.2

type Schema map[string]State

Schema is a map of state names to state definitions.

func SchemaMerge added in v0.10.3

func SchemaMerge(schemas ...Schema) Schema

SchemaMerge is deprecated, use Schema.Merge.

func (Schema) AdjacentStates added in v0.19.1

func (s Schema) AdjacentStates(state string) S

func (Schema) Clone added in v0.19.0

func (s Schema) Clone() Schema

Clone deep clones the states struct and returns a copy.

func (Schema) FilterByTag added in v0.19.0

func (s Schema) FilterByTag(tag string) Schema

FilterByTag returns a subset schema with states having the given tag.

func (Schema) Merge added in v0.19.0

func (s Schema) Merge(schemas ...Schema) Schema

Merge merges multiple state structs into one, overriding the previous state definitions. No relation-level merging takes place.

func (Schema) Names added in v0.19.0

func (s Schema) Names() S

Names is a random-order list of defined state names.

func (Schema) Parse added in v0.19.0

func (s Schema) Parse() (Schema, error)

Parse sanitizes the schema and returns potential errors.

func (Schema) Prefix added in v0.19.0

func (s Schema) Prefix(
	prefix string, removeDups bool, optAllowlist, optSkiplist S,
) Schema

Prefix will prefix all state names with [prefix]. removeDups will skip overlaps eg "FooFooName" will be "Foo".

type SemConfig added in v0.15.0

type SemConfig struct {
	// TODO
	Full     bool
	Steps    bool
	Graph    bool
	Can      bool
	Queued   bool
	Args     bool
	When     bool
	StateCtx bool
}

SemConfig defines a config for SemLogger.

type SemLogger added in v0.15.0

type SemLogger interface {

	// AddPipeOut informs that [sourceState] has been piped out into [targetMach].
	// The name of the target state is unknown.
	AddPipeOut(isAdd bool, sourceState, targetMach string)
	// AddPipeIn informs that [targetState] has been piped into this machine from
	// [sourceMach]. The name of the source state is unknown.
	AddPipeIn(isAdd bool, targetState, sourceMach string)
	// RemovePipes removes all pipes for the passed machine ID.
	RemovePipes(machId string)
	// Pipes return log entries for currently bound pipes. Useful for late
	// debugging.
	Pipes() []*LogEntry

	// IsCan return true when the machine is logging Can* methods.
	IsCan() bool
	// EnableCan enables / disables logging of Can* methods.
	EnableCan(enable bool)
	// IsSteps return true when the machine is logging transition steps.
	IsSteps() bool
	// EnableSteps enables / disables logging of transition steps.
	EnableSteps(enable bool)
	// IsGraph returns true when the machine is logging graph structures.
	IsGraph() bool
	// EnableGraph enables / disables logging of graph structures.
	EnableGraph(enable bool)
	// EnableId enables or disables the logging of the machine's ID in log
	// messages.
	EnableId(val bool)
	// IsId returns true when the machine is logging the machine's ID in log
	// messages.
	IsId() bool
	// EnableQueued enables or disables the logging of queued mutations.
	EnableQueued(val bool)
	// IsQueued returns true when the machine is logging queued mutations.
	IsQueued() bool
	// EnableStateCtx enables or disables the logging of active state contexts.
	EnableStateCtx(val bool)
	// IsStateCtx returns true when the machine is logging active state contexts.
	IsStateCtx() bool
	// EnableWhen enables or disables the logging of "when" methods.
	EnableWhen(val bool)
	// IsWhen returns true when the machine is logging "when" methods.
	IsWhen() bool
	// EnableArgs enables or disables the logging known args.
	EnableArgs(val bool)
	// IsArgs returns true when the machine is logging known args.
	IsArgs() bool

	// SetLogger sets a custom logger function.
	SetLogger(logger LoggerFn)
	// Logger returns the current custom logger function or nil.
	Logger() LoggerFn
	// SetLevel sets the log level of the machine.
	SetLevel(lvl LogLevel)
	// Level returns the log level of the machine.
	Level() LogLevel
	// SetEmpty creates an empty logger that does nothing and sets the log
	// level in one call. Useful when combined with am-dbg. Requires LogChanges
	// log level to produce any output.
	SetEmpty(lvl LogLevel)
	// SetSimple takes log.Printf and sets the log level in one
	// call. Useful for testing. Requires LogChanges log level to produce any
	// output.
	SetSimple(logf func(format string, args ...any), level LogLevel)
	// SetArgsMapper accepts a function which decides which mutation arguments
	// to log. See NewLogArgsMapper or create one.
	SetArgsMapper(mapper LogArgsMapperFn)
	// SetArgsMapperDef binds and extends the default NewLogArgsMapper and LogArgs
	// with [additional] arguments to be logged.
	SetArgsMapperDef(additional ...string)
	// ArgsMapper returns the current log args mapper function.
	ArgsMapper() LogArgsMapperFn
}

SemLogger is a semantic logger for structured events. It's consist of: - enable / enabled methods - text logger utils - setters for external semantics (eg pipes) It's WIP, and eventually it will replace (but not remove) the text logger.

type Serialized added in v0.7.0

type Serialized struct {
	// ID is the ID of a state machine.
	// TODO refac to Id with the new dbg telemetry protocol
	ID string `json:"id" yaml:"id" toml:"id"`
	// StateNames is an ordered list of state names.
	StateNames S `json:"state_names" yaml:"state_names" toml:"state_names"`
	// Time is the [Machine.Time] value.
	Time Time `json:"time" yaml:"time" toml:"time"`
	// QueueTick is a value of [Machine.QueueTick].
	QueueTick uint64 `json:"queue_tick" yaml:"queue_tick" toml:"queue_tick"`
	// MachineTick is a value of [Machine.MachineTick].
	// nolint:lll
	MachineTick uint32 `json:"machine_tick" yaml:"machine_tick" toml:"machine_tick"`
}

Serialized is a machine state serialized to a JSON/YAML/TOML compatible struct. One also needs the state Struct to re-create a state machine.

type State

type State struct {
	Auto    bool     `json:"auto,omitempty" yaml:"auto,omitempty" toml:"auto,omitempty"`
	Multi   bool     `json:"multi,omitempty" yaml:"multi,omitempty" toml:"multi,omitempty"`
	Require S        `json:"require,omitempty" yaml:"require,omitempty" toml:"require,omitempty"`
	Add     S        `json:"add,omitempty" yaml:"add,omitempty" toml:"add,omitempty"`
	Remove  S        `json:"remove,omitempty" yaml:"remove,omitempty" toml:"remove,omitempty"`
	After   S        `json:"after,omitempty" yaml:"after,omitempty" toml:"after,omitempty"`
	Tags    []string `json:"tags,omitempty" yaml:"tags,omitempty" toml:"tags,omitempty"`
}

State defines a single state of a machine, its properties, relations, and tags. nolint:lll

func StateAdd added in v0.7.0

func StateAdd(source State, overlay State) State

StateAdd is deprecated, use State.Extend.

func StateSet added in v0.7.0

func StateSet(source State, auto, multi bool, overlay State) State

StateSet is Deprecated, use State.Set.

func (State) Clone added in v0.19.0

func (s State) Clone() State

func (State) Extend added in v0.19.0

func (s State) Extend(overlay State) State

Extend adds new states to relations of the source state, without removing existing ones. Useful for adjusting shared stated to a specific machine. Only "true" values for Auto and Multi are applied from [overlay].

ssS.HandshakeDone: SharedSchema[ssS.HandshakeDone].Extend(
	am.State{
		Require: S{ssS.ClientConnected},
		Remove: S{Exception},
	}),

func (State) HasTag added in v0.19.0

func (s State) HasTag(tag string) bool

func (State) Set added in v0.19.0

func (s State) Set(auto, multi bool, overlay State) State

Set replaces passed relations and properties of the source state. Only relations in the overlay state are replaced, the rest is preserved. If [overlay] has all fields `nil`, then only [auto] and [multi] get applied.

 ssS.HandshakeDone: s.Set(false, true, State{
		Remove: S{"C"},
	})

func (State) SetRels added in v0.19.0

func (s State) SetRels(overlay State) State

SetRels is like State.Set, but inherits properties (Auto, Multi).

type StateIsActive added in v0.7.0

type StateIsActive map[string]bool

TODO optimize with indexes

type States

type States interface {
	// Names returns the state names of the state machine.
	Names() S
	// TODO
	StateGroups() (map[string][]int, []string)
	SetNames(S)
	SetStateGroups(map[string][]int, []string)
}

States is the vase interface for schema states.

type StatesBase added in v0.8.0

type StatesBase struct {
	// Exception is the only built-in state and mean a global error. All errors
	// have to [State.Require] the Exception state. If [Machine.PanicToErr] is
	// true, Exception will receive it.
	Exception string
	// contains filtered or unexported fields
}

func (*StatesBase) Names added in v0.8.0

func (b *StatesBase) Names() S

func (*StatesBase) SetNames added in v0.8.0

func (b *StatesBase) SetNames(names S)

func (*StatesBase) SetStateGroups added in v0.15.0

func (b *StatesBase) SetStateGroups(groups map[string][]int, order []string)

func (*StatesBase) StateGroups added in v0.15.0

func (b *StatesBase) StateGroups() (map[string][]int, []string)

type Step added in v0.5.0

type Step struct {
	Type StepType
	// Only for Type == StepRelation.
	RelType Relation
	// marks a final handler (FooState, FooEnd)
	IsFinal bool
	// marks a self handler (FooFoo)
	IsSelf bool
	// marks an enter handler (FooState, but not FooEnd). Requires IsFinal.
	IsEnter bool
	// Deprecated, use GetFromState(). TODO remove
	FromState string
	// TODO implement
	FromStateIdx int
	// Deprecated, use GetToState(). TODO remove
	ToState string
	// TODO implement
	ToStateIdx int
	// Deprecated, use RelType. TODO remove
	Data any
}

Step struct represents a single step within a Transition, either a relation resolving step or a handler call.

func (*Step) GetFromState added in v0.9.0

func (s *Step) GetFromState(index S) string

GetFromState returns the source state of a step. Optional, unless no GetToState().

func (*Step) GetToState added in v0.9.0

func (s *Step) GetToState(index S) string

GetToState returns the target state of a step. Optional, unless no GetFromState().

func (*Step) StringFromIndex added in v0.12.0

func (s *Step) StringFromIndex(idx S) string

type StepType added in v0.5.0

type StepType int8

StepType enum

const (
	StepRelation StepType = 1 << iota
	StepHandler
	// TODO rename to StepActivate
	StepSet
	// StepRemove indicates a step where a state goes active->inactive
	// TODO rename to StepDeactivate
	StepRemove
	// StepRemoveNotActive indicates a step where a state goes inactive->inactive
	// TODO rename to StepDeactivatePassive
	StepRemoveNotActive
	// TODO refac to StepCalled
	StepRequested
	StepCancel
)

TODO refac with dbg-proto-v2

func (StepType) String added in v0.5.0

func (tt StepType) String() string

type Subscriptions added in v0.15.1

type Subscriptions struct {
	// Mx locks the subscription manager. TODO optimize?
	Mx     sync.Mutex
	Closed chan struct{}
	// contains filtered or unexported fields
}

Subscriptions is an embed responsible for binding subscriptions, managing their indexes, processing triggers, and garbage collection. TODO stats: closed chans served, chans created, chans currently open

func NewSubscriptionManager added in v0.15.1

func NewSubscriptionManager(
	mach Api, clock Clock, is, not InternalCheckFunc, log InternalLogFunc,
) *Subscriptions

func (*Subscriptions) HasWhenArgs added in v0.15.1

func (sm *Subscriptions) HasWhenArgs() bool

func (*Subscriptions) NewStateCtx added in v0.15.1

func (sm *Subscriptions) NewStateCtx(state string) context.Context

NewStateCtx returns a new sub-context, bound to the current clock's tick of the passed state.

Context cancels when the state has been deactivated, or right away, if it isn't currently active.

State contexts are used to check state expirations and should be checked often inside goroutines.

func (*Subscriptions) ProcessStateCtx added in v0.15.1

func (sm *Subscriptions) ProcessStateCtx(
	activated, deactivated S,
) []context.CancelFunc

ProcessStateCtx collects all expired state contexts, and returns theirs cancel funcs. Uses transition caches.

func (*Subscriptions) ProcessWhen added in v0.15.1

func (sm *Subscriptions) ProcessWhen(activated, deactivated S) []chan struct{}

ProcessWhen collects all the matched active state subscriptions, and returns theirs channels.

func (*Subscriptions) ProcessWhenArgs added in v0.15.1

func (sm *Subscriptions) ProcessWhenArgs(e *Event) []chan struct{}

ProcessWhenArgs collects all the args-matching subscriptions, and returns theirs channels.

func (*Subscriptions) ProcessWhenQuery added in v0.16.0

func (sm *Subscriptions) ProcessWhenQuery() []chan struct{}

func (*Subscriptions) ProcessWhenQueue added in v0.15.1

func (sm *Subscriptions) ProcessWhenQueue(queueTick uint64) []chan struct{}

func (*Subscriptions) ProcessWhenQueueEnds added in v0.15.1

func (sm *Subscriptions) ProcessWhenQueueEnds() []chan struct{}

ProcessWhenQueueEnds collects all queue-end subscriptions, and returns theirs channels.

func (*Subscriptions) ProcessWhenTime added in v0.15.1

func (sm *Subscriptions) ProcessWhenTime(before Clock) []chan struct{}

ProcessWhenTime collects all the time-based subscriptions, and returns theirs channels.

func (*Subscriptions) QueueFlush added in v0.17.0

func (sm *Subscriptions) QueueFlush()

QueueFlush means a new queue, and all the queue-related subs are expired and have to be closed.

func (*Subscriptions) SetClock added in v0.17.0

func (sm *Subscriptions) SetClock(clock Clock)

SetClock sets a longer clock from an expanded schema.

func (*Subscriptions) When added in v0.15.1

func (sm *Subscriptions) When(states S, ctx context.Context) <-chan struct{}

func (*Subscriptions) WhenArgs added in v0.15.1

func (sm *Subscriptions) WhenArgs(
	state string, args A, ctx context.Context,
) <-chan struct{}

WhenArgs returns a channel that will be closed when the passed state becomes active with all the passed args. ArgsBase are compared using the native '=='. It's meant to be used with async Multi states, to filter out a specific call.

ctx: optional context that will close the channel when handler loop ends.

func (*Subscriptions) WhenNot added in v0.15.1

func (sm *Subscriptions) WhenNot(
	states S, ctx context.Context,
) <-chan struct{}

WhenNot returns a channel that will be closed when all the passed states become inactive or the machine gets disposed.

ctx: optional context that will close the channel early.

func (*Subscriptions) WhenQuery added in v0.16.0

func (sm *Subscriptions) WhenQuery(
	fn func(clock Clock) bool, ctx context.Context,
) <-chan struct{}

func (*Subscriptions) WhenQueue added in v0.15.1

func (sm *Subscriptions) WhenQueue(tick Result) <-chan struct{}

WhenQueue waits until the passed queueTick gets processed.

func (*Subscriptions) WhenQueueEnds added in v0.15.1

func (sm *Subscriptions) WhenQueueEnds() <-chan struct{}

WhenQueueEnds closes every time the queue ends, or the optional ctx expires. This function assumes the queue is running, and wont close early.

ctx: optional context that will close the channel early.

func (*Subscriptions) WhenTime added in v0.15.1

func (sm *Subscriptions) WhenTime(
	states S, times Time, ctx context.Context,
) <-chan struct{}

WhenTime returns a channel that will be closed when all the passed states have passed the specified time. The time is a logical clock of the state. Machine time can be sourced from Machine.Time(), or Machine.Clock().

ctx: optional context that will close the channel early.

type Time added in v0.7.0

type Time []uint64

Time is machine time, an ordered list of state ticks. It's like Clock, but indexed by int, instead of string. TODO use math/big?

func NewTime added in v0.17.0

func NewTime(index Time, activeStates []int) Time

NewTime returns a Time of the same len as [index] with active states marked by indexes in [activeStates].

func (Time) ActiveStates added in v0.12.0

func (t Time) ActiveStates(idxs []int) []int

ActiveStates returns a list of active state indexes in this machine time slice. When idxs isn't nil, only the passed indexes are considered.

func (Time) Add added in v0.10.3

func (t Time) Add(t2 Time) Time

Add sums 2 instances of Time and returns a new one.

func (Time) After added in v0.16.0

func (t Time) After(orEqual bool, time2 Time) bool

After returns true if at least 1 tick in time1 is after time2, optionally accepting equal values for true. Requires a deterministic states order, eg by using Machine.VerifyStates.

func (Time) Any added in v0.16.0

func (t Time) Any(idxs ...[]int) bool

Any is Machine.Any but for an int-based time slice.

func (Time) Any1 added in v0.8.0

func (t Time) Any1(idxs ...int) bool

Any1 is Machine.Any1 but for an int-based time slice.

func (Time) Before added in v0.16.0

func (t Time) Before(orEqual bool, time2 Time) bool

Before returns true if at least 1 tick in time1 is before time2, optionally accepting equal values for true. Requires a deterministic states order, eg by using Machine.VerifyStates.

func (Time) DiffSince added in v0.10.3

func (t Time) DiffSince(before Time) Time

DiffSince returns the number of ticks for each state in Time since the passed machine time.

func (Time) Equal added in v0.16.0

func (t Time) Equal(strict bool, time2 Time) bool

Equal checks if time1 is equal to time2. Requires a deterministic states order, eg by using Machine.VerifyStates.

func (Time) Filter added in v0.16.0

func (t Time) Filter(idxs []int) Time

Filter returns a subset of the Time slice for the given state indexes. It's not advised to slice an already sliced time slice.

func (Time) Increment added in v0.16.0

func (t Time) Increment(idx int) Time

Increment adds 1 to a state's tick value

func (Time) Is added in v0.7.0

func (t Time) Is(idxs []int) bool

Is is Machine.Is but for an int-based time slice.

func (Time) Is1 added in v0.7.0

func (t Time) Is1(idx int) bool

Is1 is Machine.Is1 but for an int-based time slice.

func (Time) NonZeroStates added in v0.16.0

func (t Time) NonZeroStates() []int

NonZeroStates returns a list of state indexes with non-zero ticks in this machine time slice.

func (Time) Not added in v0.12.0

func (t Time) Not(idxs []int) bool

Not is Machine.Not but for an int-based time slice.

func (Time) Not1 added in v0.12.0

func (t Time) Not1(idx int) bool

Not1 is Machine.Not1 but for an int-based time slice.

func (Time) String added in v0.8.0

func (t Time) String() string

String returns an integer representation of the time slice.

func (Time) Sum added in v0.9.0

func (t Time) Sum(idxs []int) uint64

Sum returns a sum of ticks for each state in Time, or narrowed down to [idxs].

func (Time) Tick added in v0.16.0

func (t Time) Tick(idx int) uint64

Tick is Machine.Tick but for an int-based time slice.

func (Time) ToIndex added in v0.16.0

func (t Time) ToIndex(index S) *TimeIndex

ToIndex returns a string-indexed version of Time.

type TimeIndex added in v0.12.0

type TimeIndex struct {
	Time
	Index S
}

TimeIndex is Time with a bound state index (list of state names). It's not suitable for storage, use Time instead. See Clock for a simpler type with ticks indexes by state names.

func NewTimeIndex added in v0.12.0

func NewTimeIndex(index S, activeStates []int) *TimeIndex

NewTimeIndex returns a TimeIndex of the same len as [index] with active states marked by indexes in [activeStates].

func (TimeIndex) ActiveStates added in v0.12.0

func (t TimeIndex) ActiveStates(states S) S

ActiveStates is Machine.ActiveStates but for a string-based time slice.

func (TimeIndex) Any added in v0.16.0

func (t TimeIndex) Any(states ...string) bool

Any is Machine.Any but for a string-based time slice.

func (TimeIndex) Any1 added in v0.12.0

func (t TimeIndex) Any1(states ...string) bool

Any1 is Machine.Any1 but for a string-based time slice.

func (TimeIndex) Filter added in v0.16.0

func (t TimeIndex) Filter(states S) *TimeIndex

Filter is Time.Filter but for a string-based time slice.

func (TimeIndex) Is added in v0.12.0

func (t TimeIndex) Is(states S) bool

Is is Machine.Is but for a string-based time slice.

func (TimeIndex) Is1 added in v0.12.0

func (t TimeIndex) Is1(state string) bool

Is1 is Machine.Is1 but for a string-based time slice.

func (TimeIndex) NonZeroStates added in v0.16.0

func (t TimeIndex) NonZeroStates() S

NonZeroStates is Time.NonZeroStates but for a string-based time slice.

func (TimeIndex) Not added in v0.12.0

func (t TimeIndex) Not(states S) bool

Not is Machine.Not but for a string-based time slice.

func (TimeIndex) Not1 added in v0.12.0

func (t TimeIndex) Not1(state string) bool

Not1 is Machine.Not1 but for a string-based time slice.

func (TimeIndex) StateName added in v0.12.0

func (t TimeIndex) StateName(idx int) string

StateName returns the name of the state at the given index.

func (TimeIndex) String added in v0.12.0

func (t TimeIndex) String() string

String returns a string representation of the time slice.

func (TimeIndex) Sum added in v0.16.0

func (t TimeIndex) Sum(states S) uint64

Sum is Time.Sum but for a string-based time slice.

type Tracer added in v0.5.0

type Tracer interface {
	TracerId() string
	TransitionInit(transition *Transition)
	TransitionStart(transition *Transition)
	TransitionFinals(transition *Transition)
	TransitionEnd(transition *Transition)
	MutationQueued(machine Api, mutation *Mutation)
	HandlerStart(transition *Transition, emitter string, handler string)
	HandlerEnd(transition *Transition, emitter string, handler string)
	// MachineInit is called only for machines with tracers added via
	// Opts.Tracers.
	MachineInit(machine Api) context.Context
	MachineDispose(machID string)
	NewSubmachine(parent, machine Api)
	Inheritable() bool
	QueueEnd(machine Api)
	SchemaChange(machine Api, old Schema)
	VerifyStates(machine Api)
}

Tracer is an interface for logging machine transitions and events, used by Opts.Tracers and Machine.BindTracer.

type TracerNoOp added in v0.17.0

type TracerNoOp struct {
	Id string
}

TracerNoOp is a no-op implementation of Tracer, used for embedding.

func (*TracerNoOp) HandlerEnd added in v0.17.0

func (t *TracerNoOp) HandlerEnd(
	transition *Transition, emitter string, handler string)

func (*TracerNoOp) HandlerStart added in v0.17.0

func (t *TracerNoOp) HandlerStart(
	transition *Transition, emitter string, handler string)

func (*TracerNoOp) Inheritable added in v0.17.0

func (t *TracerNoOp) Inheritable() bool

func (*TracerNoOp) MachineDispose added in v0.17.0

func (t *TracerNoOp) MachineDispose(machID string)

func (*TracerNoOp) MachineInit added in v0.17.0

func (t *TracerNoOp) MachineInit(machine Api) context.Context

func (*TracerNoOp) MutationQueued added in v0.17.0

func (t *TracerNoOp) MutationQueued(machine Api, mutation *Mutation)

func (*TracerNoOp) NewSubmachine added in v0.17.0

func (t *TracerNoOp) NewSubmachine(parent, machine Api)

func (*TracerNoOp) QueueEnd added in v0.17.0

func (t *TracerNoOp) QueueEnd(machine Api)

func (*TracerNoOp) SchemaChange added in v0.17.0

func (t *TracerNoOp) SchemaChange(machine Api, old Schema)

func (*TracerNoOp) TracerId added in v0.19.0

func (t *TracerNoOp) TracerId() string

func (*TracerNoOp) TransitionEnd added in v0.17.0

func (t *TracerNoOp) TransitionEnd(transition *Transition)

func (*TracerNoOp) TransitionFinals added in v0.18.4

func (t *TracerNoOp) TransitionFinals(transition *Transition)

func (*TracerNoOp) TransitionInit added in v0.17.0

func (t *TracerNoOp) TransitionInit(transition *Transition)

func (*TracerNoOp) TransitionStart added in v0.17.0

func (t *TracerNoOp) TransitionStart(transition *Transition)

func (*TracerNoOp) VerifyStates added in v0.17.0

func (t *TracerNoOp) VerifyStates(machine Api)

type Transition

type Transition struct {
	// Id is a unique identifier of the transition.
	Id string
	// Steps is a list of steps taken by this transition (so far).
	Steps []*Step
	// HasStateChanged is true if the transition has changed the state of the
	// machine. TODO useful?
	// HasStateChanged bool
	// TimeBefore is the machine time from before the transition.
	TimeBefore Time
	// TimeAfter is the machine time from after the transition. If the transition
	// has been canceled, this will be the same as TimeBefore. This field is the
	// same as TimeBefore, until the negotiation phase finishes.
	TimeAfter Time
	// TargetIndexes is a list of indexes of the target states.
	TargetIndexes []int
	// Enters is a list of states activated in this transition.
	Enters S
	// Enters is a list of states deactivated in this transition.
	Exits S
	// Mutation call which caused this transition
	Mutation *Mutation
	// Machine is the parent machine of this transition.
	Machine *Machine
	// MachApi is a subset of Machine.
	// TODO call when applicable instead of calling Machine
	// TODO rename to MachApi
	MachApi Api
	// LogEntries are log msgs produced during the transition.
	LogEntries []*LogEntry
	// PreLogEntries are log msgs produced before during the transition.
	PreLogEntries []*LogEntry
	// QueueLen is the length of the queue after the transition.
	QueueLen uint16
	// InternalLogEntriesLock is used to lock the logs to be collected by Tracers.
	// TODO getter?
	InternalLogEntriesLock sync.Mutex
	// IsCompleted returns true when the execution of the transition has been
	// fully completed.
	IsCompleted atomic.Bool
	// IsAccepted returns true if the transition has been accepted, which can
	// change during the transition's negotiation phase and while resolving
	// relations.
	IsAccepted atomic.Bool
	// TODO true for panic and timeouts
	IsBroken atomic.Bool
	// TODO confirms relations resolved and negotiation ended
	IsSettled atomic.Bool
	// contains filtered or unexported fields
}

Transition represents processing of a single mutation within a machine.

func (*Transition) Args

func (t *Transition) Args() A

ArgsBase returns the argument map passed to the mutation method (or an empty one).

func (*Transition) CalledStates

func (t *Transition) CalledStates() S

CalledStates return explicitly called / requested states of the transition.

func (*Transition) CleanCache added in v0.13.0

func (t *Transition) CleanCache()

func (*Transition) ClockAfter added in v0.7.0

func (t *Transition) ClockAfter() Clock

ClockAfter return the Clock from before the transition.

func (*Transition) ClockBefore added in v0.7.0

func (t *Transition) ClockBefore() Clock

ClockBefore return the Clock from before the transition.

func (*Transition) IsAuto

func (t *Transition) IsAuto() bool

IsAuto returns true if the transition was triggered by an auto state. Thus, it cant trigger any other auto state mutations.

func (*Transition) IsHealth added in v0.13.0

func (t *Transition) IsHealth() bool

IsHealth returns true if the transition was health-related (StateHealthcheck, StateHeartbeat).

func (*Transition) StatesBefore

func (t *Transition) StatesBefore() S

StatesBefore is a list of states before the transition.

func (*Transition) String

func (t *Transition) String() string

String representation of the transition and the steps taken so far.

func (*Transition) TargetStates

func (t *Transition) TargetStates() S

TargetStates is a list of states after parsing the relations.

func (*Transition) TimeIndexAfter added in v0.15.0

func (t *Transition) TimeIndexAfter() *TimeIndex

TimeIndexAfter return TimeAfter bound to an index, for easy querying.

func (*Transition) TimeIndexBefore added in v0.18.0

func (t *Transition) TimeIndexBefore() *TimeIndex

TimeIndexBefore return TimeIndexBefore bound to an index, for easy querying.

func (*Transition) TimeIndexCalled added in v0.18.0

func (t *Transition) TimeIndexCalled() *TimeIndex

TimeIndexCalled return CalledStates bound to an index, for easy querying.

func (*Transition) TimeIndexDiff added in v0.18.0

func (t *Transition) TimeIndexDiff() (*TimeIndex, *TimeIndex)

TimeIndexDiff return 2 time indexes of added and removed states by this transition. Added include Multi states.

func (*Transition) TimeIndexTimeDiff added in v0.18.2

func (t *Transition) TimeIndexTimeDiff() *TimeIndex

TimeIndexTimeDiff return a time index of states with tick changes as active states.

func (*Transition) TimeIndexTouched added in v0.18.0

func (t *Transition) TimeIndexTouched() *TimeIndex

TimeIndexTouched return all the touched states as active. Requires SemLogger.IsSteps to be true.

func (*Transition) Type

func (t *Transition) Type() MutationType

Type returns the type of the mutation (add, remove, set).

type WhenArgsBinding added in v0.7.0

type WhenArgsBinding struct {
	// contains filtered or unexported fields
}

type WhenBinding added in v0.7.0

type WhenBinding struct {
	Ch chan struct{}
	// means states are required to NOT be active
	Negation bool
	States   StateIsActive
	Matched  int
	Total    int
	Ctx      context.Context
}

type WhenTimeBinding added in v0.7.0

type WhenTimeBinding struct {
	Ch chan struct{}
	// map of matched to their index positions
	// TODO optimize indexes
	Index map[string]int
	// number of matches so far TODO len(Index) ?
	Matched int
	// number of total matches needed
	Total int // TODO len(Times) ?
	// optional Time to match for completed from Index
	Times     Time
	Completed StateIsActive
	Ctx       context.Context
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL