Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ members = ["./", "tools/ci"]
default = []

[dependencies]
bevy = {version ="0.10", default_features = false, features = ["serialize"]}
bevy = {version = "0.12", default_features = false, features = ["serialize"]}
serde = {version = "1.0", features = ["derive"]}
ron = "0.8"

[dev-dependencies]
bevy = {version ="0.10", default_features = true, features = ["serialize"]}
bevy = {version = "0.12", default_features = true, features = ["serialize"]}

[lib]
name = "leafwing_input_playback"
Expand Down
35 changes: 21 additions & 14 deletions examples/gamepad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ fn main() {

App::new()
// This plugin contains all the code from the original example
.add_plugin(GamepadViewerExample)
.add_plugin(InputCapturePlugin)
.add_plugin(InputPlaybackPlugin)
.add_plugins((
GamepadViewerExample,
InputCapturePlugin,
InputPlaybackPlugin,
))
// Disable all input capture and playback to start
.insert_resource(InputModesCaptured::DISABLE_ALL)
.insert_resource(PlaybackStrategy::Paused)
// Toggle between playback and capture using Space
.insert_resource(InputStrategy::Playback)
.add_system(toggle_capture_vs_playback)
.add_systems(Update, toggle_capture_vs_playback)
.run();
}

Expand Down Expand Up @@ -88,14 +90,19 @@ mod gamepad_viewer_example {
.init_resource::<ButtonMaterials>()
.init_resource::<ButtonMeshes>()
.init_resource::<FontHandle>()
.add_startup_system(setup)
.add_startup_system(setup_sticks)
.add_startup_system(setup_triggers)
.add_startup_system(setup_connected)
.add_system(update_buttons)
.add_system(update_button_values)
.add_system(update_axes)
.add_system(update_connected);
.add_systems(
Startup,
(setup, setup_sticks, setup_triggers, setup_connected),
)
.add_systems(
Update,
(
update_buttons,
update_button_values,
update_axes,
update_connected,
),
);
}
}

Expand Down Expand Up @@ -527,7 +534,7 @@ mod gamepad_viewer_example {
mut events: EventReader<GamepadEvent>,
mut query: Query<(&mut Text, &TextWithButtonValue)>,
) {
for event in events.iter() {
for event in events.read() {
if let GamepadEvent::Button(GamepadButtonChangedEvent {
gamepad: _,
button_type,
Expand All @@ -548,7 +555,7 @@ mod gamepad_viewer_example {
mut query: Query<(&mut Transform, &MoveWithAxes)>,
mut text_query: Query<(&mut Text, &TextWithAxes)>,
) {
for event in events.iter() {
for event in events.read() {
if let GamepadEvent::Axis(axis_changed_event) = event {
let axis_type = axis_changed_event.axis_type;
let value = axis_changed_event.value;
Expand Down
5 changes: 2 additions & 3 deletions examples/input_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ use leafwing_input_playback::{

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(InputCapturePlugin)
.add_system(debug_input_capture)
.add_plugins((DefaultPlugins, InputCapturePlugin))
.add_systems(Update, debug_input_capture)
.run()
}

Expand Down
13 changes: 6 additions & 7 deletions examples/input_playback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,19 @@ use leafwing_input_playback::{

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(InputCapturePlugin)
.add_plugin(InputPlaybackPlugin)
.add_plugins((DefaultPlugins, InputCapturePlugin, InputPlaybackPlugin))
// Disable all input capture and playback to start
.insert_resource(InputModesCaptured::DISABLE_ALL)
.insert_resource(PlaybackStrategy::Paused)
// Creates a little game that spawns decaying boxes where the player clicks
.insert_resource(ClearColor(Color::rgb(0.9, 0.9, 0.9)))
.add_startup_system(setup)
.add_system(spawn_boxes)
.add_system(decay_boxes)
.add_systems(Startup, setup)
.add_systems(
Update,
(spawn_boxes, decay_boxes, toggle_capture_vs_playback),
)
// Toggle between playback and capture by pressing Space
.insert_resource(InputStrategy::Playback)
.add_system(toggle_capture_vs_playback)
.run()
}

Expand Down
7 changes: 3 additions & 4 deletions examples/playback_serialized_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ use leafwing_input_playback::serde::PlaybackFilePath;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(InputPlaybackPlugin)
.add_plugins((DefaultPlugins, InputPlaybackPlugin))
.insert_resource(PlaybackFilePath::new("./data/hello_world.ron"))
.add_system(debug_keyboard_inputs)
.add_systems(Update, debug_keyboard_inputs)
.run();
}

fn debug_keyboard_inputs(mut keyboard_events: EventReader<KeyboardInput>) {
for keyboard_event in keyboard_events.iter() {
for keyboard_event in keyboard_events.read() {
dbg!(keyboard_event);
}
}
3 changes: 1 addition & 2 deletions examples/serialize_captured_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ use leafwing_input_playback::serde::PlaybackFilePath;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(InputCapturePlugin)
.add_plugins((DefaultPlugins, InputCapturePlugin))
.insert_resource(PlaybackFilePath::new("./data/test_playback.ron"))
// In this example, we're only capturing keyboard inputs
.insert_resource(InputModesCaptured {
Expand Down
3 changes: 1 addition & 2 deletions examples/useless_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ use leafwing_input_playback::serde::PlaybackFilePath;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(InputPlaybackPlugin)
.add_plugins((DefaultPlugins, InputPlaybackPlugin))
.insert_resource(PlaybackFilePath::new("./data/app_exit.ron"))
.run();
}
4 changes: 2 additions & 2 deletions src/frame_counting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use std::ops::{Add, Sub};
/// The number of frames that have elapsed since the app started
///
/// Updated in [`time_tracker`] during [`CoreStage::First`].
/// Updated in [`time_tracker`] during the [`First`] schedule.
#[derive(
Resource,
Clone,
Expand Down Expand Up @@ -38,7 +38,7 @@ impl Sub<FrameCount> for FrameCount {

/// A system which increases the value of the [`FrameCount`] resource by 1 every frame
///
/// This system should run during [`CoreStage::First`].
/// This system should run during the [`First`] schedule.
pub fn frame_counter(mut frame_count: ResMut<FrameCount>) {
frame_count.0 += 1;
}
41 changes: 25 additions & 16 deletions src/input_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//!
//! These are unified into a single [`TimestampedInputs`](crate::timestamped_input::TimestampedInputs) resource, which can be played back.

use bevy::app::{App, AppExit, CoreSet, Plugin};
use bevy::app::{App, AppExit, First, Last, Plugin};
use bevy::ecs::prelude::*;
use bevy::input::gamepad::GamepadEvent;
use bevy::input::keyboard::KeyboardInput;
Expand Down Expand Up @@ -30,20 +30,20 @@ impl Plugin for InputCapturePlugin {
// Avoid double-adding frame_counter
if !app.world.contains_resource::<FrameCount>() {
app.init_resource::<FrameCount>()
.add_system(frame_counter.in_base_set(CoreSet::First));
.add_systems(First, frame_counter);
}

app.init_resource::<TimestampedInputs>()
.init_resource::<InputModesCaptured>()
.init_resource::<PlaybackFilePath>()
.add_system(
// Capture any mocked input as well
capture_input.in_base_set(CoreSet::Last),
)
.add_system(
serialize_captured_input_on_exit
.in_base_set(CoreSet::Last)
.after(capture_input),
.add_systems(
Last,
(
// Capture any mocked input as well
capture_input,
serialize_captured_input_on_exit,
)
.chain(),
);
}
}
Expand Down Expand Up @@ -118,33 +118,42 @@ pub fn capture_input(
timestamped_input.send_multiple(
frame,
time_since_startup,
mouse_button_events.iter().cloned(),
mouse_button_events.read().cloned(),
);

timestamped_input.send_multiple(
frame,
time_since_startup,
mouse_wheel_events.iter().cloned(),
mouse_wheel_events.read().cloned(),
);
} else {
mouse_button_events.clear();
mouse_wheel_events.clear();
}

if input_modes_captured.mouse_motion {
timestamped_input.send_multiple(
frame,
time_since_startup,
cursor_moved_events.iter().cloned(),
cursor_moved_events.read().cloned(),
);
} else {
cursor_moved_events.clear();
}

if input_modes_captured.keyboard {
timestamped_input.send_multiple(frame, time_since_startup, keyboard_events.iter().cloned());
timestamped_input.send_multiple(frame, time_since_startup, keyboard_events.read().cloned());
} else {
keyboard_events.clear()
}

if input_modes_captured.gamepad {
timestamped_input.send_multiple(frame, time_since_startup, gamepad_events.iter().cloned());
timestamped_input.send_multiple(frame, time_since_startup, gamepad_events.read().cloned());
} else {
gamepad_events.clear()
}

timestamped_input.send_multiple(frame, time_since_startup, app_exit_events.iter().cloned())
timestamped_input.send_multiple(frame, time_since_startup, app_exit_events.read().cloned())
}

/// Serializes captured input to the path given in the [`PlaybackFilePath`] resource.
Expand Down
14 changes: 5 additions & 9 deletions src/input_playback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//!
//! These are played back by emulating assorted Bevy input events.

use bevy::app::{App, AppExit, CoreSet, Plugin};
use bevy::app::{App, AppExit, First, Plugin, Startup};
use bevy::ecs::{prelude::*, system::SystemParam};
use bevy::input::gamepad::GamepadEvent;
use bevy::input::{
Expand All @@ -22,7 +22,7 @@ use crate::timestamped_input::{TimestampedInputEvent, TimestampedInputs};

/// Reads from the [`TimestampedInputs`] event stream to determine which events to play back.
///
/// Events are played back during [`CoreStage::First`] to accurately mimic the behavior of native `winit`-based inputs.
/// Events are played back during the [`First`] schedule to accurately mimic the behavior of native `winit`-based inputs.
/// Which events are played back are controlled via the [`PlaybackStrategy`] resource.
///
/// Input is deserialized on app startup from the path stored in the [`PlaybackFilePath`] resource, if any.
Expand All @@ -33,19 +33,15 @@ impl Plugin for InputPlaybackPlugin {
// Avoid double-adding frame_counter
if !app.world.contains_resource::<FrameCount>() {
app.init_resource::<FrameCount>()
.add_system(frame_counter.in_base_set(CoreSet::First));
.add_systems(First, frame_counter);
}

app.init_resource::<TimestampedInputs>()
.init_resource::<PlaybackProgress>()
.init_resource::<PlaybackStrategy>()
.init_resource::<PlaybackFilePath>()
.add_startup_system(deserialize_timestamped_inputs)
.add_system(
playback_timestamped_input
.after(frame_counter)
.in_base_set(CoreSet::First),
);
.add_systems(Startup, deserialize_timestamped_inputs)
.add_systems(First, playback_timestamped_input.after(frame_counter));
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/timestamped_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,11 +410,13 @@ mod tests {
const LEFT_CLICK_PRESS: InputEvent = InputEvent::MouseButton(MouseButtonInput {
button: MouseButton::Left,
state: ButtonState::Pressed,
window: Entity::PLACEHOLDER,
});

const LEFT_CLICK_RELEASE: InputEvent = InputEvent::MouseButton(MouseButtonInput {
button: MouseButton::Left,
state: ButtonState::Released,
window: Entity::PLACEHOLDER,
});

fn complex_timestamped_input() -> TimestampedInputs {
Expand Down
13 changes: 9 additions & 4 deletions tests/input_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,31 @@ const TEST_PRESS: KeyboardInput = KeyboardInput {
scan_code: 1,
key_code: Some(KeyCode::F),
state: ButtonState::Pressed,
window: Entity::PLACEHOLDER,
};

const TEST_RELEASE: KeyboardInput = KeyboardInput {
scan_code: 1,
key_code: Some(KeyCode::F),
state: ButtonState::Released,
window: Entity::PLACEHOLDER,
};

const TEST_MOUSE: MouseButtonInput = MouseButtonInput {
button: MouseButton::Left,
state: ButtonState::Pressed,
window: Entity::PLACEHOLDER,
};

fn capture_app() -> App {
let mut app = App::new();

app.add_plugins(MinimalPlugins)
.add_plugin(WindowPlugin::default())
.add_plugin(InputPlugin)
.add_plugin(InputCapturePlugin);
app.add_plugins((
MinimalPlugins,
WindowPlugin::default(),
InputPlugin,
InputCapturePlugin,
));

app
}
Expand Down
17 changes: 11 additions & 6 deletions tests/input_playback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,27 @@ const TEST_PRESS: KeyboardInput = KeyboardInput {
scan_code: 1,
key_code: Some(KeyCode::F),
state: ButtonState::Pressed,
window: Entity::PLACEHOLDER,
};

const TEST_RELEASE: KeyboardInput = KeyboardInput {
scan_code: 1,
key_code: Some(KeyCode::F),
state: ButtonState::Released,
window: Entity::PLACEHOLDER,
};

fn playback_app(strategy: PlaybackStrategy) -> App {
let mut app = App::new();

app.add_plugins(MinimalPlugins)
.add_plugin(WindowPlugin::default())
.add_plugin(InputPlugin)
.add_plugin(InputPlaybackPlugin);

app.add_plugins((
MinimalPlugins,
WindowPlugin::default(),
InputPlugin,
InputPlaybackPlugin,
));
app.world
.remove_resource::<bevy::ecs::event::EventUpdateSignal>();
*app.world.resource_mut::<PlaybackStrategy>() = strategy;

app
Expand Down Expand Up @@ -85,7 +90,7 @@ fn minimal_playback() {
#[test]
fn capture_and_playback() {
let mut app = playback_app(PlaybackStrategy::default());
app.add_plugin(InputCapturePlugin);
app.add_plugins(InputCapturePlugin);
app.insert_resource(PlaybackStrategy::Paused);

let mut input_events = app.world.resource_mut::<Events<KeyboardInput>>();
Expand Down