pytransitions / transitions-gui

A frontend for transitions state machines
MIT License
65 stars 7 forks source link

Multiple State Machine Support #15

Closed danieljlevine closed 5 months ago

danieljlevine commented 3 years ago

I'm interested in displaying the state of multiple state machines simultaneously on the same web page. It would seem that each of the transitions-gui instances starts it's own web server. I suppose, I could use different ports for this. Any chance there's already a way to do this using a single server? I'm headed towards a hundred independent state machines (for character states, as an example). The other method that occurs to me is to perhaps use the parallel state machine.

Also is there a way to color the state machine by the state it is presently in?

aleneum commented 3 years ago

Hello @danieljlevine,

are you talking about different state machines (with individual transitions and states) or models (stateful instances using the same states/transitions)?

Any chance there's already a way to do this using a single server?

You can pass websocket_handler to the WebMachine constructor. A websocket_handler just needs to implement a send_message(<json>) method. WebMachine will not create a server if this argument is present.

The frontend code is pretty naive. It will connect to a websocket (at <url>:<port>/ws) and expect valid state machine configurations/state changes as JSON. One machine can manage multiple models. The relevant information to manage multiple models are already passed to the frontent. AFAICT it is not considered though.

Also is there a way to color the state machine by the state it is presently in?

Right now, the CSS class .current is set on an active state/node. This could be extended to set model-specific classes as well. Nodes probably have IDs and classes which can be used to customize the appearance further.

aleneum commented 3 years ago

Additionally,

WebMachine will reuse tornado's event queue when it has been initialized already. So when you initialize multiple instances of WebMachine with different ports, each WebMachine will run its own server thread but will be processed in the same event loop. This might need to be verified though.

danieljlevine commented 3 years ago

I suppose what I'm requesting is the display of several instances of the state machine (perhaps this is the state of independent models governed by the state machine I define) on the WebPage. It looks like pytransitions supports the multiple models. If I passed multiple models in to the constructor as in the pytransitions example, would transitions-gui display both models? (I suppose I could just try it.)

aleneum commented 3 years ago

In 27ea90c, I implemented handling of mutliple models. You can pass custome css-like style selectors based on model classes and model names. See examples/multiple.py. It's not polished though. If two models are in the same state, only one selector in multiple.py will be visible. Edge classes might be removed prematurely. However, there is room for customization by tinkering around with the selectors. For me, multiple.py looks like this on localhost:8080 and localhost:8081:

legend_splitscreen

aleneum commented 3 years ago

if running multiple instances of tornado in threads becomes an issue, I will have a look into whether threading can be limited to one server thread which can be reused by multiple WebMachine instances.

danieljlevine commented 3 years ago

I see, so with different models they are shown on the same state machine. Originally, I was thinking I'd have numerous replicated models being shown and identified, as opposed to having them all collapsed on to a single visualized machine. Maybe another way to do this is to be able to show a list of model IDs adorning the states they are presently in in their respective models? You are doing that nicely with colors here, but I suppose with a large number of models, this won't work so well (perhaps my idea won't either with even larger numbers of models, but it's a start). Is there a way to name my models with a string or number and have that appear within the state the model presently has. Perhaps I'd use the state entering and leaving callbacks to remove/add the ids from/to the state? I suppose while I'm at it perhaps I'd do the same for the transition to show which model id is using the transition at that moment.

Thoughts?

danieljlevine commented 3 years ago

What if I used parallel states instead to represent the different models in parallel? I could label each parallel machine with the model id (where the example uses "numbers" and "greek") and move along independently. My state machines are HSMs, so hopefully I can still do that. Maybe that's what I want.

aleneum commented 3 years ago

I think both approaches (state node extended with model names or legend entries extended with model states) are feasible. See https://js.cytoscape.org/ for a better idea about how to edit graphs. Nodes are edited in WebMachine.selectState. Currently, only classes are set and removed. If you have an idea how to edit labels instead (or in addition to) altering classes, let me know.

aleneum commented 3 years ago

What if I used parallel states instead to represent the different models in parallel?

I usually try to find the simplest FSM architecture that gets the job done. Parallel states can be a solution but can also result in quite complex configurations. If you have instances that share states and transitions (e.g. agents, NPCs), I'd go for multiple models and simple machine. If instances rather act as components (e.g. robot arm, leg, head), I'd choose parallel states.

danieljlevine commented 3 years ago

Thanks for the advice. I suppose I should consider the state annotation with the ids.

danieljlevine commented 3 years ago

Trying out the graph_css parameter. I would have thought that I could pass graph_css=[] to the NestedWebMachine constructor and have it not doing anything different. Instead it halts with Exception <class 'ValueError'>. Is graph_css supported in NestedWebMachine? I assumed, yes. It barfed on my agent_css list, so I'm just simplifying. Removing the graph_css makes it not throw the exception.

danieljlevine commented 3 years ago

Changed it to just a WebMachine and it still didn't work. Must be missing something subtle here. Also tried setting graph_css=None with same issue.

danieljlevine commented 3 years ago

Running your multiple.py example produces the same issue, but more useful output (to you perhaps):

ValueError: Passing arguments dict_keys[['graph_css']] caused an inheritance error: object init() takes exactly one argument (the instance to initialize).

I wonder if I'm not using your latest code somehow...

aleneum commented 3 years ago

Did you install transitions-gui maybe? I experience this when I accidentally use classes from site-packages instead of local versions. Try pip uninstall transitions-gui in your environment.

aleneum commented 3 years ago

I would have thought that I could pass graph_css=[] to the NestedWebMachine

I'd expect the same.

danieljlevine commented 3 years ago

So, I did install transitions and transitions-gui initially and it worked until using this graph_css feature. So I did the uninstall of transitions-gui. It doesn't seem to have helped it to start working. Here's the message I get from your multiple.py pasted into my jupyter notebook:

TypeError Traceback (most recent call last) ~/miniconda3/lib/python3.8/site-packages/transitions/core.py in init(self, model, states, initial, transitions, send_event, auto_transitions, ordered_transitions, ignore_invalid_triggers, before_state_change, after_state_change, name, queued, prepare_event, finalize_event, model_attribute, kwargs) 536 try: --> 537 super(Machine, self).init(kwargs) 538 except TypeError as err:

TypeError: object.init() takes exactly one argument (the instance to initialize)

During handling of the above exception, another exception occurred:

ValueError Traceback (most recent call last)

in 46 agents = [Agent(), Soldier(), Soldier("SgtBloom")] 47 ---> 48 agent_machine = WebMachine( 49 model=agents, 50 states=agent_states, ~/miniconda3/lib/python3.8/site-packages/transitions_gui/web.py in __init__(self, *args, **kwargs) 31 _init_default_handler(self, kwargs.pop('port', 8080), 32 kwargs.pop('daemon', False))) ---> 33 super(WebMachine, self).__init__(*args, **kwargs) 34 35 def process_message(self, message): ~/miniconda3/lib/python3.8/site-packages/transitions/extensions/markup.py in __init__(self, *args, **kwargs) 26 self._add_markup_model(m) 27 else: ---> 28 super(MarkupMachine, self).__init__(*args, **kwargs) 29 self._markup['before_state_change'] = [x for x in (rep(f) for f in self.before_state_change) if x] 30 self._markup['after_state_change'] = [x for x in (rep(f) for f in self.before_state_change) if x] ~/miniconda3/lib/python3.8/site-packages/transitions/core.py in __init__(self, model, states, initial, transitions, send_event, auto_transitions, ordered_transitions, ignore_invalid_triggers, before_state_change, after_state_change, name, queued, prepare_event, finalize_event, model_attribute, **kwargs) 537 super(Machine, self).__init__(**kwargs) 538 except TypeError as err: --> 539 raise ValueError('Passing arguments {0} caused an inheritance error: {1}'.format(kwargs.keys(), err)) 540 541 # initialize protected attributes first ValueError: Passing arguments dict_keys(['graph_css']) caused an inheritance error: object.__init__() takes exactly one argument (the instance to initialize) I see in web.py you pop the web arguments and send things along to the super class. Which looks like it finds its way to markup.py, but it doesn't seem to pop the graph_css arguments before passing things along to the super class and that's where I believe graph_css is unepexpected and things go wrong in core.py.
danieljlevine commented 3 years ago

Oh, is the problem that I have done a pip install transitions in order to put pytransitions into ~/miniconda3? I think for my particular environment, I need the correct pytransitions to be found under ~/miniconda3. So perhaps I need to pip uninstall transitions, then git clone your transitions repo then somehow install that latest source into ~/miniconda3. Does that makes sense for this to behave differently (correctly)?

If so, I see I can do something like changing into the pytransition directory with setup.py and do:

python3 setup.py install

Would that install it into ~/miniconda3?

Guess I'll give it a tray and see what happens.

danieljlevine commented 3 years ago

The above did what I expected and installed transitions into ~/miniconda3. But no difference in bahavior. So I looked at the MarkupMachine code. It seems to be reference a "markup" parameter as opposed to a "graph_css" parameter you are using in the multiple.py example. Perhaps your latest code isn't pushed?

I changed "graph_css" to "markup" and got further along in the process. However, then it complained with:

~/miniconda3/lib/python3.8/site-packages/transitions-0.8.6-py3.8.egg/transitions/extensions/markup.py in init(self, *args, kwargs) 21 22 if self._markup: ---> 23 models_markup = self._markup.pop('models', []) 24 super(MarkupMachine, self).init(None, self._markup) 25 for m in models_markup:

TypeError: pop expected at most 1 argument, got 2

aleneum commented 3 years ago

I added the current state to the legend entry. wont suffice for hundreds of states but maybe makes working with multiple states less cluttered:

legend_states

aleneum commented 3 years ago

~/miniconda3/lib/python3.8/site-packages/transitions_gui/web.py

you still use an installed version of transitions_gui. seems like you may have multiple versions installed.

aleneum commented 3 years ago
git clone https://github.com/pytransitions/transitions-gui.git 
cd transitions-gui
pip install -r requirements.txt
# [1]
python examples/multiple.py

and make sure that you dont have an older version of transitions-gui installed. You could install the package in editable mode (put pip install -e . at [1]) if you want to.

aleneum commented 3 years ago

ValueError: Passing arguments dict_keys(['graph_css']) caused an inheritance error: object.init() takes exactly one argument (the instance to initialize)

this basically means that graph_css hasn't been processed which still implies that you are dealing with an old version of WebMachine. You could import transitions_gui from an interactive shell and check its origin with __file__

danieljlevine commented 3 years ago

Ok, about to follow your git clone to install transitions-gui from source. I was able to pip uninstall a few transitions-gui modules. I also pip uninstalled transitions. So, now do I clone the source for transitions and install that from source and then do the same as you describe for transitions-gui?

aleneum commented 3 years ago

pip install -r requirements.txt in the transitions-gui code folder should install the most recent version of transitions which is sufficient.

danieljlevine commented 3 years ago

Ok, I believe I have everything working as intended. Am I using the source because what we're working with hasn't been released? Just want to make sure I'm following why I'm doing it this way as opposed to using pip install.

aleneum commented 3 years ago

I created a new dev branch dev-multimodels since all these updates to main.js might bloat git's history. Futhermore, I am not sure which changes will be kept. I added model names to nodes:

legend_node_label

danieljlevine commented 3 years ago

Ok, still working with what I've got her now on my models. I see the legends for my models. Background color doesn't seem to be getting applied for some reason. I'll keep looking at it. Thanks for getting me straightened out!

danieljlevine commented 3 years ago

I'm really liking the model names in the states they occupy. I gave a demo of it and people were pretty excited about what they saw. How can I turn off the legend? It doesn't seem to be necessary for my needs, since color doesn't mean much other than recent change.

Also, it would seem that my nested FSM doesn't color the activated transition when the transition is between sub-states of a state. Other transitions get colored fine as far as I can recall.

aleneum commented 5 months ago

This has been merged with #40