cassiozen / useStateMachine

The <1 kb state machine hook for React
MIT License
2.38k stars 47 forks source link

Hierarchical POC #73

Open devanshj opened 3 years ago

devanshj commented 3 years ago

Just a little poc I tried (not very tested), wanted to keep it fp let me know if you like it so either of us can take this forward!

I know this is the least important thing right now as it's for 1.2.0 but as usual I was bored and wanted to do something fun xD

(I have based it on api-change-for-sendt as you haven't merged it yet)

Edit, A gist how it works:

We store the state of the machine in form of a tree. Where each node can be thought of a stateful submachine. Each node has children and state which is the identifier of the active child.

So the final "state" ie an sequential array from outermost active node to innermost active node is derived via Node.state and is not the source of truth. (Might rename the property state of node to be something else because node.state and Node.state(node) read similar but are completely different)

So essentially when an event comes we find out the nextStateIdentifier, then compute the nextState from it, then diff it with the currentState for entries and exits.

So for the test case something like this happens...

got event X
resolved nextStateIdentifier to a2
derived nextState to be [a, a2]
derived currentState to be [b, b1]
Diffed them to compute entries to be [a, a2] and exits to be [b, b1]
Run effect cleanups for exits in reverse order
Run effects for entries

The Node.transition part is completely pure (it gives [newNode, entries, exits]) so it will help with testing (I'm thinking of some snapshot based testing) and also with shouldSend as we could simply send it and see if the state changes.

cassiozen commented 3 years ago

You are a machine. This is awesome! Checking the code right now.

We store the state of the machine in form of a tree. Where each node can be thought of a stateful submachine. Each node has children and state which is the identifier of the active child.

So the final "state" ie an sequential array from outermost active node to innermost active node is derived via Node.state and is not the source of truth.

Correct. Are we also representing the state as a array in case of a flat, single-level state machine?

(Might rename the property state of node to be something else because node.state and Node.state(node) read similar but are completely different)

Yeah, makes sense.

So essentially when an event comes we find out the nextStateIdentifier, then compute the nextState from it, then diff it with the currentState for entries and exits.

Yeah, we might need to use a pathfinding algorithm (opposed to just traversing the tree until we find the nodes).

So for the test case something like this happens... got event X resolved nextStateIdentifier to a2 derived nextState to be [a, a2] derived currentState to be [b, b1] Diffed them to compute entries to be [a, a2] and exits to be [a, a2] Run effect cleanups for exits in reverse order Run effects for entries

Correct, assuming you meant that exist would be [b, b1]

devanshj commented 3 years ago

You are a machine. This is awesome!

Hahaha thanks a lot!

Are we also representing the state as a array in case of a flat, single-level state machine?

Yep Node.state always returns an array for all kinds of machines.

Yeah, we might need to use a pathfinding algorithm (opposed to just traversing the tree until we find the nodes)

How would the pathfinding algorithm work without traversing the tree though? This version does a depth-first search (assuming nextState would be the one that does what you're talking about). We could memoize it, as it's stateless ie does not depend on the state of the node or the machine.

Correct, assuming you meant that exist would be [b, b1]

Right, typo (will edit for posterity)

cassiozen commented 3 years ago

How would the pathfinding algorithm work without traversing the tree though? This version does a depth-first search (assuming nextState would be the one that does what you're talking about). We could memoize it, as it's stateless ie does not depend on the state of the node or the machine.

We could traverse from the nodes and find the shortest path without necessarily traversing the whole tree from the top - but thinking about it, it seems like a premature optimization - Most likely the state tree won't be complex or big enough to benefit from this...

devanshj commented 3 years ago

We could traverse from the nodes and find the shortest path without necessarily traversing the whole tree from the top

Huh? xD I don't follow, wym by "find the shortest path"? To make sure we're on the same page we're taking about an algorithm that takes an input rootNode and "a1" and gives output ["a", "a1"].

EDIT: Oh wait we have an additional information that the current state is b.b1 so we could start looking for nodes with identifier a1 starting from there, perhaps that's what you meant by "shortest path" -- makes sense.

but thinking about it, it seems like a premature optimization - Most likely the state tree won't be complex or big enough to benefit from this...

Yep exactly, but I think it's a good improvement nonetheless.