diff --git a/src/input_capture.rs b/src/input_capture.rs index 673cd21..816e2ca 100644 --- a/src/input_capture.rs +++ b/src/input_capture.rs @@ -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, Last, Plugin}; +use bevy::app::{App, AppExit, First, Last, Plugin}; use bevy::core::{update_frame_count, FrameCount}; use bevy::ecs::prelude::*; use bevy::input::gamepad::GamepadEvent; @@ -27,22 +27,62 @@ pub struct InputCapturePlugin; impl Plugin for InputCapturePlugin { fn build(&self, app: &mut App) { - app.init_resource::() - .init_resource::() - .init_resource::() + app.add_event::() + .add_event::() + .add_systems(First, initiate_input_capture) .add_systems( Last, ( // Capture any mocked input as well capture_input, - serialize_captured_input_on_exit, + ( + serialize_captured_input_on_final_capture_frame + .run_if(resource_exists::), + serialize_captured_input_on_end_capture_event, + serialize_captured_input_on_exit, + ) + .run_if(resource_exists::), ) + .run_if(resource_exists::) .chain() .before(update_frame_count), ); } } +/// An Event that users can send to initiate input capture. +/// +/// Data is serialized to the provided `filepath` when either an [`EndInputCapture`] or an [`AppExit`] event is detected. +#[derive(Debug, Default, Event)] +pub struct BeginInputCapture { + /// The input mechanisms that will be captured, see [`InputModesCaptured`]. + pub input_modes_captured: InputModesCaptured, + /// The filepath at which to serialize captured input data. + pub filepath: Option, + /// The number of frames for which inputs should be captured. + /// If None, inputs will be captured until an [`EndInputCapture`] or [`AppExit`] event is detected. + pub frames_to_capture: Option, + /// A `Window` entity which acts as a filter for which inputs will be captured. + /// This data will not be serialized, so that a target window can be selected on playback. + pub window_to_capture: Option, +} + +/// An Event that users can send to end input capture and serialize data to disk. +#[derive(Debug, Event)] +pub struct EndInputCapture; + +/// The final [`FrameCount`] at which inputs will stop being captured. +/// +/// If this Resource is attached, [`TimestampedInputs`] will be serialized and input capture will stop once `FrameCount` reaches this value. +#[derive(Debug, Resource)] +pub struct FinalCaptureFrame(FrameCount); + +/// The `Window` entity for which inputs will be captured. +/// +/// If this Resource is attached, only input events on the window corresponding to this entity will be captured. +#[derive(Debug, Resource)] +pub struct InputCaptureWindow(Entity); + /// The input mechanisms captured via the [`InputCapturePlugin`], configured as a resource. /// /// By default, all supported input modes will be captured. @@ -86,6 +126,32 @@ impl Default for InputModesCaptured { } } +/// Initiates input capture when a [`BeginInputCapture`] is detected. +pub fn initiate_input_capture( + mut commands: Commands, + mut begin_capture_events: EventReader, + frame_count: Res, +) { + if let Some(event) = begin_capture_events.read().next() { + commands.init_resource::(); + commands.insert_resource(event.input_modes_captured.clone()); + if let Some(path) = &event.filepath { + commands.insert_resource(PlaybackFilePath::new(path)); + } else { + commands.init_resource::(); + } + if let Some(final_frame) = event.frames_to_capture { + commands.insert_resource(FinalCaptureFrame(FrameCount( + frame_count.0.wrapping_add(final_frame.0), + ))); + } + if let Some(window_entity) = &event.window_to_capture { + commands.insert_resource(InputCaptureWindow(*window_entity)); + } + } + begin_capture_events.clear(); +} + /// Captures input from the [`bevy::window`] and [`bevy::input`] event streams. /// /// The input modes can be controlled via the [`InputModesCaptured`] resource. @@ -98,6 +164,7 @@ pub fn capture_input( mut gamepad_events: EventReader, mut app_exit_events: EventReader, mut timestamped_input: ResMut, + window_to_capture: Option>, input_modes_captured: Res, frame_count: Res, time: Res