Skip to content

Conversation

@jimsynz
Copy link
Contributor

@jimsynz jimsynz commented Jan 18, 2026

Summary

  • Add custom operational states beyond :idle/:executing via states DSL section
  • Add category-based command concurrency with configurable limits
  • Add mid-execution state transitions via BB.Command.transition_state/2
  • Add reusable BB.Command.SetState handler for simple state changes
  • Add compile-time validation of state and category references

New DSL Features

States Section

states do
  initial_state :idle  # default

  state :recording do
    doc "Recording sensor data"
  end

  state :processing do
    doc "Processing recorded data"
  end
end

Categories with Concurrency Limits

commands do
  category :motion do
    concurrency_limit 1  # only one motion command at a time
  end

  category :sensing do
    concurrency_limit 2  # up to 2 concurrent sensing commands
  end

  command :move_to do
    handler MyRobot.Commands.MoveTo
    category :motion
    allowed_states [:idle]
  end
end

BB.Command.SetState Handler

commands do
  command :enter_recording do
    handler {BB.Command.SetState, to: :recording}
    allowed_states [:idle]
  end
end

New Introspection APIs

  • BB.Robot.Runtime.operational_state/1 - returns actual state (not :executing)
  • BB.Robot.Runtime.executing?/1 - returns true if any command running
  • BB.Robot.Runtime.executing?/2 - returns true if specific category occupied
  • BB.Robot.Runtime.executing_commands/1 - list of running command info
  • BB.Robot.Runtime.category_availability/1 - map of {current, limit} per category

Backwards Compatibility

state/1 returns :executing when in :idle state with commands running, matching previous behaviour. Existing robots without states or category definitions work identically.

Test plan

  • All 837 existing tests pass
  • 30 new tests for state system features
  • All quality checks pass (credo, dialyzer, formatter)

…ency

Implement an extensible state system that allows:

- Custom operational states beyond `:idle`/`:executing` via `states` DSL section
- Category-based command concurrency with configurable limits
- Mid-execution state transitions via `BB.Command.transition_state/2`
- Reusable `BB.Command.SetState` handler for simple state changes
- Compile-time validation of state and category references

New DSL entities:
- `state` entity in `states` section with `initial_state` option
- `category` entity in `commands` section with `concurrency_limit` option

New introspection APIs:
- `BB.Robot.Runtime.operational_state/1` - actual state (not `:executing`)
- `BB.Robot.Runtime.executing?/1,2` - check if commands running
- `BB.Robot.Runtime.executing_commands/1` - list running commands
- `BB.Robot.Runtime.category_availability/1` - category counts and limits

Backwards compatible: `state/1` returns `:executing` when in `:idle` with
commands running, matching previous behaviour.
Add documentation/tutorials/11-custom-states.md covering:
- Defining custom operational states via the states DSL section
- Using BB.Command.SetState for simple state transitions
- Mid-execution state transitions with BB.Command.transition_state/2
- Command categories with configurable concurrency limits
- Introspection APIs for querying execution state
- Best practices for state and category design

Also update related tutorials to link to the new content.
@jimsynz jimsynz force-pushed the feature/extensible-state-system branch 2 times, most recently from d9c5f48 to abf38b1 Compare January 18, 2026 06:09
Redesign the command preemption model to separate concerns:

- `allowed_states` now purely controls which operational states a command
  can run in. Use `:*` for "any state" which expands to all defined states
  at compile time.

- `cancel` is a new option that specifies which categories of commands
  this command can cancel when starting. Use `:*` to cancel all categories,
  or list specific categories like `[:motion, :default]`.

This makes the preemption behaviour explicit rather than magical:

Old model (confusing):
```elixir
command :move_to do
  allowed_states [:idle, :executing]  # Magic: :executing means "can preempt"
end
```

New model (explicit):
```elixir
command :move_to do
  allowed_states [:idle]
  cancel [:motion]  # Explicit: can cancel commands in :motion category
end
```

Changes:
- Add `cancel` option to command DSL with `{:wrap_list, :atom}` type
- Create WildcardExpansionTransformer to expand `:*` in both options
- Update ValidateStateRefs to remove `:executing` special case
- Update ValidateCategoryRefs to validate `cancel` references
- Update runtime to use `cancel` for cancellation logic
- Update runtime to simplify operational state checking
- Update tests to use new cancel semantics
- Update tutorials 05 and 11 to document new model
@jimsynz jimsynz force-pushed the feature/extensible-state-system branch from abf38b1 to 0b1181d Compare January 18, 2026 06:16
@jimsynz jimsynz merged commit 4d7569c into main Jan 18, 2026
15 checks passed
@jimsynz jimsynz deleted the feature/extensible-state-system branch January 18, 2026 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants