Application freezes if a custom bindings type is set for StateEvent and StateEventReader

(Taras Denysenko) #1

Hey all.
I’m just starting with amethyst (and Rust in general), so apologies if I’m missing something obvious here.

TL;DR I have encountered an issue where the game freezes when run if I use CoreApplication with StateEvent<T> and StateEventReader<T> for T other than the default T = StringBindings. The MCVE code is provided at the end of this post.

So, after finishing the Pong tutorial in the Amethyst book I decided, following the examples in chapter 6.2 “How to Define Custom Control Bindings”, to try and replace StringBindings with a custom bindings type in my version of the pong game. I managed to successfully do just that for the paddle movement, which is handled by a corresponding system via InputHandler, whose parameter I changed from StringBindings to my own custom PongBindings with an enum for axes. At this point I tested the game and everything worked as intended.

After that I attempted to use my custom bindings type to also handle the action of quitting the game, which I did in my state’s handle_event() method by matching against StateEvent::Input(InputEvent::ActionPressed(action)). In order to accomplish this, I made my state impl State<GameData<'static, 'static>, StateEvent<PongBindings>> instead of SimpleState and had to change the type parameters for CoreApplication accordingly by replacing StateEvent (which defaults to StringBindings for its T parameter) with StateEvent<PongBindings> and doing the same for StateEventReader.

When I made these changes, the game stopped working. After creating the window the game simply hangs, without drawing anything to the created window, playing any audio or handling any events (including attempts to close the window by clicking X). The only way to close it at this point is to kill the process.

I tried going in with a debugger but failed to determine the reason for this behaviour. I only found out that my state’s update() method does get regularly called, however run() never gets called for any of my systems (and, judging by lack of rendering and event handling, it never gets called on engine’s systems either).

To ensure that this wasn’t something specific to my game’s logic, I created an empty amethyst project with amethyst new and made the minimal changes to reproduce this issue by making a trivial MyBindings type and replacing the default StringBindings with it where necessary in the starter code. This is the code I got (I omitted use statements for brevity).

#[derive(Default, Debug)]
struct MyBindings;

impl BindingTypes for MyBindings {
    type Axis = ();
    type Action = ();
}

struct MyState;

impl State<GameData<'static, 'static>, StateEvent<MyBindings>> for MyState {
    fn on_start(&mut self, _data: StateData<'_, GameData<'_, '_>>) {}
}

type MyApplication<'a> = CoreApplication<
    'a,
    GameData<'static, 'static>,
    StateEvent<MyBindings>,
    StateEventReader<MyBindings>
>;

fn main() -> amethyst::Result<()> {
    amethyst::start_logger(Default::default());

    let app_root = application_root_dir()?;

    let config_dir = app_root.join("config");
    let display_config_path = config_dir.join("display.ron");

    let game_data = GameDataBuilder::default()
        .with_bundle(
            RenderingBundle::<DefaultBackend>::new()
                .with_plugin(
                    RenderToWindow::from_config_path(display_config_path)?
                        .with_clear([0.34, 0.36, 0.52, 1.0]),
                )
                .with_plugin(RenderFlat2D::default()),
        )?
        .with_bundle(TransformBundle::new())?;

    let mut game = MyApplication::new("/", MyState, game_data)?;

    game.run(); // Freeze

    Ok(())
}

This code exhibits the same behaviour as the previously described pong tutorial-based game: the window appears but nothing is ever drawn to it and no events are handled, it appears to be frozen.
Below is the log output from one of the runs.

[INFO][amethyst::app] Initializing Amethyst...
[INFO][amethyst::app] Version: 0.14.0
[INFO][amethyst::app] Platform: x86_64-unknown-linux-gnu
[INFO][amethyst::app] Amethyst git commit: 
[INFO][amethyst::app] Rustc version: 1.41.0 Stable
[INFO][amethyst::app] Rustc git commit: 5e1a799842ba6ed4a57e91f7ab9435947482f7d8
[INFO][winit::platform::platform::x11::window] Guessed window DPI factor: 0.9539082845052084
[WARN][gfx_backend_vulkan] Unable to find extension: VK_KHR_wayland_surface
[WARN][gfx_backend_vulkan] Unable to find layer: VK_LAYER_LUNARG_standard_validation
[WARN][rendy_factory::factory] Slow safety checks are enabled! Disable them in production by enabling the 'no-slow-safety-checks' feature!
[INFO][rendy_util::wrap] Slow safety checks are enabled! You can disable them in production by enabling the 'no-slow-safety-checks' feature!
^C

(The warnings in the log were present even before the changes, when the application worked, so they are likely irrelevant.)

I tested on Ubuntu 18.04 with Rust 1.41 stable and 1.42 nightly, amethyst 0.14.0 and amethyst 0.13.2 (with --features=vulkan in every case) and I got the same result each time, with the exception of different version numbers in the log.

Due to my lack of experience it’s probable I’m simply missing something, so I thought I should ask here first, before opening an issue at the engine’s github repo.

If anybody decides to try this example out, I put the MCVE code on Bitbucket: https://bitbucket.org/TerraPass/amethyst-custom-bindings-mcve/src/master/

1 Like
(Azriel Hoh) #2

Hiya, and welcome!

TL;DR: Add this to the State implementation:

    fn update(
        &mut self,
        mut data: StateData<'_, GameData<'_, '_>>,
    ) -> Trans<GameData<'static, 'static>, StateEvent<MyBindings>> {
        data.data.update(&mut data.world);
        Trans::None
    }

Explanation:

The freezing isn’t to do with the BindingTypes, but not calling data.data.update(&mut data.world); in State::update.

  • Somewhere within the application is the dispatcher – which is a graph of functions. I think the winit event loop is run somewhere within the RenderingBundle.
  • The dispatcher has to be run to make sure the event loop runs, for the window to not be frozen.
  • State::update is called every frame (Amethyst’s CoreApplication loop), and we need to tell the Dispatcher to run, which is what that data.data.update(&mut data.world) is;

Oh ya, SimpleState::update is a trait that, there’s a blanket impl State for SimpleState which calls that dispatcher update for you.

p/s: Thank you for laying out the issue so clearly (plus an MCVE!) :bowing_man:

2 Likes
(Taras Denysenko) #3

:man_facepalming: Oh, right!
Now I remember reading about needing to make this call from custom states. Completely forgot about this by the time I got through the tutorial. :sweat_smile:

Thank you so much @azriel91 for help and also for additionally explaining what this incantation actually does.