ReactKit / SwiftState

Elegant state machine for Swift.
MIT License
904 stars 93 forks source link

suggestion about state and context #23

Open bootchk opened 9 years ago

bootchk commented 9 years ago

Suggestions and comments about the design (not a bug.)

Should state be an attribute of the machine, or the object which is going through the machine?

My use case is a game having many tokens (AKA sprites) each having state and a state machine. I don't mind each token owning an instance of StateMachine, but then I must configure each instance the same way (easy enough to workaround.) But possibly a StateMachine should have a Design which is configured and passed to a StateMachine instance.

In other implementations of FSM 'context' seems to mean: the object which has state, which the state machine sets the state of, and which is passed to all actions (what you call Handlers) for them to act on. Your context is not the same thing? Maybe a solution is a delegate for the state machine?

On another topic, I don't understand the use case for AnyState. Is one use case adding transitions from any state to a reset state, so that you don't have to specify a transition from every state to the reset state? Maybe you could explain in your document. But I should read the code, and for example study what a transition from AnyState to AnyState would mean.

Thanks, SwiftState is rather elegant.

inamiy commented 9 years ago

Hi, thanks for great feedback :)

My use case is a game having many tokens (AKA sprites) each having state and a state machine. I don't mind each token owning an instance of StateMachine, but then I must configure each instance the same way (easy enough to workaround.) But possibly a StateMachine should have a Design which is configured and passed to a StateMachine instance.

Design sounds a good idea. I was also thinking about Graphviz (DOT file) support in https://github.com/ReactKit/SwiftState/issues/4, so using (Route)Design class will help its printing and also an easy configuration to StateMachine.

In other implementations of FSM 'context' seems to mean: the object which has state, which the state machine sets the state of, and which is passed to all actions (what you call Handlers) for them to act on. Your context is not the same thing? Maybe a solution is a delegate for the state machine?

I guess that is Event in SwiftState's terminology.

By the way, SwiftState's HandlerContext is just a Handler's tuple argument. I used this term similar to UIViewController Transitioning API, where view transition invokes methods with passing transitionContext argument.

On another topic, I don't understand the use case for AnyState. Is one use case adding transitions from any state to a reset state, so that you don't have to specify a transition from every state to the reset state? Maybe you could explain in your document. But I should read the code, and for example study what a transition from AnyState to AnyState would mean.

Exactly right. What you mentioned is one of good ways of using AnyState. You can also combine with condition as a blacklist to define the route, e.g. StateMachineTests.swift#L116-L129

bootchk commented 9 years ago

More discussion about context, delegate, handlers....

In your tests, you pass an unnamed closure as a Handler. The closure is bound to variables in scope, for example to "var returnedTransition: StateTransition?". What I think I need is passing a named closure as a Handler, where the named closure is a method bound at runtime in the statemachine to some delegate object instance.

For exaple:

delegate1 = SKLabelNode() delegate2 = SKLabelNode()

design = StateMachineDesign() // setScale is a method of SKNode which is superclass of SKLabelNode design.addRoute(.State0 => .State1, event: .Event0, handler: setScale(50) )

delegate1.sm = StateMachine(design: design, delegate: delegate1) delegate2.sm = StateMachine(design: design, delegate: delegate2)

delegate1.sm <-! .Event0

Then the stateMachine changes the state of delegate1 and calls method setScale bound to delegate1. I.E. this pseudocode inside StateMachine:

self.delegate.handler()

I hope that gives the idea, but I’m still thinking, I haven't tested. Possibly SwiftState already allows that.

inamiy commented 9 years ago

Though there's no direct support of delegation in current SwiftState, I think you can do the similar thing just by wrapping with closure of Handler type (will be a bit lengthy):

// NOTE: HandlerContext is omitted using `_`
machine.addRouteEvent(.Event0, transitions: [.State0 => .State1], handler: { [weak delegate] _ in delegate?.setScale(50) })

If we are going to support for a better delegation syntax as you mentioned above, however, there will be many difficulties because Swift itself doesn't support runtime messaging (needs Foundation.framework). In your example, if setScale(50) is called without delegate. prefix, it will just prompt compile error.

So, here's one alternative solution I came up with...

  1. [User side] Create free (global) func setScale() instead of delegate-bounded instance method. It's signature must be of the curried form func setScale(scale: Float, ...)(delegate: StateMachineDelegateProtocol) or func setScale(sclae: Float, ...) -> (StateMachineDelegateProtocol -> Void).
  2. [Library side] Support above free function event handler.

But (1) seems too heavy a burden for users, so I'm currently thinking of (2) not necessary for a while.

bootchk commented 9 years ago

Thanks. That all seems correct. Because Swift does not support runtime messaging, I think I will NOT attempt to use a design, but just implement a StateMachine factory that binds an instance of what would be a delegate to a instance of StateMachine.