Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .github/actions/install-linux-deps/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ inputs:
description: Install xkb (libxkbcommon-dev)
required: false
default: "false"
x264:
description: Install x264 (libx264-dev)
required: false
default: "false"
runs:
using: composite
steps:
Expand All @@ -47,3 +51,4 @@ runs:
${{ fromJSON(inputs.udev) && 'libudev-dev' || '' }}
${{ fromJSON(inputs.wayland) && 'libwayland-dev' || '' }}
${{ fromJSON(inputs.xkb) && 'libxkbcommon-dev' || '' }}
${{ fromJSON(inputs.x264) && 'libx264-164 libx264-dev' || '' }}
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ jobs:
with:
wayland: true
xkb: true
x264: true
- name: CI job
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- lints
Expand Down Expand Up @@ -374,6 +375,7 @@ jobs:
with:
wayland: true
xkb: true
x264: true
- name: Build and check doc
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- doc
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_dev_tools/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ keywords = ["bevy"]

[features]
bevy_ci_testing = ["serde", "ron"]
screenrecording = ["x264"]

[dependencies]
# bevy
Expand All @@ -19,12 +20,15 @@ bevy_camera = { path = "../bevy_camera", version = "0.18.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.18.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.18.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.18.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.18.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.18.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.18.0-dev" }
bevy_picking = { path = "../bevy_picking", version = "0.18.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.18.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.18.0-dev" }
bevy_text = { path = "../bevy_text", version = "0.18.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.18.0-dev" }
bevy_shader = { path = "../bevy_shader", version = "0.18.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.18.0-dev" }
bevy_ui_render = { path = "../bevy_ui_render", version = "0.18.0-dev" }
Expand All @@ -35,6 +39,7 @@ bevy_state = { path = "../bevy_state", version = "0.18.0-dev" }
serde = { version = "1.0", features = ["derive"], optional = true }
ron = { version = "0.10", optional = true }
tracing = { version = "0.1", default-features = false, features = ["std"] }
x264 = { version = "0.5.0", optional = true }

[lints]
workspace = true
Expand Down
20 changes: 16 additions & 4 deletions crates/bevy_dev_tools/src/ci_testing/config.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use bevy_ecs::prelude::*;
use bevy_math::{Quat, Vec3};
use serde::Deserialize;

/// A configuration struct for automated CI testing.
///
/// It gets used when the `bevy_ci_testing` feature is enabled to automatically
/// exit a Bevy app when run through the CI. This is needed because otherwise
/// Bevy apps would be stuck in the game loop and wouldn't allow the CI to progress.
#[derive(Deserialize, Resource, PartialEq, Debug, Default)]
#[derive(Deserialize, Resource, PartialEq, Debug, Default, Clone)]
pub struct CiTestingConfig {
/// The setup for this test.
#[serde(default)]
Expand All @@ -17,7 +18,7 @@ pub struct CiTestingConfig {
}

/// Setup for a test.
#[derive(Deserialize, Default, PartialEq, Debug)]
#[derive(Deserialize, Default, PartialEq, Debug, Clone)]
pub struct CiTestingSetup {
/// The amount of time in seconds between frame updates.
///
Expand All @@ -28,11 +29,11 @@ pub struct CiTestingSetup {
}

/// An event to send at a given frame, used for CI testing.
#[derive(Deserialize, PartialEq, Debug)]
#[derive(Deserialize, PartialEq, Debug, Clone)]
pub struct CiTestingEventOnFrame(pub u32, pub CiTestingEvent);

/// An event to send, used for CI testing.
#[derive(Deserialize, PartialEq, Debug)]
#[derive(Deserialize, PartialEq, Debug, Clone)]
pub enum CiTestingEvent {
/// Takes a screenshot of the entire screen, and saves the results to
/// `screenshot-{current_frame}.png`.
Expand All @@ -47,6 +48,17 @@ pub enum CiTestingEvent {
///
/// [`AppExit::Success`]: bevy_app::AppExit::Success
AppExit,
/// Starts recording the screen.
StartScreenRecording,
/// Stops recording the screen.
StopScreenRecording,
/// Smoothly moves the camera to the given position.
MoveCamera {
/// Position to move the camera to.
translation: Vec3,
/// Rotation to move the camera to.
rotation: Quat,
},
/// Sends a [`CiTestingCustomEvent`] using the given [`String`].
Custom(String),
}
Expand Down
58 changes: 43 additions & 15 deletions crates/bevy_dev_tools/src/ci_testing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
mod config;
mod systems;

use crate::EasyCameraMovementPlugin;
#[cfg(feature = "screenrecording")]
use crate::EasyScreenRecordPlugin;

pub use self::config::*;

use bevy_app::prelude::*;
Expand All @@ -26,24 +30,48 @@ pub struct CiTestingPlugin;

impl Plugin for CiTestingPlugin {
fn build(&self, app: &mut App) {
#[cfg(not(target_arch = "wasm32"))]
let config: CiTestingConfig = {
let filename = std::env::var("CI_TESTING_CONFIG")
.unwrap_or_else(|_| "ci_testing_config.ron".to_string());
std::fs::read_to_string(filename)
.map(|content| {
ron::from_str(&content)
.expect("error deserializing CI testing configuration file")
})
.unwrap_or_default()
};
let config = if !app.world().is_resource_added::<CiTestingConfig>() {
// Load configuration from file if not already setup
#[cfg(not(target_arch = "wasm32"))]
let config: CiTestingConfig = {
let filename = std::env::var("CI_TESTING_CONFIG")
.unwrap_or_else(|_| "ci_testing_config.ron".to_string());
std::fs::read_to_string(filename)
.map(|content| {
ron::from_str(&content)
.expect("error deserializing CI testing configuration file")
})
.unwrap_or_default()
};

#[cfg(target_arch = "wasm32")]
let config: CiTestingConfig = {
let config = include_str!("../../../../ci_testing_config.ron");
ron::from_str(config).expect("error deserializing CI testing configuration file")
};

#[cfg(target_arch = "wasm32")]
let config: CiTestingConfig = {
let config = include_str!("../../../../ci_testing_config.ron");
ron::from_str(config).expect("error deserializing CI testing configuration file")
config
} else {
app.world().resource::<CiTestingConfig>().clone()
};

// Add the `EasyCameraMovementPlugin` to the app if it's not already added.
// To configure the movement speed, add the plugin first.
if !app.is_plugin_added::<EasyCameraMovementPlugin>() {
app.add_plugins(EasyCameraMovementPlugin::default());
}
// Add the `EasyScreenRecordPlugin` to the app if it's not already added and one of the event is starting screenrecording.
// To configure the recording quality, add the plugin first.
#[cfg(feature = "screenrecording")]
if !app.is_plugin_added::<EasyScreenRecordPlugin>()
&& config
.events
.iter()
.any(|e| matches!(e.1, CiTestingEvent::StartScreenRecording))
{
app.add_plugins(EasyScreenRecordPlugin::default());
}

// Configure a fixed frame time if specified.
if let Some(fixed_frame_time) = config.setup.fixed_frame_time {
app.insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_secs_f32(
Expand Down
25 changes: 25 additions & 0 deletions crates/bevy_dev_tools/src/ci_testing/systems.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::CameraMovement;

use super::config::*;
use bevy_app::AppExit;
use bevy_camera::Camera;
use bevy_ecs::prelude::*;
use bevy_render::view::screenshot::{save_to_disk, Screenshot};
use tracing::{debug, info};
Expand Down Expand Up @@ -51,6 +54,28 @@ pub(crate) fn send_events(world: &mut World, mut current_frame: Local<u32>) {
*current_frame, name
);
}
CiTestingEvent::StartScreenRecording => {
info!("Started recording screen at frame {}.", *current_frame);
#[cfg(feature = "screenrecording")]
world.write_message(crate::RecordScreen::Start);
}
CiTestingEvent::StopScreenRecording => {
info!("Stopped recording screen at frame {}.", *current_frame);
#[cfg(feature = "screenrecording")]
world.write_message(crate::RecordScreen::Stop);
}
CiTestingEvent::MoveCamera {
translation,
rotation,
} => {
info!("Moved camera at frame {}.", *current_frame);
if let Ok(camera) = world.query_filtered::<Entity, With<Camera>>().single(world) {
world.entity_mut(camera).insert(CameraMovement {
translation,
rotation,
});
}
}
// Custom events are forwarded to the world.
CiTestingEvent::Custom(event_string) => {
world.write_message(CiTestingCustomEvent(event_string));
Expand Down
Loading
Loading