Merge branch 'main' into transmission

This commit is contained in:
Marco Buono 2023-05-17 00:31:36 -03:00
commit 0ef74ed789
108 changed files with 1403 additions and 827 deletions

View file

@ -16,6 +16,7 @@ rust-version = "1.67.0"
exclude = [
"benches",
"crates/bevy_ecs_compile_fail_tests",
"crates/bevy_macros_compile_fail_tests",
"crates/bevy_reflect_compile_fail_tests",
]
members = [
@ -150,6 +151,9 @@ dds = ["bevy_internal/dds"]
# KTX2 compressed texture support
ktx2 = ["bevy_internal/ktx2"]
# PNM image format support, includes pam, pbm, pgm and ppm
pnm = ["bevy_internal/pnm"]
# For KTX2 supercompression
zlib = ["bevy_internal/zlib"]

View file

@ -7,7 +7,7 @@ publish = false
license = "MIT OR Apache-2.0"
[dev-dependencies]
glam = "0.23"
glam = "0.24"
rand = "0.8"
rand_chacha = "0.3"
criterion = { version = "0.3", features = ["html_reports"] }

View file

@ -82,10 +82,11 @@ pub struct AssetServerInternal {
/// ```
/// # use bevy_asset::*;
/// # use bevy_app::*;
/// # use bevy_utils::Duration;
/// # let mut app = App::new();
/// // The asset plugin can be configured to watch for asset changes.
/// app.add_plugin(AssetPlugin {
/// watch_for_changes: true,
/// watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(200)),
/// ..Default::default()
/// });
/// ```
@ -702,7 +703,7 @@ mod test {
fn setup(asset_path: impl AsRef<Path>) -> AssetServer {
use crate::FileAssetIo;
IoTaskPool::init(Default::default);
AssetServer::new(FileAssetIo::new(asset_path, false))
AssetServer::new(FileAssetIo::new(asset_path, &None))
}
#[test]

View file

@ -5,14 +5,15 @@
use bevy_app::{App, Plugin, Update};
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_tasks::{IoTaskPool, TaskPoolBuilder};
use bevy_utils::HashMap;
use bevy_utils::{Duration, HashMap};
use std::{
ops::{Deref, DerefMut},
path::Path,
};
use crate::{
Asset, AssetEvent, AssetPlugin, AssetServer, Assets, FileAssetIo, Handle, HandleUntyped,
Asset, AssetEvent, AssetPlugin, AssetServer, Assets, ChangeWatcher, FileAssetIo, Handle,
HandleUntyped,
};
/// A helper [`App`] used for hot reloading internal assets, which are compiled-in to Bevy plugins.
@ -72,7 +73,7 @@ impl Plugin for DebugAssetServerPlugin {
let mut debug_asset_app = App::new();
debug_asset_app.add_plugin(AssetPlugin {
asset_folder: "crates".to_string(),
watch_for_changes: true,
watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(200)),
});
app.insert_non_send_resource(DebugAssetApp(debug_asset_app));
app.add_systems(Update, run_debug_asset_app);

View file

@ -1,8 +1,10 @@
use bevy_utils::{default, HashMap, HashSet};
use bevy_utils::{default, Duration, HashMap, HashSet};
use crossbeam_channel::Receiver;
use notify::{Event, RecommendedWatcher, RecursiveMode, Result, Watcher};
use std::path::{Path, PathBuf};
use crate::ChangeWatcher;
/// Watches for changes to files on the local filesystem.
///
/// When hot-reloading is enabled, the [`AssetServer`](crate::AssetServer) uses this to reload
@ -11,10 +13,11 @@ pub struct FilesystemWatcher {
pub watcher: RecommendedWatcher,
pub receiver: Receiver<Result<Event>>,
pub path_map: HashMap<PathBuf, HashSet<PathBuf>>,
pub delay: Duration,
}
impl Default for FilesystemWatcher {
fn default() -> Self {
impl FilesystemWatcher {
pub fn new(configuration: &ChangeWatcher) -> Self {
let (sender, receiver) = crossbeam_channel::unbounded();
let watcher: RecommendedWatcher = RecommendedWatcher::new(
move |res| {
@ -27,11 +30,10 @@ impl Default for FilesystemWatcher {
watcher,
receiver,
path_map: default(),
delay: configuration.delay,
}
}
}
impl FilesystemWatcher {
/// Watch for changes recursively at the provided path.
pub fn watch<P: AsRef<Path>>(&mut self, to_watch: P, to_reload: PathBuf) -> Result<()> {
self.path_map

View file

@ -1,4 +1,4 @@
use crate::{AssetIo, AssetIoError, Metadata};
use crate::{AssetIo, AssetIoError, ChangeWatcher, Metadata};
use anyhow::Result;
use bevy_utils::BoxedFuture;
use std::{
@ -59,7 +59,7 @@ impl AssetIo for AndroidAssetIo {
Ok(())
}
fn watch_for_changes(&self) -> Result<(), AssetIoError> {
fn watch_for_changes(&self, _configuration: &ChangeWatcher) -> Result<(), AssetIoError> {
bevy_log::warn!("Watching for changes is not supported on Android");
Ok(())
}

View file

@ -1,12 +1,12 @@
#[cfg(feature = "filesystem_watcher")]
use crate::{filesystem_watcher::FilesystemWatcher, AssetServer};
use crate::{AssetIo, AssetIoError, Metadata};
use crate::{AssetIo, AssetIoError, ChangeWatcher, Metadata};
use anyhow::Result;
#[cfg(feature = "filesystem_watcher")]
use bevy_ecs::system::Res;
use bevy_ecs::system::{Local, Res};
use bevy_utils::BoxedFuture;
#[cfg(feature = "filesystem_watcher")]
use bevy_utils::{default, HashSet};
use bevy_utils::{default, HashMap, Instant};
#[cfg(feature = "filesystem_watcher")]
use crossbeam_channel::TryRecvError;
use fs::File;
@ -35,13 +35,13 @@ impl FileAssetIo {
/// watching for changes.
///
/// See `get_base_path` below.
pub fn new<P: AsRef<Path>>(path: P, watch_for_changes: bool) -> Self {
pub fn new<P: AsRef<Path>>(path: P, watch_for_changes: &Option<ChangeWatcher>) -> Self {
let file_asset_io = FileAssetIo {
#[cfg(feature = "filesystem_watcher")]
filesystem_watcher: default(),
root_path: Self::get_base_path().join(path.as_ref()),
};
if watch_for_changes {
if let Some(configuration) = watch_for_changes {
#[cfg(any(
not(feature = "filesystem_watcher"),
target_arch = "wasm32",
@ -52,7 +52,7 @@ impl FileAssetIo {
wasm32 / android targets"
);
#[cfg(feature = "filesystem_watcher")]
file_asset_io.watch_for_changes().unwrap();
file_asset_io.watch_for_changes(configuration).unwrap();
}
file_asset_io
}
@ -143,10 +143,10 @@ impl AssetIo for FileAssetIo {
Ok(())
}
fn watch_for_changes(&self) -> Result<(), AssetIoError> {
fn watch_for_changes(&self, configuration: &ChangeWatcher) -> Result<(), AssetIoError> {
#[cfg(feature = "filesystem_watcher")]
{
*self.filesystem_watcher.write() = Some(default());
*self.filesystem_watcher.write() = Some(FilesystemWatcher::new(configuration));
}
#[cfg(not(feature = "filesystem_watcher"))]
bevy_log::warn!("Watching for changes is not supported when the `filesystem_watcher` feature is disabled");
@ -174,7 +174,10 @@ impl AssetIo for FileAssetIo {
feature = "filesystem_watcher",
all(not(target_arch = "wasm32"), not(target_os = "android"))
))]
pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
pub fn filesystem_watcher_system(
asset_server: Res<AssetServer>,
mut changed: Local<HashMap<PathBuf, Instant>>,
) {
let asset_io =
if let Some(asset_io) = asset_server.server.asset_io.downcast_ref::<FileAssetIo>() {
asset_io
@ -182,14 +185,15 @@ pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
return;
};
let watcher = asset_io.filesystem_watcher.read();
if let Some(ref watcher) = *watcher {
let mut changed = HashSet::<&PathBuf>::default();
loop {
let event = match watcher.receiver.try_recv() {
Ok(result) => result.unwrap(),
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => panic!("FilesystemWatcher disconnected."),
};
if let notify::event::Event {
kind: notify::event::EventKind::Modify(_),
paths,
@ -199,13 +203,22 @@ pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
for path in &paths {
let Some(set) = watcher.path_map.get(path) else {continue};
for to_reload in set {
if !changed.contains(to_reload) {
changed.insert(to_reload);
let _ = asset_server.load_untracked(to_reload.as_path().into(), true);
}
// When an asset is modified, note down the timestamp (overriding any previous modification events)
changed.insert(to_reload.to_owned(), Instant::now());
}
}
}
}
// Reload all assets whose last modification was at least 50ms ago.
//
// When changing and then saving a shader, several modification events are sent in short succession.
// Unless we wait until we are sure the shader is finished being modified (and that there will be no more events coming),
// we will sometimes get a crash when trying to reload a partially-modified shader.
for (to_reload, _) in
changed.drain_filter(|_, last_modified| last_modified.elapsed() >= watcher.delay)
{
let _ = asset_server.load_untracked(to_reload.as_path().into(), true);
}
}
}

View file

@ -25,6 +25,8 @@ use std::{
};
use thiserror::Error;
use crate::ChangeWatcher;
/// Errors that occur while loading assets.
#[derive(Error, Debug)]
pub enum AssetIoError {
@ -81,7 +83,7 @@ pub trait AssetIo: Downcast + Send + Sync + 'static {
) -> Result<(), AssetIoError>;
/// Enables change tracking in this asset I/O.
fn watch_for_changes(&self) -> Result<(), AssetIoError>;
fn watch_for_changes(&self, configuration: &ChangeWatcher) -> Result<(), AssetIoError>;
/// Returns `true` if the path is a directory.
fn is_dir(&self, path: &Path) -> bool {

View file

@ -1,4 +1,4 @@
use crate::{AssetIo, AssetIoError, Metadata};
use crate::{AssetIo, AssetIoError, ChangeWatcher, Metadata};
use anyhow::Result;
use bevy_utils::BoxedFuture;
use js_sys::Uint8Array;
@ -64,7 +64,7 @@ impl AssetIo for WasmAssetIo {
Ok(())
}
fn watch_for_changes(&self) -> Result<(), AssetIoError> {
fn watch_for_changes(&self, _configuration: &ChangeWatcher) -> Result<(), AssetIoError> {
bevy_log::warn!("Watching for changes is not supported in WASM");
Ok(())
}

View file

@ -49,6 +49,7 @@ pub use reflect::*;
use bevy_app::{prelude::*, MainScheduleOrder};
use bevy_ecs::schedule::ScheduleLabel;
use bevy_utils::Duration;
/// Asset storages are updated.
#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)]
@ -57,6 +58,30 @@ pub struct LoadAssets;
#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)]
pub struct AssetEvents;
/// Configuration for hot reloading assets by watching for changes.
#[derive(Debug, Clone)]
pub struct ChangeWatcher {
/// Minimum delay after which a file change will trigger a reload.
///
/// The change watcher will wait for this duration after a file change before reloading the
/// asset. This is useful to avoid reloading an asset multiple times when it is changed
/// multiple times in a short period of time, or to avoid reloading an asset that is still
/// being written to.
///
/// If you have a slow hard drive or expect to reload large assets, you may want to increase
/// this value.
pub delay: Duration,
}
impl ChangeWatcher {
/// Enable change watching with the given delay when a file is changed.
///
/// See [`Self::delay`] for more details on how this value is used.
pub fn with_delay(delay: Duration) -> Option<Self> {
Some(Self { delay })
}
}
/// Adds support for [`Assets`] to an App.
///
/// Assets are typed collections with change tracking, which are added as App Resources. Examples of
@ -67,14 +92,14 @@ pub struct AssetPlugin {
pub asset_folder: String,
/// Whether to watch for changes in asset files. Requires the `filesystem_watcher` feature,
/// and cannot be supported on the wasm32 arch nor android os.
pub watch_for_changes: bool,
pub watch_for_changes: Option<ChangeWatcher>,
}
impl Default for AssetPlugin {
fn default() -> Self {
Self {
asset_folder: "assets".to_string(),
watch_for_changes: false,
watch_for_changes: None,
}
}
}
@ -86,7 +111,7 @@ impl AssetPlugin {
/// delegate to the default `AssetIo` for the platform.
pub fn create_platform_default_asset_io(&self) -> Box<dyn AssetIo> {
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
let source = FileAssetIo::new(&self.asset_folder, self.watch_for_changes);
let source = FileAssetIo::new(&self.asset_folder, &self.watch_for_changes);
#[cfg(target_arch = "wasm32")]
let source = WasmAssetIo::new(&self.asset_folder);
#[cfg(target_os = "android")]

View file

@ -60,7 +60,6 @@ impl Plugin for TemporalAntiAliasPlugin {
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return };
render_app
.init_resource::<TAAPipeline>()
.init_resource::<SpecializedRenderPipelines<TAAPipeline>>()
.add_systems(ExtractSchedule, extract_taa_settings)
.add_systems(
@ -84,6 +83,12 @@ impl Plugin for TemporalAntiAliasPlugin {
],
);
}
fn finish(&self, app: &mut App) {
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return };
render_app.init_resource::<TAAPipeline>();
}
}
/// Bundle to apply temporal anti-aliasing.

View file

@ -15,4 +15,4 @@ proc-macro = true
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.11.0-dev" }
quote = "1.0"
syn = { version = "1.0", features = ["full"] }
syn = { version = "2.0", features = ["full"] }

View file

@ -1,12 +1,16 @@
use proc_macro::{Span, TokenStream};
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Index, Member, Type};
use syn::{parse_macro_input, Data, DeriveInput, Field, Index, Member, Type};
const DEREF: &str = "Deref";
const DEREF_MUT: &str = "DerefMut";
const DEREF_ATTR: &str = "deref";
pub fn derive_deref(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let ident = &ast.ident;
let (field_member, field_type) = match get_inner_field(&ast, false) {
let (field_member, field_type) = match get_deref_field(&ast, false) {
Ok(items) => items,
Err(err) => {
return err.into_compile_error().into();
@ -29,7 +33,7 @@ pub fn derive_deref_mut(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let ident = &ast.ident;
let (field_member, _) = match get_inner_field(&ast, true) {
let (field_member, _) = match get_deref_field(&ast, true) {
Ok(items) => items,
Err(err) => {
return err.into_compile_error().into();
@ -46,24 +50,62 @@ pub fn derive_deref_mut(input: TokenStream) -> TokenStream {
})
}
fn get_inner_field(ast: &DeriveInput, is_mut: bool) -> syn::Result<(Member, &Type)> {
fn get_deref_field(ast: &DeriveInput, is_mut: bool) -> syn::Result<(Member, &Type)> {
let deref_kind = if is_mut { DEREF_MUT } else { DEREF };
let deref_attr_str = format!("`#[{DEREF_ATTR}]`");
match &ast.data {
Data::Struct(data_struct) if data_struct.fields.is_empty() => Err(syn::Error::new(
Span::call_site().into(),
format!("{deref_kind} cannot be derived on field-less structs"),
)),
Data::Struct(data_struct) if data_struct.fields.len() == 1 => {
let field = data_struct.fields.iter().next().unwrap();
let member = field
.ident
.as_ref()
.map(|name| Member::Named(name.clone()))
.unwrap_or_else(|| Member::Unnamed(Index::from(0)));
let member = to_member(field, 0);
Ok((member, &field.ty))
}
_ => {
let msg = if is_mut {
"DerefMut can only be derived for structs with a single field"
Data::Struct(data_struct) => {
let mut selected_field: Option<(Member, &Type)> = None;
for (index, field) in data_struct.fields.iter().enumerate() {
for attr in &field.attrs {
if !attr.meta.require_path_only()?.is_ident(DEREF_ATTR) {
continue;
}
if selected_field.is_some() {
return Err(syn::Error::new_spanned(
attr,
format!(
"{deref_attr_str} attribute can only be used on a single field"
),
));
}
let member = to_member(field, index);
selected_field = Some((member, &field.ty));
}
}
if let Some(selected_field) = selected_field {
Ok(selected_field)
} else {
"Deref can only be derived for structs with a single field"
};
Err(syn::Error::new(Span::call_site().into(), msg))
Err(syn::Error::new(
Span::call_site().into(),
format!("deriving {deref_kind} on multi-field structs requires one field to have the {deref_attr_str} attribute"),
))
}
}
_ => Err(syn::Error::new(
Span::call_site().into(),
format!("{deref_kind} can only be derived on structs"),
)),
}
}
fn to_member(field: &Field, index: usize) -> Member {
field
.ident
.as_ref()
.map(|name| Member::Named(name.clone()))
.unwrap_or_else(|| Member::Unnamed(Index::from(index)))
}

View file

@ -17,14 +17,20 @@ pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream {
app_plugin::derive_dynamic_plugin(input)
}
/// Implements [`Deref`] for _single-item_ structs. This is especially useful when
/// utilizing the [newtype] pattern.
/// Implements [`Deref`] for structs. This is especially useful when utilizing the [newtype] pattern.
///
/// For single-field structs, the implementation automatically uses that field.
/// For multi-field structs, you must specify which field to use with the `#[deref]` attribute.
///
/// If you need [`DerefMut`] as well, consider using the other [derive] macro alongside
/// this one.
///
/// # Example
///
/// ## Tuple Structs
///
/// Using a single-field struct:
///
/// ```
/// use bevy_derive::Deref;
///
@ -32,26 +38,83 @@ pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream {
/// struct MyNewtype(String);
///
/// let foo = MyNewtype(String::from("Hello"));
/// assert_eq!(5, foo.len());
/// assert_eq!("Hello", *foo);
/// ```
///
/// Using a multi-field struct:
///
/// ```
/// # use std::marker::PhantomData;
/// use bevy_derive::Deref;
///
/// #[derive(Deref)]
/// struct MyStruct<T>(#[deref] String, PhantomData<T>);
///
/// let foo = MyStruct(String::from("Hello"), PhantomData::<usize>);
/// assert_eq!("Hello", *foo);
/// ```
///
/// ## Named Structs
///
/// Using a single-field struct:
///
/// ```
/// use bevy_derive::{Deref, DerefMut};
///
/// #[derive(Deref, DerefMut)]
/// struct MyStruct {
/// value: String,
/// }
///
/// let foo = MyStruct {
/// value: String::from("Hello")
/// };
/// assert_eq!("Hello", *foo);
/// ```
///
/// Using a multi-field struct:
///
/// ```
/// # use std::marker::PhantomData;
/// use bevy_derive::{Deref, DerefMut};
///
/// #[derive(Deref, DerefMut)]
/// struct MyStruct<T> {
/// #[deref]
/// value: String,
/// _phantom: PhantomData<T>,
/// }
///
/// let foo = MyStruct {
/// value:String::from("Hello"),
/// _phantom:PhantomData::<usize>
/// };
/// assert_eq!("Hello", *foo);
/// ```
///
/// [`Deref`]: std::ops::Deref
/// [newtype]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
/// [`DerefMut`]: std::ops::DerefMut
/// [derive]: crate::derive_deref_mut
#[proc_macro_derive(Deref)]
#[proc_macro_derive(Deref, attributes(deref))]
pub fn derive_deref(input: TokenStream) -> TokenStream {
derefs::derive_deref(input)
}
/// Implements [`DerefMut`] for _single-item_ structs. This is especially useful when
/// utilizing the [newtype] pattern.
/// Implements [`DerefMut`] for structs. This is especially useful when utilizing the [newtype] pattern.
///
/// For single-field structs, the implementation automatically uses that field.
/// For multi-field structs, you must specify which field to use with the `#[deref]` attribute.
///
/// [`DerefMut`] requires a [`Deref`] implementation. You can implement it manually or use
/// Bevy's [derive] macro for convenience.
///
/// # Example
///
/// ## Tuple Structs
///
/// Using a single-field struct:
///
/// ```
/// use bevy_derive::{Deref, DerefMut};
///
@ -63,11 +126,65 @@ pub fn derive_deref(input: TokenStream) -> TokenStream {
/// assert_eq!("Hello World!", *foo);
/// ```
///
/// Using a multi-field struct:
///
/// ```
/// # use std::marker::PhantomData;
/// use bevy_derive::{Deref, DerefMut};
///
/// #[derive(Deref, DerefMut)]
/// struct MyStruct<T>(#[deref] String, PhantomData<T>);
///
/// let mut foo = MyStruct(String::from("Hello"), PhantomData::<usize>);
/// foo.push_str(" World!");
/// assert_eq!("Hello World!", *foo);
/// ```
///
/// ## Named Structs
///
/// Using a single-field struct:
///
/// ```
/// use bevy_derive::{Deref, DerefMut};
///
/// #[derive(Deref, DerefMut)]
/// struct MyStruct {
/// value: String,
/// }
///
/// let mut foo = MyStruct {
/// value: String::from("Hello")
/// };
/// foo.push_str(" World!");
/// assert_eq!("Hello World!", *foo);
/// ```
///
/// Using a multi-field struct:
///
/// ```
/// # use std::marker::PhantomData;
/// use bevy_derive::{Deref, DerefMut};
///
/// #[derive(Deref, DerefMut)]
/// struct MyStruct<T> {
/// #[deref]
/// value: String,
/// _phantom: PhantomData<T>,
/// }
///
/// let mut foo = MyStruct {
/// value:String::from("Hello"),
/// _phantom:PhantomData::<usize>
/// };
/// foo.push_str(" World!");
/// assert_eq!("Hello World!", *foo);
/// ```
///
/// [`DerefMut`]: std::ops::DerefMut
/// [newtype]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
/// [`Deref`]: std::ops::Deref
/// [derive]: crate::derive_deref
#[proc_macro_derive(DerefMut)]
#[proc_macro_derive(DerefMut, attributes(deref))]
pub fn derive_deref_mut(input: TokenStream) -> TokenStream {
derefs::derive_deref_mut(input)
}

View file

@ -11,6 +11,6 @@ proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.11.0-dev" }
syn = "1.0"
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0"

View file

@ -1,8 +1,7 @@
use bevy_macro_utils::{get_lit_str, Symbol};
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens};
use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result};
use quote::quote;
use syn::{parse_macro_input, parse_quote, DeriveInput, Ident, LitStr, Path, Result};
pub fn derive_resource(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
@ -48,8 +47,8 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
})
}
pub const COMPONENT: Symbol = Symbol("component");
pub const STORAGE: Symbol = Symbol("storage");
pub const COMPONENT: &str = "component";
pub const STORAGE: &str = "storage";
struct Attrs {
storage: StorageTy,
@ -66,48 +65,27 @@ const TABLE: &str = "Table";
const SPARSE_SET: &str = "SparseSet";
fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
let meta_items = bevy_macro_utils::parse_attrs(ast, COMPONENT)?;
let mut attrs = Attrs {
storage: StorageTy::Table,
};
for meta in meta_items {
use syn::{
Meta::NameValue,
NestedMeta::{Lit, Meta},
};
match meta {
Meta(NameValue(m)) if m.path == STORAGE => {
attrs.storage = match get_lit_str(STORAGE, &m.lit)?.value().as_str() {
TABLE => StorageTy::Table,
SPARSE_SET => StorageTy::SparseSet,
for meta in ast.attrs.iter().filter(|a| a.path().is_ident(COMPONENT)) {
meta.parse_nested_meta(|nested| {
if nested.path.is_ident(STORAGE) {
attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {
s if s == TABLE => StorageTy::Table,
s if s == SPARSE_SET => StorageTy::SparseSet,
s => {
return Err(Error::new_spanned(
m.lit,
format!(
"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",
),
))
return Err(nested.error(format!(
"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",
)));
}
};
Ok(())
} else {
Err(nested.error("Unsuported attribute"))
}
Meta(meta_item) => {
return Err(Error::new_spanned(
meta_item.path(),
format!(
"unknown component attribute `{}`",
meta_item.path().into_token_stream()
),
));
}
Lit(lit) => {
return Err(Error::new_spanned(
lit,
"unexpected literal in component attribute",
))
}
}
})?;
}
Ok(attrs)

View file

@ -6,7 +6,8 @@ use syn::{
parse::{Parse, ParseStream},
parse_macro_input, parse_quote,
punctuated::Punctuated,
Attribute, Data, DataStruct, DeriveInput, Field, Index,
token::Comma,
Attribute, Data, DataStruct, DeriveInput, Field, Index, Meta,
};
use crate::bevy_ecs_path;
@ -14,7 +15,7 @@ use crate::bevy_ecs_path;
#[derive(Default)]
struct FetchStructAttributes {
pub is_mutable: bool,
pub derive_args: Punctuated<syn::NestedMeta, syn::token::Comma>,
pub derive_args: Punctuated<syn::Meta, syn::token::Comma>,
}
static MUTABLE_ATTRIBUTE_NAME: &str = "mutable";
@ -35,7 +36,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
let mut fetch_struct_attributes = FetchStructAttributes::default();
for attr in &ast.attrs {
if !attr
.path
.path()
.get_ident()
.map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME)
{
@ -43,7 +44,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
}
attr.parse_args_with(|input: ParseStream| {
let meta = input.parse_terminated::<syn::Meta, syn::token::Comma>(syn::Meta::parse)?;
let meta = input.parse_terminated(syn::Meta::parse, Comma)?;
for meta in meta {
let ident = meta.path().get_ident().unwrap_or_else(|| {
panic!(
@ -61,9 +62,10 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
}
} else if ident == DERIVE_ATTRIBUTE_NAME {
if let syn::Meta::List(meta_list) = meta {
fetch_struct_attributes
.derive_args
.extend(meta_list.nested.iter().cloned());
meta_list.parse_nested_meta(|meta| {
fetch_struct_attributes.derive_args.push(Meta::Path(meta.path));
Ok(())
})?;
} else {
panic!(
"Expected a structured list within the `{DERIVE_ATTRIBUTE_NAME}` attribute",
@ -463,7 +465,7 @@ fn read_world_query_field_info(field: &Field) -> syn::Result<WorldQueryFieldInfo
let mut attrs = Vec::new();
for attr in &field.attrs {
if attr
.path
.path()
.get_ident()
.map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME)
{

View file

@ -13,8 +13,8 @@ use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote};
use syn::{
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, ConstParam,
DeriveInput, GenericParam, Ident, Index, Meta, MetaList, NestedMeta, Token, TypeParam,
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
ConstParam, DeriveInput, GenericParam, Ident, Index, TypeParam,
};
enum BundleFieldKind {
@ -37,28 +37,23 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let mut field_kind = Vec::with_capacity(named_fields.len());
'field_loop: for field in named_fields.iter() {
for attr in &field.attrs {
if attr.path.is_ident(BUNDLE_ATTRIBUTE_NAME) {
if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() {
if let Some(&NestedMeta::Meta(Meta::Path(ref path))) = nested.first() {
if path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
field_kind.push(BundleFieldKind::Ignore);
continue 'field_loop;
}
return syn::Error::new(
path.span(),
format!(
"Invalid bundle attribute. Use `{BUNDLE_ATTRIBUTE_IGNORE_NAME}`"
),
)
.into_compile_error()
.into();
}
return syn::Error::new(attr.span(), format!("Invalid bundle attribute. Use `#[{BUNDLE_ATTRIBUTE_NAME}({BUNDLE_ATTRIBUTE_IGNORE_NAME})]`")).into_compile_error().into();
for field in named_fields.iter() {
for attr in field
.attrs
.iter()
.filter(|a| a.path().is_ident(BUNDLE_ATTRIBUTE_NAME))
{
if let Err(error) = attr.parse_nested_meta(|meta| {
if meta.path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
field_kind.push(BundleFieldKind::Ignore);
Ok(())
} else {
Err(meta.error(format!(
"Invalid bundle attribute. Use `{BUNDLE_ATTRIBUTE_IGNORE_NAME}`"
)))
}
}) {
return error.into_compile_error().into();
}
}
@ -308,7 +303,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
let shadowed_lifetimes: Vec<_> = generics.lifetimes().map(|_| quote!('_)).collect();
let mut punctuated_generics = Punctuated::<_, Token![,]>::new();
let mut punctuated_generics = Punctuated::<_, Comma>::new();
punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g {
GenericParam::Type(g) => GenericParam::Type(TypeParam {
default: None,
@ -321,14 +316,14 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
_ => unreachable!(),
}));
let mut punctuated_generic_idents = Punctuated::<_, Token![,]>::new();
let mut punctuated_generic_idents = Punctuated::<_, Comma>::new();
punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g {
GenericParam::Type(g) => &g.ident,
GenericParam::Const(g) => &g.ident,
_ => unreachable!(),
}));
let punctuated_generics_no_bounds: Punctuated<_, Token![,]> = lifetimeless_generics
let punctuated_generics_no_bounds: Punctuated<_, Comma> = lifetimeless_generics
.iter()
.map(|&g| match g.clone() {
GenericParam::Type(mut g) => {

View file

@ -814,10 +814,6 @@ mod tests {
#[test]
fn change_tick_wraparound() {
fn change_detected(query: Query<Ref<C>>) -> bool {
query.single().is_changed()
}
let mut world = World::new();
world.last_change_tick = Tick::new(u32::MAX);
*world.change_tick.get_mut() = 0;
@ -825,13 +821,12 @@ mod tests {
// component added: 0, changed: 0
world.spawn(C);
// system last ran: u32::MAX
let mut change_detected_system = IntoSystem::into_system(change_detected);
change_detected_system.initialize(&mut world);
world.increment_change_tick();
// Since the world is always ahead, as long as changes can't get older than `u32::MAX` (which we ensure),
// the wrapping difference will always be positive, so wraparound doesn't matter.
assert!(change_detected_system.run((), &mut world));
let mut query = world.query::<Ref<C>>();
assert!(query.single(&world).is_changed());
}
#[test]

View file

@ -380,9 +380,9 @@ impl SystemNode {
#[derive(Default)]
pub struct ScheduleGraph {
systems: Vec<SystemNode>,
system_conditions: Vec<Option<Vec<BoxedCondition>>>,
system_conditions: Vec<Vec<BoxedCondition>>,
system_sets: Vec<SystemSetNode>,
system_set_conditions: Vec<Option<Vec<BoxedCondition>>>,
system_set_conditions: Vec<Vec<BoxedCondition>>,
system_set_ids: HashMap<BoxedSystemSet, NodeId>,
uninit: Vec<(NodeId, usize)>,
hierarchy: Dag,
@ -465,20 +465,17 @@ impl ScheduleGraph {
.enumerate()
.filter_map(|(i, (system_node, condition))| {
let system = system_node.inner.as_deref()?;
let condition = condition.as_ref()?.as_slice();
Some((NodeId::System(i), system, condition))
Some((NodeId::System(i), system, condition.as_slice()))
})
}
/// Returns an iterator over all system sets in this schedule.
pub fn system_sets(&self) -> impl Iterator<Item = (NodeId, &dyn SystemSet, &[BoxedCondition])> {
self.system_set_ids.iter().map(|(_, node_id)| {
self.system_set_ids.iter().map(|(_, &node_id)| {
let set_node = &self.system_sets[node_id.index()];
let set = &*set_node.inner;
let conditions = self.system_set_conditions[node_id.index()]
.as_deref()
.unwrap_or(&[]);
(*node_id, set, conditions)
let conditions = self.system_set_conditions[node_id.index()].as_slice();
(node_id, set, conditions)
})
}
@ -662,7 +659,7 @@ impl ScheduleGraph {
// system init has to be deferred (need `&mut World`)
self.uninit.push((id, 0));
self.systems.push(SystemNode::new(config.system));
self.system_conditions.push(Some(config.conditions));
self.system_conditions.push(config.conditions);
Ok(id)
}
@ -708,8 +705,7 @@ impl ScheduleGraph {
self.update_graphs(id, graph_info)?;
// system init has to be deferred (need `&mut World`)
let system_set_conditions =
self.system_set_conditions[id.index()].get_or_insert_with(Vec::new);
let system_set_conditions = &mut self.system_set_conditions[id.index()];
self.uninit.push((id, system_set_conditions.len()));
system_set_conditions.append(&mut conditions);
@ -719,7 +715,7 @@ impl ScheduleGraph {
fn add_set(&mut self, set: BoxedSystemSet) -> NodeId {
let id = NodeId::Set(self.system_sets.len());
self.system_sets.push(SystemSetNode::new(set.dyn_clone()));
self.system_set_conditions.push(None);
self.system_set_conditions.push(Vec::new());
self.system_set_ids.insert(set, id);
id
}
@ -852,17 +848,13 @@ impl ScheduleGraph {
match id {
NodeId::System(index) => {
self.systems[index].get_mut().unwrap().initialize(world);
if let Some(v) = self.system_conditions[index].as_mut() {
for condition in v.iter_mut() {
condition.initialize(world);
}
for condition in &mut self.system_conditions[index] {
condition.initialize(world);
}
}
NodeId::Set(index) => {
if let Some(v) = self.system_set_conditions[index].as_mut() {
for condition in v.iter_mut().skip(i) {
condition.initialize(world);
}
for condition in self.system_set_conditions[index].iter_mut().skip(i) {
condition.initialize(world);
}
}
}
@ -1123,11 +1115,7 @@ impl ScheduleGraph {
.filter(|&(_i, id)| {
// ignore system sets that have no conditions
// ignore system type sets (already covered, they don't have conditions)
id.is_set()
&& self.system_set_conditions[id.index()]
.as_ref()
.filter(|v| !v.is_empty())
.is_some()
id.is_set() && !self.system_set_conditions[id.index()].is_empty()
})
.unzip();
@ -1215,7 +1203,7 @@ impl ScheduleGraph {
.zip(schedule.system_conditions.drain(..))
{
self.systems[id.index()].inner = Some(system);
self.system_conditions[id.index()] = Some(conditions);
self.system_conditions[id.index()] = conditions;
}
for (id, conditions) in schedule
@ -1223,7 +1211,7 @@ impl ScheduleGraph {
.drain(..)
.zip(schedule.set_conditions.drain(..))
{
self.system_set_conditions[id.index()] = Some(conditions);
self.system_set_conditions[id.index()] = conditions;
}
*schedule = self.build_schedule(components)?;
@ -1231,13 +1219,13 @@ impl ScheduleGraph {
// move systems into new schedule
for &id in &schedule.system_ids {
let system = self.systems[id.index()].inner.take().unwrap();
let conditions = self.system_conditions[id.index()].take().unwrap();
let conditions = std::mem::take(&mut self.system_conditions[id.index()]);
schedule.systems.push(system);
schedule.system_conditions.push(conditions);
}
for &id in &schedule.set_ids {
let conditions = self.system_set_conditions[id.index()].take().unwrap();
let conditions = std::mem::take(&mut self.system_set_conditions[id.index()]);
schedule.set_conditions.push(conditions);
}

View file

@ -14,7 +14,7 @@ use crate::{
system::Resource,
};
use bevy_ptr::Ptr;
use std::{any::TypeId, cell::UnsafeCell, marker::PhantomData, sync::atomic::Ordering};
use std::{any::TypeId, cell::UnsafeCell, marker::PhantomData};
/// Variant of the [`World`] where resource and component accesses take `&self`, and the responsibility to avoid
/// aliasing violations are given to the caller instead of being checked at compile-time by rust's unique XOR shared rule.
@ -34,7 +34,6 @@ use std::{any::TypeId, cell::UnsafeCell, marker::PhantomData, sync::atomic::Orde
/// 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.
/// access resource values.
///
/// ### Example Usage
///
@ -92,15 +91,49 @@ impl<'w> UnsafeWorldCell<'w> {
Self(world as *mut World, PhantomData)
}
/// This `&mut World` counts as a mutable borrow of every resource and component for the purposes
/// of safety invariants that talk about borrows on component/resource data not existing. This is
/// true even for methods that do not explicitly call out `&mut World` as problematic.
/// Gets a mutable reference to the [`World`] this [`UnsafeWorldCell`] belongs to.
/// This is an incredibly error-prone operation and is only valid in a small number of circumstances.
///
/// # Safety
/// - must have permission to access everything mutably
/// - there must be no other borrows on world
/// - returned borrow must not be used in any way if the world is accessed
/// through other means than the `&mut World` after this call.
/// - `self` must have been obtained from a call to [`World::as_unsafe_world_cell`]
/// (*not* `as_unsafe_world_cell_readonly` or any other method of construction that
/// does not provide mutable access to the entire world).
/// - This means that if you have an `UnsafeWorldCell` that you didn't create yourself,
/// it is likely *unsound* to call this method.
/// - The returned `&mut World` *must* be unique: it must never be allowed to exist
/// at the same time as any other borrows of the world or any accesses to its data.
/// This includes safe ways of accessing world data, such as [`UnsafeWorldCell::archetypes`]:
/// - Note that the `&mut World` *may* exist at the same time as instances of `UnsafeWorldCell`,
/// so long as none of those instances are used to access world data in any way
/// while the mutable borrow is active.
///
/// [//]: # (This test fails miri.)
/// ```no_run
/// # use bevy_ecs::prelude::*;
/// # #[derive(Component)] struct Player;
/// # fn store_but_dont_use<T>(_: T) {}
/// # let mut world = World::new();
/// // Make an UnsafeWorldCell.
/// let world_cell = world.as_unsafe_world_cell();
///
/// // SAFETY: `world_cell` was originally created from `&mut World`.
/// // We must be sure not to access any world data while `world_mut` is active.
/// let world_mut = unsafe { world_cell.world_mut() };
///
/// // We can still use `world_cell` so long as we don't access the world with it.
/// store_but_dont_use(world_cell);
///
/// // !!This is unsound!! Even though this method is safe, we cannot call it until
/// // `world_mut` is no longer active.
/// let tick = world_cell.read_change_tick();
///
/// // Use mutable access to spawn an entity.
/// world_mut.spawn(Player);
///
/// // Since we never use `world_mut` after this, the borrow is released
/// // and we are once again allowed to access the world using `world_cell`.
/// let archetypes = world_cell.archetypes();
/// ```
#[inline]
pub unsafe fn world_mut(self) -> &'w mut World {
// SAFETY:
@ -191,13 +224,17 @@ impl<'w> UnsafeWorldCell<'w> {
/// Reads the current change tick of this world.
#[inline]
#[deprecated = "this method has been renamed to `UnsafeWorldCell::change_tick`"]
pub fn read_change_tick(self) -> Tick {
self.change_tick()
}
/// Gets the current change tick of this world.
#[inline]
pub fn change_tick(self) -> Tick {
// SAFETY:
// - we only access world metadata
let tick = unsafe { self.world_metadata() }
.change_tick
.load(Ordering::Acquire);
Tick::new(tick)
unsafe { self.world_metadata() }.read_change_tick()
}
#[inline]
@ -382,7 +419,7 @@ impl<'w> UnsafeWorldCell<'w> {
// - index is in-bounds because the column is initialized and non-empty
// - the caller promises that no other reference to the ticks of the same row can exist at the same time
let ticks = unsafe {
TicksMut::from_tick_cells(ticks, self.last_change_tick(), self.read_change_tick())
TicksMut::from_tick_cells(ticks, self.last_change_tick(), self.change_tick())
};
Some(MutUntyped {
@ -430,7 +467,7 @@ impl<'w> UnsafeWorldCell<'w> {
self,
component_id: ComponentId,
) -> Option<MutUntyped<'w>> {
let change_tick = self.read_change_tick();
let change_tick = self.change_tick();
// SAFETY: we only access data that the caller has ensured is unaliased and `self`
// has permission to access.
let (ptr, ticks) = unsafe { self.unsafe_world() }
@ -650,9 +687,7 @@ impl<'w> UnsafeEntityCell<'w> {
#[inline]
pub unsafe fn get_mut<T: Component>(self) -> Option<Mut<'w, T>> {
// SAFETY: same safety requirements
unsafe {
self.get_mut_using_ticks(self.world.last_change_tick(), self.world.read_change_tick())
}
unsafe { self.get_mut_using_ticks(self.world.last_change_tick(), self.world.change_tick()) }
}
/// # Safety
@ -744,7 +779,7 @@ impl<'w> UnsafeEntityCell<'w> {
ticks: TicksMut::from_tick_cells(
cells,
self.world.last_change_tick(),
self.world.read_change_tick(),
self.world.change_tick(),
),
})
}

View file

@ -13,4 +13,4 @@ proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.11.0-dev" }
encase_derive_impl = "0.5.0"
encase_derive_impl = "0.6.1"

View file

@ -36,6 +36,7 @@ bmp = ["bevy_render/bmp"]
webp = ["bevy_render/webp"]
basis-universal = ["bevy_render/basis-universal"]
dds = ["bevy_render/dds"]
pnm = ["bevy_render/pnm"]
ktx2 = ["bevy_render/ktx2"]
# For ktx2 supercompression
zlib = ["bevy_render/zlib"]

View file

@ -10,6 +10,6 @@ keywords = ["bevy"]
[dependencies]
toml_edit = "0.19"
syn = "1.0"
syn = "2.0"
quote = "1.0"
rustc-hash = "1.0"

View file

@ -1,41 +1,32 @@
use syn::DeriveInput;
use syn::{Expr, ExprLit, Lit};
use crate::symbol::Symbol;
pub fn parse_attrs(ast: &DeriveInput, attr_name: Symbol) -> syn::Result<Vec<syn::NestedMeta>> {
let mut list = Vec::new();
for attr in ast.attrs.iter().filter(|a| a.path == attr_name) {
match attr.parse_meta()? {
syn::Meta::List(meta) => list.extend(meta.nested.into_iter()),
other => {
return Err(syn::Error::new_spanned(
other,
format!("expected #[{attr_name}(...)]"),
))
}
}
}
Ok(list)
}
pub fn get_lit_str(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<&syn::LitStr> {
if let syn::Lit::Str(lit) = lit {
pub fn get_lit_str(attr_name: Symbol, value: &Expr) -> syn::Result<&syn::LitStr> {
if let Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) = &value
{
Ok(lit)
} else {
Err(syn::Error::new_spanned(
lit,
value,
format!("expected {attr_name} attribute to be a string: `{attr_name} = \"...\"`"),
))
}
}
pub fn get_lit_bool(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<bool> {
if let syn::Lit::Bool(lit) = lit {
pub fn get_lit_bool(attr_name: Symbol, value: &Expr) -> syn::Result<bool> {
if let Expr::Lit(ExprLit {
lit: Lit::Bool(lit),
..
}) = &value
{
Ok(lit.value())
} else {
Err(syn::Error::new_spanned(
lit,
value,
format!("expected {attr_name} attribute to be a bool value, `true` or `false`: `{attr_name} = ...`"),
))
))?
}
}

View file

@ -214,7 +214,7 @@ pub fn derive_label(
) -> TokenStream {
// return true if the variant specified is an `ignore_fields` attribute
fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool {
if attr.path.get_ident().as_ref().unwrap() != &attr_name {
if attr.path().get_ident().as_ref().unwrap() != &attr_name {
return false;
}

View file

@ -0,0 +1,13 @@
[package]
name = "bevy_macros_compile_fail_tests"
version = "0.1.0"
edition = "2021"
description = "Compile fail tests for Bevy Engine's various macros"
homepage = "https://bevyengine.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
publish = false
[dependencies]
bevy_derive = { path = "../bevy_derive" }
trybuild = "1.0.71"

View file

@ -0,0 +1,6 @@
# Compile fail tests for Bevy macros
This crate is not part of the Bevy workspace in order to not fail `crater` tests for Bevy.
The tests assert on the exact compiler errors and can easily fail for new Rust versions due to updated compiler errors (e.g. changes in spans).
The `CI` workflow executes these tests on the stable rust toolchain (see [tools/ci](../../tools/ci/src/main.rs)).

View file

@ -0,0 +1 @@
// Nothing here, check out the integration tests

View file

@ -0,0 +1,6 @@
#[test]
fn test() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/deref_derive/*.fail.rs");
t.pass("tests/deref_derive/*.pass.rs");
}

View file

@ -0,0 +1,15 @@
use bevy_derive::Deref;
// Reason: `#[deref]` doesn't take any arguments
#[derive(Deref)]
struct TupleStruct(usize, #[deref()] String);
#[derive(Deref)]
struct Struct {
foo: usize,
#[deref()]
bar: String,
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: unexpected token in attribute
--> tests/deref_derive/invalid_attribute.fail.rs:6:34
|
6 | struct TupleStruct(usize, #[deref()] String);
| ^
error: unexpected token in attribute
--> tests/deref_derive/invalid_attribute.fail.rs:11:12
|
11 | #[deref()]
| ^

View file

@ -0,0 +1,9 @@
use bevy_derive::Deref;
#[derive(Deref)]
struct UnitStruct;
#[derive(Deref)]
enum Enum {}
fn main() {}

View file

@ -0,0 +1,15 @@
error: Deref cannot be derived on field-less structs
--> tests/deref_derive/invalid_item.fail.rs:3:10
|
3 | #[derive(Deref)]
| ^^^^^
|
= note: this error originates in the derive macro `Deref` (in Nightly builds, run with -Z macro-backtrace for more info)
error: Deref can only be derived on structs
--> tests/deref_derive/invalid_item.fail.rs:6:10
|
6 | #[derive(Deref)]
| ^^^^^
|
= note: this error originates in the derive macro `Deref` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,12 @@
use bevy_derive::Deref;
#[derive(Deref)]
struct TupleStruct(usize, String);
#[derive(Deref)]
struct Struct {
foo: usize,
bar: String,
}
fn main() {}

View file

@ -0,0 +1,15 @@
error: deriving Deref on multi-field structs requires one field to have the `#[deref]` attribute
--> tests/deref_derive/missing_attribute.fail.rs:3:10
|
3 | #[derive(Deref)]
| ^^^^^
|
= note: this error originates in the derive macro `Deref` (in Nightly builds, run with -Z macro-backtrace for more info)
error: deriving Deref on multi-field structs requires one field to have the `#[deref]` attribute
--> tests/deref_derive/missing_attribute.fail.rs:6:10
|
6 | #[derive(Deref)]
| ^^^^^
|
= note: this error originates in the derive macro `Deref` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,14 @@
use bevy_derive::Deref;
#[derive(Deref)]
struct TupleStruct(#[deref] usize, #[deref] String);
#[derive(Deref)]
struct Struct {
#[deref]
foo: usize,
#[deref]
bar: String,
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: `#[deref]` attribute can only be used on a single field
--> tests/deref_derive/multiple_attributes.fail.rs:4:36
|
4 | struct TupleStruct(#[deref] usize, #[deref] String);
| ^^^^^^^^
error: `#[deref]` attribute can only be used on a single field
--> tests/deref_derive/multiple_attributes.fail.rs:10:5
|
10 | #[deref]
| ^^^^^^^^

View file

@ -0,0 +1,22 @@
use bevy_derive::Deref;
#[derive(Deref)]
struct TupleStruct(usize, #[deref] String);
#[derive(Deref)]
struct Struct {
foo: usize,
#[deref]
bar: String,
}
fn main() {
let value = TupleStruct(123, "Hello world!".to_string());
let _: &String = &*value;
let value = Struct {
foo: 123,
bar: "Hello world!".to_string(),
};
let _: &String = &*value;
}

View file

@ -0,0 +1,19 @@
use bevy_derive::Deref;
#[derive(Deref)]
struct TupleStruct(String);
#[derive(Deref)]
struct Struct {
bar: String,
}
fn main() {
let value = TupleStruct("Hello world!".to_string());
let _: &String = &*value;
let value = Struct {
bar: "Hello world!".to_string(),
};
let _: &String = &*value;
}

View file

@ -0,0 +1,6 @@
#[test]
fn test() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/deref_mut_derive/*.fail.rs");
t.pass("tests/deref_mut_derive/*.pass.rs");
}

View file

@ -0,0 +1,32 @@
use bevy_derive::DerefMut;
use std::ops::Deref;
// Reason: `#[deref]` doesn't take any arguments
#[derive(DerefMut)]
struct TupleStruct(usize, #[deref()] String);
impl Deref for TupleStruct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.1
}
}
#[derive(DerefMut)]
struct Struct {
foo: usize,
#[deref()]
bar: String,
}
impl Deref for Struct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.bar
}
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: unexpected token in attribute
--> tests/deref_mut_derive/invalid_attribute.fail.rs:7:34
|
7 | struct TupleStruct(usize, #[deref()] String);
| ^
error: unexpected token in attribute
--> tests/deref_mut_derive/invalid_attribute.fail.rs:20:12
|
20 | #[deref()]
| ^

View file

@ -0,0 +1,9 @@
use bevy_derive::DerefMut;
#[derive(DerefMut)]
struct UnitStruct;
#[derive(DerefMut)]
enum Enum {}
fn main() {}

View file

@ -0,0 +1,15 @@
error: DerefMut cannot be derived on field-less structs
--> tests/deref_mut_derive/invalid_item.fail.rs:3:10
|
3 | #[derive(DerefMut)]
| ^^^^^^^^
|
= note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
error: DerefMut can only be derived on structs
--> tests/deref_mut_derive/invalid_item.fail.rs:6:10
|
6 | #[derive(DerefMut)]
| ^^^^^^^^
|
= note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,30 @@
use bevy_derive::DerefMut;
use std::ops::Deref;
#[derive(DerefMut)]
struct TupleStruct(#[deref] usize, String);
impl Deref for TupleStruct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.1
}
}
#[derive(DerefMut)]
struct Struct {
#[deref]
foo: usize,
bar: String,
}
impl Deref for Struct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.bar
}
}
fn main() {}

View file

@ -0,0 +1,25 @@
error[E0308]: mismatched types
--> tests/deref_mut_derive/mismatched_target_type.fail.rs:4:10
|
4 | #[derive(DerefMut)]
| ^^^^^^^^
| |
| expected `&mut String`, found `&mut usize`
| expected `&mut String` because of return type
|
= note: expected mutable reference `&mut String`
found mutable reference `&mut usize`
= note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/deref_mut_derive/mismatched_target_type.fail.rs:15:10
|
15 | #[derive(DerefMut)]
| ^^^^^^^^
| |
| expected `&mut String`, found `&mut usize`
| expected `&mut String` because of return type
|
= note: expected mutable reference `&mut String`
found mutable reference `&mut usize`
= note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,29 @@
use bevy_derive::DerefMut;
use std::ops::Deref;
#[derive(DerefMut)]
struct TupleStruct(usize, String);
impl Deref for TupleStruct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.1
}
}
#[derive(DerefMut)]
struct Struct {
foo: usize,
bar: String,
}
impl Deref for Struct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.bar
}
}
fn main() {}

View file

@ -0,0 +1,15 @@
error: deriving DerefMut on multi-field structs requires one field to have the `#[deref]` attribute
--> tests/deref_mut_derive/missing_attribute.fail.rs:4:10
|
4 | #[derive(DerefMut)]
| ^^^^^^^^
|
= note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
error: deriving DerefMut on multi-field structs requires one field to have the `#[deref]` attribute
--> tests/deref_mut_derive/missing_attribute.fail.rs:15:10
|
15 | #[derive(DerefMut)]
| ^^^^^^^^
|
= note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,13 @@
use bevy_derive::DerefMut;
#[derive(DerefMut)]
struct TupleStruct(usize, #[deref] String);
#[derive(DerefMut)]
struct Struct {
foo: usize,
#[deref]
bar: String,
}
fn main() {}

View file

@ -0,0 +1,33 @@
error[E0277]: the trait bound `TupleStruct: Deref` is not satisfied
--> tests/deref_mut_derive/missing_deref.fail.rs:4:8
|
4 | struct TupleStruct(usize, #[deref] String);
| ^^^^^^^^^^^ the trait `Deref` is not implemented for `TupleStruct`
|
note: required by a bound in `DerefMut`
--> $RUST/core/src/ops/deref.rs
error[E0277]: the trait bound `Struct: Deref` is not satisfied
--> tests/deref_mut_derive/missing_deref.fail.rs:7:8
|
7 | struct Struct {
| ^^^^^^ the trait `Deref` is not implemented for `Struct`
|
note: required by a bound in `DerefMut`
--> $RUST/core/src/ops/deref.rs
error[E0277]: the trait bound `TupleStruct: Deref` is not satisfied
--> tests/deref_mut_derive/missing_deref.fail.rs:3:10
|
3 | #[derive(DerefMut)]
| ^^^^^^^^ the trait `Deref` is not implemented for `TupleStruct`
|
= note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `Struct: Deref` is not satisfied
--> tests/deref_mut_derive/missing_deref.fail.rs:6:10
|
6 | #[derive(DerefMut)]
| ^^^^^^^^ the trait `Deref` is not implemented for `Struct`
|
= note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,31 @@
use bevy_derive::DerefMut;
use std::ops::Deref;
#[derive(DerefMut)]
struct TupleStruct(#[deref] usize, #[deref] String);
impl Deref for TupleStruct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.1
}
}
#[derive(DerefMut)]
struct Struct {
#[deref]
foo: usize,
#[deref]
bar: String,
}
impl Deref for Struct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.bar
}
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: `#[deref]` attribute can only be used on a single field
--> tests/deref_mut_derive/multiple_attributes.fail.rs:5:36
|
5 | struct TupleStruct(#[deref] usize, #[deref] String);
| ^^^^^^^^
error: `#[deref]` attribute can only be used on a single field
--> tests/deref_mut_derive/multiple_attributes.fail.rs:19:5
|
19 | #[deref]
| ^^^^^^^^

View file

@ -0,0 +1,39 @@
use bevy_derive::DerefMut;
use std::ops::Deref;
#[derive(DerefMut)]
struct TupleStruct(usize, #[deref] String);
impl Deref for TupleStruct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.1
}
}
#[derive(DerefMut)]
struct Struct {
foo: usize,
#[deref]
bar: String,
}
impl Deref for Struct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.bar
}
}
fn main() {
let mut value = TupleStruct(123, "Hello world!".to_string());
let _: &mut String = &mut *value;
let mut value = Struct {
foo: 123,
bar: "Hello world!".to_string(),
};
let _: &mut String = &mut *value;
}

View file

@ -0,0 +1,37 @@
use bevy_derive::DerefMut;
use std::ops::Deref;
#[derive(DerefMut)]
struct TupleStruct(#[deref] String);
impl Deref for TupleStruct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(DerefMut)]
struct Struct {
#[deref]
bar: String,
}
impl Deref for Struct {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.bar
}
}
fn main() {
let mut value = TupleStruct("Hello world!".to_string());
let _: &mut String = &mut *value;
let mut value = Struct {
bar: "Hello world!".to_string(),
};
let _: &mut String = &mut *value;
}

View file

@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"]
[dependencies]
glam = { version = "0.23", features = ["bytemuck"] }
glam = { version = "0.24", features = ["bytemuck"] }
serde = { version = "1", features = ["derive"], optional = true }
[features]

View file

@ -11,7 +11,7 @@ license = "Zlib AND (MIT OR Apache-2.0)"
keywords = ["bevy", "3D", "graphics", "algorithm", "tangent"]
[dependencies]
glam = "0.23"
glam = "0.24"
[[example]]
name = "generate"

View file

@ -31,7 +31,7 @@ thiserror = "1.0"
once_cell = "1.11"
serde = "1"
smallvec = { version = "1.6", features = ["serde", "union", "const_generics"], optional = true }
glam = { version = "0.23", features = ["serde"], optional = true }
glam = { version = "0.24", features = ["serde"], optional = true }
[dev-dependencies]
ron = "0.8.0"

View file

@ -19,7 +19,7 @@ documentation = []
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.11.0-dev" }
syn = { version = "1.0", features = ["full"] }
syn = { version = "2.0", features = ["full"] }
proc-macro2 = "1.0"
quote = "1.0"
uuid = { version = "1.1", features = ["v4"] }

View file

@ -13,7 +13,7 @@ use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{Meta, NestedMeta, Path};
use syn::{Meta, Path};
// The "special" trait idents that are used internally for reflection.
// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`
@ -45,12 +45,16 @@ pub(crate) enum TraitImpl {
impl TraitImpl {
/// Merges this [`TraitImpl`] with another.
///
/// Returns whichever value is not [`TraitImpl::NotImplemented`].
/// If both values are [`TraitImpl::NotImplemented`], then that is returned.
/// Otherwise, an error is returned if neither value is [`TraitImpl::NotImplemented`].
pub fn merge(self, other: TraitImpl) -> Result<TraitImpl, syn::Error> {
match (self, other) {
(TraitImpl::NotImplemented, value) | (value, TraitImpl::NotImplemented) => Ok(value),
/// Update `self` with whichever value is not [`TraitImpl::NotImplemented`].
/// If `other` is [`TraitImpl::NotImplemented`], then `self` is not modified.
/// An error is returned if neither value is [`TraitImpl::NotImplemented`].
pub fn merge(&mut self, other: TraitImpl) -> Result<(), syn::Error> {
match (&self, other) {
(TraitImpl::NotImplemented, value) => {
*self = value;
Ok(())
}
(_, TraitImpl::NotImplemented) => Ok(()),
(_, TraitImpl::Implemented(span) | TraitImpl::Custom(_, span)) => {
Err(syn::Error::new(span, CONFLICTING_TYPE_DATA_MESSAGE))
}
@ -128,15 +132,12 @@ pub(crate) struct ReflectTraits {
}
impl ReflectTraits {
/// Create a new [`ReflectTraits`] instance from a set of nested metas.
pub fn from_nested_metas(
nested_metas: &Punctuated<NestedMeta, Comma>,
) -> Result<Self, syn::Error> {
pub fn from_metas(metas: Punctuated<Meta, Comma>) -> Result<Self, syn::Error> {
let mut traits = ReflectTraits::default();
for nested_meta in nested_metas.iter() {
match nested_meta {
for meta in &metas {
match meta {
// Handles `#[reflect( Hash, Default, ... )]`
NestedMeta::Meta(Meta::Path(path)) => {
Meta::Path(path) => {
// Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`)
let Some(segment) = path.segments.iter().next() else {
continue;
@ -149,14 +150,13 @@ impl ReflectTraits {
match ident_name.as_str() {
DEBUG_ATTR => {
traits.debug = traits.debug.merge(TraitImpl::Implemented(span))?;
traits.debug.merge(TraitImpl::Implemented(span))?;
}
PARTIAL_EQ_ATTR => {
traits.partial_eq =
traits.partial_eq.merge(TraitImpl::Implemented(span))?;
traits.partial_eq.merge(TraitImpl::Implemented(span))?;
}
HASH_ATTR => {
traits.hash = traits.hash.merge(TraitImpl::Implemented(span))?;
traits.hash.merge(TraitImpl::Implemented(span))?;
}
// We only track reflected idents for traits not considered special
_ => {
@ -170,7 +170,7 @@ impl ReflectTraits {
}
}
// Handles `#[reflect( Hash(custom_hash_fn) )]`
NestedMeta::Meta(Meta::List(list)) => {
Meta::List(list) => {
// Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`)
let Some(segment) = list.path.segments.iter().next() else {
continue;
@ -181,23 +181,25 @@ impl ReflectTraits {
// Track the span where the trait is implemented for future errors
let span = ident.span();
let list_meta = list.nested.iter().next();
if let Some(NestedMeta::Meta(Meta::Path(path))) = list_meta {
list.parse_nested_meta(|meta| {
// This should be the path of the custom function
let trait_func_ident = TraitImpl::Custom(path.clone(), span);
let trait_func_ident = TraitImpl::Custom(meta.path, span);
match ident.as_str() {
DEBUG_ATTR => {
traits.debug = traits.debug.merge(trait_func_ident)?;
traits.debug.merge(trait_func_ident)?;
}
PARTIAL_EQ_ATTR => {
traits.partial_eq = traits.partial_eq.merge(trait_func_ident)?;
traits.partial_eq.merge(trait_func_ident)?;
}
HASH_ATTR => {
traits.hash = traits.hash.merge(trait_func_ident)?;
traits.hash.merge(trait_func_ident)?;
}
_ => {
return Err(syn::Error::new(span, "Can only use custom functions for special traits (i.e. `Hash`, `PartialEq`, `Debug`)"));
}
_ => {}
}
}
Ok(())
})?;
}
_ => {}
}
@ -289,26 +291,20 @@ impl ReflectTraits {
/// Merges the trait implementations of this [`ReflectTraits`] with another one.
///
/// An error is returned if the two [`ReflectTraits`] have conflicting implementations.
pub fn merge(self, other: ReflectTraits) -> Result<Self, syn::Error> {
Ok(ReflectTraits {
debug: self.debug.merge(other.debug)?,
hash: self.hash.merge(other.hash)?,
partial_eq: self.partial_eq.merge(other.partial_eq)?,
idents: {
let mut idents = self.idents;
for ident in other.idents {
add_unique_ident(&mut idents, ident)?;
}
idents
},
})
pub fn merge(&mut self, other: ReflectTraits) -> Result<(), syn::Error> {
self.debug.merge(other.debug)?;
self.hash.merge(other.hash)?;
self.partial_eq.merge(other.partial_eq)?;
for ident in other.idents {
add_unique_ident(&mut self.idents, ident)?;
}
Ok(())
}
}
impl Parse for ReflectTraits {
fn parse(input: ParseStream) -> syn::Result<Self> {
let result = Punctuated::<NestedMeta, Comma>::parse_terminated(input)?;
ReflectTraits::from_nested_metas(&result)
ReflectTraits::from_metas(Punctuated::<Meta, Comma>::parse_terminated(input)?)
}
}

View file

@ -4,11 +4,12 @@ use crate::fq_std::{FQAny, FQDefault, FQSend, FQSync};
use crate::utility::{members_to_serialization_denylist, WhereClauseOptions};
use bit_set::BitSet;
use quote::quote;
use syn::token::Comma;
use crate::{utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Field, Fields, Generics, Ident, Meta, Path, Token, Variant};
use syn::{Data, DeriveInput, Field, Fields, Generics, Ident, Meta, Path, Variant};
pub(crate) enum ReflectDerive<'a> {
Struct(ReflectStruct<'a>),
@ -136,8 +137,8 @@ impl<'a> ReflectDerive<'a> {
#[cfg(feature = "documentation")]
let mut doc = crate::documentation::Documentation::default();
for attribute in input.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
match attribute {
for attribute in &input.attrs {
match &attribute.meta {
Meta::List(meta_list) if meta_list.path.is_ident(REFLECT_ATTRIBUTE_NAME) => {
if !matches!(reflect_mode, None | Some(ReflectMode::Normal)) {
return Err(syn::Error::new(
@ -147,8 +148,10 @@ impl<'a> ReflectDerive<'a> {
}
reflect_mode = Some(ReflectMode::Normal);
let new_traits = ReflectTraits::from_nested_metas(&meta_list.nested)?;
traits = traits.merge(new_traits)?;
let new_traits = ReflectTraits::from_metas(
meta_list.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?,
)?;
traits.merge(new_traits)?;
}
Meta::List(meta_list) if meta_list.path.is_ident(REFLECT_VALUE_ATTRIBUTE_NAME) => {
if !matches!(reflect_mode, None | Some(ReflectMode::Value)) {
@ -159,8 +162,10 @@ impl<'a> ReflectDerive<'a> {
}
reflect_mode = Some(ReflectMode::Value);
let new_traits = ReflectTraits::from_nested_metas(&meta_list.nested)?;
traits = traits.merge(new_traits)?;
let new_traits = ReflectTraits::from_metas(
meta_list.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?,
)?;
traits.merge(new_traits)?;
}
Meta::Path(path) if path.is_ident(REFLECT_VALUE_ATTRIBUTE_NAME) => {
if !matches!(reflect_mode, None | Some(ReflectMode::Value)) {
@ -174,7 +179,11 @@ impl<'a> ReflectDerive<'a> {
}
#[cfg(feature = "documentation")]
Meta::NameValue(pair) if pair.path.is_ident("doc") => {
if let syn::Lit::Str(lit) = pair.lit {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit),
..
}) = &pair.value
{
doc.push(lit.value());
}
}
@ -247,7 +256,7 @@ impl<'a> ReflectDerive<'a> {
}
fn collect_enum_variants(
variants: &'a Punctuated<Variant, Token![,]>,
variants: &'a Punctuated<Variant, Comma>,
) -> Result<Vec<EnumVariant<'a>>, syn::Error> {
let sifter: utility::ResultSifter<EnumVariant<'a>> = variants
.iter()

View file

@ -3,7 +3,7 @@
use crate::fq_std::FQOption;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{Attribute, Lit, Meta};
use syn::{Attribute, Expr, ExprLit, Lit, Meta};
/// A struct used to represent a type's documentation, if any.
///
@ -21,18 +21,18 @@ impl Documentation {
pub fn from_attributes<'a>(attributes: impl IntoIterator<Item = &'a Attribute>) -> Self {
let docs = attributes
.into_iter()
.filter_map(|attr| {
let meta = attr.parse_meta().ok()?;
match meta {
Meta::NameValue(pair) if pair.path.is_ident("doc") => {
if let Lit::Str(lit) = pair.lit {
Some(lit.value())
} else {
None
}
.filter_map(|attr| match &attr.meta {
Meta::NameValue(pair) if pair.path.is_ident("doc") => {
if let Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) = &pair.value
{
Some(lit.value())
} else {
None
}
_ => None,
}
_ => None,
})
.collect();

View file

@ -7,7 +7,7 @@
use crate::REFLECT_ATTRIBUTE_NAME;
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{Attribute, Lit, Meta, NestedMeta};
use syn::{Attribute, Expr, ExprLit, Lit, Meta};
pub(crate) static IGNORE_SERIALIZATION_ATTR: &str = "skip_serializing";
pub(crate) static IGNORE_ALL_ATTR: &str = "ignore";
@ -76,10 +76,9 @@ pub(crate) fn parse_field_attrs(attrs: &[Attribute]) -> Result<ReflectFieldAttr,
let attrs = attrs
.iter()
.filter(|a| a.path.is_ident(REFLECT_ATTRIBUTE_NAME));
.filter(|a| a.path().is_ident(REFLECT_ATTRIBUTE_NAME));
for attr in attrs {
let meta = attr.parse_meta()?;
if let Err(err) = parse_meta(&mut args, &meta) {
if let Err(err) = parse_meta(&mut args, &attr.meta) {
if let Some(ref mut error) = errors {
error.combine(err);
} else {
@ -117,18 +116,15 @@ fn parse_meta(args: &mut ReflectFieldAttr, meta: &Meta) -> Result<(), syn::Error
format!("unknown attribute parameter: {}", path.to_token_stream()),
)),
Meta::NameValue(pair) if pair.path.is_ident(DEFAULT_ATTR) => {
let lit = &pair.lit;
match lit {
Lit::Str(lit_str) => {
args.default = DefaultBehavior::Func(lit_str.parse()?);
Ok(())
}
err => {
Err(syn::Error::new(
err.span(),
format!("expected a string literal containing the name of a function, but found: {}", err.to_token_stream()),
))
}
if let Expr::Lit(ExprLit {lit: Lit::Str(lit_str), ..}) = &pair.value {
args.default = DefaultBehavior::Func(lit_str.parse()?);
Ok(())
}
else {
Err(syn::Error::new(
pair.span(),
format!("expected a string literal containing the name of a function, but found: {}", pair.to_token_stream()),
))?
}
}
Meta::NameValue(pair) => {
@ -142,10 +138,8 @@ fn parse_meta(args: &mut ReflectFieldAttr, meta: &Meta) -> Result<(), syn::Error
Err(syn::Error::new(list.path.span(), "unexpected property"))
}
Meta::List(list) => {
for nested in &list.nested {
if let NestedMeta::Meta(meta) = nested {
parse_meta(args, meta)?;
}
if let Ok(meta) = list.parse_args() {
parse_meta(args, &meta)?;
}
Ok(())
}

View file

@ -3,6 +3,7 @@ extern crate proc_macro;
use bevy_macro_utils::BevyManifest;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::token::Comma;
use syn::*;
use uuid::Uuid;
@ -15,22 +16,13 @@ pub(crate) fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::To
let type_ident = ast.ident;
let mut uuid = None;
for attribute in ast.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
let Meta::NameValue(name_value) = attribute else {
for attribute in ast.attrs.iter().filter(|attr| attr.path().is_ident("uuid")) {
let Meta::NameValue(ref name_value) = attribute.meta else {
continue;
};
if name_value
.path
.get_ident()
.map(|i| i != "uuid")
.unwrap_or(true)
{
continue;
}
let uuid_str = match name_value.lit {
Lit::Str(lit_str) => lit_str,
let uuid_str = match &name_value.value {
Expr::Lit(ExprLit{lit: Lit::Str(lit_str), ..}) => lit_str,
_ => panic!("`uuid` attribute must take the form `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"`."),
};
@ -101,7 +93,7 @@ impl Parse for TypeUuidDef {
fn parse(input: ParseStream) -> Result<Self> {
let type_ident = input.parse::<Ident>()?;
let generics = input.parse::<Generics>()?;
input.parse::<Token![,]>()?;
input.parse::<Comma>()?;
let uuid = input.parse::<LitStr>()?.value();
let uuid = Uuid::parse_str(&uuid).map_err(|err| input.error(format!("{err}")))?;

View file

@ -1636,6 +1636,24 @@ bevy_reflect::tests::should_reflect_debug::Test {
assert_eq!("Foo".to_string(), format!("{foo:?}"));
}
#[test]
fn custom_debug_function() {
#[derive(Reflect)]
#[reflect(Debug(custom_debug))]
struct Foo {
a: u32,
}
fn custom_debug(_x: &Foo, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "123")
}
let foo = Foo { a: 1 };
let foo: &dyn Reflect = &foo;
assert_eq!("123", format!("{:?}", foo));
}
#[cfg(feature = "glam")]
mod glam {
use super::*;

View file

@ -17,6 +17,7 @@ jpeg = ["image/jpeg"]
bmp = ["image/bmp"]
webp = ["image/webp"]
dds = ["ddsfile"]
pnm = ["image/pnm"]
bevy_ci_testing = ["bevy_app/bevy_ci_testing"]
shader_format_glsl = ["naga/glsl-in", "naga/wgsl-out"]
@ -69,7 +70,7 @@ thread_local = "1.1"
thiserror = "1.0"
futures-lite = "1.4.0"
anyhow = "1.0"
hexasphere = "8.1"
hexasphere = "9.0"
parking_lot = "0.12.1"
regex = "1.5"
ddsfile = { version = "0.5.0", optional = true }
@ -79,7 +80,7 @@ flate2 = { version = "1.0.22", optional = true }
ruzstd = { version = "0.2.4", optional = true }
# For transcoding of UASTC/ETC1S universal formats, and for .basis file support
basis-universal = { version = "0.2.0", optional = true }
encase = { version = "0.5", features = ["glam"] }
encase = { version = "0.6.1", features = ["glam"] }
# For wgpu profiling using tracing. Use `RUST_LOG=info` to also capture the wgpu spans.
profiling = { version = "1", features = ["profile-with-tracing"], optional = true }
async-channel = "1.8"

View file

@ -14,6 +14,6 @@ proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.11.0-dev" }
syn = "1.0"
syn = "2.0"
proc-macro2 = "1.0"
quote = "1.0"

View file

@ -5,7 +5,8 @@ use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Data, DataStruct, Error, Fields, LitInt, LitStr, NestedMeta, Result, Token,
token::Comma,
Data, DataStruct, Error, Fields, LitInt, LitStr, Meta, Result,
};
const UNIFORM_ATTRIBUTE_NAME: Symbol = Symbol("uniform");
@ -48,7 +49,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
// Read struct-level attributes
for attr in &ast.attrs {
if let Some(attr_ident) = attr.path.get_ident() {
if let Some(attr_ident) = attr.path().get_ident() {
if attr_ident == BIND_GROUP_DATA_ATTRIBUTE_NAME {
if let Ok(prepared_data_ident) =
attr.parse_args_with(|input: ParseStream| input.parse::<Ident>())
@ -117,7 +118,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
// Read field-level attributes
for field in fields.iter() {
for attr in &field.attrs {
let Some(attr_ident) = attr.path.get_ident() else {
let Some(attr_ident) = attr.path().get_ident() else {
continue;
};
@ -462,14 +463,14 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
/// like `#[uniform(LitInt, Ident)]`
struct UniformBindingMeta {
lit_int: LitInt,
_comma: Token![,],
_comma: Comma,
ident: Ident,
}
/// Represents the arguments for any general binding attribute.
///
/// If parsed, represents an attribute
/// like `#[foo(LitInt, ...)]` where the rest is optional [`NestedMeta`].
/// like `#[foo(LitInt, ...)]` where the rest is optional [`Meta`].
enum BindingMeta {
IndexOnly(LitInt),
IndexWithOptions(BindingIndexOptions),
@ -480,13 +481,13 @@ enum BindingMeta {
/// This represents, for example, `#[texture(0, dimension = "2d_array")]`.
struct BindingIndexOptions {
lit_int: LitInt,
_comma: Token![,],
meta_list: Punctuated<NestedMeta, Token![,]>,
_comma: Comma,
meta_list: Punctuated<Meta, Comma>,
}
impl Parse for BindingMeta {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek2(Token![,]) {
if input.peek2(Comma) {
input.parse().map(Self::IndexWithOptions)
} else {
input.parse().map(Self::IndexOnly)
@ -499,7 +500,7 @@ impl Parse for BindingIndexOptions {
Ok(Self {
lit_int: input.parse()?,
_comma: input.parse()?,
meta_list: input.parse_terminated(NestedMeta::parse)?,
meta_list: input.parse_terminated(Meta::parse, Comma)?,
})
}
}
@ -523,7 +524,7 @@ fn get_uniform_binding_attr(attr: &syn::Attribute) -> Result<(u32, Ident)> {
Ok((binding_index, ident))
}
fn get_binding_nested_attr(attr: &syn::Attribute) -> Result<(u32, Vec<NestedMeta>)> {
fn get_binding_nested_attr(attr: &syn::Attribute) -> Result<(u32, Vec<Meta>)> {
let binding_meta = attr.parse_args_with(BindingMeta::parse)?;
match binding_meta {
@ -598,43 +599,39 @@ const VISIBILITY_COMPUTE: Symbol = Symbol("compute");
const VISIBILITY_ALL: Symbol = Symbol("all");
const VISIBILITY_NONE: Symbol = Symbol("none");
fn get_visibility_flag_value(
nested_metas: &Punctuated<NestedMeta, Token![,]>,
) -> Result<ShaderStageVisibility> {
fn get_visibility_flag_value(meta: Meta) -> Result<ShaderStageVisibility> {
let mut visibility = VisibilityFlags::vertex_fragment();
for meta in nested_metas {
use syn::{Meta::Path, NestedMeta::Meta};
match meta {
// Parse `visibility(all)]`.
Meta(Path(path)) if path == VISIBILITY_ALL => {
return Ok(ShaderStageVisibility::All)
}
// Parse `visibility(none)]`.
Meta(Path(path)) if path == VISIBILITY_NONE => {
return Ok(ShaderStageVisibility::None)
}
// Parse `visibility(vertex, ...)]`.
Meta(Path(path)) if path == VISIBILITY_VERTEX => {
visibility.vertex = true;
}
// Parse `visibility(fragment, ...)]`.
Meta(Path(path)) if path == VISIBILITY_FRAGMENT => {
visibility.fragment = true;
}
// Parse `visibility(compute, ...)]`.
Meta(Path(path)) if path == VISIBILITY_COMPUTE => {
visibility.compute = true;
}
Meta(Path(path)) => return Err(Error::new_spanned(
path,
"Not a valid visibility flag. Must be `all`, `none`, or a list-combination of `vertex`, `fragment` and/or `compute`."
)),
_ => return Err(Error::new_spanned(
meta,
"Invalid visibility format: `visibility(...)`.",
)),
use syn::Meta::Path;
match meta {
// Parse `#[visibility(all)]`.
Path(path) if path == VISIBILITY_ALL => {
return Ok(ShaderStageVisibility::All)
}
// Parse `#[visibility(none)]`.
Path(path) if path == VISIBILITY_NONE => {
return Ok(ShaderStageVisibility::None)
}
// Parse `#[visibility(vertex, ...)]`.
Path(path) if path == VISIBILITY_VERTEX => {
visibility.vertex = true;
}
// Parse `#[visibility(fragment, ...)]`.
Path(path) if path == VISIBILITY_FRAGMENT => {
visibility.fragment = true;
}
// Parse `#[visibility(compute, ...)]`.
Path(path) if path == VISIBILITY_COMPUTE => {
visibility.compute = true;
}
Path(path) => return Err(Error::new_spanned(
path,
"Not a valid visibility flag. Must be `all`, `none`, or a list-combination of `vertex`, `fragment` and/or `compute`."
)),
_ => return Err(Error::new_spanned(
meta,
"Invalid visibility format: `visibility(...)`.",
)),
}
Ok(ShaderStageVisibility::Flags(visibility))
@ -727,7 +724,7 @@ const DEPTH: &str = "depth";
const S_INT: &str = "s_int";
const U_INT: &str = "u_int";
fn get_texture_attrs(metas: Vec<NestedMeta>) -> Result<TextureAttrs> {
fn get_texture_attrs(metas: Vec<Meta>) -> Result<TextureAttrs> {
let mut dimension = Default::default();
let mut sample_type = Default::default();
let mut multisampled = Default::default();
@ -737,35 +734,32 @@ fn get_texture_attrs(metas: Vec<NestedMeta>) -> Result<TextureAttrs> {
let mut visibility = ShaderStageVisibility::vertex_fragment();
for meta in metas {
use syn::{
Meta::{List, NameValue},
NestedMeta::Meta,
};
use syn::Meta::{List, NameValue};
match meta {
// Parse #[texture(0, dimension = "...")].
Meta(NameValue(m)) if m.path == DIMENSION => {
let value = get_lit_str(DIMENSION, &m.lit)?;
NameValue(m) if m.path == DIMENSION => {
let value = get_lit_str(DIMENSION, &m.value)?;
dimension = get_texture_dimension_value(value)?;
}
// Parse #[texture(0, sample_type = "...")].
Meta(NameValue(m)) if m.path == SAMPLE_TYPE => {
let value = get_lit_str(SAMPLE_TYPE, &m.lit)?;
NameValue(m) if m.path == SAMPLE_TYPE => {
let value = get_lit_str(SAMPLE_TYPE, &m.value)?;
sample_type = get_texture_sample_type_value(value)?;
}
// Parse #[texture(0, multisampled = "...")].
Meta(NameValue(m)) if m.path == MULTISAMPLED => {
multisampled = get_lit_bool(MULTISAMPLED, &m.lit)?;
NameValue(m) if m.path == MULTISAMPLED => {
multisampled = get_lit_bool(MULTISAMPLED, &m.value)?;
}
// Parse #[texture(0, filterable = "...")].
Meta(NameValue(m)) if m.path == FILTERABLE => {
filterable = get_lit_bool(FILTERABLE, &m.lit)?.into();
NameValue(m) if m.path == FILTERABLE => {
filterable = get_lit_bool(FILTERABLE, &m.value)?.into();
filterable_ident = m.path.into();
}
// Parse #[texture(0, visibility(...))].
Meta(List(m)) if m.path == VISIBILITY => {
visibility = get_visibility_flag_value(&m.nested)?;
List(m) if m.path == VISIBILITY => {
visibility = get_visibility_flag_value(Meta::Path(m.path))?;
}
Meta(NameValue(m)) => {
NameValue(m) => {
return Err(Error::new_spanned(
m.path,
"Not a valid name. Available attributes: `dimension`, `sample_type`, `multisampled`, or `filterable`."
@ -865,26 +859,23 @@ const FILTERING: &str = "filtering";
const NON_FILTERING: &str = "non_filtering";
const COMPARISON: &str = "comparison";
fn get_sampler_attrs(metas: Vec<NestedMeta>) -> Result<SamplerAttrs> {
fn get_sampler_attrs(metas: Vec<Meta>) -> Result<SamplerAttrs> {
let mut sampler_binding_type = Default::default();
let mut visibility = ShaderStageVisibility::vertex_fragment();
for meta in metas {
use syn::{
Meta::{List, NameValue},
NestedMeta::Meta,
};
use syn::Meta::{List, NameValue};
match meta {
// Parse #[sampler(0, sampler_type = "..."))].
Meta(NameValue(m)) if m.path == SAMPLER_TYPE => {
let value = get_lit_str(DIMENSION, &m.lit)?;
NameValue(m) if m.path == SAMPLER_TYPE => {
let value = get_lit_str(DIMENSION, &m.value)?;
sampler_binding_type = get_sampler_binding_type_value(value)?;
}
// Parse #[sampler(0, visibility(...))].
Meta(List(m)) if m.path == VISIBILITY => {
visibility = get_visibility_flag_value(&m.nested)?;
List(m) if m.path == VISIBILITY => {
visibility = get_visibility_flag_value(Meta::Path(m.path))?;
}
Meta(NameValue(m)) => {
NameValue(m) => {
return Err(Error::new_spanned(
m.path,
"Not a valid name. Available attributes: `sampler_type`.",
@ -928,22 +919,22 @@ struct StorageAttrs {
const READ_ONLY: Symbol = Symbol("read_only");
const BUFFER: Symbol = Symbol("buffer");
fn get_storage_binding_attr(metas: Vec<NestedMeta>) -> Result<StorageAttrs> {
fn get_storage_binding_attr(metas: Vec<Meta>) -> Result<StorageAttrs> {
let mut visibility = ShaderStageVisibility::vertex_fragment();
let mut read_only = false;
let mut buffer = false;
for meta in metas {
use syn::{Meta::List, Meta::Path, NestedMeta::Meta};
use syn::{Meta::List, Meta::Path};
match meta {
// Parse #[storage(0, visibility(...))].
Meta(List(m)) if m.path == VISIBILITY => {
visibility = get_visibility_flag_value(&m.nested)?;
List(m) if m.path == VISIBILITY => {
visibility = get_visibility_flag_value(Meta::Path(m.path))?;
}
Meta(Path(path)) if path == READ_ONLY => {
Path(path) if path == READ_ONLY => {
read_only = true;
}
Meta(Path(path)) if path == BUFFER => {
Path(path) if path == BUFFER => {
buffer = true;
}
_ => {

View file

@ -20,7 +20,7 @@ pub fn derive_extract_component(input: TokenStream) -> TokenStream {
let filter = if let Some(attr) = ast
.attrs
.iter()
.find(|a| a.path.is_ident("extract_component_filter"))
.find(|a| a.path().is_ident("extract_component_filter"))
{
let filter = match attr.parse_args::<syn::Type>() {
Ok(filter) => filter,

View file

@ -36,6 +36,14 @@ const FILE_EXTENSIONS: &[&str] = &[
"ktx2",
#[cfg(feature = "webp")]
"webp",
#[cfg(feature = "pnm")]
"pam",
#[cfg(feature = "pnm")]
"pbm",
#[cfg(feature = "pnm")]
"pgm",
#[cfg(feature = "pnm")]
"ppm",
];
impl AssetLoader for ImageTextureLoader {

View file

@ -1,6 +1,5 @@
use crate::Val;
use bevy_reflect::{FromReflect, Reflect, ReflectFromReflect};
use std::ops::{Div, DivAssign, Mul, MulAssign};
/// A type which is commonly used to define margins, paddings and borders.
///
@ -282,152 +281,6 @@ impl Default for UiRect {
}
}
/// A 2-dimensional area defined by a width and height.
///
/// It is commonly used to define the size of a text or UI element.
#[derive(Copy, Clone, PartialEq, Debug, Reflect, FromReflect)]
#[reflect(FromReflect, PartialEq)]
pub struct Size {
/// The width of the 2-dimensional area.
pub width: Val,
/// The height of the 2-dimensional area.
pub height: Val,
}
impl Size {
pub const DEFAULT: Self = Self::AUTO;
/// Creates a new [`Size`] from a width and a height.
///
/// # Example
///
/// ```
/// # use bevy_ui::{Size, Val};
/// #
/// let size = Size::new(Val::Px(100.0), Val::Px(200.0));
///
/// assert_eq!(size.width, Val::Px(100.0));
/// assert_eq!(size.height, Val::Px(200.0));
/// ```
pub const fn new(width: Val, height: Val) -> Self {
Size { width, height }
}
/// Creates a new [`Size`] where both sides take the given value.
///
/// # Example
///
/// ```
/// # use bevy_ui::{Size, Val};
/// #
/// let size = Size::all(Val::Px(10.));
///
/// assert_eq!(size.width, Val::Px(10.0));
/// assert_eq!(size.height, Val::Px(10.0));
/// ```
pub const fn all(value: Val) -> Self {
Self {
width: value,
height: value,
}
}
/// Creates a new [`Size`] where `width` takes the given value,
/// and `height` is set to [`Val::Auto`].
///
/// # Example
///
/// ```
/// # use bevy_ui::{Size, Val};
/// #
/// let size = Size::width(Val::Px(10.));
///
/// assert_eq!(size.width, Val::Px(10.0));
/// assert_eq!(size.height, Val::Auto);
/// ```
pub const fn width(width: Val) -> Self {
Self {
width,
height: Val::Auto,
}
}
/// Creates a new [`Size`] where `height` takes the given value,
/// and `width` is set to [`Val::Auto`].
///
/// # Example
///
/// ```
/// # use bevy_ui::{Size, Val};
/// #
/// let size = Size::height(Val::Px(10.));
///
/// assert_eq!(size.width, Val::Auto);
/// assert_eq!(size.height, Val::Px(10.));
/// ```
pub const fn height(height: Val) -> Self {
Self {
width: Val::Auto,
height,
}
}
/// Creates a Size where both values are [`Val::Auto`].
pub const AUTO: Self = Self::all(Val::Auto);
}
impl Default for Size {
fn default() -> Self {
Self::DEFAULT
}
}
impl From<(Val, Val)> for Size {
fn from(vals: (Val, Val)) -> Self {
Self {
width: vals.0,
height: vals.1,
}
}
}
impl Mul<f32> for Size {
type Output = Size;
fn mul(self, rhs: f32) -> Self::Output {
Self::Output {
width: self.width * rhs,
height: self.height * rhs,
}
}
}
impl MulAssign<f32> for Size {
fn mul_assign(&mut self, rhs: f32) {
self.width *= rhs;
self.height *= rhs;
}
}
impl Div<f32> for Size {
type Output = Size;
fn div(self, rhs: f32) -> Self::Output {
Self::Output {
width: self.width / rhs,
height: self.height / rhs,
}
}
}
impl DivAssign<f32> for Size {
fn div_assign(&mut self, rhs: f32) {
self.width /= rhs;
self.height /= rhs;
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -446,91 +299,6 @@ mod tests {
assert_eq!(UiRect::default(), UiRect::DEFAULT);
}
#[test]
fn test_size_from() {
let size: Size = (Val::Px(20.), Val::Px(30.)).into();
assert_eq!(
size,
Size {
width: Val::Px(20.),
height: Val::Px(30.),
}
);
}
#[test]
fn test_size_mul() {
assert_eq!(Size::all(Val::Px(10.)) * 2., Size::all(Val::Px(20.)));
let mut size = Size::all(Val::Px(10.));
size *= 2.;
assert_eq!(size, Size::all(Val::Px(20.)));
}
#[test]
fn test_size_div() {
assert_eq!(
Size::new(Val::Px(20.), Val::Px(20.)) / 2.,
Size::new(Val::Px(10.), Val::Px(10.))
);
let mut size = Size::new(Val::Px(20.), Val::Px(20.));
size /= 2.;
assert_eq!(size, Size::new(Val::Px(10.), Val::Px(10.)));
}
#[test]
fn test_size_all() {
let length = Val::Px(10.);
assert_eq!(
Size::all(length),
Size {
width: length,
height: length
}
);
}
#[test]
fn test_size_width() {
let width = Val::Px(10.);
assert_eq!(
Size::width(width),
Size {
width,
..Default::default()
}
);
}
#[test]
fn test_size_height() {
let height = Val::Px(7.);
assert_eq!(
Size::height(height),
Size {
height,
..Default::default()
}
);
}
#[test]
fn size_default_equals_const_default() {
assert_eq!(
Size::default(),
Size {
width: Val::Auto,
height: Val::Auto
}
);
assert_eq!(Size::default(), Size::DEFAULT);
}
#[test]
fn test_uirect_axes() {
let x = Val::Px(1.);

View file

@ -3,8 +3,8 @@ use taffy::style_helpers;
use crate::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, GridAutoFlow,
GridPlacement, GridTrack, GridTrackRepetition, JustifyContent, JustifyItems, JustifySelf,
MaxTrackSizingFunction, MinTrackSizingFunction, PositionType, RepeatedGridTrack, Size, Style,
UiRect, Val,
MaxTrackSizingFunction, MinTrackSizingFunction, PositionType, RepeatedGridTrack, Style, UiRect,
Val,
};
use super::LayoutContext;
@ -63,15 +63,6 @@ impl UiRect {
}
}
impl Size {
fn map_to_taffy_size<T>(self, map_fn: impl Fn(Val) -> T) -> taffy::geometry::Size<T> {
taffy::geometry::Size {
width: map_fn(self.width),
height: map_fn(self.height),
}
}
}
pub fn from_style(context: &LayoutContext, style: &Style) -> taffy::style::Style {
taffy::style::Style {
display: style.display.into(),
@ -102,17 +93,23 @@ pub fn from_style(context: &LayoutContext, style: &Style) -> taffy::style::Style
flex_grow: style.flex_grow,
flex_shrink: style.flex_shrink,
flex_basis: style.flex_basis.into_dimension(context),
size: style.size.map_to_taffy_size(|s| s.into_dimension(context)),
min_size: style
.min_size
.map_to_taffy_size(|s| s.into_dimension(context)),
max_size: style
.max_size
.map_to_taffy_size(|s| s.into_dimension(context)),
size: taffy::prelude::Size {
width: style.width.into_dimension(context),
height: style.height.into_dimension(context),
},
min_size: taffy::prelude::Size {
width: style.min_width.into_dimension(context),
height: style.min_height.into_dimension(context),
},
max_size: taffy::prelude::Size {
width: style.max_width.into_dimension(context),
height: style.max_height.into_dimension(context),
},
aspect_ratio: style.aspect_ratio,
gap: style
.gap
.map_to_taffy_size(|s| s.into_length_percentage(context)),
gap: taffy::prelude::Size {
width: style.column_gap.into_length_percentage(context),
height: style.row_gap.into_length_percentage(context),
},
grid_auto_flow: style.grid_auto_flow.into(),
grid_template_rows: style
.grid_template_rows
@ -439,24 +436,16 @@ mod tests {
flex_grow: 1.,
flex_shrink: 0.,
flex_basis: Val::Px(0.),
size: Size {
width: Val::Px(0.),
height: Val::Auto,
},
min_size: Size {
width: Val::Px(0.),
height: Val::Percent(0.),
},
max_size: Size {
width: Val::Auto,
height: Val::Px(0.),
},
width: Val::Px(0.),
height: Val::Auto,
min_width: Val::Px(0.),
min_height: Val::Percent(0.),
max_width: Val::Auto,
max_height: Val::Px(0.),
aspect_ratio: None,
overflow: crate::Overflow::clip(),
gap: Size {
width: Val::Px(0.),
height: Val::Percent(0.),
},
column_gap: Val::Px(0.),
row_gap: Val::Percent(0.),
grid_auto_flow: GridAutoFlow::ColumnDense,
grid_template_rows: vec![
GridTrack::px(10.0),

View file

@ -109,7 +109,6 @@ impl Plugin for UiPlugin {
.register_type::<Overflow>()
.register_type::<OverflowAxis>()
.register_type::<PositionType>()
.register_type::<Size>()
.register_type::<UiRect>()
.register_type::<Style>()
.register_type::<BackgroundColor>()

View file

@ -1,4 +1,4 @@
use crate::{Size, UiRect};
use crate::UiRect;
use bevy_asset::Handle;
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
use bevy_math::{Rect, Vec2};
@ -15,7 +15,7 @@ use std::ops::{Div, DivAssign, Mul, MulAssign};
use thiserror::Error;
/// Describes the size of a UI node
#[derive(Component, Debug, Clone, Reflect)]
#[derive(Component, Debug, Copy, Clone, Reflect)]
#[reflect(Component, Default)]
pub struct Node {
/// The size of the node as width and height in logical pixels
@ -26,10 +26,19 @@ pub struct Node {
impl Node {
/// The calculated node size as width and height in logical pixels
/// automatically calculated by [`super::layout::ui_layout_system`]
pub fn size(&self) -> Vec2 {
pub const fn size(&self) -> Vec2 {
self.calculated_size
}
/// Returns the size of the node in physical pixels based on the given scale factor.
#[inline]
pub fn physical_size(&self, scale_factor: f64) -> Vec2 {
Vec2::new(
(self.calculated_size.x as f64 * scale_factor) as f32,
(self.calculated_size.y as f64 * scale_factor) as f32,
)
}
/// Returns the logical pixel coordinates of the UI node, based on its [`GlobalTransform`].
#[inline]
pub fn logical_rect(&self, transform: &GlobalTransform) -> Rect {
@ -38,11 +47,17 @@ impl Node {
/// Returns the physical pixel coordinates of the UI node, based on its [`GlobalTransform`] and the scale factor.
#[inline]
pub fn physical_rect(&self, transform: &GlobalTransform, scale_factor: f32) -> Rect {
pub fn physical_rect(&self, transform: &GlobalTransform, scale_factor: f64) -> Rect {
let rect = self.logical_rect(transform);
Rect {
min: rect.min / scale_factor,
max: rect.max / scale_factor,
min: Vec2::new(
(rect.min.x as f64 * scale_factor) as f32,
(rect.min.y as f64 * scale_factor) as f32,
),
max: Vec2::new(
(rect.max.x as f64 * scale_factor) as f32,
(rect.max.y as f64 * scale_factor) as f32,
),
}
}
}
@ -336,32 +351,35 @@ pub struct Style {
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/bottom>
pub bottom: Val,
/// The ideal size of the node
/// The ideal width of the node. `width` is used when it is within the bounds defined by `min_width` and `max_width`.
///
/// `size.width` is used when it is within the bounds defined by `min_size.width` and `max_size.width`.
/// `size.height` is used when it is within the bounds defined by `min_size.height` and `max_size.height`.
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/width>
pub width: Val,
/// The ideal height of the node. `height` is used when it is within the bounds defined by `min_height` and `max_height`.
///
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/width> <br />
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/height>
pub size: Size,
pub height: Val,
/// The minimum size of the node
/// The minimum width of the node. `min_width` is used if it is greater than either `width` and/or `max_width`.
///
/// `min_size.width` is used if it is greater than either `size.width` or `max_size.width`, or both.
/// `min_size.height` is used if it is greater than either `size.height` or `max_size.height`, or both.
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-width>
pub min_width: Val,
/// The minimum height of the node. `min_height` is used if it is greater than either `height` and/or `max_height`.
///
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-width> <br />
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-height>
pub min_size: Size,
pub min_height: Val,
/// The maximum size of the node
/// The maximum width of the node. `max_width` is used if it is within the bounds defined by `min_width` and `width`.
///
/// `max_size.width` is used if it is within the bounds defined by `min_size.width` and `size.width`.
/// `max_size.height` is used if it is within the bounds defined by `min_size.height` and `size.height.
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-width>
pub max_width: Val,
/// The maximum height of the node. `max_height` is used if it is within the bounds defined by `min_height` and `height`.
///
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-width> <br />
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-height>
pub max_size: Size,
pub max_height: Val,
/// The aspect ratio of the node (defined as `width / height`)
///
@ -506,12 +524,19 @@ pub struct Style {
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis>
pub flex_basis: Val,
/// The size of the gutters between items in flexbox layout or rows/columns in a grid layout
/// The size of the gutters between items in a vertical flexbox layout or between rows in a grid layout
///
/// Note: Values of `Val::Auto` are not valid and are treated as zero.
///
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/gap>
pub gap: Size,
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap>
pub row_gap: Val,
/// The size of the gutters between items in a horizontal flexbox layout or between column in a grid layout
///
/// Note: Values of `Val::Auto` are not valid and are treated as zero.
///
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-gap>
pub column_gap: Val,
/// Controls whether automatically placed grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used.
/// Only affect Grid layouts
@ -576,12 +601,16 @@ impl Style {
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Val::Auto,
size: Size::AUTO,
min_size: Size::AUTO,
max_size: Size::AUTO,
width: Val::Auto,
height: Val::Auto,
min_width: Val::Auto,
min_height: Val::Auto,
max_width: Val::Auto,
max_height: Val::Auto,
aspect_ratio: None,
overflow: Overflow::DEFAULT,
gap: Size::all(Val::Px(0.0)),
row_gap: Val::Px(0.0),
column_gap: Val::Px(0.0),
grid_auto_flow: GridAutoFlow::DEFAULT,
grid_template_rows: Vec::new(),
grid_template_columns: Vec::new(),

View file

@ -18,10 +18,6 @@ use bevy_text::{
use bevy_window::{PrimaryWindow, Window};
use taffy::style::AvailableSpace;
fn scale_value(value: f32, factor: f64) -> f32 {
(value as f64 * factor) as f32
}
/// Text system flags
///
/// Used internally by [`measure_text_system`] and [`text_system`] to schedule text for processing.
@ -178,10 +174,7 @@ fn queue_text(
) {
// Skip the text node if it is waiting for a new measure func
if !text_flags.needs_new_measure_func {
let node_size = Vec2::new(
scale_value(node.size().x, scale_factor),
scale_value(node.size().y, scale_factor),
);
let physical_node_size = node.physical_size(scale_factor);
match text_pipeline.queue_text(
fonts,
@ -189,7 +182,7 @@ fn queue_text(
scale_factor,
text.alignment,
text.linebreak_behavior,
node_size,
physical_node_size,
font_atlas_set_storage,
texture_atlases,
textures,

View file

@ -9,6 +9,6 @@ license = "MIT OR Apache-2.0"
proc-macro = true
[dependencies]
syn = "1.0"
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0"

View file

@ -57,6 +57,7 @@ The default feature set enables most of the expected features of a game engine,
|jpeg|JPEG image format support|
|minimp3|MP3 audio format support (through minimp3)|
|mp3|MP3 audio format support|
|pnm|PNM image format support, includes pam, pbm, pgm and ppm|
|serialize|Enable serialization support through serde|
|shader_format_glsl|Enable support for shaders in GLSL|
|shader_format_spirv|Enable support for shaders in SPIR-V|

View file

@ -277,15 +277,21 @@ impl Plugin for ColoredMesh2dPlugin {
Shader::from_wgsl(COLORED_MESH2D_SHADER),
);
// Register our custom draw function and pipeline, and add our render systems
// Register our custom draw function, and add our render systems
app.get_sub_app_mut(RenderApp)
.unwrap()
.add_render_command::<Transparent2d, DrawColoredMesh2d>()
.init_resource::<ColoredMesh2dPipeline>()
.init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
.add_systems(ExtractSchedule, extract_colored_mesh2d)
.add_systems(Render, queue_colored_mesh2d.in_set(RenderSet::Queue));
}
fn finish(&self, app: &mut App) {
// Register our custom pipeline
app.get_sub_app_mut(RenderApp)
.unwrap()
.init_resource::<ColoredMesh2dPipeline>();
}
}
/// Extract the [`ColoredMesh2d`] marker component into the render app

View file

@ -3,7 +3,7 @@
//! It does not know anything about the asset formats, only how to talk to the underlying storage.
use bevy::{
asset::{AssetIo, AssetIoError, Metadata},
asset::{AssetIo, AssetIoError, ChangeWatcher, Metadata},
prelude::*,
utils::BoxedFuture,
};
@ -39,9 +39,9 @@ impl AssetIo for CustomAssetIo {
self.0.watch_path_for_changes(to_watch, to_reload)
}
fn watch_for_changes(&self) -> Result<(), AssetIoError> {
fn watch_for_changes(&self, configuration: &ChangeWatcher) -> Result<(), AssetIoError> {
info!("watch_for_changes()");
self.0.watch_for_changes()
self.0.watch_for_changes(configuration)
}
fn get_metadata(&self, path: &Path) -> Result<Metadata, AssetIoError> {

View file

@ -2,13 +2,13 @@
//! running. This lets you immediately see the results of your changes without restarting the game.
//! This example illustrates hot reloading mesh changes.
use bevy::prelude::*;
use bevy::{asset::ChangeWatcher, prelude::*, utils::Duration};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(AssetPlugin {
// Tell the asset server to watch for asset changes on disk:
watch_for_changes: true,
watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(200)),
..default()
}))
.add_systems(Startup, setup)

View file

@ -69,7 +69,7 @@ fn setup(mut commands: Commands) {
commands
.spawn(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
width: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
flex_direction: FlexDirection::Column,

View file

@ -53,7 +53,7 @@ fn setup_menu(mut commands: Commands) {
.spawn(NodeBundle {
style: Style {
// center button
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
width: Val::Percent(100.),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
@ -64,7 +64,8 @@ fn setup_menu(mut commands: Commands) {
parent
.spawn(ButtonBundle {
style: Style {
size: Size::new(Val::Px(150.0), Val::Px(65.0)),
width: Val::Px(150.),
height: Val::Px(65.),
// horizontally center child text
justify_content: JustifyContent::Center,
// vertically center child text

View file

@ -388,7 +388,7 @@ fn display_score(mut commands: Commands, game: Res<Game>) {
commands
.spawn(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
width: Val::Percent(100.),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()

View file

@ -85,7 +85,7 @@ mod splash {
style: Style {
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
width: Val::Percent(100.0),
..default()
},
..default()
@ -96,7 +96,7 @@ mod splash {
parent.spawn(ImageBundle {
style: Style {
// This will set the logo to be 200px wide, and auto adjust its height
size: Size::new(Val::Px(200.0), Val::Auto),
width: Val::Px(200.0),
..default()
},
image: UiImage::new(icon),
@ -152,7 +152,8 @@ mod game {
.spawn((
NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
width: Val::Percent(100.0),
height: Val::Percent(100.0),
// center children
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
@ -397,14 +398,15 @@ mod menu {
fn main_menu_setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Common style for all buttons on the screen
let button_style = Style {
size: Size::new(Val::Px(250.0), Val::Px(65.0)),
width: Val::Px(250.0),
height: Val::Px(65.0),
margin: UiRect::all(Val::Px(20.0)),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
};
let button_icon_style = Style {
size: Size::new(Val::Px(30.0), Val::Auto),
width: Val::Px(30.0),
// This takes the icons out of the flexbox flow, to be positioned exactly
position_type: PositionType::Absolute,
// The icon will be close to the left border of the button
@ -422,7 +424,7 @@ mod menu {
.spawn((
NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
width: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
@ -529,7 +531,8 @@ mod menu {
fn settings_menu_setup(mut commands: Commands) {
let button_style = Style {
size: Size::new(Val::Px(200.0), Val::Px(65.0)),
width: Val::Px(200.0),
height: Val::Px(65.0),
margin: UiRect::all(Val::Px(20.0)),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
@ -546,7 +549,7 @@ mod menu {
.spawn((
NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
width: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
@ -594,7 +597,8 @@ mod menu {
fn display_settings_menu_setup(mut commands: Commands, display_quality: Res<DisplayQuality>) {
let button_style = Style {
size: Size::new(Val::Px(200.0), Val::Px(65.0)),
width: Val::Px(200.0),
height: Val::Px(65.0),
margin: UiRect::all(Val::Px(20.0)),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
@ -610,7 +614,7 @@ mod menu {
.spawn((
NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
width: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
@ -656,7 +660,8 @@ mod menu {
] {
let mut entity = parent.spawn(ButtonBundle {
style: Style {
size: Size::new(Val::Px(150.0), Val::Px(65.0)),
width: Val::Px(150.0),
height: Val::Px(65.0),
..button_style.clone()
},
background_color: NORMAL_BUTTON.into(),
@ -692,7 +697,8 @@ mod menu {
fn sound_settings_menu_setup(mut commands: Commands, volume: Res<Volume>) {
let button_style = Style {
size: Size::new(Val::Px(200.0), Val::Px(65.0)),
width: Val::Px(200.0),
height: Val::Px(65.0),
margin: UiRect::all(Val::Px(20.0)),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
@ -708,7 +714,7 @@ mod menu {
.spawn((
NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
width: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
@ -746,7 +752,8 @@ mod menu {
for volume_setting in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] {
let mut entity = parent.spawn(ButtonBundle {
style: Style {
size: Size::new(Val::Px(30.0), Val::Px(65.0)),
width: Val::Px(30.0),
height: Val::Px(65.0),
..button_style.clone()
},
background_color: NORMAL_BUTTON.into(),

View file

@ -1,5 +1,5 @@
//! This example illustrates loading scenes from files.
use bevy::{prelude::*, tasks::IoTaskPool, utils::Duration};
use bevy::{asset::ChangeWatcher, prelude::*, tasks::IoTaskPool, utils::Duration};
use std::{fs::File, io::Write};
fn main() {
@ -7,7 +7,7 @@ fn main() {
.add_plugins(DefaultPlugins.set(AssetPlugin {
// This tells the AssetServer to watch for changes to assets.
// It enables our scenes to automatically reload in game when we modify their files.
watch_for_changes: true,
watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(200)),
..default()
}))
.register_type::<ComponentA>()

View file

@ -6,6 +6,7 @@
//! This is a fairly low level example and assumes some familiarity with rendering concepts and wgpu.
use bevy::{
asset::ChangeWatcher,
core_pipeline::{
clear_color::ClearColorConfig, core_3d,
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
@ -29,13 +30,14 @@ use bevy::{
view::{ExtractedView, ViewTarget},
RenderApp,
},
utils::Duration,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(AssetPlugin {
// Hot reloading the shader works correctly
watch_for_changes: true,
watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(200)),
..default()
}))
.add_plugin(PostProcessPlugin)
@ -68,8 +70,6 @@ impl Plugin for PostProcessPlugin {
};
render_app
// Initialize the pipeline
.init_resource::<PostProcessPipeline>()
// Bevy's renderer uses a render graph which is a collection of nodes in a directed acyclic graph.
// It currently runs on each view/camera and executes each node in the specified order.
// It will make sure that any node that needs a dependency from another node
@ -97,6 +97,17 @@ impl Plugin for PostProcessPlugin {
],
);
}
fn finish(&self, app: &mut App) {
// We need to get the render app from the main app
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
// Initialize the pipeline
.init_resource::<PostProcessPipeline>();
}
}
/// The post process node used for the render graph

View file

@ -9,30 +9,17 @@ use bevy::{
render_resource::{AsBindGroupError, PreparedBindGroup, *},
renderer::RenderDevice,
texture::FallbackImage,
RenderApp,
},
};
use std::num::NonZeroU32;
use std::{num::NonZeroU32, process::exit};
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()));
let render_device = app.world.resource::<RenderDevice>();
// check if the device support the required feature
if !render_device
.features()
.contains(WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING)
{
error!(
"Render device doesn't support feature \
SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, \
which is required for texture binding arrays"
);
return;
}
app.add_plugin(MaterialPlugin::<BindlessMaterial>::default())
app.add_plugin(GpuFeatureSupportChecker)
.add_plugin(MaterialPlugin::<BindlessMaterial>::default())
.add_systems(Startup, setup)
.run();
}
@ -42,6 +29,34 @@ const TILE_ID: [usize; 16] = [
19, 23, 4, 33, 12, 69, 30, 48, 10, 65, 40, 47, 57, 41, 44, 46,
];
struct GpuFeatureSupportChecker;
impl Plugin for GpuFeatureSupportChecker {
fn build(&self, _app: &mut App) {}
fn finish(&self, app: &mut App) {
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return
};
let render_device = render_app.world.resource::<RenderDevice>();
// Check if the device support the required feature. If not, exit the example.
// In a real application, you should setup a fallback for the missing feature
if !render_device
.features()
.contains(WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING)
{
error!(
"Render device doesn't support feature \
SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, \
which is required for texture binding arrays"
);
exit(1);
}
}
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,

View file

@ -78,7 +78,7 @@ fn setup(mut commands: Commands) {
commands
.spawn(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
width: Val::Percent(100.),
..default()
},
..default()
@ -106,7 +106,8 @@ fn spawn_button(
let mut builder = commands.spawn((
ButtonBundle {
style: Style {
size: Size::new(Val::Percent(width), Val::Percent(width)),
width: Val::Percent(width),
height: Val::Percent(width),
bottom: Val::Percent(100.0 / total * i as f32),
left: Val::Percent(100.0 / total * j as f32),
align_items: AlignItems::Center,

View file

@ -63,7 +63,7 @@ fn setup(mut commands: Commands) {
commands.spawn(TextBundle {
text: text.clone(),
style: Style {
size: Size::width(Val::Px(1000.)),
width: Val::Px(1000.),
..Default::default()
},
..Default::default()

View file

@ -6,9 +6,11 @@
//! With no arguments it will load the `FlightHelmet` glTF model from the repository assets subdirectory.
use bevy::{
asset::ChangeWatcher,
math::Vec3A,
prelude::*,
render::primitives::{Aabb, Sphere},
utils::Duration,
window::WindowPlugin,
};
@ -36,7 +38,7 @@ fn main() {
.set(AssetPlugin {
asset_folder: std::env::var("CARGO_MANIFEST_DIR")
.unwrap_or_else(|_| ".".to_string()),
watch_for_changes: true,
watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(200)),
}),
)
.add_plugin(CameraControllerPlugin)

View file

@ -1,2 +0,0 @@
pub mod camera_controller_plugin;
pub mod scene_viewer_plugin;

View file

@ -49,7 +49,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands
.spawn(NodeBundle {
style: Style {
size: Size::width(Val::Percent(100.0)),
width: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
@ -60,7 +60,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
parent
.spawn(ButtonBundle {
style: Style {
size: Size::new(Val::Px(150.0), Val::Px(65.0)),
width: Val::Px(150.0),
height: Val::Px(65.0),
// horizontally center child text
justify_content: JustifyContent::Center,
// vertically center child text

View file

@ -26,7 +26,7 @@ fn spawn_layout(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(NodeBundle {
style: Style {
// fill the entire window
size: Size::all(Val::Percent(100.)),
width: Val::Percent(100.),
flex_direction: FlexDirection::Column,
align_items: AlignItems::Center,
..Default::default()
@ -65,7 +65,8 @@ fn spawn_layout(mut commands: Commands, asset_server: Res<AssetServer>) {
builder
.spawn(NodeBundle {
style: Style {
min_size: Size::new(Val::Px(850.), Val::Px(1020.)),
width: Val::Px(850.),
height: Val::Px(1020.),
flex_direction: FlexDirection::Column,
..Default::default()
},
@ -124,7 +125,8 @@ fn spawn_child_node(
flex_direction: FlexDirection::Column,
align_items,
justify_content,
size: Size::all(Val::Px(160.)),
width: Val::Px(160.),
height: Val::Px(160.),
margin: UiRect::all(MARGIN),
..Default::default()
},

View file

@ -26,7 +26,8 @@ fn spawn_layout(mut commands: Commands, asset_server: Res<AssetServer>) {
/// Use the CSS Grid algorithm for laying out this node
display: Display::Grid,
/// Make node fill the entirety it's parent (in this case the window)
size: Size::all(Val::Percent(100.)),
width: Val::Percent(100.0),
height: Val::Percent(100.0),
/// Set the grid to have 2 columns with sizes [min-content, minmax(0, 1fr)]
/// - The first column will size to the size of it's contents
/// - The second column will take up the remaining available space
@ -67,7 +68,7 @@ fn spawn_layout(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(NodeBundle {
style: Style {
/// Make the height of the node fill its parent
size: Size::height(Val::Percent(100.0)),
height: Val::Percent(100.0),
/// Make the grid have a 1:1 aspect ratio meaning it will scale as an exact square
/// As the height is set explicitly, this means the width will adjust to match the height
aspect_ratio: Some(1.0),
@ -82,7 +83,8 @@ fn spawn_layout(mut commands: Commands, asset_server: Res<AssetServer>) {
/// This creates 4 exactly evenly sized rows
grid_template_rows: RepeatedGridTrack::flex(4, 1.0),
/// Set a 12px gap/gutter between rows and columns
gap: Size::all(Val::Px(12.0)),
row_gap: Val::Px(12.0),
column_gap: Val::Px(12.0),
..default()
},
background_color: BackgroundColor(Color::DARK_GRAY),
@ -130,7 +132,7 @@ fn spawn_layout(mut commands: Commands, asset_server: Res<AssetServer>) {
// can be top-aligned. Normally you'd use flexbox for this, but this is the CSS Grid example so we're using grid.
grid_template_rows: vec![GridTrack::auto(), GridTrack::auto(), GridTrack::fr(1.0)],
// Add a 10px gap between rows
gap: Size::height(Val::Px(10.)),
row_gap: Val::Px(10.),
..default()
},
background_color: BackgroundColor(Color::BLACK),

View file

@ -27,7 +27,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
style: Style {
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
size: Size::width(Val::Percent(100.)),
width: Val::Percent(100.),
..Default::default()
},
background_color: Color::ANTIQUE_WHITE.into(),
@ -71,7 +71,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
parent
.spawn(NodeBundle {
style: Style {
size: Size::all(Val::Px(100.)),
width: Val::Px(100.),
height: Val::Px(100.),
padding: UiRect {
left: Val::Px(25.),
top: Val::Px(25.),
@ -87,7 +88,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
parent.spawn(ImageBundle {
image: UiImage::new(image.clone()),
style: Style {
min_size: Size::all(Val::Px(100.)),
min_width: Val::Px(100.),
min_height: Val::Px(100.),
..Default::default()
},
background_color: Color::WHITE.into(),

View file

@ -86,7 +86,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands
.spawn(NodeBundle {
style: Style {
size: Size::width(Val::Percent(100.)),
width: Val::Percent(100.),
height: Val::Percent(100.),
flex_direction: FlexDirection::Column,
..default()
},
@ -96,7 +97,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
parent
.spawn(NodeBundle {
style: Style {
size: Size::height(Val::Px(32.)),
height: Val::Px(32.),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
@ -149,7 +150,7 @@ fn spawn_row(parent: &mut ChildBuilder, spawn_children: impl FnOnce(&mut ChildBu
parent
.spawn(NodeBundle {
style: Style {
size: Size::width(Val::Percent(50.)),
width: Val::Percent(50.),
align_items: AlignItems::Center,
justify_content: JustifyContent::SpaceEvenly,
..default()
@ -168,7 +169,7 @@ fn spawn_image(
parent.spawn(ImageBundle {
image: UiImage::new(asset_server.load("branding/bevy_logo_dark_big.png")),
style: Style {
size: Size::height(Val::Px(100.)),
height: Val::Px(100.),
position_type: PositionType::Absolute,
top: Val::Px(-50.),
left: Val::Px(-200.),
@ -209,7 +210,8 @@ fn spawn_container(
.spawn((
NodeBundle {
style: Style {
size: Size::new(Val::Px(CONTAINER_SIZE), Val::Px(CONTAINER_SIZE)),
width: Val::Px(CONTAINER_SIZE),
height: Val::Px(CONTAINER_SIZE),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
overflow: Overflow::clip(),
@ -304,10 +306,13 @@ fn next_container_size(
for (mut style, mut container) in &mut containers {
container.0 = (container.0 + 1) % 3;
style.size = match container.0 {
1 => Size::new(Val::Px(CONTAINER_SIZE), Val::Px(30.)),
2 => Size::new(Val::Px(30.), Val::Px(CONTAINER_SIZE)),
_ => Size::new(Val::Px(CONTAINER_SIZE), Val::Px(CONTAINER_SIZE)),
style.width = match container.0 {
2 => Val::Px(30.),
_ => Val::Px(CONTAINER_SIZE),
};
style.height = match container.0 {
1 => Val::Px(30.),
_ => Val::Px(CONTAINER_SIZE),
};
}
}

View file

@ -18,7 +18,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands
.spawn(NodeBundle {
style: Style {
size: Size::width(Val::Percent(100.)),
width: Val::Percent(100.),
height: Val::Percent(100.),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
flex_direction: FlexDirection::Column,
@ -30,7 +31,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
parent
.spawn(NodeBundle {
style: Style {
size: Size::all(Val::Px(250.0)),
width: Val::Px(250.),
height: Val::Px(250.),
margin: UiRect::bottom(Val::Px(15.)),
..default()
},

View file

@ -123,7 +123,8 @@ fn spawn_bar(parent: &mut ChildBuilder) {
.spawn(NodeBundle {
style: Style {
align_items: AlignItems::Stretch,
size: Size::new(Val::Percent(100.), Val::Px(100.)),
width: Val::Percent(100.),
height: Val::Px(100.),
padding: UiRect::all(Val::Px(4.)),
..Default::default()
},
@ -181,8 +182,8 @@ fn spawn_button_row(parent: &mut ChildBuilder, constraint: Constraint, text_styl
parent
.spawn(NodeBundle {
style: Style {
min_size: Size::width(Val::Px(200.)),
max_size: Size::width(Val::Px(200.)),
min_width: Val::Px(200.),
max_width: Val::Px(200.),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..Default::default()
@ -259,7 +260,7 @@ fn spawn_button(
parent
.spawn(NodeBundle {
style: Style {
size: Size::width(Val::Px(100.)),
width: Val::Px(100.),
justify_content: JustifyContent::Center,
..Default::default()
},
@ -311,13 +312,13 @@ fn update_buttons(
style.flex_basis = value.0;
}
Constraint::Width => {
style.size.width = value.0;
style.width = value.0;
}
Constraint::MinWidth => {
style.min_size.width = value.0;
style.min_width = value.0;
}
Constraint::MaxWidth => {
style.max_size.width = value.0;
style.max_width = value.0;
}
}
}

View file

@ -56,7 +56,7 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
position_type: PositionType::Absolute,
top: Val::Px(5.0),
right: Val::Px(15.0),
max_size: Size::width(Val::Px(400.)),
max_width: Val::Px(400.),
..default()
})
);
@ -127,10 +127,7 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
position_type: PositionType::Absolute,
bottom: Val::Px(5.0),
left: Val::Px(15.0),
size: Size {
width: Val::Px(200.0),
..default()
},
width: Val::Px(200.0),
..default()
}),
);

View file

@ -25,7 +25,7 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
size: Size::new(Val::Percent(100.), Val::Percent(100.)),
width: Val::Percent(100.),
..Default::default()
},
background_color: Color::BLACK.into(),
@ -40,7 +40,8 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
flex_direction: FlexDirection::Row,
justify_content: JustifyContent::SpaceAround,
align_items: AlignItems::Center,
size: Size::new(Val::Percent(100.), Val::Percent(50.)),
width: Val::Percent(100.),
height: Val::Percent(50.),
..Default::default()
},
..Default::default()
@ -63,7 +64,8 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
style: Style {
justify_content: justification,
flex_direction: FlexDirection::Column,
size: Size::new(Val::Percent(16.), Val::Percent(95.)),
width: Val::Percent(16.),
height: Val::Percent(95.),
overflow: Overflow::clip(),
..Default::default()
},

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