Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return to the caller state?

I am using C#'s stateless state-machine, but I've had this issue in the past with other state-machine implementation. The question is mostly theoretical, though I can add some dummy code if asked for.

I have a situation which I am quite unsure how to solve. Lets say you have 2 states (walk and run), and they both can transition to a 3rd state (jump). Once the 3rd state is done, I wish to go back to the caller state. In a way, I'd have to be able to influence the callee trigger, but this is only passed on to OnEntry and the likes, not the state itself.

Using EntryFrom doesn't seem to work, because there is no way to influence the "jump" state itself, only the OnEntry function. Using PermitDynamic doesn't seem to work either, because once in the "jump" state, I have no idea where I came from.

I could have 2 jump states (jumpfromrun, jumpfromwalk) with a single OnEntry function, to minimize some duplicate code. This is what I currently use.

I could save the previous state in an external variable and pass that to my state update. I do not wish to do this (at all).

Is there a known technique or way to deal with this sort of problem? Having a shared state, that should transition back to the caller? Thank you!

like image 286
scx Avatar asked Nov 14 '25 12:11

scx


1 Answers

From reading the docs, it seems like this is a pretty good usecase for substates.

You can have a jump state:

stateMachine.Configure(State.Jumping)
    .OnEntry(() => StartJump())
    .OnExit(() => EndJump());

and two jump sub-states. One for walking, one for running:

stateMachine.Configure(State.WalkJumping)
    .SubstateOf(State.Jumping)
    .Permit(Trigger.DoneJumping, State.Walking);

stateMachine.Configure(State.RunJumping)
    .SubstateOf(State.Jumping)
    .Permit(Trigger.DoneJumping, State.Running);

Then you make walk and run route to WalkJumping and RunJumping, respectively.

stateMachine.Configure(State.Walking)
    .OnEntry(() => StartWalking())
    .OnExit(() => EndWalking());
    .Permit(Trigger.Jump, State.WalkJumping);


stateMachine.Configure(State.Running)
    .OnEntry(() => StartRunning())
    .OnExit(() => EndRunning());
    .Permit(Trigger.Jump, State.RunJumping);

Using substrates like this, as opposed to just regular states, has the added benefit that the State.Jumping can define the same OnEntry and OnExit that will be inherited by both WalkJumping and EndJumping.

Additionally, it gives you an easier way to check if you're in the middle of a jump. Instead of:

var state = stateMachine.state;
var isJumping = state == State.WalkJumping || state == RunJumping;

You can simply do:

var isJumping = stateMachine.IsInState(State.Jumping);

Which will properly consider all substates, which are currently WalkJumping and RunJumping. But if you add a new jump substate in the future, e.g. DoubleJumping, then the isJumping predicate will automatically cover that case too, without needing to remember to fix it.

like image 200
Alexander Avatar answered Nov 17 '25 07:11

Alexander



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!