diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72713f3..84cea5f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,7 +14,7 @@
- feat\(machine\): add empty roadmap methods [\#55](https://github.com/pancsta/asyncmachine-go/pull/55) (@pancsta)
- feat\(machine\): add Eval [\#54](https://github.com/pancsta/asyncmachine-go/pull/54) (@pancsta)
- refac\(pkg/machine\): rename many identifiers, shorten [\#53](https://github.com/pancsta/asyncmachine-go/pull/53) (@pancsta)
-- feat\(machine\): drop add dependencies \(lo, uuid\) [\#52](https://github.com/pancsta/asyncmachine-go/pull/52) (@pancsta)
+- feat\(machine\): drop all dependencies \(lo, uuid\) [\#52](https://github.com/pancsta/asyncmachine-go/pull/52) (@pancsta)
- feat\(machine\): alloc handler goroutine on demand [\#51](https://github.com/pancsta/asyncmachine-go/pull/51) (@pancsta)
- feat\(machine\): add Transition.ClocksAfter [\#50](https://github.com/pancsta/asyncmachine-go/pull/50) (@pancsta)
- feat\(machine\): add HasStateChangedSince [\#49](https://github.com/pancsta/asyncmachine-go/pull/49) (@pancsta)
diff --git a/README.md b/README.md
index 3c7a6db..e236bae 100644
--- a/README.md
+++ b/README.md
@@ -2,130 +2,24 @@
![TUI Debugger](assets/am-dbg.gif)
-**asyncmachine-go** is a minimal implementation of [AsyncMachine](https://github.com/TobiaszCudnik/asyncmachine) in
-Golang using **channels and context**. It aims at simplicity and speed.
+**asyncmachine-go** is a minimal implementation of [AsyncMachine](https://github.com/TobiaszCudnik/asyncmachine)
+in Golang using **channels and context**. It aims at simplicity and speed.
-It can be used as a lightweight in-memory [Temporal](https://github.com/temporalio/temporal) alternative, worker for
-[Asynq](https://github.com/hibiken/asynq), or to create simple consensus engines, stateful firewalls, telemetry, bots,
-etc.
+It can be used as a lightweight in-memory [Temporal](https://github.com/temporalio/temporal)
+alternative, worker for [Asynq](https://github.com/hibiken/asynq), or to create simple consensus engines, stateful
+firewalls, telemetry, bots, etc.
> **asyncmachine-go** is a general purpose state machine for managing complex asynchronous workflows in a safe and
-> structured way.
-
-
-
-See am-dbg's states structure and relations
-
-```go
-var States = am.Struct{
-
- ///// Input events
-
- ClientMsg: {Multi: true},
- ConnectEvent: {Multi: true},
- DisconnectEvent: {Multi: true},
-
- // user scrolling tx / steps
- UserFwd: {
- Add: S{Fwd},
- Remove: GroupPlaying,
- },
- UserBack: {
- Add: S{Back},
- Remove: GroupPlaying,
- },
- UserFwdStep: {
- Add: S{FwdStep},
- Require: S{ClientSelected},
- Remove: am.SMerge(GroupPlaying, S{LogUserScrolled}),
- },
- UserBackStep: {
- Add: S{BackStep},
- Require: S{ClientSelected},
- Remove: am.SMerge(GroupPlaying, S{LogUserScrolled}),
- },
-
- ///// External state (eg UI)
-
- TreeFocused: {Remove: GroupFocused},
- LogFocused: {Remove: GroupFocused},
- SidebarFocused: {Remove: GroupFocused},
- TimelineTxsFocused: {Remove: GroupFocused},
- TimelineStepsFocused: {Remove: GroupFocused},
- MatrixFocused: {Remove: GroupFocused},
- DialogFocused: {Remove: GroupFocused},
- StateNameSelected: {Require: S{ClientSelected}},
- HelpDialog: {Remove: GroupDialog},
- ExportDialog: {
- Require: S{ClientSelected},
- Remove: GroupDialog,
- },
- LogUserScrolled: {},
- Ready: {Require: S{Start}},
-
- ///// Actions
-
- Start: {},
- TreeLogView: {
- Auto: true,
- Remove: GroupViews,
- },
- MatrixView: {Remove: GroupViews},
- TreeMatrixView: {Remove: GroupViews},
- TailMode: {
- Require: S{ClientSelected},
- Remove: GroupPlaying,
- },
- Playing: {
- Require: S{ClientSelected},
- Remove: am.SMerge(GroupPlaying, S{LogUserScrolled}),
- },
- Paused: {
- Auto: true,
- Require: S{ClientSelected},
- Remove: GroupPlaying,
- },
-
- // tx / steps back / fwd
- Fwd: {
- Require: S{ClientSelected},
- Remove: S{Playing},
- },
- Back: {
- Require: S{ClientSelected},
- Remove: S{Playing},
- },
- FwdStep: {
- Require: S{ClientSelected},
- Remove: S{Playing},
- },
- BackStep: {
- Require: S{ClientSelected},
- Remove: S{Playing},
- },
-
- ScrollToTx: {Require: S{ClientSelected}},
-
- // client
- SelectingClient: {Remove: S{ClientSelected}},
- ClientSelected: {
- Remove: S{SelectingClient, LogUserScrolled},
- },
- RemoveClient: {Require: S{ClientSelected}},
-}
-```
-
-
+> structured way
## Comparison
-Common differences with other state machines:
+Common differences from other state machines:
- many states can be active at the same time
- transitions between all the states are allowed
- - unless constrained
- states are connected by relations
-- every mutation can be rejected
+- every transition can be rejected
- error is a state
## Usage
@@ -133,15 +27,16 @@ Common differences with other state machines:
### Basics
```go
+// This machine has 1 async and 1 sync states.
+// ProcessingFile -> FileProcessed
package main
-
import (
"context"
-
am "github.com/pancsta/asyncmachine-go/pkg/machine"
)
func main() {
+ // init the machine
ctx := context.Background()
mach := am.New(ctx, am.Struct{
"ProcessingFile": {
@@ -175,14 +70,17 @@ type Handlers struct {
// negotiation handler
func (h *Handlers) ProcessingFileEnter(e *am.Event) bool {
- // read-only ops (decide if moving fwd is ok)
+ // read-only ops
+ // (decide if moving fwd is ok)
+ // no blocking
// lock-free critical zone
return true
}
// final handler
func (h *Handlers) ProcessingFileState(e *am.Event) {
- // read & write ops (but no blocking)
+ // read & write ops
+ // no blocking
// lock-free critical zone
mach := e.Machine
// tick-based context
@@ -198,7 +96,7 @@ func (h *Handlers) ProcessingFileState(e *am.Event) {
mach.AddErr(err)
return
}
- // re-check the ctx after a blocking call
+ // re-check the tick ctx after a blocking call
if stateCtx.Err() != nil {
return // expired
}
@@ -368,9 +266,10 @@ var States = am.Struct{
-### [Temporal FileProcessing workflow](/examples/temporal-fileprocessing/fileprocessing.go)
+### [Temporal FileProcessing Workflow](/examples/temporal-fileprocessing/fileprocessing.go)
- [origin](https://github.com/temporalio/samples-go/blob/main/fileprocessing/)
+- [Asynq worker version](examples/asynq-fileprocessing/fileprocessing_task.go)
@@ -398,8 +297,6 @@ var States = am.Struct{
-### [AM as an Asynq worker](examples/asynq-fileprocessing/fileprocessing_task.go)
-
## Documentation
- [godoc](https://godoc.org/github.com/pancsta/asyncmachine-go/pkg/machine)
@@ -423,7 +320,7 @@ var States = am.Struct{
-Example template for Foo and Bar
+Check the example template for Foo and Bar
```go
package states
@@ -472,8 +369,8 @@ See [`tools/cmd/am-gen`](tools/cmd/am-gen/README.md) for more info.
![TUI Debugger](assets/am-dbg.png)
-`am-dbg` is a multi-client debugger lightweight enough to be kept open in the background while receiving data from >100
- machines simultaneously (and potentially many more). Some features include:
+`am-dbg` is a lightweight, multi-client debugger for AM. It easily handles >100
+ client machines simultaneously (and potentially many more). Some features include:
- states tree
- log view
@@ -527,7 +424,7 @@ a lot of inspectable data.
- **pubsub host** - eg `ps-17` (20 states)
- PubSub machine is a simple event loop with Multi states which get responses via arg channels. Heavy use of `Eval`.
+ PubSub machine is a simple event loop with Multi states which get responses via arg channels. Heavy use of `Machine.Eval()`.
- **discovery** - eg `ps-17-disc` (10 states)
Discovery machine is a simple event loop with Multi states and a periodic refresh state.
- **discovery bootstrap** - eg `ps-17-disc-bf3` (5 states)
@@ -555,8 +452,8 @@ or the [pdf results](https://github.com/pancsta/go-libp2p-pubsub-benchmark/raw/m
State-only machine (no handlers, no goroutine). States represent correlations with peer machines.
See
-[github.com/pancsta/**go-libp2p-pubsub-benchmark**](https://github.com/pancsta/go-libp2p-pubsub-benchmark/tree/main/internal/sim)
-and the [pubsub machines](https://github.com/pancsta/go-libp2p-pubsub/tree/psmon-states/states) for more info.
+[github.com/pancsta/**go-libp2p-pubsub-benchmark**](https://github.com/pancsta/go-libp2p-pubsub-benchmark?tab=readme-ov-file#libp2p-pubsub-simulator)
+for more info.
### am-dbg
@@ -566,9 +463,119 @@ am-dbg is a [tview](https://github.com/rivo/tview/) TUI app with a single machin
- external state (11 states)
- actions (14 states)
-This machine features a decent amount of relations within a large number od states and 4 state groups. It's also a good
+This machine features a decent amount of relations within a large number of states and 4 state groups. It's also a good
example to see how easily an AM-based program can be controller with a script in [tools/cmd/am-dbg-demo](tools/cmd/am-dbg-demo/main.go#L68).
+
+
+Check am-dbg's states structure and relations
+
+```go
+// States map defines relations and properties of states.
+var States = am.Struct{
+ ///// Input events
+
+ ClientMsg: {Multi: true},
+ ConnectEvent: {Multi: true},
+ DisconnectEvent: {Multi: true},
+
+ // user scrolling tx / steps
+ UserFwd: {
+ Add: S{Fwd},
+ Remove: GroupPlaying,
+ },
+ UserBack: {
+ Add: S{Back},
+ Remove: GroupPlaying,
+ },
+ UserFwdStep: {
+ Add: S{FwdStep},
+ Require: S{ClientSelected},
+ Remove: am.SMerge(GroupPlaying, S{LogUserScrolled}),
+ },
+ UserBackStep: {
+ Add: S{BackStep},
+ Require: S{ClientSelected},
+ Remove: am.SMerge(GroupPlaying, S{LogUserScrolled}),
+ },
+
+ ///// External state (eg UI)
+
+ // focus group
+
+ TreeFocused: {Remove: GroupFocused},
+ LogFocused: {Remove: GroupFocused},
+ SidebarFocused: {Remove: GroupFocused},
+ TimelineTxsFocused: {Remove: GroupFocused},
+ TimelineStepsFocused: {Remove: GroupFocused},
+ MatrixFocused: {Remove: GroupFocused},
+ DialogFocused: {Remove: GroupFocused},
+
+ StateNameSelected: {Require: S{ClientSelected}},
+ HelpDialog: {Remove: GroupDialog},
+ ExportDialog: {
+ Require: S{ClientSelected},
+ Remove: GroupDialog,
+ },
+ LogUserScrolled: {},
+ Ready: {Require: S{Start}},
+
+ ///// Actions
+
+ Start: {},
+ TreeLogView: {
+ Auto: true,
+ Remove: GroupViews,
+ },
+ MatrixView: {Remove: GroupViews},
+ TreeMatrixView: {Remove: GroupViews},
+ TailMode: {
+ Require: S{ClientSelected},
+ Remove: GroupPlaying,
+ },
+ Playing: {
+ Require: S{ClientSelected},
+ Remove: am.SMerge(GroupPlaying, S{LogUserScrolled}),
+ },
+ Paused: {
+ Auto: true,
+ Require: S{ClientSelected},
+ Remove: GroupPlaying,
+ },
+
+ // tx / steps back / fwd
+
+ Fwd: {
+ Require: S{ClientSelected},
+ Remove: S{Playing},
+ },
+ Back: {
+ Require: S{ClientSelected},
+ Remove: S{Playing},
+ },
+ FwdStep: {
+ Require: S{ClientSelected},
+ Remove: S{Playing},
+ },
+ BackStep: {
+ Require: S{ClientSelected},
+ Remove: S{Playing},
+ },
+
+ ScrollToTx: {Require: S{ClientSelected}},
+
+ // client selection
+
+ SelectingClient: {Remove: S{ClientSelected}},
+ ClientSelected: {
+ Remove: S{SelectingClient, LogUserScrolled},
+ },
+ RemoveClient: {Require: S{ClientSelected}},
+}
+```
+
+
+
See [tools/debugger/states](tools/debugger/states) for more info.
## Roadmap
@@ -599,8 +606,6 @@ See also [issues](https://github.com/pancsta/asyncmachine-go/issues).
Latest release: `v0.5.0`
-## [v0.5.0](https://github.com/pancsta/asyncmachine-go/tree/v0.5.0) (2024-06-06)
-
- feat: add tools/cmd/am-gen [\#63](https://github.com/pancsta/asyncmachine-go/pull/63) (@pancsta)
- feat\(am-dbg\): add `--select-connected` and `--clean-on-connect`
[\#62](https://github.com/pancsta/asyncmachine-go/pull/62) (@pancsta)
@@ -613,7 +618,7 @@ Latest release: `v0.5.0`
- feat\(machine\): add empty roadmap methods [\#55](https://github.com/pancsta/asyncmachine-go/pull/55) (@pancsta)
- feat\(machine\): add Eval [\#54](https://github.com/pancsta/asyncmachine-go/pull/54) (@pancsta)
- refac\(pkg/machine\): rename many identifiers, shorten [\#53](https://github.com/pancsta/asyncmachine-go/pull/53) (@pancsta)
-- feat\(machine\): drop add dependencies \(lo, uuid\) [\#52](https://github.com/pancsta/asyncmachine-go/pull/52) (@pancsta)
+- feat\(machine\): drop all dependencies \(lo, uuid\) [\#52](https://github.com/pancsta/asyncmachine-go/pull/52) (@pancsta)
- feat\(machine\): alloc handler goroutine on demand [\#51](https://github.com/pancsta/asyncmachine-go/pull/51) (@pancsta)
- feat\(machine\): add Transition.ClocksAfter [\#50](https://github.com/pancsta/asyncmachine-go/pull/50) (@pancsta)
- feat\(machine\): add HasStateChangedSince [\#49](https://github.com/pancsta/asyncmachine-go/pull/49) (@pancsta)
diff --git a/assets/prometheus-grafana.light.png b/assets/prometheus-grafana.light.png
index 71fd714..07cf91d 100644
Binary files a/assets/prometheus-grafana.light.png and b/assets/prometheus-grafana.light.png differ
diff --git a/pkg/machine/machine.go b/pkg/machine/machine.go
index f77cf18..45d0231 100644
--- a/pkg/machine/machine.go
+++ b/pkg/machine/machine.go
@@ -1,12 +1,12 @@
-// Package machine is a dependency-free implementation of AsyncMachine in Golang
-// using channels and context. It aims at simplicity and speed.
+// Package machine is a general purpose state machine for managing complex
+// async workflows in a safe and structured way.
//
// It can be used as a lightweight in-memory Temporal [1] alternative, worker
// for Asynq [2], or to write simple consensus engines, stateful firewalls,
// telemetry, bots, etc.
//
-// asyncmachine-go is a general purpose state machine for managing complex
-// async workflows in a safe and structured way.
+// The project itself is a minimal implementation of AsyncMachine in Golang
+// using channels and context. It aims at simplicity and speed.
//
// [1]: https://github.com/temporalio/temporal
// [2]: https://github.com/hibiken/asynq
diff --git a/pkg/machine/machine_test.go b/pkg/machine/machine_test.go
index 1d62c02..1ba9ee8 100644
--- a/pkg/machine/machine_test.go
+++ b/pkg/machine/machine_test.go
@@ -10,25 +10,23 @@ import (
"github.com/stretchr/testify/assert"
)
-// TODO ExampleNew
func ExampleNew() {
t := &testing.T{} // Replace this with actual *testing.T in real test cases
initialState := S{"A"}
mach := NewNoRels(t, initialState)
- // Use the machine m here
+ // machine mach ready
_ = mach
}
-// TODO ExampleNewCommon
func ExampleNewCommon() {
t := &testing.T{} // Replace this with actual *testing.T in real test cases
initialState := S{"A"}
mach := NewNoRels(t, initialState)
- // Use the machine m here
+ // machine mach ready
_ = mach
}
diff --git a/pkg/machine/misc.go b/pkg/machine/misc.go
index 4e3ef24..48ea8f0 100644
--- a/pkg/machine/misc.go
+++ b/pkg/machine/misc.go
@@ -217,8 +217,8 @@ type Mutation struct {
Eval func()
}
-// StateWasCalled returns true if the Mutation was called with the passed
-// state (or does it come from relations).
+// StateWasCalled returns true if the Mutation was called (directly) with the
+// passed state (in opposite to it coming from an `Add` relation).
func (m Mutation) StateWasCalled(state string) bool {
return slices.Contains(m.CalledStates, state)
}
diff --git a/pkg/machine/transition.go b/pkg/machine/transition.go
index 3edf1ba..52f41d8 100644
--- a/pkg/machine/transition.go
+++ b/pkg/machine/transition.go
@@ -26,7 +26,7 @@ func newSteps(from string, toStates S, stepType StepType,
return ret
}
-// Transition represents processing of a single mutation withing a machine.
+// Transition represents processing of a single mutation within a machine.
type Transition struct {
ID string
// List of steps taken by this transition (so far).
@@ -41,15 +41,15 @@ type Transition struct {
// clocks of the states from after the transition
// TODO timeAfter, produce Clocks via ClockAfter(), add index diffs
ClocksAfter Clocks
- // Struct with "enter" handlers to execute
+ // State names with "enter" handlers to execute
Enters S
- // Struct with "exit" handlers to executed
+ // State names with "exit" handlers to executed
Exits S
// target states after parsing the relations
TargetStates S
// was the transition accepted (during the negotiation phase)
Accepted bool
- // Mutation call which cased this transition
+ // Mutation call which caused this transition
Mutation *Mutation
// Parent machine
Machine *Machine
diff --git a/tools/debugger/states/ss_dbg.go b/tools/debugger/states/ss_dbg.go
index c5e1d00..426cf1e 100644
--- a/tools/debugger/states/ss_dbg.go
+++ b/tools/debugger/states/ss_dbg.go
@@ -8,7 +8,7 @@ type S = am.S
// States map defines relations and properties of states.
var States = am.Struct{
- ///// Input events
+ // /// Input events
ClientMsg: {Multi: true},
ConnectEvent: {Multi: true},
@@ -34,7 +34,9 @@ var States = am.Struct{
Remove: am.SMerge(GroupPlaying, S{LogUserScrolled}),
},
- ///// External state (eg UI)
+ // /// External state (eg UI)
+
+ // focus group
TreeFocused: {Remove: GroupFocused},
LogFocused: {Remove: GroupFocused},
@@ -43,8 +45,9 @@ var States = am.Struct{
TimelineStepsFocused: {Remove: GroupFocused},
MatrixFocused: {Remove: GroupFocused},
DialogFocused: {Remove: GroupFocused},
- StateNameSelected: {Require: S{ClientSelected}},
- HelpDialog: {Remove: GroupDialog},
+
+ StateNameSelected: {Require: S{ClientSelected}},
+ HelpDialog: {Remove: GroupDialog},
ExportDialog: {
Require: S{ClientSelected},
Remove: GroupDialog,
@@ -52,7 +55,7 @@ var States = am.Struct{
LogUserScrolled: {},
Ready: {Require: S{Start}},
- ///// Actions
+ // /// Actions
Start: {},
TreeLogView: {
@@ -76,6 +79,7 @@ var States = am.Struct{
},
// tx / steps back / fwd
+
Fwd: {
Require: S{ClientSelected},
Remove: S{Playing},
@@ -95,7 +99,8 @@ var States = am.Struct{
ScrollToTx: {Require: S{ClientSelected}},
- // client
+ // client selection
+
SelectingClient: {Remove: S{ClientSelected}},
ClientSelected: {
Remove: S{SelectingClient, LogUserScrolled},
@@ -121,7 +126,7 @@ var (
}
)
-//#region boilerplate defs
+// #region boilerplate defs
// Names of all the states (pkg enum).
@@ -176,7 +181,7 @@ const (
// Names is an ordered list of all the state names.
var Names = S{
- ///// Input events
+ // /// Input events
ClientMsg,
ConnectEvent,
@@ -188,7 +193,7 @@ var Names = S{
UserFwdStep,
UserBackStep,
- ///// External state (eg UI)
+ // /// External state (eg UI)
TreeFocused,
LogFocused,
@@ -203,7 +208,7 @@ var Names = S{
LogUserScrolled,
Ready,
- ///// Actions
+ // /// Actions
Start,
TreeLogView,
@@ -229,4 +234,4 @@ var Names = S{
am.Exception,
}
-//#endregion
+// #endregion