Merge branch 'main' into transmission

This commit is contained in:
Marco Buono 2023-10-08 21:00:43 -03:00
commit 32e9a9cc62
247 changed files with 4074 additions and 1948 deletions

View file

@ -89,6 +89,14 @@ jobs:
include:
- device: "iPhone 13"
os_version: "15"
- device: "iPhone 14"
os_version: "16"
- device: "iPhone 15"
os_version: "17"
- device: "Xiaomi Redmi Note 11"
os_version: "11.0"
- device: "Google Pixel 6"
os_version: "12.0"
- device: "Samsung Galaxy S23"
os_version: "13.0"
steps:

View file

@ -199,6 +199,9 @@ serialize = ["bevy_internal/serialize"]
# Enables multithreaded parallelism in the engine. Disabling it forces all engine tasks to run on a single thread.
multi-threaded = ["bevy_internal/multi-threaded"]
# Use async-io's implementation of block_on instead of futures-lite's implementation. This is preferred if your application uses async-io.
async-io = ["bevy_internal/async-io"]
# Wayland display server support
wayland = ["bevy_internal/wayland"]
@ -252,7 +255,6 @@ bevy_dylib = { path = "crates/bevy_dylib", version = "0.12.0-dev", default-featu
bevy_internal = { path = "crates/bevy_internal", version = "0.12.0-dev", default-features = false }
[dev-dependencies]
anyhow = "1.0.4"
rand = "0.8.0"
ron = "0.8.0"
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,6 @@
[![MIT/Apache 2.0](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license)
[![Crates.io](https://img.shields.io/crates/d/bevy.svg)](https://crates.io/crates/bevy)
[![Rust](https://github.com/bevyengine/bevy/workflows/CI/badge.svg)](https://github.com/bevyengine/bevy/actions)
![iOS cron CI](https://github.com/bevyengine/bevy/workflows/iOS%20cron%20CI/badge.svg)
[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy)
## What is Bevy?

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,6 @@
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
// we can import items from shader modules in the assets folder with a quoted path
#import "shaders/custom_material_import.wgsl" COLOR_MULTIPLIER
struct CustomMaterial {
color: vec4<f32>,
@ -12,5 +14,5 @@ struct CustomMaterial {
fn fragment(
mesh: MeshVertexOutput,
) -> @location(0) vec4<f32> {
return material.color * textureSample(base_color_texture, base_color_sampler, mesh.uv);
return material.color * textureSample(base_color_texture, base_color_sampler, mesh.uv) * COLOR_MULTIPLIER;
}

View file

@ -0,0 +1,2 @@
// this is made available to the importing module
const COLOR_MULTIPLIER: vec4<f32> = vec4<f32>(1.0, 1.0, 1.0, 0.5);

View file

@ -27,6 +27,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
for mut data in self.1.iter_mut(&mut self.0) {
data.0 *= 2.0;

View file

@ -27,6 +27,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
self.1.for_each_mut(&mut self.0, |mut data| {
data.0 *= 2.0;

View file

@ -38,6 +38,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
self.1.for_each_mut(&mut self.0, |mut data| {
data.0 *= 2.0;

View file

@ -55,6 +55,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
self.1.for_each_mut(&mut self.0, |mut data| {
data.0 .0 *= 2.0;

View file

@ -65,6 +65,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
self.1.for_each_mut(&mut self.0, |mut data| {
data.0 .0 *= 2.0;

View file

@ -38,6 +38,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
for mut data in self.1.iter_mut(&mut self.0) {
data.0 *= 2.0;

View file

@ -55,6 +55,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
for mut data in self.1.iter_mut(&mut self.0) {
data.0 .0 *= 2.0;

View file

@ -65,6 +65,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
for mut data in self.1.iter_mut(&mut self.0) {
data.0 .0 *= 2.0;

View file

@ -33,6 +33,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
for (velocity, mut position) in self.1.iter_mut(&mut self.0) {
position.0 += velocity.0;

View file

@ -33,6 +33,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
self.1
.for_each_mut(&mut self.0, |(velocity, mut position)| {

View file

@ -35,6 +35,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
self.1
.for_each_mut(&mut self.0, |(velocity, mut position)| {

View file

@ -55,6 +55,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
self.1.for_each_mut(&mut self.0, |mut item| {
item.1 .0 += item.0 .0;

View file

@ -57,6 +57,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
self.1.for_each_mut(&mut self.0, |mut item| {
item.1 .0 += item.0 .0;

View file

@ -35,6 +35,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
for (velocity, mut position) in self.1.iter_mut(&mut self.0) {
position.0 += velocity.0;

View file

@ -41,6 +41,7 @@ impl Benchmark {
Self(world, Box::new(system))
}
#[inline(never)]
pub fn run(&mut self) {
self.1.run((), &mut self.0);
}

View file

@ -55,6 +55,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
for mut item in self.1.iter_mut(&mut self.0) {
item.1 .0 += item.0 .0;

View file

@ -57,6 +57,7 @@ impl<'w> Benchmark<'w> {
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
for mut item in self.1.iter_mut(&mut self.0) {
item.1 .0 += item.0 .0;

View file

@ -1 +1 @@
doc-valid-idents = ["sRGB", "NaN", "iOS", "glTF", "GitHub", "WebGPU"]
doc-valid-idents = ["sRGB", "NaN", "iOS", "glTF", "GitHub", "WebGPU", "GilRs"]

View file

@ -14,4 +14,4 @@ bevy_app = { path = "../bevy_app", version = "0.12.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.12.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0-dev" }
accesskit = "0.11"
accesskit = "0.12"

View file

@ -4,17 +4,18 @@
#![allow(clippy::type_complexity)]
#![forbid(unsafe_code)]
use std::{
num::NonZeroU128,
sync::{atomic::AtomicBool, Arc},
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
pub use accesskit;
use accesskit::{NodeBuilder, NodeId};
use accesskit::NodeBuilder;
use bevy_app::Plugin;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
prelude::{Component, Entity, Event},
schedule::SystemSet,
system::Resource,
};
@ -30,6 +31,46 @@ pub struct ActionRequest(pub accesskit::ActionRequest);
#[derive(Resource, Default, Clone, Debug, Deref, DerefMut)]
pub struct AccessibilityRequested(Arc<AtomicBool>);
impl AccessibilityRequested {
/// Returns `true` if an access technology is active and accessibility tree
/// updates should be sent.
pub fn get(&self) -> bool {
self.load(Ordering::SeqCst)
}
/// Sets whether accessibility updates were requested by an access technology.
pub fn set(&self, value: bool) {
self.store(value, Ordering::SeqCst);
}
}
/// Resource whose value determines whether the accessibility tree is updated
/// via the ECS.
///
/// Set to `false` in cases where an external GUI library is sending
/// accessibility updates instead. Without this, the external library and ECS
/// will generate conflicting updates.
#[derive(Resource, Clone, Debug, Deref, DerefMut)]
pub struct ManageAccessibilityUpdates(bool);
impl Default for ManageAccessibilityUpdates {
fn default() -> Self {
Self(true)
}
}
impl ManageAccessibilityUpdates {
/// Returns `true` if the ECS should update the accessibility tree.
pub fn get(&self) -> bool {
self.0
}
/// Sets whether the ECS should update the accessibility tree.
pub fn set(&mut self, value: bool) {
self.0 = value;
}
}
/// Component to wrap a [`accesskit::Node`], representing this entity to the platform's
/// accessibility API.
///
@ -47,22 +88,16 @@ impl From<NodeBuilder> for AccessibilityNode {
}
}
/// Extensions to ease integrating entities with [`AccessKit`](https://accesskit.dev).
pub trait AccessKitEntityExt {
/// Convert an entity to a stable [`NodeId`].
fn to_node_id(&self) -> NodeId;
}
impl AccessKitEntityExt for Entity {
fn to_node_id(&self) -> NodeId {
let id = NonZeroU128::new(self.to_bits() as u128 + 1);
NodeId(id.unwrap())
}
}
/// Resource representing which entity has keyboard focus, if any.
#[derive(Resource, Default, Deref, DerefMut)]
pub struct Focus(Option<Entity>);
pub struct Focus(pub Option<Entity>);
/// Set enum for the systems relating to accessibility
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum AccessibilitySystem {
/// Update the accessibility tree
Update,
}
/// Plugin managing non-GUI aspects of integrating with accessibility APIs.
pub struct AccessibilityPlugin;
@ -70,6 +105,7 @@ pub struct AccessibilityPlugin;
impl Plugin for AccessibilityPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.init_resource::<AccessibilityRequested>()
.init_resource::<ManageAccessibilityUpdates>()
.init_resource::<Focus>();
}
}

View file

@ -453,16 +453,16 @@ fn entity_from_path(
/// Verify that there are no ancestors of a given entity that have an [`AnimationPlayer`].
fn verify_no_ancestor_player(
player_parent: Option<&Parent>,
parents: &Query<(Option<With<AnimationPlayer>>, Option<&Parent>)>,
parents: &Query<(Has<AnimationPlayer>, Option<&Parent>)>,
) -> bool {
let Some(mut current) = player_parent.map(Parent::get) else {
return true;
};
loop {
let Ok((maybe_player, parent)) = parents.get(current) else {
let Ok((has_player, parent)) = parents.get(current) else {
return true;
};
if maybe_player.is_some() {
if has_player {
return false;
}
if let Some(parent) = parent {
@ -483,7 +483,7 @@ pub fn animation_player(
names: Query<&Name>,
transforms: Query<&mut Transform>,
morphs: Query<&mut MorphWeights>,
parents: Query<(Option<With<AnimationPlayer>>, Option<&Parent>)>,
parents: Query<(Has<AnimationPlayer>, Option<&Parent>)>,
mut animation_players: Query<(Entity, Option<&Parent>, &mut AnimationPlayer)>,
) {
animation_players
@ -515,7 +515,7 @@ fn run_animation_player(
transforms: &Query<&mut Transform>,
morphs: &Query<&mut MorphWeights>,
maybe_parent: Option<&Parent>,
parents: &Query<(Option<With<AnimationPlayer>>, Option<&Parent>)>,
parents: &Query<(Has<AnimationPlayer>, Option<&Parent>)>,
children: &Query<&Children>,
) {
let paused = player.paused;
@ -565,8 +565,16 @@ fn run_animation_player(
}
}
/// Update `weights` based on weights in `keyframes` at index `key_index`
/// with a linear interpolation on `key_lerp`.
/// Update `weights` based on weights in `keyframe` with a linear interpolation
/// on `key_lerp`.
fn lerp_morph_weights(weights: &mut [f32], keyframe: impl Iterator<Item = f32>, key_lerp: f32) {
let zipped = weights.iter_mut().zip(keyframe);
for (morph_weight, keyframe) in zipped {
*morph_weight += (keyframe - *morph_weight) * key_lerp;
}
}
/// Extract a keyframe from a list of keyframes by index.
///
/// # Panics
///
@ -575,16 +583,10 @@ fn run_animation_player(
/// This happens when `keyframes` is not formatted as described in
/// [`Keyframes::Weights`]. A possible cause is [`AnimationClip`] not being
/// meant to be used for the [`MorphWeights`] of the entity it's being applied to.
fn lerp_morph_weights(weights: &mut [f32], key_lerp: f32, keyframes: &[f32], key_index: usize) {
let target_count = weights.len();
fn get_keyframe(target_count: usize, keyframes: &[f32], key_index: usize) -> &[f32] {
let start = target_count * key_index;
let end = target_count * (key_index + 1);
let zipped = weights.iter_mut().zip(&keyframes[start..end]);
for (morph_weight, keyframe) in zipped {
let minus_lerp = 1.0 - key_lerp;
*morph_weight = (*morph_weight * minus_lerp) + (keyframe * key_lerp);
}
&keyframes[start..end]
}
#[allow(clippy::too_many_arguments)]
@ -599,7 +601,7 @@ fn apply_animation(
transforms: &Query<&mut Transform>,
morphs: &Query<&mut MorphWeights>,
maybe_parent: Option<&Parent>,
parents: &Query<(Option<With<AnimationPlayer>>, Option<&Parent>)>,
parents: &Query<(Has<AnimationPlayer>, Option<&Parent>)>,
children: &Query<&Children>,
) {
if let Some(animation_clip) = animations.get(&animation.animation_clip) {
@ -657,7 +659,12 @@ fn apply_animation(
}
Keyframes::Weights(keyframes) => {
if let Ok(morphs) = &mut morphs {
lerp_morph_weights(morphs.weights_mut(), weight, keyframes, 0);
let target_count = morphs.weights().len();
lerp_morph_weights(
morphs.weights_mut(),
get_keyframe(target_count, keyframes, 0).iter().copied(),
weight,
);
}
}
}
@ -707,7 +714,14 @@ fn apply_animation(
}
Keyframes::Weights(keyframes) => {
if let Ok(morphs) = &mut morphs {
lerp_morph_weights(morphs.weights_mut(), weight, keyframes, step_start);
let target_count = morphs.weights().len();
let morph_start = get_keyframe(target_count, keyframes, step_start);
let morph_end = get_keyframe(target_count, keyframes, step_start + 1);
let result = morph_start
.iter()
.zip(morph_end)
.map(|(a, b)| *a + lerp * (*b - *a));
lerp_morph_weights(morphs.weights_mut(), result, weight);
}
}
}

View file

@ -334,7 +334,7 @@ impl App {
/// initial state.
///
/// If you would like to control how other systems run based on the current state,
/// you can emulate this behavior using the [`in_state`] [`Condition`](bevy_ecs::schedule::Condition).
/// you can emulate this behavior using the [`in_state`] [`Condition`].
///
/// Note that you can also apply state transitions at other points in the schedule
/// by adding the [`apply_state_transition`] system manually.
@ -859,6 +859,83 @@ impl App {
.configure_schedules(schedule_build_settings);
self
}
/// When doing [ambiguity checking](bevy_ecs::schedule::ScheduleBuildSettings) this
/// ignores systems that are ambiguious on [`Component`] T.
///
/// This settings only applies to the main world. To apply this to other worlds call the
/// [corresponding method](World::allow_ambiguous_component) on World
///
/// ## Example
///
/// ```rust
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::schedule::{LogLevel, ScheduleBuildSettings};
/// # use bevy_utils::default;
///
/// #[derive(Component)]
/// struct A;
///
/// // these systems are ambiguous on A
/// fn system_1(_: Query<&mut A>) {}
/// fn system_2(_: Query<&A>) {}
///
/// let mut app = App::new();
/// app.configure_schedules(ScheduleBuildSettings {
/// ambiguity_detection: LogLevel::Error,
/// ..default()
/// });
///
/// app.add_systems(Update, ( system_1, system_2 ));
/// app.allow_ambiguous_component::<A>();
///
/// // running the app does not error.
/// app.update();
/// ```
pub fn allow_ambiguous_component<T: Component>(&mut self) -> &mut Self {
self.world.allow_ambiguous_component::<T>();
self
}
/// When doing [ambiguity checking](bevy_ecs::schedule::ScheduleBuildSettings) this
/// ignores systems that are ambiguious on [`Resource`] T.
///
/// This settings only applies to the main world. To apply this to other worlds call the
/// [corresponding method](World::allow_ambiguous_resource) on World
///
/// ## Example
///
/// ```rust
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::schedule::{LogLevel, ScheduleBuildSettings};
/// # use bevy_utils::default;
///
/// #[derive(Resource)]
/// struct R;
///
/// // these systems are ambiguous on R
/// fn system_1(_: ResMut<R>) {}
/// fn system_2(_: Res<R>) {}
///
/// let mut app = App::new();
/// app.configure_schedules(ScheduleBuildSettings {
/// ambiguity_detection: LogLevel::Error,
/// ..default()
/// });
/// app.insert_resource(R);
///
/// app.add_systems(Update, ( system_1, system_2 ));
/// app.allow_ambiguous_resource::<R>();
///
/// // running the app does not error.
/// app.update();
/// ```
pub fn allow_ambiguous_resource<T: Resource>(&mut self) -> &mut Self {
self.world.allow_ambiguous_resource::<T>();
self
}
}
fn run_once(mut app: App) {

View file

@ -51,7 +51,7 @@ pub trait Plugin: Downcast + Any + Send + Sync {
std::any::type_name::<Self>()
}
/// If the plugin can be meaningfully instantiated several times in an [`App`](crate::App),
/// If the plugin can be meaningfully instantiated several times in an [`App`],
/// override this method to return `false`.
fn is_unique(&self) -> bool {
true

View file

@ -23,7 +23,6 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.12.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0-dev" }
anyhow = "1.0"
async-broadcast = "0.5"
async-fs = "1.5"
async-lock = "2.8"
@ -36,7 +35,7 @@ parking_lot = { version = "0.12", features = ["arc_lock", "send_guard"] }
ron = "0.8"
serde = { version = "1", features = ["derive"] }
thiserror = "1.0"
notify-debouncer-full = { version = "0.2.0", optional = true }
notify-debouncer-full = { version = "0.3.1", optional = true }
[target.'cfg(target_os = "android")'.dependencies]
bevy_winit = { path = "../bevy_winit", version = "0.12.0-dev" }

View file

@ -66,9 +66,16 @@ fn derive_dependency_visitor_internal(
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
// prevent unused variable warning in case there are no dependencies
let visit = if field_visitors.is_empty() {
quote! { _visit }
} else {
quote! { visit }
};
Ok(quote! {
impl #impl_generics #bevy_asset_path::VisitAssetDependencies for #struct_name #type_generics #where_clause {
fn visit_dependencies(&self, visit: &mut impl FnMut(#bevy_asset_path::UntypedAssetId)) {
fn visit_dependencies(&self, #visit: &mut impl FnMut(#bevy_asset_path::UntypedAssetId)) {
#(#field_visitors)*
}
}

View file

@ -2,7 +2,6 @@ use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWatcher, EmptyPathStream, PathStream,
Reader, VecReader,
};
use anyhow::Result;
use bevy_log::error;
use bevy_utils::BoxedFuture;
use std::{ffi::CString, path::Path};

View file

@ -1,5 +1,4 @@
use crate::io::{AssetSourceEvent, AssetWatcher};
use anyhow::Result;
use bevy_log::error;
use bevy_utils::Duration;
use crossbeam_channel::Sender;

View file

@ -5,7 +5,6 @@ use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWatcher, AssetWriter, AssetWriterError,
PathStream, Reader, Writer,
};
use anyhow::Result;
use async_fs::{read_dir, File};
use bevy_utils::BoxedFuture;
use futures_lite::StreamExt;

View file

@ -1,5 +1,4 @@
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
use anyhow::Result;
use bevy_utils::{BoxedFuture, HashMap};
use crossbeam_channel::{Receiver, Sender};
use parking_lot::RwLock;

View file

@ -1,5 +1,4 @@
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
use anyhow::Result;
use bevy_utils::{BoxedFuture, HashMap};
use futures_io::AsyncRead;
use futures_lite::{ready, Stream};

View file

@ -3,7 +3,6 @@ use crate::{
processor::{AssetProcessorData, ProcessStatus},
AssetPath,
};
use anyhow::Result;
use async_lock::RwLockReadGuardArc;
use bevy_log::trace;
use bevy_utils::BoxedFuture;

View file

@ -2,7 +2,6 @@ use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWatcher, EmptyPathStream, PathStream,
Reader, VecReader,
};
use anyhow::Result;
use bevy_log::error;
use bevy_utils::BoxedFuture;
use js_sys::{Uint8Array, JSON};

View file

@ -1,3 +1,5 @@
#![allow(clippy::type_complexity)]
pub mod io;
pub mod meta;
pub mod processor;
@ -33,7 +35,6 @@ pub use path::*;
pub use reflect::*;
pub use server::*;
pub use anyhow;
pub use bevy_utils::BoxedFuture;
use crate::{
@ -261,6 +262,9 @@ pub trait AssetApp {
/// * Registering the [`Asset`] in the [`AssetServer`]
/// * Initializing the [`AssetEvent`] resource for the [`Asset`]
/// * Adding other relevant systems and resources for the [`Asset`]
/// * Ignoring schedule ambiguities in [`Assets`] resource. Any time a system takes
/// mutable access to this resource this causes a conflict, but they rarely actually
/// modify the same underlying asset.
fn init_asset<A: Asset>(&mut self) -> &mut Self;
/// Registers the asset type `T` using `[App::register]`,
/// and adds [`ReflectAsset`] type data to `T` and [`ReflectHandle`] type data to [`Handle<T>`] in the type registry.
@ -301,6 +305,7 @@ impl AssetApp for App {
));
}
self.insert_resource(assets)
.allow_ambiguous_resource::<Assets<A>>()
.add_event::<AssetEvent<A>>()
.register_type::<Handle<A>>()
.register_type::<AssetId<A>>()
@ -422,19 +427,24 @@ mod tests {
Reader,
},
loader::{AssetLoader, LoadContext},
Asset, AssetApp, AssetEvent, AssetId, AssetPlugin, AssetProvider, AssetProviders,
AssetServer, Assets, DependencyLoadState, LoadState, RecursiveDependencyLoadState,
Asset, AssetApp, AssetEvent, AssetId, AssetPath, AssetPlugin, AssetProvider,
AssetProviders, AssetServer, Assets, DependencyLoadState, LoadState,
RecursiveDependencyLoadState,
};
use bevy_app::{App, Update};
use bevy_core::TaskPoolPlugin;
use bevy_ecs::event::ManualEventReader;
use bevy_ecs::prelude::*;
use bevy_ecs::{
event::ManualEventReader,
schedule::{LogLevel, ScheduleBuildSettings},
};
use bevy_log::LogPlugin;
use bevy_reflect::TypePath;
use bevy_utils::BoxedFuture;
use futures_lite::AsyncReadExt;
use serde::{Deserialize, Serialize};
use std::path::Path;
use thiserror::Error;
#[derive(Asset, TypePath, Debug)]
pub struct CoolText {
@ -462,24 +472,40 @@ mod tests {
#[derive(Default)]
struct CoolTextLoader;
#[derive(Error, Debug)]
enum CoolTextLoaderError {
#[error("Could not load dependency: {dependency}")]
CannotLoadDependency { dependency: AssetPath<'static> },
#[error("A RON error occurred during loading")]
RonSpannedError(#[from] ron::error::SpannedError),
#[error("An IO error occurred during loading")]
Io(#[from] std::io::Error),
}
impl AssetLoader for CoolTextLoader {
type Asset = CoolText;
type Settings = ();
type Error = CoolTextLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let mut ron: CoolTextRon = ron::de::from_bytes(&bytes)?;
let mut embedded = String::new();
for dep in ron.embedded_dependencies {
let loaded = load_context.load_direct(&dep).await?;
let loaded = load_context.load_direct(&dep).await.map_err(|_| {
Self::Error::CannotLoadDependency {
dependency: dep.into(),
}
})?;
let cool = loaded.get::<CoolText>().unwrap();
embedded.push_str(&cool.text);
}
@ -1166,4 +1192,23 @@ mod tests {
None
});
}
#[test]
fn ignore_system_ambiguities_on_assets() {
let mut app = App::new();
app.add_plugins(AssetPlugin::default())
.init_asset::<CoolText>();
fn uses_assets(_asset: ResMut<Assets<CoolText>>) {}
app.add_systems(Update, (uses_assets, uses_assets));
app.edit_schedule(Update, |s| {
s.set_build_settings(ScheduleBuildSettings {
ambiguity_detection: LogLevel::Error,
..Default::default()
});
});
// running schedule does not error on ambiguity between the 2 uses_assets systems
app.world.run_schedule(Update);
}
}

View file

@ -26,13 +26,15 @@ pub trait AssetLoader: Send + Sync + 'static {
type Asset: crate::Asset;
/// The settings type used by this [`AssetLoader`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [error](`std::error::Error`) which could be encountered by this loader.
type Error: std::error::Error + Send + Sync + 'static;
/// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
fn load<'a>(
&'a self,
reader: &'a mut Reader,
settings: &'a Self::Settings,
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>>;
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>>;
/// Returns a list of extensions supported by this asset loader, without the preceding dot.
fn extensions(&self) -> &[&str];
@ -46,7 +48,10 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
reader: &'a mut Reader,
meta: Box<dyn AssetMetaDyn>,
load_context: LoadContext<'a>,
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, AssetLoaderError>>;
) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn std::error::Error + Send + Sync + 'static>>,
>;
/// Returns a list of extensions supported by this asset loader, without the preceding dot.
fn extensions(&self) -> &[&str];
@ -64,17 +69,6 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
fn asset_type_id(&self) -> TypeId;
}
/// An error encountered during [`AssetLoader::load`].
#[derive(Error, Debug)]
pub enum AssetLoaderError {
/// Any error that occurs during load.
#[error(transparent)]
Load(#[from] anyhow::Error),
/// A failure to deserialize metadata during load.
#[error(transparent)]
DeserializeMeta(#[from] DeserializeMetaError),
}
impl<L> ErasedAssetLoader for L
where
L: AssetLoader + Send + Sync,
@ -85,7 +79,10 @@ where
reader: &'a mut Reader,
meta: Box<dyn AssetMetaDyn>,
mut load_context: LoadContext<'a>,
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, AssetLoaderError>> {
) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn std::error::Error + Send + Sync + 'static>>,
> {
Box::pin(async move {
let settings = meta
.loader_settings()

View file

@ -193,12 +193,13 @@ impl VisitAssetDependencies for () {
impl AssetLoader for () {
type Asset = ();
type Settings = ();
type Error = std::io::Error;
fn load<'a>(
&'a self,
_reader: &'a mut crate::io::Reader,
_settings: &'a Self::Settings,
_load_context: &'a mut crate::LoadContext,
) -> bevy_utils::BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {
) -> bevy_utils::BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
unreachable!();
}

View file

@ -13,8 +13,8 @@ use crate::{
get_asset_hash, get_full_asset_hash, AssetAction, AssetActionMinimal, AssetHash, AssetMeta,
AssetMetaDyn, AssetMetaMinimal, ProcessedInfo, ProcessedInfoMinimal,
},
AssetLoadError, AssetLoaderError, AssetPath, AssetServer, DeserializeMetaError,
LoadDirectError, MissingAssetLoaderForExtensionError, CANNOT_WATCH_ERROR_MESSAGE,
AssetLoadError, AssetPath, AssetServer, DeserializeMetaError,
MissingAssetLoaderForExtensionError, CANNOT_WATCH_ERROR_MESSAGE,
};
use bevy_ecs::prelude::*;
use bevy_log::{debug, error, trace, warn};
@ -166,7 +166,7 @@ impl AssetProcessor {
let processor = _processor.clone();
std::thread::spawn(move || {
processor.process_assets();
futures_lite::future::block_on(processor.listen_for_source_change_events());
bevy_tasks::block_on(processor.listen_for_source_change_events());
});
}
}
@ -190,7 +190,7 @@ impl AssetProcessor {
});
// This must happen _after_ the scope resolves or it will happen "too early"
// Don't move this into the async scope above! process_assets is a blocking/sync function this is fine
futures_lite::future::block_on(self.finish_processing_assets());
bevy_tasks::block_on(self.finish_processing_assets());
let end_time = std::time::Instant::now();
debug!("Processing finished in {:?}", end_time - start_time);
}
@ -817,7 +817,7 @@ impl AssetProcessor {
break;
}
LogEntryError::UnfinishedTransaction(path) => {
debug!("Asset {path:?} did not finish processing. Clearning state for that asset");
debug!("Asset {path:?} did not finish processing. Clearing state for that asset");
if let Err(err) = self.destination_writer().remove(&path).await {
match err {
AssetWriterError::Io(err) => {
@ -991,7 +991,7 @@ pub(crate) struct ProcessorAssetInfo {
/// * when processing assets in parallel, the processor might read an asset's process_dependencies when processing new versions of those dependencies
/// * this second scenario almost certainly isn't possible with the current implementation, but its worth protecting against
/// This lock defends against those scenarios by ensuring readers don't read while processed files are being written. And it ensures
/// Because this lock is shared across meta and asset bytes, readers can esure they don't read "old" versions of metadata with "new" asset data.
/// Because this lock is shared across meta and asset bytes, readers can ensure they don't read "old" versions of metadata with "new" asset data.
pub(crate) file_transaction_lock: Arc<async_lock::RwLock<()>>,
status_sender: async_broadcast::Sender<ProcessStatus>,
status_receiver: async_broadcast::Receiver<ProcessStatus>,
@ -1130,20 +1130,17 @@ impl ProcessorAssetInfos {
Err(err) => {
error!("Failed to process asset {:?}: {:?}", asset_path, err);
// if this failed because a dependency could not be loaded, make sure it is reprocessed if that dependency is reprocessed
if let ProcessError::AssetLoadError(AssetLoadError::AssetLoaderError {
error: AssetLoaderError::Load(loader_error),
..
if let ProcessError::AssetLoadError(AssetLoadError::CannotLoadDependency {
path: dependency,
}) = err
{
if let Some(error) = loader_error.downcast_ref::<LoadDirectError>() {
let info = self.get_mut(&asset_path).expect("info should exist");
info.processed_info = Some(ProcessedInfo {
hash: AssetHash::default(),
full_hash: AssetHash::default(),
process_dependencies: vec![],
});
self.add_dependant(&error.dependency, asset_path.to_owned());
}
let info = self.get_mut(&asset_path).expect("info should exist");
info.processed_info = Some(ProcessedInfo {
hash: AssetHash::default(),
full_hash: AssetHash::default(),
process_dependencies: vec![],
});
self.add_dependant(&dependency, asset_path.to_owned());
}
let info = self.get_mut(&asset_path).expect("info should exist");

View file

@ -91,7 +91,7 @@ pub enum ProcessError {
#[error("The wrong meta type was passed into a processor. This is probably an internal implementation error.")]
WrongMetaType,
#[error("Encountered an error while saving the asset: {0}")]
AssetSaveError(anyhow::Error),
AssetSaveError(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Assets without extensions are not supported.")]
ExtensionRequired,
}
@ -122,7 +122,7 @@ impl<Loader: AssetLoader, Saver: AssetSaver<Asset = Loader::Asset>> Process
.saver
.save(writer, saved_asset, &settings.saver_settings)
.await
.map_err(ProcessError::AssetSaveError)?;
.map_err(|error| ProcessError::AssetSaveError(Box::new(error)))?;
Ok(output_settings)
})
}

View file

@ -7,9 +7,14 @@ use std::ops::Deref;
/// Saves an [`Asset`] of a given [`AssetSaver::Asset`] type. [`AssetSaver::OutputLoader`] will then be used to load the saved asset
/// in the final deployed application. The saver should produce asset bytes in a format that [`AssetSaver::OutputLoader`] can read.
pub trait AssetSaver: Send + Sync + 'static {
/// The top level [`Asset`] saved by this [`AssetSaver`].
type Asset: Asset;
/// The settings type used by this [`AssetSaver`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [`AssetLoader`] used to load this [`Asset`]
type OutputLoader: AssetLoader;
/// The type of [error](`std::error::Error`) which could be encountered by this saver.
type Error: std::error::Error + Send + Sync + 'static;
/// Saves the given runtime [`Asset`] by writing it to a byte format using `writer`. The passed in `settings` can influence how the
/// `asset` is saved.
@ -18,7 +23,7 @@ pub trait AssetSaver: Send + Sync + 'static {
writer: &'a mut Writer,
asset: SavedAsset<'a, Self::Asset>,
settings: &'a Self::Settings,
) -> BoxedFuture<'a, Result<<Self::OutputLoader as AssetLoader>::Settings, anyhow::Error>>;
) -> BoxedFuture<'a, Result<<Self::OutputLoader as AssetLoader>::Settings, Self::Error>>;
}
/// A type-erased dynamic variant of [`AssetSaver`] that allows callers to save assets without knowing the actual type of the [`AssetSaver`].
@ -30,7 +35,7 @@ pub trait ErasedAssetSaver: Send + Sync + 'static {
writer: &'a mut Writer,
asset: &'a ErasedLoadedAsset,
settings: &'a dyn Settings,
) -> BoxedFuture<'a, Result<(), anyhow::Error>>;
) -> BoxedFuture<'a, Result<(), Box<dyn std::error::Error + Send + Sync + 'static>>>;
/// The type name of the [`AssetSaver`].
fn type_name(&self) -> &'static str;
@ -42,7 +47,7 @@ impl<S: AssetSaver> ErasedAssetSaver for S {
writer: &'a mut Writer,
asset: &'a ErasedLoadedAsset,
settings: &'a dyn Settings,
) -> BoxedFuture<'a, Result<(), anyhow::Error>> {
) -> BoxedFuture<'a, Result<(), Box<dyn std::error::Error + Send + Sync + 'static>>> {
Box::pin(async move {
let settings = settings
.downcast_ref::<S::Settings>()

View file

@ -3,7 +3,7 @@ mod info;
use crate::{
folder::LoadedFolder,
io::{AssetReader, AssetReaderError, AssetSourceEvent, AssetWatcher, Reader},
loader::{AssetLoader, AssetLoaderError, ErasedAssetLoader, LoadContext, LoadedAsset},
loader::{AssetLoader, ErasedAssetLoader, LoadContext, LoadedAsset},
meta::{
loader_settings_meta_transform, AssetActionMinimal, AssetMetaDyn, AssetMetaMinimal,
MetaTransform, Settings,
@ -666,11 +666,9 @@ impl AssetServer {
}
};
let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
AssetLoadError::AssetLoaderError {
let meta = loader.deserialize_meta(&meta_bytes).map_err(|_| {
AssetLoadError::CannotLoadDependency {
path: asset_path.clone().into_owned(),
loader: loader.type_name(),
error: AssetLoaderError::DeserializeMeta(e),
}
})?;
@ -698,13 +696,10 @@ impl AssetServer {
let asset_path = asset_path.clone().into_owned();
let load_context =
LoadContext::new(self, asset_path.clone(), load_dependencies, populate_hashes);
loader.load(reader, meta, load_context).await.map_err(|e| {
AssetLoadError::AssetLoaderError {
loader: loader.type_name(),
path: asset_path,
error: e,
}
})
loader
.load(reader, meta, load_context)
.await
.map_err(|_| AssetLoadError::CannotLoadDependency { path: asset_path })
}
}
@ -861,12 +856,8 @@ pub enum AssetLoadError {
CannotLoadProcessedAsset { path: AssetPath<'static> },
#[error("Asset '{path}' is configured to be ignored. It cannot be loaded.")]
CannotLoadIgnoredAsset { path: AssetPath<'static> },
#[error("Asset '{path}' encountered an error in {loader}: {error}")]
AssetLoaderError {
path: AssetPath<'static>,
loader: &'static str,
error: AssetLoaderError,
},
#[error("Asset '{path}' is a dependency. It cannot be loaded directly.")]
CannotLoadDependency { path: AssetPath<'static> },
}
/// An error that occurs when an [`AssetLoader`] is not registered for a given extension.

View file

@ -21,7 +21,6 @@ bevy_utils = { path = "../bevy_utils", version = "0.12.0-dev" }
# other
rodio = { version = "0.17", default-features = false }
parking_lot = "0.12.1"
[target.'cfg(target_os = "android")'.dependencies]
oboe = { version = "0.5", optional = true }

View file

@ -13,7 +13,7 @@ use crate::AudioSink;
///
/// ## Note
///
/// Initializing this resource will leak [`rodio::OutputStream`](rodio::OutputStream)
/// Initializing this resource will leak [`rodio::OutputStream`]
/// using [`std::mem::forget`].
/// This is done to avoid storing this in the struct (and making this `!Send`)
/// while preventing it from dropping (to avoid halting of audio).

View file

@ -1,5 +1,4 @@
use bevy_asset::{
anyhow::Error,
io::{AsyncReadExt, Reader},
Asset, AssetLoader, LoadContext,
};
@ -42,13 +41,14 @@ pub struct AudioLoader;
impl AssetLoader for AudioLoader {
type Asset = AudioSource;
type Settings = ();
type Error = std::io::Error;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
_load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<AudioSource, Error>> {
) -> BoxedFuture<'a, Result<AudioSource, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;

View file

@ -7,7 +7,7 @@
fn coords_to_ray_direction(position: vec2<f32>, viewport: vec4<f32>) -> vec3<f32> {
// Using world positions of the fragment and camera to calculate a ray direction
// break down at large translations. This code only needs to know the ray direction.
// breaks down at large translations. This code only needs to know the ray direction.
// The ray direction is along the direction from the camera to the fragment position.
// In view space, the camera is at the origin, so the view space ray direction is
// along the direction of the fragment position - (0,0,0) which is just the

View file

@ -1,26 +1,11 @@
use proc_macro::{Span, TokenStream};
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data::Enum, DeriveInput};
use syn::{parse_macro_input, DeriveInput};
use crate::bevy_ecs_path;
pub fn derive_states(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let error = || {
syn::Error::new(
Span::call_site().into(),
"derive(States) only supports fieldless enums",
)
.into_compile_error()
.into()
};
let Enum(enumeration) = ast.data else {
return error();
};
if enumeration.variants.iter().any(|v| !v.fields.is_empty()) {
return error();
}
let generics = ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
@ -28,17 +13,9 @@ pub fn derive_states(input: TokenStream) -> TokenStream {
trait_path.segments.push(format_ident!("schedule").into());
trait_path.segments.push(format_ident!("States").into());
let struct_name = &ast.ident;
let idents = enumeration.variants.iter().map(|v| &v.ident);
let len = idents.len();
quote! {
impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {
type Iter = std::array::IntoIter<Self, #len>;
fn variants() -> Self::Iter {
[#(Self::#idents,)*].into_iter()
}
}
impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {}
}
.into()
}

View file

@ -27,7 +27,7 @@ use crate::{
};
use std::{
hash::Hash,
ops::{Index, IndexMut},
ops::{Index, IndexMut, RangeFrom},
};
/// An opaque location within a [`Archetype`].
@ -70,7 +70,7 @@ impl ArchetypeRow {
///
/// [`World`]: crate::world::World
/// [`EMPTY`]: crate::archetype::ArchetypeId::EMPTY
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
// SAFETY: Must be repr(transparent) due to the safety requirements on EntityLocation
#[repr(transparent)]
pub struct ArchetypeId(u32);
@ -83,13 +83,26 @@ impl ArchetypeId {
/// This must always have an all-1s bit pattern to ensure soundness in fast entity id space allocation.
pub const INVALID: ArchetypeId = ArchetypeId(u32::MAX);
/// Create an `ArchetypeId` from a plain value.
///
/// This is useful if you need to store the `ArchetypeId` as a plain value,
/// for example in a specialized data structure such as a bitset.
///
/// While it doesn't break any safety invariants, you should ensure the
/// values comes from a pre-existing [`ArchetypeId::index`] in this world
/// to avoid panics and other unexpected behaviors.
#[inline]
pub(crate) const fn new(index: usize) -> Self {
pub const fn new(index: usize) -> Self {
ArchetypeId(index as u32)
}
/// The plain value of this `ArchetypeId`.
///
/// In bevy, this is mostly used to store archetype ids in [`FixedBitSet`]s.
///
/// [`FixedBitSet`]: fixedbitset::FixedBitSet
#[inline]
pub(crate) fn index(self) -> usize {
pub fn index(self) -> usize {
self.0 as usize
}
}
@ -525,24 +538,23 @@ impl Archetype {
}
}
/// An opaque generational id that changes every time the set of [`Archetypes`] changes.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct ArchetypeGeneration(usize);
/// The next [`ArchetypeId`] in an [`Archetypes`] collection.
///
/// This is used in archetype update methods to limit archetype updates to the
/// ones added since the last time the method ran.
#[derive(Debug, Copy, Clone)]
pub struct ArchetypeGeneration(ArchetypeId);
impl ArchetypeGeneration {
/// The first archetype.
#[inline]
pub(crate) const fn initial() -> Self {
ArchetypeGeneration(0)
}
#[inline]
pub(crate) fn value(self) -> usize {
self.0
pub const fn initial() -> Self {
ArchetypeGeneration(ArchetypeId::EMPTY)
}
}
#[derive(Hash, PartialEq, Eq)]
struct ArchetypeIdentity {
struct ArchetypeComponents {
table_components: Box<[ComponentId]>,
sparse_set_components: Box<[ComponentId]>,
}
@ -603,25 +615,29 @@ impl SparseSetIndex for ArchetypeComponentId {
pub struct Archetypes {
pub(crate) archetypes: Vec<Archetype>,
pub(crate) archetype_component_count: usize,
archetype_ids: bevy_utils::HashMap<ArchetypeIdentity, ArchetypeId>,
by_components: bevy_utils::HashMap<ArchetypeComponents, ArchetypeId>,
}
impl Archetypes {
pub(crate) fn new() -> Self {
let mut archetypes = Archetypes {
archetypes: Vec::new(),
archetype_ids: Default::default(),
by_components: Default::default(),
archetype_component_count: 0,
};
archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new());
archetypes
}
/// Returns the current archetype generation. This is an ID indicating the current set of archetypes
/// that are registered with the world.
/// Returns the "generation", a handle to the current highest archetype ID.
///
/// This can be used with the `Index` [`Archetypes`] implementation to
/// iterate over newly introduced [`Archetype`]s since the last time this
/// function was called.
#[inline]
pub fn generation(&self) -> ArchetypeGeneration {
ArchetypeGeneration(self.archetypes.len())
let id = ArchetypeId::new(self.archetypes.len());
ArchetypeGeneration(id)
}
/// Fetches the total number of [`Archetype`]s within the world.
@ -692,7 +708,7 @@ impl Archetypes {
table_components: Vec<ComponentId>,
sparse_set_components: Vec<ComponentId>,
) -> ArchetypeId {
let archetype_identity = ArchetypeIdentity {
let archetype_identity = ArchetypeComponents {
sparse_set_components: sparse_set_components.clone().into_boxed_slice(),
table_components: table_components.clone().into_boxed_slice(),
};
@ -700,7 +716,7 @@ impl Archetypes {
let archetypes = &mut self.archetypes;
let archetype_component_count = &mut self.archetype_component_count;
*self
.archetype_ids
.by_components
.entry(archetype_identity)
.or_insert_with(move || {
let id = ArchetypeId::new(archetypes.len());
@ -739,6 +755,14 @@ impl Archetypes {
}
}
impl Index<RangeFrom<ArchetypeGeneration>> for Archetypes {
type Output = [Archetype];
#[inline]
fn index(&self, index: RangeFrom<ArchetypeGeneration>) -> &Self::Output {
&self.archetypes[index.start.0.index()..]
}
}
impl Index<ArchetypeId> for Archetypes {
type Output = Archetype;

View file

@ -381,6 +381,15 @@ macro_rules! impl_methods {
ticks: self.ticks,
}
}
/// Allows you access to the dereferenced value of this pointer without immediately
/// triggering change detection.
pub fn as_deref_mut(&mut self) -> Mut<'_, <$target as Deref>::Target>
where $target: DerefMut
{
self.reborrow().map_unchanged(|v| v.deref_mut())
}
}
};
}
@ -526,7 +535,7 @@ impl_debug!(Res<'w, T>, Resource);
///
/// See the [`Resource`] documentation for usage.
///
/// If you need a shared borrow, use [`Res`](crate::system::Res) instead.
/// If you need a shared borrow, use [`Res`] instead.
///
/// # Panics
///
@ -907,6 +916,7 @@ mod tests {
use bevy_ecs_macros::Resource;
use bevy_ptr::PtrMut;
use bevy_reflect::{FromType, ReflectFromPtr};
use std::ops::{Deref, DerefMut};
use crate::{
self as bevy_ecs,
@ -929,6 +939,19 @@ mod tests {
#[derive(Resource, PartialEq)]
struct R2(u8);
impl Deref for R2 {
type Target = u8;
fn deref(&self) -> &u8 {
&self.0
}
}
impl DerefMut for R2 {
fn deref_mut(&mut self) -> &mut u8 {
&mut self.0
}
}
#[test]
fn change_expiration() {
fn change_detected(query: Query<Ref<C>>) -> bool {
@ -1143,6 +1166,32 @@ mod tests {
);
}
#[test]
fn as_deref_mut() {
let mut world = World::new();
world.insert_resource(R2(0));
// Resources are Changed when first added
world.increment_change_tick();
// This is required to update world::last_change_tick
world.clear_trackers();
let mut r = world.resource_mut::<R2>();
assert!(!r.is_changed(), "Resource must begin unchanged.");
let mut r = r.as_deref_mut();
assert!(
!r.is_changed(),
"Dereferencing should not mark the item as changed yet"
);
r.set_if_neq(3);
assert!(
r.is_changed(),
"Resource must be changed after setting to a different value."
);
}
#[test]
fn mut_untyped_to_reflect() {
let last_run = Tick::new(2);

View file

@ -266,7 +266,7 @@ impl ComponentInfo {
}
/// A value which uniquely identifies the type of a [`Component`] within a
/// [`World`](crate::world::World).
/// [`World`].
///
/// Each time a new `Component` type is registered within a `World` using
/// [`World::init_component`](crate::world::World::init_component) or

View file

@ -44,7 +44,7 @@ use crate::{
storage::{SparseSetIndex, TableId, TableRow},
};
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fmt, mem, sync::atomic::Ordering};
use std::{convert::TryFrom, fmt, hash::Hash, mem, sync::atomic::Ordering};
#[cfg(target_has_atomic = "64")]
use std::sync::atomic::AtomicI64 as AtomicIdCursor;
@ -115,12 +115,19 @@ type IdCursor = isize;
/// [`EntityCommands`]: crate::system::EntityCommands
/// [`Query::get`]: crate::system::Query::get
/// [`World`]: crate::world::World
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
pub struct Entity {
generation: u32,
index: u32,
}
impl Hash for Entity {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.to_bits().hash(state);
}
}
pub(crate) enum AllocAtWithoutReplacement {
Exists(EntityLocation),
DidNotExist,

View file

@ -427,8 +427,7 @@ macro_rules! impl_tick_filter {
table_ticks: None,
sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet)
.then(|| {
world.unsafe_world()
.storages()
world.storages()
.sparse_sets
.get(id)
.debug_checked_unwrap()
@ -617,10 +616,10 @@ impl_tick_filter!(
/// A marker trait to indicate that the filter works at an archetype level.
///
/// This is needed to implement [`ExactSizeIterator`](std::iter::ExactSizeIterator) for
/// This is needed to implement [`ExactSizeIterator`] for
/// [`QueryIter`](crate::query::QueryIter) that contains archetype-level filters.
///
/// The trait must only be implement for filters where its corresponding [`WorldQuery::IS_ARCHETYPAL`](crate::query::WorldQuery::IS_ARCHETYPAL)
/// The trait must only be implement for filters where its corresponding [`WorldQuery::IS_ARCHETYPAL`]
/// is [`prim@true`]. As such, only the [`With`] and [`Without`] filters can implement the trait.
/// [Tuples](prim@tuple) and [`Or`] filters are automatically implemented with the trait only if its containing types
/// also implement the same trait.

View file

@ -12,7 +12,7 @@ use crate::{
world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId},
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::Instrument;
use bevy_utils::tracing::Span;
use fixedbitset::FixedBitSet;
use std::{any::TypeId, borrow::Borrow, fmt, mem::MaybeUninit};
@ -39,6 +39,8 @@ pub struct QueryState<Q: WorldQuery, F: ReadOnlyWorldQuery = ()> {
pub(crate) matched_archetype_ids: Vec<ArchetypeId>,
pub(crate) fetch_state: Q::State,
pub(crate) filter_state: F::State,
#[cfg(feature = "trace")]
par_iter_span: Span,
}
impl<Q: WorldQuery, F: ReadOnlyWorldQuery> std::fmt::Debug for QueryState<Q, F> {
@ -125,6 +127,12 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
matched_tables: Default::default(),
matched_archetypes: Default::default(),
archetype_component_access: Default::default(),
#[cfg(feature = "trace")]
par_iter_span: bevy_utils::tracing::info_span!(
"par_for_each",
query = std::any::type_name::<Q>(),
filter = std::any::type_name::<F>(),
),
};
state.update_archetypes(world);
state
@ -208,12 +216,11 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) {
self.validate_world(world.id());
let archetypes = world.archetypes();
let new_generation = archetypes.generation();
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
let archetype_index_range = old_generation.value()..new_generation.value();
let old_generation =
std::mem::replace(&mut self.archetype_generation, archetypes.generation());
for archetype_index in archetype_index_range {
self.new_archetype(&archetypes[ArchetypeId::new(archetype_index)]);
for archetype in &archetypes[old_generation..] {
self.new_archetype(archetype);
}
}
@ -330,10 +337,12 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
) -> Result<[ROQueryItem<'w, Q>; N], QueryEntityError> {
self.update_archetypes(world);
// SAFETY: update_archetypes validates the `World` matches
// SAFETY:
// - We have read-only access to the entire world.
// - `update_archetypes` validates that the `World` matches.
unsafe {
self.get_many_read_only_manual(
world,
world.as_unsafe_world_cell_readonly(),
entities,
world.last_change_tick(),
world.read_change_tick(),
@ -625,11 +634,13 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
///
/// # Safety
///
/// This must be called on the same `World` that the `Query` was generated from:
/// * `world` must have permission to read all of the components returned from this call.
/// No mutable references may coexist with any of the returned references.
/// * This must be called on the same `World` that the `Query` was generated from:
/// use `QueryState::validate_world` to verify this.
pub(crate) unsafe fn get_many_read_only_manual<'w, const N: usize>(
&self,
world: &'w World,
world: UnsafeWorldCell<'w>,
entities: [Entity; N],
last_run: Tick,
this_run: Tick,
@ -639,12 +650,9 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
for (value, entity) in std::iter::zip(&mut values, entities) {
// SAFETY: fetch is read-only
// and world must be validated
let item = self.as_readonly().get_unchecked_manual(
world.as_unsafe_world_cell_readonly(),
entity,
last_run,
this_run,
)?;
let item = self
.as_readonly()
.get_unchecked_manual(world, entity, last_run, this_run)?;
*value = MaybeUninit::new(item);
}
@ -1205,7 +1213,9 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
while offset < table.entity_count() {
let func = func.clone();
let len = batch_size.min(table.entity_count() - offset);
let task = async move {
scope.spawn(async move {
#[cfg(feature = "trace")]
let _span = self.par_iter_span.enter();
let mut fetch =
Q::init_fetch(world, &self.fetch_state, last_run, this_run);
let mut filter =
@ -1223,17 +1233,7 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
}
func(Q::fetch(&mut fetch, *entity, row));
}
};
#[cfg(feature = "trace")]
let span = bevy_utils::tracing::info_span!(
"par_for_each",
query = std::any::type_name::<Q>(),
filter = std::any::type_name::<F>(),
count = len,
);
#[cfg(feature = "trace")]
let task = task.instrument(span);
scope.spawn(task);
});
offset += batch_size;
}
}
@ -1249,7 +1249,9 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
while offset < archetype.len() {
let func = func.clone();
let len = batch_size.min(archetype.len() - offset);
let task = async move {
scope.spawn(async move {
#[cfg(feature = "trace")]
let _span = self.par_iter_span.enter();
let mut fetch =
Q::init_fetch(world, &self.fetch_state, last_run, this_run);
let mut filter =
@ -1277,19 +1279,8 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
archetype_entity.table_row(),
));
}
};
});
#[cfg(feature = "trace")]
let span = bevy_utils::tracing::info_span!(
"par_for_each",
query = std::any::type_name::<Q>(),
filter = std::any::type_name::<F>(),
count = len,
);
#[cfg(feature = "trace")]
let task = task.instrument(span);
scope.spawn(task);
offset += batch_size;
}
}

View file

@ -279,7 +279,7 @@ impl<C: Component + Reflect + FromWorld> FromType<C> for ReflectComponent {
},
reflect_unchecked_mut: |entity| {
// SAFETY: reflect_unchecked_mut is an unsafe function pointer used by
// `reflect_unchecked_mut` which must be called with an UnsafeEntityCell with access to the the component `C` on the `entity`
// `reflect_unchecked_mut` which must be called with an UnsafeEntityCell with access to the component `C` on the `entity`
unsafe {
entity.get_mut::<C>().map(|c| Mut {
value: c.value as &mut dyn Reflect,

View file

@ -259,8 +259,7 @@ impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> {
// SAFETY: Only reads World removed component events
unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {}
// SAFETY: no component value access, removed component events can be read in parallel and are
// never mutably borrowed during system execution
// SAFETY: no component value access.
unsafe impl<'a> SystemParam for &'a RemovedComponentEvents {
type State = ();
type Item<'w, 's> = &'w RemovedComponentEvents;
@ -274,6 +273,6 @@ unsafe impl<'a> SystemParam for &'a RemovedComponentEvents {
world: UnsafeWorldCell<'w>,
_change_tick: Tick,
) -> Self::Item<'w, 's> {
world.world_metadata().removed_components()
world.removed_components()
}
}

View file

@ -1096,7 +1096,7 @@ mod tests {
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0, 2);
// Run every other cycle oppsite to the last one
// Run every other cycle opposite to the last one
schedule.add_systems(increment_counter.run_if(not(every_other_time)));
schedule.run(&mut world);
@ -1190,7 +1190,7 @@ mod tests {
.distributive_run_if(resource_changed_or_removed::<State<TestState>>())
.distributive_run_if(resource_removed::<State<TestState>>())
.distributive_run_if(state_exists::<TestState>())
.distributive_run_if(in_state(TestState::A))
.distributive_run_if(in_state(TestState::A).or_else(in_state(TestState::B)))
.distributive_run_if(state_changed::<TestState>())
.distributive_run_if(on_event::<TestEvent>())
.distributive_run_if(any_with_component::<TestComponent>())

View file

@ -59,7 +59,7 @@ pub type SystemConfig = NodeConfig<BoxedSystem>;
/// A collections of generic [`NodeConfig`]s.
pub enum NodeConfigs<T> {
/// Configuratin for a single node.
/// Configuration for a single node.
NodeConfig(NodeConfig<T>),
/// Configuration for a tuple of nested `Configs` instances.
Configs {

View file

@ -718,6 +718,9 @@ mod tests {
}
mod system_ambiguity {
use std::collections::BTreeSet;
use super::*;
// Required to make the derive macro behave
use crate as bevy_ecs;
use crate::event::Events;
@ -981,14 +984,12 @@ mod tests {
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
struct TestSchedule;
// Tests that the correct ambiguities were reported in the correct order.
#[test]
fn correct_ambiguities() {
use super::*;
#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
struct TestSchedule;
fn system_a(_res: ResMut<R>) {}
fn system_b(_res: ResMut<R>) {}
fn system_c(_res: ResMut<R>) {}
@ -1008,9 +1009,11 @@ mod tests {
));
schedule.graph_mut().initialize(&mut world);
let _ = schedule
.graph_mut()
.build_schedule(world.components(), &TestSchedule.dyn_clone());
let _ = schedule.graph_mut().build_schedule(
world.components(),
&TestSchedule.dyn_clone(),
&BTreeSet::new(),
);
let ambiguities: Vec<_> = schedule
.graph()
@ -1050,19 +1053,16 @@ mod tests {
// Related issue https://github.com/bevyengine/bevy/issues/9641
#[test]
fn anonymous_set_name() {
use super::*;
#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
struct TestSchedule;
let mut schedule = Schedule::new(TestSchedule);
schedule.add_systems((resmut_system, resmut_system).run_if(|| true));
let mut world = World::new();
schedule.graph_mut().initialize(&mut world);
let _ = schedule
.graph_mut()
.build_schedule(world.components(), &TestSchedule.dyn_clone());
let _ = schedule.graph_mut().build_schedule(
world.components(),
&TestSchedule.dyn_clone(),
&BTreeSet::new(),
);
let ambiguities: Vec<_> = schedule
.graph()
@ -1078,5 +1078,24 @@ mod tests {
)
);
}
#[test]
fn ignore_component_resource_ambiguities() {
let mut world = World::new();
world.insert_resource(R);
world.allow_ambiguous_resource::<R>();
let mut schedule = Schedule::new(TestSchedule);
//check resource
schedule.add_systems((resmut_system, res_system));
schedule.initialize(&mut world).unwrap();
assert!(schedule.graph().conflicting_systems().is_empty());
// check components
world.allow_ambiguous_component::<A>();
schedule.add_systems((write_component_system, read_component_system));
schedule.initialize(&mut world).unwrap();
assert!(schedule.graph().conflicting_systems().is_empty());
}
}
}

View file

@ -1,11 +1,12 @@
use std::{
collections::BTreeSet,
fmt::{Debug, Write},
result::Result,
};
use bevy_utils::default;
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
use bevy_utils::{default, tracing::info};
use bevy_utils::{
petgraph::{algo::TarjanScc, prelude::*},
thiserror::Error,
@ -18,6 +19,7 @@ use fixedbitset::FixedBitSet;
use crate::{
self as bevy_ecs,
component::{ComponentId, Components, Tick},
prelude::Component,
schedule::*,
system::{BoxedSystem, Resource, System},
world::World,
@ -27,6 +29,8 @@ use crate::{
#[derive(Default, Resource)]
pub struct Schedules {
inner: HashMap<BoxedScheduleLabel, Schedule>,
/// List of [`ComponentId`]s to ignore when reporting system order ambiguity conflicts
pub ignored_scheduling_ambiguities: BTreeSet<ComponentId>,
}
impl Schedules {
@ -34,6 +38,7 @@ impl Schedules {
pub fn new() -> Self {
Self {
inner: HashMap::new(),
ignored_scheduling_ambiguities: BTreeSet::new(),
}
}
@ -110,6 +115,38 @@ impl Schedules {
schedule.set_build_settings(schedule_build_settings.clone());
}
}
/// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`.
pub fn allow_ambiguous_component<T: Component>(&mut self, world: &mut World) {
self.ignored_scheduling_ambiguities
.insert(world.init_component::<T>());
}
/// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`.
pub fn allow_ambiguous_resource<T: Resource>(&mut self, world: &mut World) {
self.ignored_scheduling_ambiguities
.insert(world.components.init_resource::<T>());
}
/// Iterate through the [`ComponentId`]'s that will be ignored.
pub fn iter_ignored_ambiguities(&self) -> impl Iterator<Item = &ComponentId> + '_ {
self.ignored_scheduling_ambiguities.iter()
}
/// Prints the names of the components and resources with [`info`]
///
/// May panic or retrieve incorrect names if [`Components`] is not from the same
/// world
pub fn print_ignored_ambiguities(&self, components: &Components) {
let mut message =
"System order ambiguities caused by conflicts on the following types are ignored:\n"
.to_string();
for id in self.iter_ignored_ambiguities() {
writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
}
info!("{}", message);
}
}
fn make_executor(kind: ExecutorKind) -> Box<dyn SystemExecutor> {
@ -237,7 +274,7 @@ impl Schedule {
/// Set whether the schedule applies deferred system buffers on final time or not. This is a catch-all
/// in case a system uses commands but was not explicitly ordered before an instance of
/// [`apply_deferred`](crate::prelude::apply_deferred). By default this
/// [`apply_deferred`]. By default this
/// setting is true, but may be disabled if needed.
pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self {
self.executor.set_apply_final_deferred(apply_final_deferred);
@ -251,7 +288,7 @@ impl Schedule {
world.check_change_ticks();
self.initialize(world)
.unwrap_or_else(|e| panic!("Error when intializing schedule {:?}: {e}", self.name));
.unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.name));
self.executor.run(&mut self.executable, world);
}
@ -262,8 +299,16 @@ impl Schedule {
pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> {
if self.graph.changed {
self.graph.initialize(world);
self.graph
.update_schedule(&mut self.executable, world.components(), &self.name)?;
let ignored_ambiguities = world
.get_resource_or_insert_with::<Schedules>(Schedules::default)
.ignored_scheduling_ambiguities
.clone();
self.graph.update_schedule(
&mut self.executable,
world.components(),
&ignored_ambiguities,
&self.name,
)?;
self.graph.changed = false;
self.executor_initialized = false;
}
@ -871,6 +916,7 @@ impl ScheduleGraph {
&mut self,
components: &Components,
schedule_label: &BoxedScheduleLabel,
ignored_ambiguities: &BTreeSet<ComponentId>,
) -> Result<SystemSchedule, ScheduleBuildError> {
// check hierarchy for cycles
self.hierarchy.topsort =
@ -919,8 +965,11 @@ impl ScheduleGraph {
let ambiguous_with_flattened = self.get_ambiguous_with_flattened(&set_systems);
// check for conflicts
let conflicting_systems =
self.get_conflicting_systems(&flat_results.disconnected, &ambiguous_with_flattened);
let conflicting_systems = self.get_conflicting_systems(
&flat_results.disconnected,
&ambiguous_with_flattened,
ignored_ambiguities,
);
self.optionally_check_conflicts(&conflicting_systems, components, schedule_label)?;
self.conflicting_systems = conflicting_systems;
@ -1040,6 +1089,7 @@ impl ScheduleGraph {
&self,
flat_results_disconnected: &Vec<(NodeId, NodeId)>,
ambiguous_with_flattened: &GraphMap<NodeId, (), Undirected>,
ignored_ambiguities: &BTreeSet<ComponentId>,
) -> Vec<(NodeId, NodeId, Vec<ComponentId>)> {
let mut conflicting_systems = Vec::new();
for &(a, b) in flat_results_disconnected {
@ -1058,8 +1108,15 @@ impl ScheduleGraph {
let access_a = system_a.component_access();
let access_b = system_b.component_access();
if !access_a.is_compatible(access_b) {
let conflicts = access_a.get_conflicts(access_b);
conflicting_systems.push((a, b, conflicts));
let conflicts: Vec<_> = access_a
.get_conflicts(access_b)
.into_iter()
.filter(|id| !ignored_ambiguities.contains(id))
.collect();
if !conflicts.is_empty() {
conflicting_systems.push((a, b, conflicts));
}
}
}
}
@ -1171,6 +1228,7 @@ impl ScheduleGraph {
&mut self,
schedule: &mut SystemSchedule,
components: &Components,
ignored_ambiguities: &BTreeSet<ComponentId>,
schedule_label: &BoxedScheduleLabel,
) -> Result<(), ScheduleBuildError> {
if !self.uninit.is_empty() {
@ -1196,7 +1254,7 @@ impl ScheduleGraph {
self.system_set_conditions[id.index()] = conditions;
}
*schedule = self.build_schedule(components, schedule_label)?;
*schedule = self.build_schedule(components, schedule_label, ignored_ambiguities)?;
// move systems into new schedule
for &id in &schedule.system_ids {
@ -1570,7 +1628,7 @@ impl ScheduleGraph {
message
}
/// convert conflics to human readable format
/// convert conflicts to human readable format
pub fn conflicts_to_string<'a>(
&'a self,
ambiguities: &'a [(NodeId, NodeId, Vec<ComponentId>)],

View file

@ -40,13 +40,7 @@ pub use bevy_ecs_macros::States;
/// }
///
/// ```
pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug + Default {
/// The type returned when iterating over all [`variants`](States::variants) of this type.
type Iter: Iterator<Item = Self>;
/// Returns an iterator over all the state variants.
fn variants() -> Self::Iter;
}
pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug + Default {}
/// The label of a [`Schedule`](super::Schedule) that runs whenever [`State<S>`]
/// enters this state.

View file

@ -88,9 +88,9 @@ pub trait Command: Send + 'static {
///
/// # Implementing
///
/// Each built-in command is implemented as a separate method, e.g. [`spawn`](#method.spawn).
/// Each built-in command is implemented as a separate method, e.g. [`Commands::spawn`].
/// In addition to the pre-defined command methods, you can add commands with any arbitrary
/// behavior using [`Commands::add`](#method.add), which accepts any type implementing [`Command`].
/// behavior using [`Commands::add`], which accepts any type implementing [`Command`].
///
/// Since closures and other functions implement this trait automatically, this allows one-shot,
/// anonymous custom commands.

View file

@ -1,5 +1,5 @@
use crate::{
archetype::{ArchetypeComponentId, ArchetypeGeneration, ArchetypeId},
archetype::{ArchetypeComponentId, ArchetypeGeneration},
component::{ComponentId, Tick},
prelude::FromWorld,
query::{Access, FilteredAccessSet},
@ -270,16 +270,11 @@ impl<Param: SystemParam> SystemState<Param> {
#[inline]
pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) {
let archetypes = world.archetypes();
let new_generation = archetypes.generation();
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
let archetype_index_range = old_generation.value()..new_generation.value();
let old_generation =
std::mem::replace(&mut self.archetype_generation, archetypes.generation());
for archetype_index in archetype_index_range {
Param::new_archetype(
&mut self.param_state,
&archetypes[ArchetypeId::new(archetype_index)],
&mut self.meta,
);
for archetype in &archetypes[old_generation..] {
Param::new_archetype(&mut self.param_state, archetype, &mut self.meta);
}
}
@ -515,17 +510,12 @@ where
fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
assert!(self.world_id == Some(world.id()), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with.");
let archetypes = world.archetypes();
let new_generation = archetypes.generation();
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
let archetype_index_range = old_generation.value()..new_generation.value();
let old_generation =
std::mem::replace(&mut self.archetype_generation, archetypes.generation());
for archetype_index in archetype_index_range {
for archetype in &archetypes[old_generation..] {
let param_state = self.param_state.as_mut().unwrap();
F::Param::new_archetype(
param_state,
&archetypes[ArchetypeId::new(archetype_index)],
&mut self.system_meta,
);
F::Param::new_archetype(param_state, archetype, &mut self.system_meta);
}
}

View file

@ -6,7 +6,7 @@
//!
//! System functions can have parameters, through which one can query and mutate Bevy ECS state.
//! Only types that implement [`SystemParam`] can be used, automatically fetching data from
//! the [`World`](crate::world::World).
//! the [`World`].
//!
//! System functions often look like this:
//!

View file

@ -874,14 +874,12 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
&self,
entities: [Entity; N],
) -> Result<[ROQueryItem<'_, Q>; N], QueryEntityError> {
// SAFETY: it is the scheduler's responsibility to ensure that `Query` is never handed out on the wrong `World`.
// SAFETY:
// - `&self` ensures there is no mutable access to any components accessible to this query.
// - `self.world` matches `self.state`.
unsafe {
self.state.get_many_read_only_manual(
self.world.unsafe_world(),
entities,
self.last_run,
self.this_run,
)
self.state
.get_many_read_only_manual(self.world, entities, self.last_run, self.this_run)
}
}
@ -1107,7 +1105,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
/// Returns a mutable reference to the component `T` of the given entity.
///
/// In case of a nonexisting entity, mismatched component or missing write acess, a [`QueryComponentError`] is returned instead.
/// In case of a nonexisting entity, mismatched component or missing write access, a [`QueryComponentError`] is returned instead.
///
/// # Example
///

View file

@ -43,7 +43,7 @@ pub struct SystemId(Entity);
impl World {
/// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`].
///
/// It's possible to register the same systems more than once, they'll be stored seperately.
/// It's possible to register the same systems more than once, they'll be stored separately.
///
/// This is different from adding systems to a [`Schedule`](crate::schedule::Schedule),
/// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system.

View file

@ -174,7 +174,7 @@ impl<'a> From<&'a EntityWorldMut<'_>> for EntityRef<'a> {
impl<'w> From<EntityMut<'w>> for EntityRef<'w> {
fn from(value: EntityMut<'w>) -> Self {
// SAFETY:
// - `EntityMut` gurantees exclusive access to all of the entity's components.
// - `EntityMut` guarantees exclusive access to all of the entity's components.
unsafe { EntityRef::new(value.0) }
}
}
@ -182,7 +182,7 @@ impl<'w> From<EntityMut<'w>> for EntityRef<'w> {
impl<'a> From<&'a EntityMut<'_>> for EntityRef<'a> {
fn from(value: &'a EntityMut<'_>) -> Self {
// SAFETY:
// - `EntityMut` gurantees exclusive access to all of the entity's components.
// - `EntityMut` guarantees exclusive access to all of the entity's components.
// - `&value` ensures there are no mutable accesses.
unsafe { EntityRef::new(value.0) }
}

View file

@ -383,7 +383,7 @@ impl World {
}
}
/// Returns the components of an [`Entity`](crate::entity::Entity) through [`ComponentInfo`](crate::component::ComponentInfo).
/// Returns the components of an [`Entity`] through [`ComponentInfo`].
#[inline]
pub fn inspect_entity(&self, entity: Entity) -> Vec<&ComponentInfo> {
let entity_location = self
@ -1766,7 +1766,7 @@ impl World {
/// Iterates all component change ticks and clamps any older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
/// This prevents overflow and thus prevents false positives.
///
/// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD)
/// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`]
/// times since the previous pass.
// TODO: benchmark and optimize
pub fn check_change_ticks(&mut self) {
@ -2087,6 +2087,20 @@ impl World {
pub fn run_schedule(&mut self, label: impl AsRef<dyn ScheduleLabel>) {
self.schedule_scope(label, |world, sched| sched.run(world));
}
/// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`.
pub fn allow_ambiguous_component<T: Component>(&mut self) {
let mut schedules = self.remove_resource::<Schedules>().unwrap_or_default();
schedules.allow_ambiguous_component::<T>(self);
self.insert_resource(schedules);
}
/// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`.
pub fn allow_ambiguous_resource<T: Resource>(&mut self) {
let mut schedules = self.remove_resource::<Schedules>().unwrap_or_default();
schedules.allow_ambiguous_resource::<T>(self);
self.insert_resource(schedules);
}
}
impl fmt::Debug for World {

View file

@ -12,6 +12,7 @@ use crate::{
},
entity::{Entities, Entity, EntityLocation},
prelude::Component,
removal_detection::RemovedComponentEvents,
storage::{Column, ComponentSparseSet, Storages},
system::Resource,
};
@ -25,14 +26,14 @@ use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData};
/// In rust, having a `&mut World` means that there are absolutely no other references to the safe world alive at the same time,
/// without exceptions. Not even unsafe code can change this.
///
/// But there are situations where careful shared mutable access through a type is possible and safe. For this, rust provides the [`UnsafeCell`](std::cell::UnsafeCell)
/// But there are situations where careful shared mutable access through a type is possible and safe. For this, rust provides the [`UnsafeCell`]
/// escape hatch, which allows you to get a `*mut T` from a `&UnsafeCell<T>` and around which safe abstractions can be built.
///
/// Access to resources and components can be done uniquely using [`World::resource_mut`] and [`World::entity_mut`], and shared using [`World::resource`] and [`World::entity`].
/// These methods use lifetimes to check at compile time that no aliasing rules are being broken.
///
/// This alone is not enough to implement bevy systems where multiple systems can access *disjoint* parts of the world concurrently. For this, bevy stores all values of
/// resources and components (and [`ComponentTicks`](crate::component::ComponentTicks)) in [`UnsafeCell`](std::cell::UnsafeCell)s, and carefully validates disjoint access patterns using
/// resources and components (and [`ComponentTicks`]) in [`UnsafeCell`]s, and carefully validates disjoint access patterns using
/// APIs like [`System::component_access`](crate::system::System::component_access).
///
/// A system then can be executed using [`System::run_unsafe`](crate::system::System::run_unsafe) with a `&World` and use methods with interior mutability to access resource values.
@ -156,7 +157,7 @@ impl<'w> UnsafeWorldCell<'w> {
// - caller ensures there is no `&mut World` this makes it okay to make a `&World`
// - caller ensures there is no mutable borrows of world data, this means the caller cannot
// misuse the returned `&World`
unsafe { &*self.0 }
unsafe { self.unsafe_world() }
}
/// Gets a reference to the [`World`] this [`UnsafeWorldCell`] belong to.
@ -185,7 +186,7 @@ impl<'w> UnsafeWorldCell<'w> {
/// - must not be used in a way that would conflict with any
/// live exclusive borrows on world data
#[inline]
pub(crate) unsafe fn unsafe_world(self) -> &'w World {
unsafe fn unsafe_world(self) -> &'w World {
// SAFETY:
// - caller ensures that the returned `&World` is not used in a way that would conflict
// with any existing mutable borrows of world data
@ -224,6 +225,13 @@ impl<'w> UnsafeWorldCell<'w> {
&unsafe { self.world_metadata() }.components
}
/// Retrieves this world's collection of [removed components](RemovedComponentEvents).
pub fn removed_components(self) -> &'w RemovedComponentEvents {
// SAFETY:
// - we only access world metadata
&unsafe { self.world_metadata() }.removed_components
}
/// Retrieves this world's [`Bundles`] collection.
#[inline]
pub fn bundles(self) -> &'w Bundles {
@ -901,7 +909,7 @@ impl<'w> UnsafeWorldCell<'w> {
}
}
/// Get an untyped pointer to a particular [`Component`](crate::component::Component) on a particular [`Entity`] in the provided [`World`](crate::world::World).
/// Get an untyped pointer to a particular [`Component`] on a particular [`Entity`] in the provided [`World`].
///
/// # Safety
/// - `location` must refer to an archetype that contains `entity`
@ -929,7 +937,7 @@ unsafe fn get_component(
}
}
/// Get an untyped pointer to a particular [`Component`](crate::component::Component) and its [`ComponentTicks`]
/// Get an untyped pointer to a particular [`Component`] and its [`ComponentTicks`]
///
/// # Safety
/// - `location` must refer to an archetype that contains `entity`

View file

@ -336,19 +336,19 @@ impl<'w> WorldCell<'w> {
}
}
/// Sends an [`Event`](crate::event::Event).
/// Sends an [`Event`].
#[inline]
pub fn send_event<E: Event>(&self, event: E) {
self.send_event_batch(std::iter::once(event));
}
/// Sends the default value of the [`Event`](crate::event::Event) of type `E`.
/// Sends the default value of the [`Event`] of type `E`.
#[inline]
pub fn send_event_default<E: Event + Default>(&self) {
self.send_event_batch(std::iter::once(E::default()));
}
/// Sends a batch of [`Event`](crate::event::Event)s from an iterator.
/// Sends a batch of [`Event`]s from an iterator.
#[inline]
pub fn send_event_batch<E: Event>(&self, events: impl Iterator<Item = E>) {
match self.get_resource_mut::<Events<E>>() {

View file

@ -2,7 +2,7 @@ error[E0502]: cannot borrow `e_mut` as mutable because it is also borrowed as im
--> tests/ui/entity_ref_mut_lifetime_safety.rs:17:26
|
16 | let gotten: &A = e_mut.get::<A>().unwrap();
| ---------------- immutable borrow occurs here
| ----- immutable borrow occurs here
17 | let gotten2: A = e_mut.take::<A>().unwrap();
| ^^^^^^^^^^^^^^^^^ mutable borrow occurs here
18 | assert_eq!(gotten, &gotten2); // oops UB
@ -12,9 +12,9 @@ error[E0499]: cannot borrow `e_mut` as mutable more than once at a time
--> tests/ui/entity_ref_mut_lifetime_safety.rs:25:30
|
24 | let mut gotten: Mut<A> = e_mut.get_mut::<A>().unwrap();
| -------------------- first mutable borrow occurs here
| ----- first mutable borrow occurs here
25 | let mut gotten2: A = e_mut.take::<A>().unwrap();
| ^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
| ^^^^^ second mutable borrow occurs here
26 | assert_eq!(&mut *gotten, &mut gotten2); // oops UB
| ------ first borrow later used here
@ -25,7 +25,7 @@ error[E0505]: cannot move out of `e_mut` because it is borrowed
| --------- binding `e_mut` declared here
...
32 | let gotten: &A = e_mut.get::<A>().unwrap();
| ---------------- borrow of `e_mut` occurs here
| ----- borrow of `e_mut` occurs here
33 | e_mut.despawn();
| ^^^^^ move out of `e_mut` occurs here
34 | assert_eq!(gotten, &A(Box::new(14_usize))); // oops UB
@ -35,7 +35,7 @@ error[E0502]: cannot borrow `e_mut` as mutable because it is also borrowed as im
--> tests/ui/entity_ref_mut_lifetime_safety.rs:42:34
|
41 | let gotten: &A = e_mut.get::<A>().unwrap();
| ---------------- immutable borrow occurs here
| ----- immutable borrow occurs here
42 | let gotten_mut: Mut<A> = e_mut.get_mut::<A>().unwrap();
| ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
43 | assert_eq!(gotten, &*gotten_mut); // oops UB
@ -45,9 +45,9 @@ error[E0502]: cannot borrow `e_mut` as immutable because it is also borrowed as
--> tests/ui/entity_ref_mut_lifetime_safety.rs:48:26
|
47 | let gotten_mut: Mut<A> = e_mut.get_mut::<A>().unwrap();
| -------------------- mutable borrow occurs here
| ----- mutable borrow occurs here
48 | let gotten: &A = e_mut.get::<A>().unwrap();
| ^^^^^^^^^^^^^^^^ immutable borrow occurs here
| ^^^^^ immutable borrow occurs here
49 | assert_eq!(gotten, &*gotten_mut); // oops UB
| ---------- mutable borrow later used here
@ -55,7 +55,7 @@ error[E0502]: cannot borrow `e_mut` as mutable because it is also borrowed as im
--> tests/ui/entity_ref_mut_lifetime_safety.rs:54:9
|
53 | let gotten: &A = e_mut.get::<A>().unwrap();
| ---------------- immutable borrow occurs here
| ----- immutable borrow occurs here
54 | e_mut.insert::<B>(B);
| ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
55 | assert_eq!(gotten, &A(Box::new(16_usize))); // oops UB
@ -65,8 +65,8 @@ error[E0499]: cannot borrow `e_mut` as mutable more than once at a time
--> tests/ui/entity_ref_mut_lifetime_safety.rs:61:9
|
60 | let mut gotten_mut: Mut<A> = e_mut.get_mut::<A>().unwrap();
| -------------------- first mutable borrow occurs here
| ----- first mutable borrow occurs here
61 | e_mut.insert::<B>(B);
| ^^^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
| ^^^^^ second mutable borrow occurs here
62 | assert_eq!(&mut *gotten_mut, &mut A(Box::new(16_usize))); // oops UB
| ---------- first borrow later used here

View file

@ -2,7 +2,7 @@ error[E0502]: cannot borrow `query` as mutable because it is also borrowed as im
--> tests/ui/query_lifetime_safety.rs:17:39
|
16 | let data: &Foo = query.get(e).unwrap();
| ------------ immutable borrow occurs here
| ----- immutable borrow occurs here
17 | let mut data2: Mut<Foo> = query.get_mut(e).unwrap();
| ^^^^^^^^^^^^^^^^ mutable borrow occurs here
18 | assert_eq!(data, &mut *data2); // oops UB
@ -12,9 +12,9 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as
--> tests/ui/query_lifetime_safety.rs:23:30
|
22 | let mut data2: Mut<Foo> = query.get_mut(e).unwrap();
| ---------------- mutable borrow occurs here
| ----- mutable borrow occurs here
23 | let data: &Foo = query.get(e).unwrap();
| ^^^^^^^^^^^^ immutable borrow occurs here
| ^^^^^ immutable borrow occurs here
24 | assert_eq!(data, &mut *data2); // oops UB
| ----- mutable borrow later used here
@ -22,7 +22,7 @@ error[E0502]: cannot borrow `query` as mutable because it is also borrowed as im
--> tests/ui/query_lifetime_safety.rs:29:39
|
28 | let data: &Foo = query.get_component::<Foo>(e).unwrap();
| ----------------------------- immutable borrow occurs here
| ----- immutable borrow occurs here
29 | let mut data2: Mut<Foo> = query.get_component_mut(e).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
30 | assert_eq!(data, &mut *data2); // oops UB
@ -32,9 +32,9 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as
--> tests/ui/query_lifetime_safety.rs:35:30
|
34 | let mut data2: Mut<Foo> = query.get_component_mut(e).unwrap();
| -------------------------- mutable borrow occurs here
| ----- mutable borrow occurs here
35 | let data: &Foo = query.get_component::<Foo>(e).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here
| ^^^^^ immutable borrow occurs here
36 | assert_eq!(data, &mut *data2); // oops UB
| ----- mutable borrow later used here
@ -42,7 +42,7 @@ error[E0502]: cannot borrow `query` as mutable because it is also borrowed as im
--> tests/ui/query_lifetime_safety.rs:41:39
|
40 | let data: &Foo = query.single();
| -------------- immutable borrow occurs here
| ----- immutable borrow occurs here
41 | let mut data2: Mut<Foo> = query.single_mut();
| ^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
42 | assert_eq!(data, &mut *data2); // oops UB
@ -52,9 +52,9 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as
--> tests/ui/query_lifetime_safety.rs:47:30
|
46 | let mut data2: Mut<Foo> = query.single_mut();
| ------------------ mutable borrow occurs here
| ----- mutable borrow occurs here
47 | let data: &Foo = query.single();
| ^^^^^^^^^^^^^^ immutable borrow occurs here
| ^^^^^ immutable borrow occurs here
48 | assert_eq!(data, &mut *data2); // oops UB
| ----- mutable borrow later used here
@ -62,7 +62,7 @@ error[E0502]: cannot borrow `query` as mutable because it is also borrowed as im
--> tests/ui/query_lifetime_safety.rs:53:39
|
52 | let data: &Foo = query.get_single().unwrap();
| ------------------ immutable borrow occurs here
| ----- immutable borrow occurs here
53 | let mut data2: Mut<Foo> = query.get_single_mut().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
54 | assert_eq!(data, &mut *data2); // oops UB
@ -72,9 +72,9 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as
--> tests/ui/query_lifetime_safety.rs:59:30
|
58 | let mut data2: Mut<Foo> = query.get_single_mut().unwrap();
| ---------------------- mutable borrow occurs here
| ----- mutable borrow occurs here
59 | let data: &Foo = query.get_single().unwrap();
| ^^^^^^^^^^^^^^^^^^ immutable borrow occurs here
| ^^^^^ immutable borrow occurs here
60 | assert_eq!(data, &mut *data2); // oops UB
| ----- mutable borrow later used here
@ -82,7 +82,7 @@ error[E0502]: cannot borrow `query` as mutable because it is also borrowed as im
--> tests/ui/query_lifetime_safety.rs:65:39
|
64 | let data: &Foo = query.iter().next().unwrap();
| ------------ immutable borrow occurs here
| ----- immutable borrow occurs here
65 | let mut data2: Mut<Foo> = query.iter_mut().next().unwrap();
| ^^^^^^^^^^^^^^^^ mutable borrow occurs here
66 | assert_eq!(data, &mut *data2); // oops UB
@ -92,9 +92,9 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as
--> tests/ui/query_lifetime_safety.rs:71:30
|
70 | let mut data2: Mut<Foo> = query.iter_mut().next().unwrap();
| ---------------- mutable borrow occurs here
| ----- mutable borrow occurs here
71 | let data: &Foo = query.iter().next().unwrap();
| ^^^^^^^^^^^^ immutable borrow occurs here
| ^^^^^ immutable borrow occurs here
72 | assert_eq!(data, &mut *data2); // oops UB
| ----- mutable borrow later used here
@ -102,7 +102,7 @@ error[E0502]: cannot borrow `query` as mutable because it is also borrowed as im
--> tests/ui/query_lifetime_safety.rs:79:13
|
78 | query.for_each(|data| opt_data = Some(data));
| -------------------------------------------- immutable borrow occurs here
| ----- immutable borrow occurs here
79 | query.for_each_mut(|data| opt_data_2 = Some(data));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
80 | assert_eq!(opt_data.unwrap(), &mut *opt_data_2.unwrap()); // oops UB
@ -112,8 +112,8 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as
--> tests/ui/query_lifetime_safety.rs:87:13
|
86 | query.for_each_mut(|data| opt_data_2 = Some(data));
| -------------------------------------------------- mutable borrow occurs here
| ----- mutable borrow occurs here
87 | query.for_each(|data| opt_data = Some(data));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here
| ^^^^^ immutable borrow occurs here
88 | assert_eq!(opt_data.unwrap(), &mut *opt_data_2.unwrap()); // oops UB
| ---------- mutable borrow later used here

View file

@ -7,7 +7,7 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as
| mutable borrow occurs here
| mutable borrow later used here
9 | for _ in query.to_readonly().iter() {}
| ^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here
| ^^^^^ immutable borrow occurs here
error[E0502]: cannot borrow `query` as mutable because it is also borrowed as immutable
--> tests/ui/query_to_readonly.rs:14:18
@ -24,10 +24,10 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as
--> tests/ui/query_to_readonly.rs:39:30
|
36 | let mut mut_foo = query.single_mut();
| ------------------ mutable borrow occurs here
| ----- mutable borrow occurs here
...
39 | let readonly_query = query.to_readonly();
| ^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here
| ^^^^^ immutable borrow occurs here
...
43 | *mut_foo = Foo;
| ------- mutable borrow later used here
@ -36,7 +36,7 @@ error[E0502]: cannot borrow `query` as mutable because it is also borrowed as im
--> tests/ui/query_to_readonly.rs:55:27
|
51 | let readonly_query = query.to_readonly();
| ------------------- immutable borrow occurs here
| ----- immutable borrow occurs here
...
55 | let mut mut_foo = query.single_mut();
| ^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

View file

@ -2,9 +2,9 @@ error[E0499]: cannot borrow `query` as mutable more than once at a time
--> tests/ui/system_query_get_lifetime_safety.rs:8:14
|
7 | let a1 = query.get_mut(e).unwrap();
| ---------------- first mutable borrow occurs here
| ----- first mutable borrow occurs here
8 | let a2 = query.get_mut(e).unwrap();
| ^^^^^^^^^^^^^^^^ second mutable borrow occurs here
| ^^^^^ second mutable borrow occurs here
9 | // this should fail to compile
10 | println!("{} {}", a1.0, a2.0);
| -- first borrow later used here

View file

@ -2,7 +2,7 @@ error[E0502]: cannot borrow `query` as mutable because it is also borrowed as im
--> tests/ui/system_query_get_many_lifetime_safety.rs:8:14
|
7 | let a1 = query.get_many([e, e]).unwrap();
| ---------------------- immutable borrow occurs here
| ----- immutable borrow occurs here
8 | let a2 = query.get_mut(e).unwrap();
| ^^^^^^^^^^^^^^^^ mutable borrow occurs here
9 | // this should fail to compile

View file

@ -2,9 +2,9 @@ error[E0499]: cannot borrow `query` as mutable more than once at a time
--> tests/ui/system_query_get_many_mut_lifetime_safety.rs:8:14
|
7 | let a1 = query.get_many_mut([e, e]).unwrap();
| -------------------------- first mutable borrow occurs here
| ----- first mutable borrow occurs here
8 | let a2 = query.get_mut(e).unwrap();
| ^^^^^^^^^^^^^^^^ second mutable borrow occurs here
| ^^^^^ second mutable borrow occurs here
9 | // this should fail to compile
10 | println!("{} {}", a1[0].0, a2.0);
| ----- first borrow later used here

View file

@ -2,10 +2,10 @@ error[E0499]: cannot borrow `query` as mutable more than once at a time
--> tests/ui/system_query_iter_lifetime_safety.rs:10:21
|
7 | let mut iter = query.iter_mut();
| ---------------- first mutable borrow occurs here
| ----- first mutable borrow occurs here
...
10 | let mut iter2 = query.iter_mut();
| ^^^^^^^^^^^^^^^^ second mutable borrow occurs here
| ^^^^^ second mutable borrow occurs here
...
14 | println!("{}", a.0);
| --- first borrow later used here

View file

@ -2,7 +2,7 @@ error[E0499]: cannot borrow `iter` as mutable more than once at a time
--> tests/ui/system_query_iter_many_mut_lifetime_safety.rs:9:25
|
9 | while let Some(a) = iter.fetch_next() {
| ^^^^^^^^^^^^^^^^^ `iter` was mutably borrowed here in the previous iteration of the loop
| ^^^^ `iter` was mutably borrowed here in the previous iteration of the loop
10 | // this should fail to compile
11 | results.push(a);
| --------------- first borrow used here, in later iteration of loop
| ------- first borrow used here, in later iteration of loop

View file

@ -2,10 +2,10 @@ error[E0499]: cannot borrow `queries` as mutable more than once at a time
--> tests/ui/system_query_set_get_lifetime_safety.rs:10:14
|
7 | let mut q2 = queries.p0();
| ------------ first mutable borrow occurs here
| ------- first mutable borrow occurs here
...
10 | let q1 = queries.p1();
| ^^^^^^^^^^^^ second mutable borrow occurs here
| ^^^^^^^ second mutable borrow occurs here
...
14 | b.0 = a.0
| - first borrow later used here
@ -14,10 +14,10 @@ error[E0499]: cannot borrow `queries` as mutable more than once at a time
--> tests/ui/system_query_set_get_lifetime_safety.rs:21:18
|
18 | let q1 = queries.p1();
| ------------ first mutable borrow occurs here
| ------- first mutable borrow occurs here
...
21 | let mut q2 = queries.p0();
| ^^^^^^^^^^^^ second mutable borrow occurs here
| ^^^^^^^ second mutable borrow occurs here
...
25 | b.0 = a.0
| --- first borrow later used here

View file

@ -2,10 +2,10 @@ error[E0499]: cannot borrow `queries` as mutable more than once at a time
--> tests/ui/system_query_set_iter_lifetime_safety.rs:11:14
|
7 | let mut q2 = queries.p0();
| ------------ first mutable borrow occurs here
| ------- first mutable borrow occurs here
...
11 | let q1 = queries.p1();
| ^^^^^^^^^^^^ second mutable borrow occurs here
| ^^^^^^^ second mutable borrow occurs here
...
16 | b.0 = a.0
| - first borrow later used here
@ -14,10 +14,10 @@ error[E0499]: cannot borrow `queries` as mutable more than once at a time
--> tests/ui/system_query_set_iter_lifetime_safety.rs:24:18
|
20 | let q1 = queries.p1();
| ------------ first mutable borrow occurs here
| ------- first mutable borrow occurs here
...
24 | let mut q2 = queries.p0();
| ^^^^^^^^^^^^ second mutable borrow occurs here
| ^^^^^^^ second mutable borrow occurs here
...
29 | b.0 = a.0;
| --- first borrow later used here

View file

@ -2,10 +2,10 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as
--> tests/ui/system_state_iter_mut_overlap_safety.rs:18:13
|
15 | let mut_vec = query.iter_mut().collect::<Vec<bevy_ecs::prelude::Mut<A>>>();
| ---------------- mutable borrow occurs here
| ----- mutable borrow occurs here
...
18 | query.iter().collect::<Vec<&A>>(),
| ^^^^^^^^^^^^ immutable borrow occurs here
| ^^^^^ immutable borrow occurs here
...
23 | mut_vec.iter().map(|m| **m).collect::<Vec<A>>(),
| -------------- mutable borrow later used here
| ------- mutable borrow later used here

View file

@ -1,4 +1,10 @@
//! Systems and type definitions for gamepad handling in Bevy.
//!
//! This crate is built on top of [GilRs](gilrs), a library
//! that handles abstracting over platform-specific gamepad APIs.
#![allow(clippy::type_complexity)]
#![warn(missing_docs)]
mod converter;
mod gilrs_system;
@ -12,6 +18,7 @@ use gilrs::GilrsBuilder;
use gilrs_system::{gilrs_event_startup_system, gilrs_event_system};
use rumble::{play_gilrs_rumble, RunningRumbleEffects};
/// Plugin that provides gamepad handling to an [`App`].
#[derive(Default)]
pub struct GilrsPlugin;

View file

@ -1,4 +1,4 @@
//! A module for the [`Gizmos`](crate::gizmos::Gizmos) [`SystemParam`](bevy_ecs::system::SystemParam).
//! A module for the [`Gizmos`] [`SystemParam`].
use std::{f32::consts::TAU, iter};
@ -23,7 +23,7 @@ pub(crate) struct GizmoStorage {
pub strip_colors: Vec<ColorItem>,
}
/// A [`SystemParam`](bevy_ecs::system::SystemParam) for drawing gizmos.
/// A [`SystemParam`] for drawing gizmos.
///
/// They are drawn in immediate mode, which means they will be rendered only for
/// the frames in which they are spawned.

View file

@ -14,7 +14,7 @@
//! # bevy_ecs::system::assert_is_system(system);
//! ```
//!
//! See the documentation on [`Gizmos`](crate::gizmos::Gizmos) for more examples.
//! See the documentation on [`Gizmos`] for more examples.
pub mod gizmos;
@ -149,7 +149,7 @@ pub struct GizmoConfig {
pub line_width: f32,
/// Apply perspective to gizmo lines.
///
/// This setting only affects 3D, non-orhographic cameras.
/// This setting only affects 3D, non-orthographic cameras.
///
/// Defaults to `false`.
pub line_perspective: bool,

View file

@ -1,4 +1,9 @@
//! Plugin providing an [`AssetLoader`](bevy_asset::AssetLoader) and type definitions
//! for loading glTF 2.0 (a standard 3D scene definition format) files in Bevy.
//!
//! The [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) defines the format of the glTF files.
#![allow(clippy::type_complexity)]
#![warn(missing_docs)]
#[cfg(feature = "bevy_animation")]
use bevy_animation::AnimationClip;
@ -27,6 +32,11 @@ pub struct GltfPlugin {
}
impl GltfPlugin {
/// Register a custom vertex attribute so that it is recognized when loading a glTF file with the [`GltfLoader`].
///
/// `name` must be the attribute name as found in the glTF data, which must start with an underscore.
/// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
/// for additional details on custom attributes.
pub fn add_custom_vertex_attribute(
mut self,
name: &str,
@ -64,50 +74,81 @@ impl Plugin for GltfPlugin {
/// Representation of a loaded glTF file.
#[derive(Asset, Debug, TypePath)]
pub struct Gltf {
/// All scenes loaded from the glTF file.
pub scenes: Vec<Handle<Scene>>,
/// Named scenes loaded from the glTF file.
pub named_scenes: HashMap<String, Handle<Scene>>,
/// All meshes loaded from the glTF file.
pub meshes: Vec<Handle<GltfMesh>>,
/// Named meshes loaded from the glTF file.
pub named_meshes: HashMap<String, Handle<GltfMesh>>,
/// All materials loaded from the glTF file.
pub materials: Vec<Handle<StandardMaterial>>,
/// Named materials loaded from the glTF file.
pub named_materials: HashMap<String, Handle<StandardMaterial>>,
/// All nodes loaded from the glTF file.
pub nodes: Vec<Handle<GltfNode>>,
/// Named nodes loaded from the glTF file.
pub named_nodes: HashMap<String, Handle<GltfNode>>,
/// Default scene to be displayed.
pub default_scene: Option<Handle<Scene>>,
/// All animations loaded from the glTF file.
#[cfg(feature = "bevy_animation")]
pub animations: Vec<Handle<AnimationClip>>,
/// Named animations loaded from the glTF file.
#[cfg(feature = "bevy_animation")]
pub named_animations: HashMap<String, Handle<AnimationClip>>,
}
/// A glTF node with all of its child nodes, its [`GltfMesh`],
/// [`Transform`](bevy_transform::prelude::Transform) and an optional [`GltfExtras`].
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-node).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GltfNode {
/// Direct children of the node.
pub children: Vec<GltfNode>,
/// Mesh of the node.
pub mesh: Option<Handle<GltfMesh>>,
/// Local transform.
pub transform: bevy_transform::prelude::Transform,
/// Additional data.
pub extras: Option<GltfExtras>,
}
/// A glTF mesh, which may consist of multiple [`GltfPrimitives`](GltfPrimitive)
/// and an optional [`GltfExtras`].
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GltfMesh {
/// Primitives of the glTF mesh.
pub primitives: Vec<GltfPrimitive>,
/// Additional data.
pub extras: Option<GltfExtras>,
}
/// Part of a [`GltfMesh`] that consists of a [`Mesh`], an optional [`StandardMaterial`] and [`GltfExtras`].
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh-primitive).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GltfPrimitive {
/// Topology to be rendered.
pub mesh: Handle<Mesh>,
/// Material to apply to the `mesh`.
pub material: Option<Handle<StandardMaterial>>,
/// Additional data.
pub extras: Option<GltfExtras>,
/// Additional data of the `material`.
pub material_extras: Option<GltfExtras>,
}
/// Additional untyped data that can be present on most glTF types.
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
#[derive(Clone, Debug, Reflect, Default, Component)]
#[reflect(Component)]
pub struct GltfExtras {
/// Content of the extra data.
pub value: String,
}

View file

@ -1,7 +1,6 @@
use crate::{vertex_attributes::convert_attribute, Gltf, GltfExtras, GltfNode};
use bevy_asset::{
anyhow, io::Reader, AssetLoadError, AssetLoader, AsyncReadExt, Handle, LoadContext,
ReadAssetBytesError,
io::Reader, AssetLoadError, AssetLoader, AsyncReadExt, Handle, LoadContext, ReadAssetBytesError,
};
use bevy_core::Name;
use bevy_core_pipeline::prelude::Camera3dBundle;
@ -49,51 +48,76 @@ use thiserror::Error;
/// An error that occurs when loading a glTF file.
#[derive(Error, Debug)]
pub enum GltfError {
/// Unsupported primitive mode.
#[error("unsupported primitive mode")]
UnsupportedPrimitive { mode: Mode },
UnsupportedPrimitive {
/// The primitive mode.
mode: Mode,
},
/// Invalid glTF file.
#[error("invalid glTF file: {0}")]
Gltf(#[from] gltf::Error),
/// Binary blob is missing.
#[error("binary blob is missing")]
MissingBlob,
/// Decoding the base64 mesh data failed.
#[error("failed to decode base64 mesh data")]
Base64Decode(#[from] base64::DecodeError),
/// Unsupported buffer format.
#[error("unsupported buffer format")]
BufferFormatUnsupported,
/// Invalid image mime type.
#[error("invalid image mime type: {0}")]
InvalidImageMimeType(String),
/// Error when loading a texture. Might be due to a disabled image file format feature.
#[error("You may need to add the feature for the file format: {0}")]
ImageError(#[from] TextureError),
/// Failed to read bytes from an asset path.
#[error("failed to read bytes from an asset path: {0}")]
ReadAssetBytesError(#[from] ReadAssetBytesError),
/// Failed to load asset from an asset path.
#[error("failed to load asset from an asset path: {0}")]
AssetLoadError(#[from] AssetLoadError),
/// Missing sampler for an animation.
#[error("Missing sampler for animation {0}")]
MissingAnimationSampler(usize),
/// Failed to generate tangents.
#[error("failed to generate tangents: {0}")]
GenerateTangentsError(#[from] bevy_render::mesh::GenerateTangentsError),
/// Failed to generate morph targets.
#[error("failed to generate morph targets: {0}")]
MorphTarget(#[from] bevy_render::mesh::morph::MorphBuildError),
/// Failed to load a file.
#[error("failed to load file: {0}")]
Io(#[from] std::io::Error),
}
/// Loads glTF files with all of their data as their corresponding bevy representations.
pub struct GltfLoader {
/// List of compressed image formats handled by the loader.
pub supported_compressed_formats: CompressedImageFormats,
/// Custom vertex attributes that will be recognized when loading a glTF file.
///
/// Keys must be the attribute names as found in the glTF data, which must start with an underscore.
/// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
/// for additional details on custom attributes.
pub custom_vertex_attributes: HashMap<String, MeshVertexAttribute>,
}
impl AssetLoader for GltfLoader {
type Asset = Gltf;
type Settings = ();
type Error = GltfError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a (),
load_context: &'a mut LoadContext,
) -> bevy_utils::BoxedFuture<'a, Result<Gltf, anyhow::Error>> {
) -> bevy_utils::BoxedFuture<'a, Result<Gltf, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
Ok(load_gltf(self, &bytes, load_context).await?)
load_gltf(self, &bytes, load_context).await
})
}

View file

@ -263,6 +263,7 @@ pub(crate) fn convert_attribute(
gltf::Semantic::Tangents => Some((Mesh::ATTRIBUTE_TANGENT, ConversionMode::Any)),
gltf::Semantic::Colors(0) => Some((Mesh::ATTRIBUTE_COLOR, ConversionMode::Rgba)),
gltf::Semantic::TexCoords(0) => Some((Mesh::ATTRIBUTE_UV_0, ConversionMode::TexCoord)),
gltf::Semantic::TexCoords(1) => Some((Mesh::ATTRIBUTE_UV_1, ConversionMode::TexCoord)),
gltf::Semantic::Joints(0) => {
Some((Mesh::ATTRIBUTE_JOINT_INDEX, ConversionMode::JointIndex))
}

View file

@ -664,12 +664,12 @@ mod tests {
};
/// Assert the (non)existence and state of the child's [`Parent`] component.
fn assert_parent(world: &mut World, child: Entity, parent: Option<Entity>) {
fn assert_parent(world: &World, child: Entity, parent: Option<Entity>) {
assert_eq!(world.get::<Parent>(child).map(|p| p.get()), parent);
}
/// Assert the (non)existence and state of the parent's [`Children`] component.
fn assert_children(world: &mut World, parent: Entity, children: Option<&[Entity]>) {
fn assert_children(world: &World, parent: Entity, children: Option<&[Entity]>) {
assert_eq!(world.get::<Children>(parent).map(|c| &**c), children);
}

View file

@ -26,7 +26,7 @@ impl Parent {
/// Gets the parent [`Entity`] as a slice of length 1.
///
/// Useful for making APIs that require a type or homogenous storage
/// Useful for making APIs that require a type or homogeneous storage
/// for both [`Children`] & [`Parent`] that is agnostic to edge direction.
///
/// [`Children`]: super::children::Children

View file

@ -24,7 +24,7 @@ use bevy_ecs::schedule::State;
///
/// In case multiple systems are checking for [`Input::just_pressed`] or [`Input::just_released`]
/// but only one should react, for example in the case of triggering
/// [`State`](bevy_ecs::schedule::State) change, you should consider clearing the input state, either by:
/// [`State`] change, you should consider clearing the input state, either by:
///
/// * Using [`Input::clear_just_pressed`] or [`Input::clear_just_released`] instead.
/// * Calling [`Input::clear`] or [`Input::reset`] immediately after the state change.

View file

@ -19,7 +19,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
///
/// ## Usage
///
/// The event is consumed inside of the [`keyboard_input_system`](crate::keyboard::keyboard_input_system)
/// The event is consumed inside of the [`keyboard_input_system`]
/// to update the [`Input<KeyCode>`](crate::Input<KeyCode>) resource.
#[derive(Event, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
#[reflect(Debug, PartialEq)]
@ -70,16 +70,16 @@ pub fn keyboard_input_system(
}
}
/// The key code of a [`KeyboardInput`](crate::keyboard::KeyboardInput).
/// The key code of a [`KeyboardInput`].
///
/// ## Usage
///
/// It is used as the generic `T` value of an [`Input`](crate::Input) to create a `Res<Input<KeyCode>>`.
/// The resource values are mapped to the current layout of the keyboard and correlate to an [`ScanCode`](ScanCode).
/// It is used as the generic `T` value of an [`Input`] to create a `Res<Input<KeyCode>>`.
/// The resource values are mapped to the current layout of the keyboard and correlate to an [`ScanCode`].
///
/// ## Updating
///
/// The resource is updated inside of the [`keyboard_input_system`](crate::keyboard::keyboard_input_system).
/// The resource is updated inside of the [`keyboard_input_system`].
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy, Reflect)]
#[reflect(Debug, Hash, PartialEq)]
#[cfg_attr(
@ -443,16 +443,16 @@ pub enum KeyCode {
Cut,
}
/// The scan code of a [`KeyboardInput`](crate::keyboard::KeyboardInput).
/// The scan code of a [`KeyboardInput`].
///
/// ## Usage
///
/// It is used as the generic `<T>` value of an [`Input`](crate::Input) to create a `Res<Input<ScanCode>>`.
/// The resource values are mapped to the physical location of a key on the keyboard and correlate to an [`KeyCode`](KeyCode)
/// It is used as the generic `<T>` value of an [`Input`] to create a `Res<Input<ScanCode>>`.
/// The resource values are mapped to the physical location of a key on the keyboard and correlate to an [`KeyCode`]
///
/// ## Updating
///
/// The resource is updated inside of the [`keyboard_input_system`](crate::keyboard::keyboard_input_system).
/// The resource is updated inside of the [`keyboard_input_system`].
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy, Reflect)]
#[reflect(Debug, Hash, PartialEq)]
#[cfg_attr(

View file

@ -19,7 +19,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
///
/// ## Usage
///
/// The event is read inside of the [`mouse_button_input_system`](crate::mouse::mouse_button_input_system)
/// The event is read inside of the [`mouse_button_input_system`]
/// to update the [`Input<MouseButton>`](crate::Input<MouseButton>) resource.
#[derive(Event, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
#[reflect(Debug, PartialEq)]
@ -41,12 +41,12 @@ pub struct MouseButtonInput {
///
/// ## Usage
///
/// It is used as the generic `T` value of an [`Input`](crate::Input) to create a `bevy`
/// It is used as the generic `T` value of an [`Input`] to create a `bevy`
/// resource.
///
/// ## Updating
///
/// The resource is updated inside of the [`mouse_button_input_system`](crate::mouse::mouse_button_input_system).
/// The resource is updated inside of the [`mouse_button_input_system`].
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Reflect)]
#[reflect(Debug, Hash, PartialEq)]
#[cfg_attr(
@ -88,7 +88,7 @@ pub struct MouseMotion {
/// The scroll unit.
///
/// Describes how a value of a [`MouseWheel`](crate::mouse::MouseWheel) event has to be interpreted.
/// Describes how a value of a [`MouseWheel`] event has to be interpreted.
///
/// The value of the event can either be interpreted as the amount of lines or the amount of pixels
/// to scroll.
@ -102,12 +102,12 @@ pub struct MouseMotion {
pub enum MouseScrollUnit {
/// The line scroll unit.
///
/// The delta of the associated [`MouseWheel`](crate::mouse::MouseWheel) event corresponds
/// The delta of the associated [`MouseWheel`] event corresponds
/// to the amount of lines or rows to scroll.
Line,
/// The pixel scroll unit.
///
/// The delta of the associated [`MouseWheel`](crate::mouse::MouseWheel) event corresponds
/// The delta of the associated [`MouseWheel`] event corresponds
/// to the amount of pixels to scroll.
Pixel,
}

View file

@ -53,7 +53,7 @@ pub struct TouchInput {
pub id: u64,
}
/// A force description of a [`Touch`](crate::touch::Touch) input.
/// A force description of a [`Touch`] input.
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
@ -92,7 +92,7 @@ pub enum ForceTouch {
Normalized(f64),
}
/// A phase of a [`TouchInput`](crate::touch::TouchInput).
/// A phase of a [`TouchInput`].
///
/// ## Usage
///
@ -223,7 +223,7 @@ impl From<&TouchInput> for Touch {
///
/// ## Updating
///
/// The resource is updated inside of the [`touch_screen_input_system`](crate::touch::touch_screen_input_system).
/// The resource is updated inside of the [`touch_screen_input_system`].
#[derive(Debug, Clone, Default, Resource)]
pub struct Touches {
/// A collection of every [`Touch`] that is currently being pressed.

View file

@ -63,6 +63,7 @@ shader_format_spirv = ["bevy_render/shader_format_spirv"]
serialize = ["bevy_core/serialize", "bevy_input/serialize", "bevy_time/serialize", "bevy_window/serialize", "bevy_transform/serialize", "bevy_math/serialize", "bevy_scene?/serialize"]
multi-threaded = ["bevy_asset/multi-threaded", "bevy_ecs/multi-threaded", "bevy_tasks/multi-threaded"]
async-io = ["bevy_tasks/async-io"]
# Display server protocol support (X11 is enabled by default)
wayland = ["bevy_winit/wayland"]
@ -75,7 +76,7 @@ subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]
pbr_transmission_textures = ["bevy_pbr?/pbr_transmission_textures", "bevy_gltf?/pbr_transmission_textures"]
# Optimise for WebGL2
webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl", "bevy_gizmos?/webgl"]
webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl", "bevy_gizmos?/webgl", "bevy_sprite?/webgl"]
# enable systems that allow for automated testing on CI
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_time/bevy_ci_testing", "bevy_render?/bevy_ci_testing", "bevy_render?/ci_limits"]

View file

@ -64,7 +64,7 @@ impl PluginGroup for DefaultPlugins {
#[cfg(feature = "bevy_winit")]
{
group = group.add(bevy_winit::WinitPlugin);
group = group.add(bevy_winit::WinitPlugin::default());
}
#[cfg(feature = "bevy_render")]

View file

@ -13,3 +13,4 @@ toml_edit = "0.19"
syn = "2.0"
quote = "1.0"
rustc-hash = "1.0"
proc-macro2 = "1.0"

View file

@ -2,6 +2,7 @@ use syn::{Expr, ExprLit, Lit};
use crate::symbol::Symbol;
/// Get a [literal string](struct@syn::LitStr) from the provided [expression](Expr).
pub fn get_lit_str(attr_name: Symbol, value: &Expr) -> syn::Result<&syn::LitStr> {
if let Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
@ -16,6 +17,7 @@ pub fn get_lit_str(attr_name: Symbol, value: &Expr) -> syn::Result<&syn::LitStr>
}
}
/// Get a [literal boolean](struct@syn::LitBool) from the provided [expression](Expr) as a [`bool`].
pub fn get_lit_bool(attr_name: Symbol, value: &Expr) -> syn::Result<bool> {
if let Expr::Lit(ExprLit {
lit: Lit::Bool(lit),

Some files were not shown because too many files have changed in this diff Show more