mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 22:18:33 +00:00
Merge branch 'main' into transmission
This commit is contained in:
commit
0ef74ed789
108 changed files with 1403 additions and 827 deletions
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -10,6 +10,6 @@ keywords = ["bevy"]
|
|||
|
||||
[dependencies]
|
||||
toml_edit = "0.19"
|
||||
syn = "1.0"
|
||||
syn = "2.0"
|
||||
quote = "1.0"
|
||||
rustc-hash = "1.0"
|
||||
|
|
|
@ -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} = ...`"),
|
||||
))
|
||||
))?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
13
crates/bevy_macros_compile_fail_tests/Cargo.toml
Normal file
13
crates/bevy_macros_compile_fail_tests/Cargo.toml
Normal 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"
|
6
crates/bevy_macros_compile_fail_tests/README.md
Normal file
6
crates/bevy_macros_compile_fail_tests/README.md
Normal 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)).
|
1
crates/bevy_macros_compile_fail_tests/src/lib.rs
Normal file
1
crates/bevy_macros_compile_fail_tests/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
|||
// Nothing here, check out the integration tests
|
|
@ -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");
|
||||
}
|
|
@ -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() {}
|
|
@ -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()]
|
||||
| ^
|
|
@ -0,0 +1,9 @@
|
|||
use bevy_derive::Deref;
|
||||
|
||||
#[derive(Deref)]
|
||||
struct UnitStruct;
|
||||
|
||||
#[derive(Deref)]
|
||||
enum Enum {}
|
||||
|
||||
fn main() {}
|
|
@ -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)
|
|
@ -0,0 +1,12 @@
|
|||
use bevy_derive::Deref;
|
||||
|
||||
#[derive(Deref)]
|
||||
struct TupleStruct(usize, String);
|
||||
|
||||
#[derive(Deref)]
|
||||
struct Struct {
|
||||
foo: usize,
|
||||
bar: String,
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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]
|
||||
| ^^^^^^^^
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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() {}
|
|
@ -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()]
|
||||
| ^
|
|
@ -0,0 +1,9 @@
|
|||
use bevy_derive::DerefMut;
|
||||
|
||||
#[derive(DerefMut)]
|
||||
struct UnitStruct;
|
||||
|
||||
#[derive(DerefMut)]
|
||||
enum Enum {}
|
||||
|
||||
fn main() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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]
|
||||
| ^^^^^^^^
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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}")))?;
|
||||
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
_ => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>>,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
pub mod camera_controller_plugin;
|
||||
pub mod scene_viewer_plugin;
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue