Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid unwrap() on invalid asset #344

Merged
merged 5 commits into from
Jun 22, 2024
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
20 changes: 10 additions & 10 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,35 +51,35 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Build & run tests (${{ matrix.dimensions }} no GPU)
run: cargo test --no-default-features --features ${{ matrix.dimensions }}
- name: Build & run lib tests (${{ matrix.dimensions }} no GPU)
run: cargo test --lib --no-default-features --features ${{ matrix.dimensions }}
env:
CARGO_INCREMENTAL: 0
if: runner.os == 'linux' && matrix.dimensions != 'all'
- name: Build & run tests (${{ matrix.dimensions }} DX12)
- name: Build & run lib tests (${{ matrix.dimensions }} DX12)
shell: bash
run: WGPU_BACKEND=dx12 cargo test --no-default-features --features ${{ matrix.dimensions }} --features gpu_tests
run: WGPU_BACKEND=dx12 cargo test --lib --no-default-features --features ${{ matrix.dimensions }} --features gpu_tests
env:
CARGO_INCREMENTAL: 0
if: runner.os == 'windows' && matrix.dimensions != 'all'
- name: Build & run tests (${{ matrix.dimensions }} METAL)
- name: Build & run all tests (${{ matrix.dimensions }} METAL)
shell: bash
run: WGPU_BACKEND=metal cargo test --no-default-features --features ${{ matrix.dimensions }} --features gpu_tests
env:
CARGO_INCREMENTAL: 0
if: runner.os == 'macos' && matrix.dimensions != 'all'
- name: Build & run tests (all no GPU)
run: cargo test --no-default-features --features "2d 3d"
- name: Build & run lib tests (all no GPU)
run: cargo test --lib --no-default-features --features "2d 3d"
env:
CARGO_INCREMENTAL: 0
if: runner.os == 'linux' && matrix.dimensions == 'all'
- name: Build & run tests (all DX12)
- name: Build & run lib tests (all DX12)
shell: bash
run: WGPU_BACKEND=dx12 cargo test --no-default-features --features "2d 3d gpu_tests"
run: WGPU_BACKEND=dx12 cargo test --lib --no-default-features --features "2d 3d gpu_tests"
env:
CARGO_INCREMENTAL: 0
if: runner.os == 'windows' && matrix.dimensions == 'all'
- name: Build & run tests (all METAL)
- name: Build & run all tests (all METAL)
shell: bash
run: WGPU_BACKEND=metal cargo test --no-default-features --features "2d 3d gpu_tests"
env:
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ required-features = [ "bevy/bevy_winit", "bevy/bevy_pbr", "3d" ]
name = "ordering"
required-features = [ "bevy/bevy_winit", "bevy/bevy_pbr", "3d" ]

[[test]]
name = "empty_effect"
path = "gpu_tests/empty_effect.rs"
harness = false

[workspace]
resolver = "2"
members = ["."]
41 changes: 41 additions & 0 deletions gpu_tests/empty_effect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//! Test that an empty (invalid) bundle doesn't produce any error.

use bevy::{app::AppExit, core_pipeline::tonemapping::Tonemapping, log::LogPlugin, prelude::*};

use bevy_hanabi::prelude::*;

#[derive(Default, Resource)]
struct Frame(pub u32);

fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut app = App::default();
app.insert_resource(ClearColor(Color::DARK_GRAY))
.add_plugins(DefaultPlugins.set(LogPlugin {
level: bevy::log::Level::INFO,
filter: "bevy_hanabi=debug".to_string(),
update_subscriber: None,
}))
.add_plugins(HanabiPlugin)
.init_resource::<Frame>()
.add_systems(Startup, setup)
.add_systems(Update, timeout)
.run();

Ok(())
}

fn setup(mut commands: Commands) {
commands.spawn(Camera3dBundle {
tonemapping: Tonemapping::None,
..default()
});
commands.spawn(ParticleEffectBundle::default());
}

fn timeout(mut frame: ResMut<Frame>, mut ev_app_exit: EventWriter<AppExit>) {
frame.0 += 1;
if frame.0 >= 10 {
info!("SUCCESS!");
ev_app_exit.send(AppExit);
}
}
8 changes: 1 addition & 7 deletions src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use bevy::prelude::*;
/// [`ParticleEffect`].
///
/// [`EffectProperties`]: crate::EffectProperties
#[derive(Bundle, Clone)]
#[derive(Default, Bundle, Clone)]
pub struct ParticleEffectBundle {
/// The particle effect instance itself.
pub effect: ParticleEffect,
Expand Down Expand Up @@ -75,12 +75,6 @@ pub struct ParticleEffectBundle {
pub view_visibility: ViewVisibility,
}

impl Default for ParticleEffectBundle {
fn default() -> Self {
Self::new(Handle::<EffectAsset>::default())
}
}

impl ParticleEffectBundle {
/// Create a new particle effect bundle from an effect description.
pub fn new(handle: Handle<EffectAsset>) -> Self {
Expand Down
41 changes: 41 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,47 @@ else { return c1; }
}
}

// Regression test for #343
#[test]
fn test_compile_effect_invalid_handle() {
let mut app = make_test_app();

let effect_entity = {
let world = &mut app.world;

// Spawn particle effect
let entity = world.spawn(ParticleEffectBundle::default()).id();

// Spawn a camera, otherwise ComputedVisibility stays at HIDDEN
world.spawn(Camera3dBundle::default());

entity
};

// Tick once
app.update();

// Check
{
let world = &mut app.world;

let (entity, particle_effect, compiled_particle_effect) = world
.query::<(Entity, &ParticleEffect, &CompiledParticleEffect)>()
.iter(world)
.next()
.unwrap();
assert_eq!(entity, effect_entity);
assert_eq!(particle_effect.handle, Handle::<EffectAsset>::default());

// `compile_effects()` cannot update the CompiledParticleEffect because the asset is invalid
assert_eq!(
compiled_particle_effect.asset,
Handle::<EffectAsset>::default()
);
assert!(compiled_particle_effect.effect_shader.is_none());
}
}

// Regression test for #228
#[test]
fn test_compile_effect_changed() {
Expand Down
8 changes: 4 additions & 4 deletions src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1428,9 +1428,9 @@ pub(crate) fn extract_effects(
extracted_effects.added_effects = query
.p1()
.iter()
.map(|(entity, effect)| {
.filter_map(|(entity, effect)| {
let handle = effect.asset.clone_weak();
let asset = effects.get(&effect.asset).unwrap();
let asset = effects.get(&effect.asset)?;
let particle_layout = asset.particle_layout();
assert!(
particle_layout.size() > 0,
Expand All @@ -1441,14 +1441,14 @@ pub(crate) fn extract_effects(
let property_layout = asset.property_layout();

trace!("Found new effect: entity {:?} | capacities {:?} | particle_layout {:?} | property_layout {:?} | layout_flags {:?}", entity, asset.capacities(), particle_layout, property_layout, effect.layout_flags);
AddedEffect {
Some(AddedEffect {
entity,
capacities: asset.capacities().to_vec(),
particle_layout,
property_layout,
layout_flags: effect.layout_flags,
handle,
}
})
})
.collect();

Expand Down
Loading