[READ-ONLY] Mirror of https://github.com/andrioid/statesman. xstate inspired schema compatible statechart library for Go
fsm go statecharts
0

Configure Feed

Select the types of activity you want to include in your feed.

statesman / definition.go
5.4 kB 181 lines
1package statesman 2 3// Definition is the validated, structural machine model the engine walks. It is 4// non-generic: guards and actions are referenced by Callsite index, never by Go 5// type, which is the core/codegen boundary (decision 36) — the generated 6// constructor provides closures keyed by the same Callsite ids. 7// 8// internal/schema builds a Definition from machine.json (Type strings filled, 9// Callsite ids assigned in document order); `statesman generate` emits the same 10// shape as Go and the matching dispatch closures. 11type Definition struct { 12 ID string // machine id; must equal the package name (decision 50) 13 Root *StateNode 14 nodes map[StateID]*StateNode 15 16 // ActionCount / GuardCount are the number of distinct callsites enumerated, 17 // so a runtime or codegen consumer can size its dispatch table. 18 ActionCount int 19 GuardCount int 20} 21 22// Lookup returns the node with the given id, or nil. 23func (d *Definition) Lookup(id StateID) *StateNode { return d.nodes[id] } 24 25// NewDefinition assembles a Definition from a fully-built root node, indexing 26// every node by ID for Lookup. actionCount/guardCount are the totals from the 27// builder's callsite enumeration (they size the dispatch tables). Used by 28// internal/schema and by generated code; the engine never builds one itself. 29func NewDefinition(id string, root *StateNode, actionCount, guardCount int) *Definition { 30 d := &Definition{ 31 ID: id, 32 Root: root, 33 nodes: make(map[StateID]*StateNode), 34 ActionCount: actionCount, 35 GuardCount: guardCount, 36 } 37 var index func(n *StateNode) 38 index = func(n *StateNode) { 39 d.nodes[n.ID] = n 40 for _, c := range n.Children { 41 index(c) 42 } 43 } 44 index(root) 45 return d 46} 47 48// StateKind classifies a state node. 49type StateKind uint8 50 51const ( 52 StateAtomic StateKind = iota 53 StateCompound 54 StateParallel 55 StateFinal 56 StateHistory 57) 58 59func (k StateKind) String() string { 60 switch k { 61 case StateAtomic: 62 return "atomic" 63 case StateCompound: 64 return "compound" 65 case StateParallel: 66 return "parallel" 67 case StateFinal: 68 return "final" 69 case StateHistory: 70 return "history" 71 default: 72 return "unknown" 73 } 74} 75 76// HistoryKind is shallow or deep, for history pseudo-states. 77type HistoryKind uint8 78 79const ( 80 HistoryShallow HistoryKind = iota 81 HistoryDeep 82) 83 84// ActionRef references an entry/exit/transition action. Type is the JSON action 85// type (for codegen name resolution); Params are the raw literal params (for 86// stub type inference only — never read as a map at runtime). Callsite is the 87// stable id the opaque applier dispatches on. 88type ActionRef struct { 89 Type string 90 Params map[string]any 91 Callsite int 92} 93 94// GuardRef references a transition guard. Same Callsite contract as ActionRef, 95// in a separate id space. 96type GuardRef struct { 97 Type string 98 Params map[string]any 99 Callsite int 100} 101 102// Delay is a resolved `after` delay: literal milliseconds when Symbol == "", 103// otherwise a symbolic delays.go const name resolved at codegen. 104type Delay struct { 105 Millis int64 106 Symbol string 107} 108 109// Transition is one transition out of a state. Event == "" means eventless 110// (`always`). Targets is empty for a targetless (internal, action-only) 111// transition; in v1 it holds at most one explicit target (fan-out to multiple 112// states happens only via parallel/initial entry closure). 113type Transition struct { 114 Source *StateNode 115 Event string 116 Guard *GuardRef 117 Targets []*StateNode 118 Actions []ActionRef 119 DocOrder int 120 IsAfter bool 121 Delay Delay // valid when IsAfter 122} 123 124// Eventless reports whether t is an `always` transition. 125func (t *Transition) Eventless() bool { return t.Event == "" && !t.IsAfter } 126 127// Internal reports whether t is targetless (action-only; no exit/entry). 128func (t *Transition) Internal() bool { return len(t.Targets) == 0 } 129 130// Invoke is an invoked actor declared on a state. ID is required (decision 10); 131// Src names the Go actor symbol. OnDone/OnError are the transitions wired on the 132// generated done.invoke.<id> / error.invoke.<id> events. 133type Invoke struct { 134 ID string 135 Src string 136 OnDone []*Transition 137 OnError []*Transition 138} 139 140// StateNode is a node in the machine tree. 141type StateNode struct { 142 ID StateID 143 Key string // local key under Parent.Children 144 Kind StateKind 145 Parent *StateNode 146 Children []*StateNode // document order 147 Initial *StateNode // compound: default child (nil otherwise) 148 HistoryKind HistoryKind // history nodes only 149 HistoryTo []*StateNode // history default target (nil => use shallow default) 150 Entry []ActionRef 151 Exit []ActionRef 152 Invokes []*Invoke 153 Transitions []*Transition // on + always + after, in document order 154 DocOrder int 155} 156 157// IsAtomic reports whether n is a leaf for selection purposes (atomic or final). 158func (n *StateNode) IsAtomic() bool { return n.Kind == StateAtomic || n.Kind == StateFinal } 159 160// ProperAncestors returns n's ancestors from its parent up to and including the 161// root, innermost first. 162func (n *StateNode) ProperAncestors() []*StateNode { 163 var out []*StateNode 164 for p := n.Parent; p != nil; p = p.Parent { 165 out = append(out, p) 166 } 167 return out 168} 169 170// IsDescendant reports whether n is a proper descendant of anc. 171func (n *StateNode) IsDescendant(anc *StateNode) bool { 172 if anc == nil { 173 return false 174 } 175 for p := n.Parent; p != nil; p = p.Parent { 176 if p == anc { 177 return true 178 } 179 } 180 return false 181}