Best practices for communication between state and systems

(Marco Fruhwirth) #1

Sorry if this is a little bit out of order, but it is a complex topic…

While working on a pause button for Evoli, we realised that the pause button should control the game states.

The button should display ‘Play’ when the game is in the pause state and display ‘Pause’ when the game is in the main state. Clicking on ‘Pause’ should push the pause state on the stack and clicking on ‘Play’ would pop the pause state.

In addition, pressing P should toggle between the two states. This can be done in handle_event.

The first question was, how to change the state from within the UI system. After browsing the examples, I stumbled upon TransEvent and realised that it is possible to use TransEvent to change the state from within a system.
But after further investigation, I stumbled on the PR regarding communication between state and systems using resources: https://github.com/amethyst/amethyst/pull/1154

There are a lot of good points in that PR, but I can’t see a consensus on how communication between state and systems should be done.

The second question is how the UI system determines the current state we are in. This can be done using resources (enums or boolean flags per state).

Some of my notes:

  • Systems should not know about the states and rely on resources only (But why)
  • Systems run by the game data dispatcher might know about states since they are independent of them.
  • The only responsibility of states is to run the dispatchers and perform transitions
  • State-specific systems should not use TransEvent, because they will stop themselves, so they can only do transitions one-way.

There will be three states:

  • Menu state
  • Main state
  • Paused state

The UI only exists in the main state and in the paused state, so the UI system should not be attached to the game data dispatcher because it should not run in the menu state.

Instead, the main state should have two dispatchers. One for the game and one for the UI and the UI dispatcher runs in shadow_update.

So we have systems specific to states, systems that run in shadow update and also global game data systems. I feel like that is too specific and in that case, tightly coupling a system to states and using TransEvent should be fine.

Any thoughts?

2 Likes

(Victor Cornillère) #2

In the PR you linked to, they mention sending a custom type of event from the System and handling that event in the handle_event method of the State. While I agree it would work well, I don’t see how they manage to get those events.
Doesn’t the handle_event method only accept StateEvent as an argument ? How do you make it accept custom event types ?

I saw that you can change the type of StateEvent by not using SimpleState and implementing a different State trait. But then, how do you make it handle the original StateEvent with ui, input and window events ?

1 Like

(Victor Cornillère) #3

I thought about something.
In the book, about communication between systems and states, they mention using a Game structure that holds the latest user action.
But isn’t that redundant with the InputHandler ?
Can’t we define a Pause action in the InputHandler that would be bound to the P button and send this action from the TimeControl system when the Pause button is clicked ?
The state would then check if the Pause action is down and push or pop states accordingly.

0 Likes

(Marco Fruhwirth) #4

Yes, you can access other event types by using State instead of SimpleState. In that case the core application will use a different event reader. In both cases you should be able to access the event channel using world.write_resource::<EventReader<StateEvent>>. But I think a solution with custom event channels/readers might be preferable.

0 Likes

(Marco Fruhwirth) #5

For reference: The related PR by @sunreef https://github.com/amethyst/evoli/pull/40

1 Like

(Joël Lupien) #6

I didn’t read everything, but don’t use TransEvent. Its made for automation.

If you want to do state changes from an event, that means you want to react to that event from your State::handle_event method. For example the raw UiEvent::Click event. Alternatively, if you want more automation you can use add a event retrigger component https://amethyst.rs/doc/master/doc/amethyst_ui/struct.uibuttonactionretrigger on your button that emits a custom event (ie MyUiEvent::Pause) and the State::handle_event method checks for those.

2 Likes

Evoli: Dev-Log