Compare commits
43 Commits
custom
...
release-0.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
22e39c4abf | ||
![]() |
9ac0d9b50d | ||
![]() |
960af2b2c9 | ||
![]() |
220b08d379 | ||
![]() |
8a8532bb53 | ||
![]() |
a4b3467530 | ||
![]() |
d61660806c | ||
![]() |
6d295036f2 | ||
![]() |
86d0b8af9d | ||
![]() |
f5ccf683d6 | ||
![]() |
79da6fd02f | ||
![]() |
0c199dd74d | ||
![]() |
9cfc48145b | ||
![]() |
5c3c8e7189 | ||
![]() |
d5b891ea73 | ||
![]() |
3cf377c490 | ||
![]() |
100767560f | ||
![]() |
5581926ad3 | ||
![]() |
24b4c517d0 | ||
![]() |
c458093cc3 | ||
![]() |
46cbb8f781 | ||
![]() |
425c8b8b9f | ||
![]() |
55e74ea8e9 | ||
![]() |
44fbc2f717 | ||
![]() |
0dee514e61 | ||
![]() |
efa8c22ff8 | ||
![]() |
387755748c | ||
![]() |
ce69959a69 | ||
![]() |
520a09a083 | ||
![]() |
a3a7376034 | ||
![]() |
0483b69a79 | ||
![]() |
63828621e8 | ||
![]() |
f78a9460db | ||
![]() |
6dc602da44 | ||
![]() |
e69ab92baf | ||
![]() |
6ebc0a2dfb | ||
![]() |
b32976504d | ||
![]() |
26dfe42623 | ||
![]() |
4667d80243 | ||
![]() |
e0a532cb4b | ||
![]() |
e8ba68d702 | ||
![]() |
07d6a12f46 | ||
![]() |
e6695d5e20 |
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
categories = ["game-engines", "graphics", "gui", "rendering"]
|
||||
description = "A refreshingly simple data-driven game engine and app framework"
|
||||
@ -276,8 +276,8 @@ file_watcher = ["bevy_internal/file_watcher"]
|
||||
embedded_watcher = ["bevy_internal/embedded_watcher"]
|
||||
|
||||
[dependencies]
|
||||
bevy_dylib = { path = "crates/bevy_dylib", version = "0.12.0", default-features = false, optional = true }
|
||||
bevy_internal = { path = "crates/bevy_internal", version = "0.12.0", default-features = false }
|
||||
bevy_dylib = { path = "crates/bevy_dylib", version = "0.12.1", default-features = false, optional = true }
|
||||
bevy_internal = { path = "crates/bevy_internal", version = "0.12.1", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.0"
|
||||
@ -1109,7 +1109,7 @@ required-features = ["file_watcher"]
|
||||
name = "Hot Reloading of Assets"
|
||||
description = "Demonstrates automatic reloading of assets when modified on disk"
|
||||
category = "Assets"
|
||||
wasm = true
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "asset_processing"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_a11y"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides accessibility support for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -10,8 +10,8 @@ keywords = ["bevy", "accessibility", "a11y"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
|
||||
accesskit = "0.12"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_animation"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides animation functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -10,14 +10,14 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.0" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.1" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
|
||||
|
@ -190,15 +190,20 @@ impl PlayingAnimation {
|
||||
self.elapsed += delta;
|
||||
self.seek_time += delta * self.speed;
|
||||
|
||||
if (self.seek_time > clip_duration && self.speed > 0.0)
|
||||
|| (self.seek_time < 0.0 && self.speed < 0.0)
|
||||
{
|
||||
self.completions += 1;
|
||||
}
|
||||
let over_time = self.speed > 0.0 && self.seek_time >= clip_duration;
|
||||
let under_time = self.speed < 0.0 && self.seek_time < 0.0;
|
||||
|
||||
if over_time || under_time {
|
||||
self.completions += 1;
|
||||
|
||||
if self.is_finished() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if self.seek_time >= clip_duration {
|
||||
self.seek_time %= clip_duration;
|
||||
}
|
||||
// Note: assumes delta is never lower than -clip_duration
|
||||
if self.seek_time < 0.0 {
|
||||
self.seek_time += clip_duration;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_app"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides core App functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -16,11 +16,11 @@ bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", optional = true }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", optional = true }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
|
@ -190,6 +190,7 @@ impl Default for App {
|
||||
app.init_resource::<AppTypeRegistry>();
|
||||
|
||||
app.add_plugins(MainSchedulePlugin);
|
||||
|
||||
app.add_event::<AppExit>();
|
||||
|
||||
#[cfg(feature = "bevy_ci_testing")]
|
||||
@ -309,14 +310,6 @@ impl App {
|
||||
panic!("App::run() was called from within Plugin::build(), which is not allowed.");
|
||||
}
|
||||
|
||||
if app.plugins_state() == PluginsState::Ready {
|
||||
// If we're already ready, we finish up now and advance one frame.
|
||||
// This prevents black frames during the launch transition on iOS.
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
app.update();
|
||||
}
|
||||
|
||||
let runner = std::mem::replace(&mut app.runner, Box::new(run_once));
|
||||
(runner)(app);
|
||||
}
|
||||
@ -986,20 +979,14 @@ impl App {
|
||||
}
|
||||
|
||||
fn run_once(mut app: App) {
|
||||
let plugins_state = app.plugins_state();
|
||||
if plugins_state != PluginsState::Cleaned {
|
||||
while app.plugins_state() == PluginsState::Adding {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
bevy_tasks::tick_global_task_pools_on_main_thread();
|
||||
}
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
while app.plugins_state() == PluginsState::Adding {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
bevy_tasks::tick_global_task_pools_on_main_thread();
|
||||
}
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
|
||||
// if plugins where cleaned before the runner start, an update already ran
|
||||
if plugins_state != PluginsState::Cleaned {
|
||||
app.update();
|
||||
}
|
||||
app.update();
|
||||
}
|
||||
|
||||
/// An event that indicates the [`App`] should exit. This will fully exit the app process at the
|
||||
|
@ -84,12 +84,7 @@ impl Plugin for ScheduleRunnerPlugin {
|
||||
|
||||
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
||||
match run_mode {
|
||||
RunMode::Once => {
|
||||
// if plugins where cleaned before the runner start, an update already ran
|
||||
if plugins_state != PluginsState::Cleaned {
|
||||
app.update();
|
||||
}
|
||||
}
|
||||
RunMode::Once => app.update(),
|
||||
RunMode::Loop { wait } => {
|
||||
let mut tick = move |app: &mut App,
|
||||
wait: Option<Duration>|
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_asset"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides asset functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -18,13 +18,13 @@ asset_processor = []
|
||||
watch = []
|
||||
|
||||
[dependencies]
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_asset_macros = { path = "macros", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_asset_macros = { path = "macros", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
|
||||
async-broadcast = "0.5"
|
||||
async-fs = "1.5"
|
||||
@ -38,10 +38,9 @@ parking_lot = { version = "0.12", features = ["arc_lock", "send_guard"] }
|
||||
ron = "0.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
notify-debouncer-full = { version = "0.3.1", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
bevy_winit = { path = "../bevy_winit", version = "0.12.0" }
|
||||
bevy_winit = { path = "../bevy_winit", version = "0.12.1" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
@ -49,5 +48,8 @@ web-sys = { version = "0.3", features = ["Request", "Window", "Response"] }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
notify-debouncer-full = { version = "0.3.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.0" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.1" }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_asset_macros"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Derive implementations for bevy_asset"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.0" }
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.1" }
|
||||
|
||||
syn = "2.0"
|
||||
proc-macro2 = "1.0"
|
||||
|
@ -1,6 +1,6 @@
|
||||
use bevy_macro_utils::BevyManifest;
|
||||
use proc_macro::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_macro_input, Data, DeriveInput, Path};
|
||||
|
||||
pub(crate) fn bevy_asset_path() -> syn::Path {
|
||||
@ -41,33 +41,71 @@ fn derive_dependency_visitor_internal(
|
||||
ast: &DeriveInput,
|
||||
bevy_asset_path: &Path,
|
||||
) -> Result<proc_macro2::TokenStream, syn::Error> {
|
||||
let mut field_visitors = Vec::new();
|
||||
if let Data::Struct(data_struct) = &ast.data {
|
||||
for field in &data_struct.fields {
|
||||
if field
|
||||
.attrs
|
||||
.iter()
|
||||
.any(|a| a.path().is_ident(DEPENDENCY_ATTRIBUTE))
|
||||
{
|
||||
if let Some(field_ident) = &field.ident {
|
||||
field_visitors.push(quote! {
|
||||
#bevy_asset_path::VisitAssetDependencies::visit_dependencies(&self.#field_ident, visit);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
Span::call_site().into(),
|
||||
"Asset derive currently only works on structs",
|
||||
));
|
||||
}
|
||||
|
||||
let struct_name = &ast.ident;
|
||||
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
|
||||
|
||||
let visit_dep = |to_read| quote!(#bevy_asset_path::VisitAssetDependencies::visit_dependencies(#to_read, visit););
|
||||
let is_dep_attribute = |a: &syn::Attribute| a.path().is_ident(DEPENDENCY_ATTRIBUTE);
|
||||
let field_has_dep = |f: &syn::Field| f.attrs.iter().any(is_dep_attribute);
|
||||
|
||||
let body = match &ast.data {
|
||||
Data::Struct(data_struct) => {
|
||||
let fields = data_struct.fields.iter();
|
||||
let field_visitors = fields.enumerate().filter(|(_, f)| field_has_dep(f));
|
||||
let field_visitors = field_visitors.map(|(i, field)| match &field.ident {
|
||||
Some(ident) => visit_dep(quote!(&self.#ident)),
|
||||
None => {
|
||||
let index = syn::Index::from(i);
|
||||
visit_dep(quote!(&self.#index))
|
||||
}
|
||||
});
|
||||
Some(quote!( #(#field_visitors)* ))
|
||||
}
|
||||
Data::Enum(data_enum) => {
|
||||
let variant_has_dep = |v: &syn::Variant| v.fields.iter().any(field_has_dep);
|
||||
let any_case_required = data_enum.variants.iter().any(variant_has_dep);
|
||||
let cases = data_enum.variants.iter().filter(|v| variant_has_dep(v));
|
||||
let cases = cases.map(|variant| {
|
||||
let ident = &variant.ident;
|
||||
let fields = &variant.fields;
|
||||
|
||||
let field_visitors = fields.iter().enumerate().filter(|(_, f)| field_has_dep(f));
|
||||
|
||||
let field_visitors = field_visitors.map(|(i, field)| match &field.ident {
|
||||
Some(ident) => visit_dep(quote!(#ident)),
|
||||
None => {
|
||||
let ident = format_ident!("member{i}");
|
||||
visit_dep(quote!(#ident))
|
||||
}
|
||||
});
|
||||
let fields = match fields {
|
||||
syn::Fields::Named(fields) => {
|
||||
let named = fields.named.iter().map(|f| f.ident.as_ref());
|
||||
quote!({ #(#named,)* .. })
|
||||
}
|
||||
syn::Fields::Unnamed(fields) => {
|
||||
let named = (0..fields.unnamed.len()).map(|i| format_ident!("member{i}"));
|
||||
quote!( ( #(#named,)* ) )
|
||||
}
|
||||
syn::Fields::Unit => unreachable!("Can't pass filter is_dep_attribute"),
|
||||
};
|
||||
quote!(Self::#ident #fields => {
|
||||
#(#field_visitors)*
|
||||
})
|
||||
});
|
||||
|
||||
any_case_required.then(|| quote!(match self { #(#cases)*, _ => {} }))
|
||||
}
|
||||
Data::Union(_) => {
|
||||
return Err(syn::Error::new(
|
||||
Span::call_site().into(),
|
||||
"Asset derive currently doesn't work on unions",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// prevent unused variable warning in case there are no dependencies
|
||||
let visit = if field_visitors.is_empty() {
|
||||
let visit = if body.is_none() {
|
||||
quote! { _visit }
|
||||
} else {
|
||||
quote! { visit }
|
||||
@ -76,7 +114,7 @@ fn derive_dependency_visitor_internal(
|
||||
Ok(quote! {
|
||||
impl #impl_generics #bevy_asset_path::VisitAssetDependencies for #struct_name #type_generics #where_clause {
|
||||
fn visit_dependencies(&self, #visit: &mut impl FnMut(#bevy_asset_path::UntypedAssetId)) {
|
||||
#(#field_visitors)*
|
||||
#body
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate as bevy_asset;
|
||||
use crate::{self as bevy_asset, LoadState};
|
||||
use crate::{Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, UntypedHandle};
|
||||
use bevy_ecs::{
|
||||
prelude::EventWriter,
|
||||
@ -87,12 +87,11 @@ pub struct LoadedUntypedAsset {
|
||||
// PERF: do we actually need this to be an enum? Can we just use an "invalid" generation instead
|
||||
#[derive(Default)]
|
||||
enum Entry<A: Asset> {
|
||||
/// None is an indicator that this entry does not have live handles.
|
||||
#[default]
|
||||
None,
|
||||
Some {
|
||||
value: Option<A>,
|
||||
generation: u32,
|
||||
},
|
||||
/// Some is an indicator that there is a live handle active for the entry at this [`AssetIndex`]
|
||||
Some { value: Option<A>, generation: u32 },
|
||||
}
|
||||
|
||||
/// Stores [`Asset`] values in a Vec-like storage identified by [`AssetIndex`].
|
||||
@ -151,7 +150,26 @@ impl<A: Asset> DenseAssetStorage<A> {
|
||||
}
|
||||
|
||||
/// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists).
|
||||
pub(crate) fn remove(&mut self, index: AssetIndex) -> Option<A> {
|
||||
/// This will recycle the id and allow new entries to be inserted.
|
||||
pub(crate) fn remove_dropped(&mut self, index: AssetIndex) -> Option<A> {
|
||||
self.remove_internal(index, |dense_storage| {
|
||||
dense_storage.storage[index.index as usize] = Entry::None;
|
||||
dense_storage.allocator.recycle(index);
|
||||
})
|
||||
}
|
||||
|
||||
/// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists).
|
||||
/// This will _not_ recycle the id. New values with the current ID can still be inserted. The ID will
|
||||
/// not be reused until [`DenseAssetStorage::remove_dropped`] is called.
|
||||
pub(crate) fn remove_still_alive(&mut self, index: AssetIndex) -> Option<A> {
|
||||
self.remove_internal(index, |_| {})
|
||||
}
|
||||
|
||||
fn remove_internal(
|
||||
&mut self,
|
||||
index: AssetIndex,
|
||||
removed_action: impl FnOnce(&mut Self),
|
||||
) -> Option<A> {
|
||||
self.flush();
|
||||
let value = match &mut self.storage[index.index as usize] {
|
||||
Entry::None => return None,
|
||||
@ -166,8 +184,7 @@ impl<A: Asset> DenseAssetStorage<A> {
|
||||
}
|
||||
}
|
||||
};
|
||||
self.storage[index.index as usize] = Entry::None;
|
||||
self.allocator.recycle(index);
|
||||
removed_action(self);
|
||||
value
|
||||
}
|
||||
|
||||
@ -393,11 +410,25 @@ impl<A: Asset> Assets<A> {
|
||||
pub fn remove_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
|
||||
let id: AssetId<A> = id.into();
|
||||
match id {
|
||||
AssetId::Index { index, .. } => self.dense_storage.remove(index),
|
||||
AssetId::Index { index, .. } => self.dense_storage.remove_still_alive(index),
|
||||
AssetId::Uuid { uuid } => self.hash_map.remove(&uuid),
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes (and returns) the [`Asset`] with the given `id`, if its exists.
|
||||
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
|
||||
pub(crate) fn remove_dropped(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
|
||||
let id: AssetId<A> = id.into();
|
||||
let result = match id {
|
||||
AssetId::Index { index, .. } => self.dense_storage.remove_dropped(index),
|
||||
AssetId::Uuid { uuid } => self.hash_map.remove(&uuid),
|
||||
};
|
||||
if result.is_some() {
|
||||
self.queued_events.push(AssetEvent::Removed { id });
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns `true` if there are no assets in this collection.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.dense_storage.is_empty() && self.hash_map.is_empty()
|
||||
@ -466,16 +497,21 @@ impl<A: Asset> Assets<A> {
|
||||
let mut not_ready = Vec::new();
|
||||
while let Ok(drop_event) = assets.handle_provider.drop_receiver.try_recv() {
|
||||
let id = drop_event.id;
|
||||
if !assets.contains(id.typed()) {
|
||||
not_ready.push(drop_event);
|
||||
continue;
|
||||
}
|
||||
if drop_event.asset_server_managed {
|
||||
if infos.process_handle_drop(id.untyped(TypeId::of::<A>())) {
|
||||
assets.remove(id.typed());
|
||||
let untyped = id.untyped(TypeId::of::<A>());
|
||||
if let Some(info) = infos.get(untyped) {
|
||||
if info.load_state == LoadState::Loading
|
||||
|| info.load_state == LoadState::NotLoaded
|
||||
{
|
||||
not_ready.push(drop_event);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if infos.process_handle_drop(untyped) {
|
||||
assets.remove_dropped(id.typed());
|
||||
}
|
||||
} else {
|
||||
assets.remove(id.typed());
|
||||
assets.remove_dropped(id.typed());
|
||||
}
|
||||
}
|
||||
// TODO: this is _extremely_ inefficient find a better fix
|
||||
|
@ -66,7 +66,12 @@ impl EmbeddedAssetRegistry {
|
||||
Box::new(MemoryAssetReader {
|
||||
root: processed_dir.clone(),
|
||||
})
|
||||
});
|
||||
})
|
||||
// Note that we only add a processed watch warning because we don't want to warn
|
||||
// noisily about embedded watching (which is niche) when users enable file watching.
|
||||
.with_processed_watch_warning(
|
||||
"Consider enabling the `embedded_watcher` cargo feature.",
|
||||
);
|
||||
|
||||
#[cfg(feature = "embedded_watcher")]
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ mod file_asset;
|
||||
#[cfg(not(feature = "multi-threaded"))]
|
||||
mod sync_file_asset;
|
||||
|
||||
use bevy_log::warn;
|
||||
#[cfg(feature = "file_watcher")]
|
||||
pub use file_watcher::*;
|
||||
|
||||
@ -44,12 +45,12 @@ impl FileAssetReader {
|
||||
/// See `get_base_path` below.
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> Self {
|
||||
let root_path = Self::get_base_path().join(path.as_ref());
|
||||
std::fs::create_dir_all(&root_path).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
if let Err(e) = std::fs::create_dir_all(&root_path) {
|
||||
warn!(
|
||||
"Failed to create root directory {:?} for file asset reader: {:?}",
|
||||
root_path, e
|
||||
)
|
||||
});
|
||||
);
|
||||
}
|
||||
Self { root_path }
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
#[cfg(all(feature = "file_watcher", target_arch = "wasm32"))]
|
||||
compile_error!(
|
||||
"The \"file_watcher\" feature for hot reloading does not work \
|
||||
on WASM.\nDisable \"file_watcher\" \
|
||||
when compiling to WASM"
|
||||
);
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod android;
|
||||
pub mod embedded;
|
||||
|
@ -128,6 +128,8 @@ pub struct AssetSourceBuilder {
|
||||
+ Sync,
|
||||
>,
|
||||
>,
|
||||
pub watch_warning: Option<&'static str>,
|
||||
pub processed_watch_warning: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl AssetSourceBuilder {
|
||||
@ -156,23 +158,31 @@ impl AssetSourceBuilder {
|
||||
|
||||
if watch {
|
||||
let (sender, receiver) = crossbeam_channel::unbounded();
|
||||
match self.watcher.as_mut().and_then(|w|(w)(sender)) {
|
||||
match self.watcher.as_mut().and_then(|w| (w)(sender)) {
|
||||
Some(w) => {
|
||||
source.watcher = Some(w);
|
||||
source.event_receiver = Some(receiver);
|
||||
},
|
||||
None => warn!("{id} does not have an AssetWatcher configured. Consider enabling the `file_watcher` feature. Note that Web and Android do not currently support watching assets."),
|
||||
}
|
||||
None => {
|
||||
if let Some(warning) = self.watch_warning {
|
||||
warn!("{id} does not have an AssetWatcher configured. {warning}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if watch_processed {
|
||||
let (sender, receiver) = crossbeam_channel::unbounded();
|
||||
match self.processed_watcher.as_mut().and_then(|w|(w)(sender)) {
|
||||
match self.processed_watcher.as_mut().and_then(|w| (w)(sender)) {
|
||||
Some(w) => {
|
||||
source.processed_watcher = Some(w);
|
||||
source.processed_event_receiver = Some(receiver);
|
||||
},
|
||||
None => warn!("{id} does not have a processed AssetWatcher configured. Consider enabling the `file_watcher` feature. Note that Web and Android do not currently support watching assets."),
|
||||
}
|
||||
None => {
|
||||
if let Some(warning) = self.processed_watch_warning {
|
||||
warn!("{id} does not have a processed AssetWatcher configured. {warning}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(source)
|
||||
@ -238,6 +248,18 @@ impl AssetSourceBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables a warning for the unprocessed source watcher, which will print when watching is enabled and the unprocessed source doesn't have a watcher.
|
||||
pub fn with_watch_warning(mut self, warning: &'static str) -> Self {
|
||||
self.watch_warning = Some(warning);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables a warning for the processed source watcher, which will print when watching is enabled and the processed source doesn't have a watcher.
|
||||
pub fn with_processed_watch_warning(mut self, warning: &'static str) -> Self {
|
||||
self.processed_watch_warning = Some(warning);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a builder containing the "platform default source" for the given `path` and `processed_path`.
|
||||
/// For most platforms, this will use [`FileAssetReader`](crate::io::file::FileAssetReader) / [`FileAssetWriter`](crate::io::file::FileAssetWriter),
|
||||
/// but some platforms (such as Android) have their own default readers / writers / watchers.
|
||||
@ -248,7 +270,8 @@ impl AssetSourceBuilder {
|
||||
.with_watcher(AssetSource::get_default_watcher(
|
||||
path.to_string(),
|
||||
Duration::from_millis(300),
|
||||
));
|
||||
))
|
||||
.with_watch_warning(AssetSource::get_default_watch_warning());
|
||||
if let Some(processed_path) = processed_path {
|
||||
default
|
||||
.with_processed_reader(AssetSource::get_default_reader(processed_path.to_string()))
|
||||
@ -257,6 +280,7 @@ impl AssetSourceBuilder {
|
||||
processed_path.to_string(),
|
||||
Duration::from_millis(300),
|
||||
))
|
||||
.with_processed_watch_warning(AssetSource::get_default_watch_warning())
|
||||
} else {
|
||||
default
|
||||
}
|
||||
@ -428,6 +452,16 @@ impl AssetSource {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the default non-existent [`AssetWatcher`] warning for the current platform.
|
||||
pub fn get_default_watch_warning() -> &'static str {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
return "Web does not currently support watching assets.";
|
||||
#[cfg(target_os = "android")]
|
||||
return "Android does not currently support watching assets.";
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
|
||||
return "Consider enabling the `file_watcher` feature.";
|
||||
}
|
||||
|
||||
/// Returns a builder function for this platform's default [`AssetWatcher`]. `path` is the relative path to
|
||||
/// the asset root. This will return [`None`] if this platform does not support watching assets by default.
|
||||
/// `file_debounce_time` is the amount of time to wait (and debounce duplicate events) before returning an event.
|
||||
|
@ -77,7 +77,7 @@ impl AssetReader for HttpWasmAssetReader {
|
||||
path: &'a Path,
|
||||
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
|
||||
Box::pin(async move {
|
||||
let meta_path = get_meta_path(path);
|
||||
let meta_path = get_meta_path(&self.root_path.join(path));
|
||||
Ok(self.fetch_bytes(meta_path).await?)
|
||||
})
|
||||
}
|
||||
|
@ -45,10 +45,12 @@ use bevy_app::{App, First, MainScheduleOrder, Plugin, PostUpdate};
|
||||
use bevy_ecs::{
|
||||
reflect::AppTypeRegistry,
|
||||
schedule::{IntoSystemConfigs, IntoSystemSetConfigs, ScheduleLabel, SystemSet},
|
||||
system::Resource,
|
||||
world::FromWorld,
|
||||
};
|
||||
use bevy_log::error;
|
||||
use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath};
|
||||
use bevy_utils::HashSet;
|
||||
use std::{any::TypeId, sync::Arc};
|
||||
|
||||
/// Provides "asset" loading and processing functionality. An [`Asset`] is a "runtime value" that is loaded from an [`AssetSource`],
|
||||
@ -97,6 +99,20 @@ pub enum AssetMode {
|
||||
Processed,
|
||||
}
|
||||
|
||||
/// Configures how / if meta files will be checked. If an asset's meta file is not checked, the default meta for the asset
|
||||
/// will be used.
|
||||
// TODO: To avoid breaking Bevy 0.12 users in 0.12.1, this is a Resource. In Bevy 0.13 this should be changed to a field on AssetPlugin (if it is still needed).
|
||||
#[derive(Debug, Default, Clone, Resource)]
|
||||
pub enum AssetMetaCheck {
|
||||
/// Always check if assets have meta files. If the meta does not exist, the default meta will be used.
|
||||
#[default]
|
||||
Always,
|
||||
/// Only look up meta files for the provided paths. The default meta will be used for any paths not contained in this set.
|
||||
Paths(HashSet<AssetPath<'static>>),
|
||||
/// Never check if assets have meta files and always use the default meta. If meta files exist, they will be ignored and the default meta will be used.
|
||||
Never,
|
||||
}
|
||||
|
||||
impl Default for AssetPlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@ -139,9 +155,16 @@ impl Plugin for AssetPlugin {
|
||||
AssetMode::Unprocessed => {
|
||||
let mut builders = app.world.resource_mut::<AssetSourceBuilders>();
|
||||
let sources = builders.build_sources(watch, false);
|
||||
app.insert_resource(AssetServer::new(
|
||||
let meta_check = app
|
||||
.world
|
||||
.get_resource::<AssetMetaCheck>()
|
||||
.cloned()
|
||||
.unwrap_or_else(AssetMetaCheck::default);
|
||||
|
||||
app.insert_resource(AssetServer::new_with_meta_check(
|
||||
sources,
|
||||
AssetServerMode::Unprocessed,
|
||||
meta_check,
|
||||
watch,
|
||||
));
|
||||
}
|
||||
@ -157,6 +180,7 @@ impl Plugin for AssetPlugin {
|
||||
sources,
|
||||
processor.server().data.loaders.clone(),
|
||||
AssetServerMode::Processed,
|
||||
AssetMetaCheck::Always,
|
||||
watch,
|
||||
))
|
||||
.insert_resource(processor)
|
||||
@ -166,9 +190,10 @@ impl Plugin for AssetPlugin {
|
||||
{
|
||||
let mut builders = app.world.resource_mut::<AssetSourceBuilders>();
|
||||
let sources = builders.build_sources(false, watch);
|
||||
app.insert_resource(AssetServer::new(
|
||||
app.insert_resource(AssetServer::new_with_meta_check(
|
||||
sources,
|
||||
AssetServerMode::Processed,
|
||||
AssetMetaCheck::Always,
|
||||
watch,
|
||||
));
|
||||
}
|
||||
@ -1180,4 +1205,35 @@ mod tests {
|
||||
// running schedule does not error on ambiguity between the 2 uses_assets systems
|
||||
app.world.run_schedule(Update);
|
||||
}
|
||||
|
||||
// validate the Asset derive macro for various asset types
|
||||
#[derive(Asset, TypePath)]
|
||||
pub struct TestAsset;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Asset, TypePath)]
|
||||
pub enum EnumTestAsset {
|
||||
Unnamed(#[dependency] Handle<TestAsset>),
|
||||
Named {
|
||||
#[dependency]
|
||||
handle: Handle<TestAsset>,
|
||||
#[dependency]
|
||||
vec_handles: Vec<Handle<TestAsset>>,
|
||||
#[dependency]
|
||||
embedded: TestAsset,
|
||||
},
|
||||
StructStyle(#[dependency] TestAsset),
|
||||
Empty,
|
||||
}
|
||||
|
||||
#[derive(Asset, TypePath)]
|
||||
pub struct StructTestAsset {
|
||||
#[dependency]
|
||||
handle: Handle<TestAsset>,
|
||||
#[dependency]
|
||||
embedded: TestAsset,
|
||||
}
|
||||
|
||||
#[derive(Asset, TypePath)]
|
||||
pub struct TupleTestAsset(#[dependency] Handle<TestAsset>);
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ use crate::{
|
||||
Settings,
|
||||
},
|
||||
path::AssetPath,
|
||||
Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, UntypedAssetId,
|
||||
UntypedHandle,
|
||||
Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, LoadedUntypedAsset,
|
||||
UntypedAssetId, UntypedHandle,
|
||||
};
|
||||
use bevy_ecs::world::World;
|
||||
use bevy_utils::{BoxedFuture, CowArc, HashMap, HashSet};
|
||||
@ -28,7 +28,7 @@ pub trait AssetLoader: Send + Sync + 'static {
|
||||
/// The settings type used by this [`AssetLoader`].
|
||||
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
|
||||
/// The type of [error](`std::error::Error`) which could be encountered by this loader.
|
||||
type Error: std::error::Error + Send + Sync + 'static;
|
||||
type Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>;
|
||||
/// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
|
||||
fn load<'a>(
|
||||
&'a self,
|
||||
@ -90,7 +90,9 @@ where
|
||||
.expect("Loader settings should exist")
|
||||
.downcast_ref::<L::Settings>()
|
||||
.expect("AssetLoader settings should match the loader type");
|
||||
let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context).await?;
|
||||
let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
|
||||
.await
|
||||
.map_err(|error| error.into())?;
|
||||
Ok(load_context.finish(asset, Some(meta)).into())
|
||||
})
|
||||
}
|
||||
@ -351,6 +353,13 @@ impl<'a> LoadContext<'a> {
|
||||
|
||||
/// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This will not assign dependencies to the given `asset`. If adding an asset
|
||||
/// with dependencies generated from calls such as [`LoadContext::load`], use
|
||||
/// [`LoadContext::labeled_asset_scope`] or [`LoadContext::begin_labeled_asset`] to generate a
|
||||
/// new [`LoadContext`] to track the dependencies for the labeled asset.
|
||||
///
|
||||
/// See [`AssetPath`] for more on labeled assets.
|
||||
pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
|
||||
self.labeled_asset_scope(label, |_| asset)
|
||||
@ -460,6 +469,21 @@ impl<'a> LoadContext<'a> {
|
||||
handle
|
||||
}
|
||||
|
||||
/// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset without knowing its type.
|
||||
pub fn load_untyped<'b>(
|
||||
&mut self,
|
||||
path: impl Into<AssetPath<'b>>,
|
||||
) -> Handle<LoadedUntypedAsset> {
|
||||
let path = path.into().to_owned();
|
||||
let handle = if self.should_load_dependencies {
|
||||
self.asset_server.load_untyped(path)
|
||||
} else {
|
||||
self.asset_server.get_or_create_path_handle(path, None)
|
||||
};
|
||||
self.dependencies.insert(handle.id().untyped());
|
||||
handle
|
||||
}
|
||||
|
||||
/// Loads the [`Asset`] of type `A` at the given `path` with the given [`AssetLoader::Settings`] settings `S`. This is a "deferred"
|
||||
/// load. If the settings type `S` does not match the settings expected by `A`'s asset loader, an error will be printed to the log
|
||||
/// and the asset load will fail.
|
||||
|
@ -115,7 +115,7 @@ impl<'a> AssetPath<'a> {
|
||||
///
|
||||
/// This will return a [`ParseAssetPathError`] if `asset_path` is in an invalid format.
|
||||
pub fn try_parse(asset_path: &'a str) -> Result<AssetPath<'a>, ParseAssetPathError> {
|
||||
let (source, path, label) = Self::parse_internal(asset_path).unwrap();
|
||||
let (source, path, label) = Self::parse_internal(asset_path)?;
|
||||
Ok(Self {
|
||||
source: match source {
|
||||
Some(source) => AssetSourceId::Name(CowArc::Borrowed(source)),
|
||||
|
@ -13,7 +13,7 @@ use crate::{
|
||||
get_asset_hash, get_full_asset_hash, AssetAction, AssetActionMinimal, AssetHash, AssetMeta,
|
||||
AssetMetaDyn, AssetMetaMinimal, ProcessedInfo, ProcessedInfoMinimal,
|
||||
},
|
||||
AssetLoadError, AssetPath, AssetServer, AssetServerMode, DeserializeMetaError,
|
||||
AssetLoadError, AssetMetaCheck, AssetPath, AssetServer, AssetServerMode, DeserializeMetaError,
|
||||
MissingAssetLoaderForExtensionError,
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
@ -72,7 +72,12 @@ impl AssetProcessor {
|
||||
// The asset processor uses its own asset server with its own id space
|
||||
let mut sources = source.build_sources(false, false);
|
||||
sources.gate_on_processor(data.clone());
|
||||
let server = AssetServer::new(sources, AssetServerMode::Processed, false);
|
||||
let server = AssetServer::new_with_meta_check(
|
||||
sources,
|
||||
AssetServerMode::Processed,
|
||||
AssetMetaCheck::Always,
|
||||
false,
|
||||
);
|
||||
Self { server, data }
|
||||
}
|
||||
|
||||
|
@ -138,7 +138,7 @@ impl<Loader: AssetLoader, Saver: AssetSaver<Asset = Loader::Asset>> Process
|
||||
.saver
|
||||
.save(writer, saved_asset, &settings.saver_settings)
|
||||
.await
|
||||
.map_err(|error| ProcessError::AssetSaveError(Box::new(error)))?;
|
||||
.map_err(|error| ProcessError::AssetSaveError(error.into()))?;
|
||||
Ok(output_settings)
|
||||
})
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ pub trait AssetSaver: Send + Sync + 'static {
|
||||
/// The type of [`AssetLoader`] used to load this [`Asset`]
|
||||
type OutputLoader: AssetLoader;
|
||||
/// The type of [error](`std::error::Error`) which could be encountered by this saver.
|
||||
type Error: std::error::Error + Send + Sync + 'static;
|
||||
type Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>;
|
||||
|
||||
/// Saves the given runtime [`Asset`] by writing it to a byte format using `writer`. The passed in `settings` can influence how the
|
||||
/// `asset` is saved.
|
||||
@ -53,7 +53,9 @@ impl<S: AssetSaver> ErasedAssetSaver for S {
|
||||
.downcast_ref::<S::Settings>()
|
||||
.expect("AssetLoader settings should match the loader type");
|
||||
let saved_asset = SavedAsset::<S::Asset>::from_loaded(asset).unwrap();
|
||||
self.save(writer, saved_asset, settings).await?;
|
||||
if let Err(err) = self.save(writer, saved_asset, settings).await {
|
||||
return Err(err.into());
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ impl AssetInfos {
|
||||
),
|
||||
type_name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -116,7 +117,7 @@ impl AssetInfos {
|
||||
path: Option<AssetPath<'static>>,
|
||||
meta_transform: Option<MetaTransform>,
|
||||
loading: bool,
|
||||
) -> Result<UntypedHandle, MissingHandleProviderError> {
|
||||
) -> Result<UntypedHandle, GetOrCreateHandleInternalError> {
|
||||
let provider = handle_providers
|
||||
.get(&type_id)
|
||||
.ok_or(MissingHandleProviderError(type_id))?;
|
||||
@ -151,11 +152,13 @@ impl AssetInfos {
|
||||
) -> (Handle<A>, bool) {
|
||||
let result = self.get_or_create_path_handle_internal(
|
||||
path,
|
||||
TypeId::of::<A>(),
|
||||
Some(TypeId::of::<A>()),
|
||||
loading_mode,
|
||||
meta_transform,
|
||||
);
|
||||
let (handle, should_load) = unwrap_with_context(result, std::any::type_name::<A>());
|
||||
// it is ok to unwrap because TypeId was specified above
|
||||
let (handle, should_load) =
|
||||
unwrap_with_context(result, std::any::type_name::<A>()).unwrap();
|
||||
(handle.typed_unchecked(), should_load)
|
||||
}
|
||||
|
||||
@ -167,20 +170,25 @@ impl AssetInfos {
|
||||
loading_mode: HandleLoadingMode,
|
||||
meta_transform: Option<MetaTransform>,
|
||||
) -> (UntypedHandle, bool) {
|
||||
let result =
|
||||
self.get_or_create_path_handle_internal(path, type_id, loading_mode, meta_transform);
|
||||
unwrap_with_context(result, type_name)
|
||||
let result = self.get_or_create_path_handle_internal(
|
||||
path,
|
||||
Some(type_id),
|
||||
loading_mode,
|
||||
meta_transform,
|
||||
);
|
||||
// it is ok to unwrap because TypeId was specified above
|
||||
unwrap_with_context(result, type_name).unwrap()
|
||||
}
|
||||
|
||||
/// Retrieves asset tracking data, or creates it if it doesn't exist.
|
||||
/// Returns true if an asset load should be kicked off
|
||||
pub fn get_or_create_path_handle_internal(
|
||||
pub(crate) fn get_or_create_path_handle_internal(
|
||||
&mut self,
|
||||
path: AssetPath<'static>,
|
||||
type_id: TypeId,
|
||||
type_id: Option<TypeId>,
|
||||
loading_mode: HandleLoadingMode,
|
||||
meta_transform: Option<MetaTransform>,
|
||||
) -> Result<(UntypedHandle, bool), MissingHandleProviderError> {
|
||||
) -> Result<(UntypedHandle, bool), GetOrCreateHandleInternalError> {
|
||||
match self.path_to_id.entry(path.clone()) {
|
||||
Entry::Occupied(entry) => {
|
||||
let id = *entry.get();
|
||||
@ -211,6 +219,9 @@ impl AssetInfos {
|
||||
// We must create a new strong handle for the existing id and ensure that the drop of the old
|
||||
// strong handle doesn't remove the asset from the Assets collection
|
||||
info.handle_drops_to_skip += 1;
|
||||
let type_id = type_id.ok_or(
|
||||
GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified,
|
||||
)?;
|
||||
let provider = self
|
||||
.handle_providers
|
||||
.get(&type_id)
|
||||
@ -227,6 +238,8 @@ impl AssetInfos {
|
||||
HandleLoadingMode::NotLoading => false,
|
||||
HandleLoadingMode::Request | HandleLoadingMode::Force => true,
|
||||
};
|
||||
let type_id = type_id
|
||||
.ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?;
|
||||
let handle = Self::create_handle_internal(
|
||||
&mut self.infos,
|
||||
&self.handle_providers,
|
||||
@ -247,6 +260,10 @@ impl AssetInfos {
|
||||
self.infos.get(&id)
|
||||
}
|
||||
|
||||
pub(crate) fn contains_key(&self, id: UntypedAssetId) -> bool {
|
||||
self.infos.contains_key(&id)
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut(&mut self, id: UntypedAssetId) -> Option<&mut AssetInfo> {
|
||||
self.infos.get_mut(&id)
|
||||
}
|
||||
@ -624,13 +641,23 @@ pub(crate) enum HandleLoadingMode {
|
||||
#[error("Cannot allocate a handle because no handle provider exists for asset type {0:?}")]
|
||||
pub struct MissingHandleProviderError(TypeId);
|
||||
|
||||
fn unwrap_with_context<T>(
|
||||
result: Result<T, MissingHandleProviderError>,
|
||||
/// An error encountered during [`AssetInfos::get_or_create_path_handle_internal`].
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum GetOrCreateHandleInternalError {
|
||||
#[error(transparent)]
|
||||
MissingHandleProviderError(#[from] MissingHandleProviderError),
|
||||
#[error("Handle does not exist but TypeId was not specified.")]
|
||||
HandleMissingButTypeIdNotSpecified,
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_with_context<T>(
|
||||
result: Result<T, GetOrCreateHandleInternalError>,
|
||||
type_name: &'static str,
|
||||
) -> T {
|
||||
) -> Option<T> {
|
||||
match result {
|
||||
Ok(value) => value,
|
||||
Err(_) => {
|
||||
Ok(value) => Some(value),
|
||||
Err(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified) => None,
|
||||
Err(GetOrCreateHandleInternalError::MissingHandleProviderError(_)) => {
|
||||
panic!("Cannot allocate an Asset Handle of type '{type_name}' because the asset type has not been initialized. \
|
||||
Make sure you have called app.init_asset::<{type_name}>()")
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ use crate::{
|
||||
MetaTransform, Settings,
|
||||
},
|
||||
path::AssetPath,
|
||||
Asset, AssetEvent, AssetHandleProvider, AssetId, Assets, DeserializeMetaError,
|
||||
Asset, AssetEvent, AssetHandleProvider, AssetId, AssetMetaCheck, Assets, DeserializeMetaError,
|
||||
ErasedLoadedAsset, Handle, LoadedUntypedAsset, UntypedAssetId, UntypedHandle,
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
@ -54,6 +54,7 @@ pub(crate) struct AssetServerData {
|
||||
asset_event_receiver: Receiver<InternalAssetEvent>,
|
||||
sources: AssetSources,
|
||||
mode: AssetServerMode,
|
||||
meta_check: AssetMetaCheck,
|
||||
}
|
||||
|
||||
/// The "asset mode" the server is currently in.
|
||||
@ -69,13 +70,37 @@ impl AssetServer {
|
||||
/// Create a new instance of [`AssetServer`]. If `watch_for_changes` is true, the [`AssetReader`] storage will watch for changes to
|
||||
/// asset sources and hot-reload them.
|
||||
pub fn new(sources: AssetSources, mode: AssetServerMode, watching_for_changes: bool) -> Self {
|
||||
Self::new_with_loaders(sources, Default::default(), mode, watching_for_changes)
|
||||
Self::new_with_loaders(
|
||||
sources,
|
||||
Default::default(),
|
||||
mode,
|
||||
AssetMetaCheck::Always,
|
||||
watching_for_changes,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new instance of [`AssetServer`]. If `watch_for_changes` is true, the [`AssetReader`] storage will watch for changes to
|
||||
/// asset sources and hot-reload them.
|
||||
pub fn new_with_meta_check(
|
||||
sources: AssetSources,
|
||||
mode: AssetServerMode,
|
||||
meta_check: AssetMetaCheck,
|
||||
watching_for_changes: bool,
|
||||
) -> Self {
|
||||
Self::new_with_loaders(
|
||||
sources,
|
||||
Default::default(),
|
||||
mode,
|
||||
meta_check,
|
||||
watching_for_changes,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_loaders(
|
||||
sources: AssetSources,
|
||||
loaders: Arc<RwLock<AssetLoaders>>,
|
||||
mode: AssetServerMode,
|
||||
meta_check: AssetMetaCheck,
|
||||
watching_for_changes: bool,
|
||||
) -> Self {
|
||||
let (asset_event_sender, asset_event_receiver) = crossbeam_channel::unbounded();
|
||||
@ -85,6 +110,7 @@ impl AssetServer {
|
||||
data: Arc::new(AssetServerData {
|
||||
sources,
|
||||
mode,
|
||||
meta_check,
|
||||
asset_event_sender,
|
||||
asset_event_receiver,
|
||||
loaders,
|
||||
@ -280,8 +306,11 @@ impl AssetServer {
|
||||
handle
|
||||
}
|
||||
|
||||
/// Asynchronously load an asset that you do not know the type of statically. If you _do_ know the type of the asset,
|
||||
/// you should use [`AssetServer::load`]. If you don't know the type of the asset, but you can't use an async method,
|
||||
/// consider using [`AssetServer::load_untyped`].
|
||||
#[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
|
||||
pub(crate) async fn load_untyped_async<'a>(
|
||||
pub async fn load_untyped_async<'a>(
|
||||
&self,
|
||||
path: impl Into<AssetPath<'a>>,
|
||||
) -> Result<UntypedHandle, AssetLoadError> {
|
||||
@ -346,7 +375,10 @@ impl AssetServer {
|
||||
)
|
||||
.into(),
|
||||
}),
|
||||
Err(_) => server.send_asset_event(InternalAssetEvent::Failed { id }),
|
||||
Err(err) => {
|
||||
error!("{err}");
|
||||
server.send_asset_event(InternalAssetEvent::Failed { id });
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@ -379,35 +411,53 @@ impl AssetServer {
|
||||
e
|
||||
})?;
|
||||
|
||||
let (handle, should_load) = match input_handle {
|
||||
// This contains Some(UntypedHandle), if it was retrievable
|
||||
// If it is None, that is because it was _not_ retrievable, due to
|
||||
// 1. The handle was not already passed in for this path, meaning we can't just use that
|
||||
// 2. The asset has not been loaded yet, meaning there is no existing Handle for it
|
||||
// 3. The path has a label, meaning the AssetLoader's root asset type is not the path's asset type
|
||||
//
|
||||
// In the None case, the only course of action is to wait for the asset to load so we can allocate the
|
||||
// handle for that type.
|
||||
//
|
||||
// TODO: Note that in the None case, multiple asset loads for the same path can happen at the same time
|
||||
// (rather than "early out-ing" in the "normal" case)
|
||||
// This would be resolved by a universal asset id, as we would not need to resolve the asset type
|
||||
// to generate the ID. See this issue: https://github.com/bevyengine/bevy/issues/10549
|
||||
let handle_result = match input_handle {
|
||||
Some(handle) => {
|
||||
// if a handle was passed in, the "should load" check was already done
|
||||
(handle, true)
|
||||
Some((handle, true))
|
||||
}
|
||||
None => {
|
||||
let mut infos = self.data.infos.write();
|
||||
infos.get_or_create_path_handle_untyped(
|
||||
let result = infos.get_or_create_path_handle_internal(
|
||||
path.clone(),
|
||||
loader.asset_type_id(),
|
||||
loader.asset_type_name(),
|
||||
path.label().is_none().then(|| loader.asset_type_id()),
|
||||
HandleLoadingMode::Request,
|
||||
meta_transform,
|
||||
)
|
||||
);
|
||||
unwrap_with_context(result, loader.asset_type_name())
|
||||
}
|
||||
};
|
||||
|
||||
if path.label().is_none() && handle.type_id() != loader.asset_type_id() {
|
||||
return Err(AssetLoadError::RequestedHandleTypeMismatch {
|
||||
path: path.into_owned(),
|
||||
requested: handle.type_id(),
|
||||
actual_asset_name: loader.asset_type_name(),
|
||||
loader_name: loader.type_name(),
|
||||
});
|
||||
}
|
||||
|
||||
if !should_load && !force {
|
||||
return Ok(handle);
|
||||
}
|
||||
let handle = if let Some((handle, should_load)) = handle_result {
|
||||
if path.label().is_none() && handle.type_id() != loader.asset_type_id() {
|
||||
return Err(AssetLoadError::RequestedHandleTypeMismatch {
|
||||
path: path.into_owned(),
|
||||
requested: handle.type_id(),
|
||||
actual_asset_name: loader.asset_type_name(),
|
||||
loader_name: loader.type_name(),
|
||||
});
|
||||
}
|
||||
if !should_load && !force {
|
||||
return Ok(handle);
|
||||
}
|
||||
Some(handle)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// if the handle result is None, we definitely need to load the asset
|
||||
|
||||
let (base_handle, base_path) = if path.label().is_some() {
|
||||
let mut infos = self.data.infos.write();
|
||||
@ -421,7 +471,7 @@ impl AssetServer {
|
||||
);
|
||||
(base_handle, base_path)
|
||||
} else {
|
||||
(handle.clone(), path.clone())
|
||||
(handle.clone().unwrap(), path.clone())
|
||||
};
|
||||
|
||||
if let Some(meta_transform) = base_handle.meta_transform() {
|
||||
@ -432,26 +482,24 @@ impl AssetServer {
|
||||
.load_with_meta_loader_and_reader(&base_path, meta, &*loader, &mut *reader, true, false)
|
||||
.await
|
||||
{
|
||||
Ok(mut loaded_asset) => {
|
||||
if let Some(label) = path.label_cow() {
|
||||
if !loaded_asset.labeled_assets.contains_key(&label) {
|
||||
return Err(AssetLoadError::MissingLabel {
|
||||
base_path,
|
||||
label: label.to_string(),
|
||||
});
|
||||
Ok(loaded_asset) => {
|
||||
let final_handle = if let Some(label) = path.label_cow() {
|
||||
match loaded_asset.labeled_assets.get(&label) {
|
||||
Some(labeled_asset) => labeled_asset.handle.clone(),
|
||||
None => {
|
||||
return Err(AssetLoadError::MissingLabel {
|
||||
base_path,
|
||||
label: label.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (_, labeled_asset) in loaded_asset.labeled_assets.drain() {
|
||||
self.send_asset_event(InternalAssetEvent::Loaded {
|
||||
id: labeled_asset.handle.id(),
|
||||
loaded_asset: labeled_asset.asset,
|
||||
});
|
||||
}
|
||||
self.send_asset_event(InternalAssetEvent::Loaded {
|
||||
id: base_handle.id(),
|
||||
loaded_asset,
|
||||
});
|
||||
Ok(handle)
|
||||
} else {
|
||||
// if the path does not have a label, the handle must exist at this point
|
||||
handle.unwrap()
|
||||
};
|
||||
|
||||
self.send_loaded_asset(base_handle.id(), loaded_asset);
|
||||
Ok(final_handle)
|
||||
}
|
||||
Err(err) => {
|
||||
self.send_asset_event(InternalAssetEvent::Failed {
|
||||
@ -462,6 +510,16 @@ impl AssetServer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends a load event for the given `loaded_asset` and does the same recursively for all
|
||||
/// labeled assets.
|
||||
fn send_loaded_asset(&self, id: UntypedAssetId, mut loaded_asset: ErasedLoadedAsset) {
|
||||
for (_, labeled_asset) in loaded_asset.labeled_assets.drain() {
|
||||
self.send_loaded_asset(labeled_asset.handle.id(), labeled_asset.asset);
|
||||
}
|
||||
|
||||
self.send_asset_event(InternalAssetEvent::Loaded { id, loaded_asset });
|
||||
}
|
||||
|
||||
/// Kicks off a reload of the asset stored at the given path. This will only reload the asset if it currently loaded.
|
||||
pub fn reload<'a>(&self, path: impl Into<AssetPath<'a>>) {
|
||||
let server = self.clone();
|
||||
@ -623,7 +681,10 @@ impl AssetServer {
|
||||
)
|
||||
.into(),
|
||||
}),
|
||||
Err(_) => server.send_asset_event(InternalAssetEvent::Failed { id }),
|
||||
Err(err) => {
|
||||
error!("Failed to load folder. {err}");
|
||||
server.send_asset_event(InternalAssetEvent::Failed { id });
|
||||
},
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@ -646,6 +707,9 @@ impl AssetServer {
|
||||
}
|
||||
|
||||
/// Retrieves the main [`LoadState`] of a given asset `id`.
|
||||
///
|
||||
/// Note that this is "just" the root asset load state. To check if an asset _and_ its recursive
|
||||
/// dependencies have loaded, see [`AssetServer::is_loaded_with_dependencies`].
|
||||
pub fn get_load_state(&self, id: impl Into<UntypedAssetId>) -> Option<LoadState> {
|
||||
self.data.infos.read().get(id.into()).map(|i| i.load_state)
|
||||
}
|
||||
@ -676,6 +740,13 @@ impl AssetServer {
|
||||
.unwrap_or(RecursiveDependencyLoadState::NotLoaded)
|
||||
}
|
||||
|
||||
/// Returns true if the asset and all of its dependencies (recursive) have been loaded.
|
||||
pub fn is_loaded_with_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
|
||||
let id = id.into();
|
||||
self.load_state(id) == LoadState::Loaded
|
||||
&& self.recursive_dependency_load_state(id) == RecursiveDependencyLoadState::Loaded
|
||||
}
|
||||
|
||||
/// Returns an active handle for the given path, if the asset at the given path has already started loading,
|
||||
/// or is still "alive".
|
||||
pub fn get_handle<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Option<Handle<A>> {
|
||||
@ -691,6 +762,12 @@ impl AssetServer {
|
||||
self.data.infos.read().get_id_handle(id)
|
||||
}
|
||||
|
||||
/// Returns `true` if the given `id` corresponds to an asset that is managed by this [`AssetServer`].
|
||||
/// Otherwise, returns false.
|
||||
pub fn is_managed(&self, id: impl Into<UntypedAssetId>) -> bool {
|
||||
self.data.infos.read().contains_key(id.into())
|
||||
}
|
||||
|
||||
/// Returns an active untyped handle for the given path, if the asset at the given path has already started loading,
|
||||
/// or is still "alive".
|
||||
pub fn get_handle_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedHandle> {
|
||||
@ -776,44 +853,57 @@ impl AssetServer {
|
||||
AssetServerMode::Processed { .. } => source.processed_reader()?,
|
||||
};
|
||||
let reader = asset_reader.read(asset_path.path()).await?;
|
||||
match asset_reader.read_meta_bytes(asset_path.path()).await {
|
||||
Ok(meta_bytes) => {
|
||||
// TODO: this isn't fully minimal yet. we only need the loader
|
||||
let minimal: AssetMetaMinimal = ron::de::from_bytes(&meta_bytes).map_err(|e| {
|
||||
AssetLoadError::DeserializeMeta {
|
||||
path: asset_path.clone_owned(),
|
||||
error: Box::new(DeserializeMetaError::DeserializeMinimal(e)),
|
||||
}
|
||||
})?;
|
||||
let loader_name = match minimal.asset {
|
||||
AssetActionMinimal::Load { loader } => loader,
|
||||
AssetActionMinimal::Process { .. } => {
|
||||
return Err(AssetLoadError::CannotLoadProcessedAsset {
|
||||
path: asset_path.clone_owned(),
|
||||
})
|
||||
}
|
||||
AssetActionMinimal::Ignore => {
|
||||
return Err(AssetLoadError::CannotLoadIgnoredAsset {
|
||||
path: asset_path.clone_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
|
||||
let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
|
||||
AssetLoadError::DeserializeMeta {
|
||||
path: asset_path.clone_owned(),
|
||||
error: Box::new(e),
|
||||
}
|
||||
})?;
|
||||
let read_meta = match &self.data.meta_check {
|
||||
AssetMetaCheck::Always => true,
|
||||
AssetMetaCheck::Paths(paths) => paths.contains(asset_path),
|
||||
AssetMetaCheck::Never => false,
|
||||
};
|
||||
|
||||
Ok((meta, loader, reader))
|
||||
if read_meta {
|
||||
match asset_reader.read_meta_bytes(asset_path.path()).await {
|
||||
Ok(meta_bytes) => {
|
||||
// TODO: this isn't fully minimal yet. we only need the loader
|
||||
let minimal: AssetMetaMinimal =
|
||||
ron::de::from_bytes(&meta_bytes).map_err(|e| {
|
||||
AssetLoadError::DeserializeMeta {
|
||||
path: asset_path.clone_owned(),
|
||||
error: Box::new(DeserializeMetaError::DeserializeMinimal(e)),
|
||||
}
|
||||
})?;
|
||||
let loader_name = match minimal.asset {
|
||||
AssetActionMinimal::Load { loader } => loader,
|
||||
AssetActionMinimal::Process { .. } => {
|
||||
return Err(AssetLoadError::CannotLoadProcessedAsset {
|
||||
path: asset_path.clone_owned(),
|
||||
})
|
||||
}
|
||||
AssetActionMinimal::Ignore => {
|
||||
return Err(AssetLoadError::CannotLoadIgnoredAsset {
|
||||
path: asset_path.clone_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
|
||||
let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
|
||||
AssetLoadError::DeserializeMeta {
|
||||
path: asset_path.clone_owned(),
|
||||
error: Box::new(e),
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok((meta, loader, reader))
|
||||
}
|
||||
Err(AssetReaderError::NotFound(_)) => {
|
||||
let loader = self.get_path_asset_loader(asset_path).await?;
|
||||
let meta = loader.default_meta();
|
||||
Ok((meta, loader, reader))
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
Err(AssetReaderError::NotFound(_)) => {
|
||||
let loader = self.get_path_asset_loader(asset_path).await?;
|
||||
let meta = loader.default_meta();
|
||||
Ok((meta, loader, reader))
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
} else {
|
||||
let loader = self.get_path_asset_loader(asset_path).await?;
|
||||
let meta = loader.default_meta();
|
||||
Ok((meta, loader, reader))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_audio"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides audio functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -10,14 +10,14 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
rodio = { version = "0.17", default-features = false }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_core"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides core functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -11,12 +11,12 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0", features = ["bevy_reflect"] }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1", features = ["bevy_reflect"] }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1", features = ["bevy_reflect"] }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
bytemuck = "1.5"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_core_pipeline"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"Bevy Contributors <bevyengine@gmail.com>",
|
||||
@ -19,17 +19,17 @@ tonemapping_luts = ["bevy_render/ktx2", "bevy_render/zstd"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.0" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.0" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.1" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
bitflags = "2.3"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_derive"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides derive implementations for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.12.0" }
|
||||
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.12.1" }
|
||||
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_diagnostic"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides diagnostic functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -14,12 +14,12 @@ dynamic_linking = []
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.0" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.1" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
|
||||
# MacOS
|
||||
[target.'cfg(all(target_os="macos"))'.dependencies]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_dylib"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Force the Bevy Engine to be dynamically linked for faster linking"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -12,4 +12,4 @@ keywords = ["bevy"]
|
||||
crate-type = ["dylib"]
|
||||
|
||||
[dependencies]
|
||||
bevy_internal = { path = "../bevy_internal", version = "0.12.0", default-features = false }
|
||||
bevy_internal = { path = "../bevy_internal", version = "0.12.1", default-features = false }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_dynamic_plugin"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides dynamic plugin loading capabilities for non-wasm platforms"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -10,7 +10,7 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
libloading = { version = "0.8" }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_ecs"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Bevy Engine's entity component system"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -15,11 +15,11 @@ multi-threaded = ["bevy_tasks/multi-threaded"]
|
||||
default = ["bevy_reflect"]
|
||||
|
||||
[dependencies]
|
||||
bevy_ptr = { path = "../bevy_ptr", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", optional = true }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_ecs_macros = { path = "macros", version = "0.12.0" }
|
||||
bevy_ptr = { path = "../bevy_ptr", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", optional = true }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_ecs_macros = { path = "macros", version = "0.12.1" }
|
||||
|
||||
async-channel = "1.4"
|
||||
event-listener = "2.5"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_ecs_macros"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
description = "Bevy ECS Macros"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.0" }
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.1" }
|
||||
|
||||
syn = "2.0"
|
||||
quote = "1.0"
|
||||
|
@ -749,8 +749,30 @@ impl<'a, E: Event> ExactSizeIterator for EventIteratorWithId<'a, E> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A system that calls [`Events::update`] once per frame.
|
||||
pub fn event_update_system<T: Event>(mut events: ResMut<Events<T>>) {
|
||||
#[doc(hidden)]
|
||||
#[derive(Resource, Default)]
|
||||
pub struct EventUpdateSignal(bool);
|
||||
|
||||
/// A system that queues a call to [`Events::update`].
|
||||
pub fn event_queue_update_system(signal: Option<ResMut<EventUpdateSignal>>) {
|
||||
if let Some(mut s) = signal {
|
||||
s.0 = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// A system that calls [`Events::update`].
|
||||
pub fn event_update_system<T: Event>(
|
||||
signal: Option<ResMut<EventUpdateSignal>>,
|
||||
mut events: ResMut<Events<T>>,
|
||||
) {
|
||||
if let Some(mut s) = signal {
|
||||
// If we haven't got a signal to update the events, but we *could* get such a signal
|
||||
// return early and update the events later.
|
||||
if !std::mem::replace(&mut s.0, false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
events.update();
|
||||
}
|
||||
|
||||
|
@ -189,15 +189,18 @@ fn insert_reflect(
|
||||
type_registry: &TypeRegistry,
|
||||
component: Box<dyn Reflect>,
|
||||
) {
|
||||
let type_info = component.reflect_type_path();
|
||||
let type_info = component
|
||||
.get_represented_type_info()
|
||||
.expect("component should represent a type.");
|
||||
let type_path = type_info.type_path();
|
||||
let Some(mut entity) = world.get_entity_mut(entity) else {
|
||||
panic!("error[B0003]: Could not insert a reflected component (of type {}) for entity {entity:?} because it doesn't exist in this World.", component.reflect_type_path());
|
||||
panic!("error[B0003]: Could not insert a reflected component (of type {type_path}) for entity {entity:?} because it doesn't exist in this World.");
|
||||
};
|
||||
let Some(type_registration) = type_registry.get_with_type_path(type_info) else {
|
||||
panic!("Could not get type registration (for component type {}) because it doesn't exist in the TypeRegistry.", component.reflect_type_path());
|
||||
let Some(type_registration) = type_registry.get_with_type_path(type_path) else {
|
||||
panic!("Could not get type registration (for component type {type_path}) because it doesn't exist in the TypeRegistry.");
|
||||
};
|
||||
let Some(reflect_component) = type_registration.data::<ReflectComponent>() else {
|
||||
panic!("Could not get ReflectComponent data (for component type {}) because it doesn't exist in this TypeRegistration.", component.reflect_type_path());
|
||||
panic!("Could not get ReflectComponent data (for component type {type_path}) because it doesn't exist in this TypeRegistration.");
|
||||
};
|
||||
reflect_component.insert(&mut entity, &*component);
|
||||
}
|
||||
@ -346,17 +349,22 @@ mod tests {
|
||||
let mut commands = system_state.get_mut(&mut world);
|
||||
|
||||
let entity = commands.spawn_empty().id();
|
||||
let entity2 = commands.spawn_empty().id();
|
||||
|
||||
let boxed_reflect_component_a = Box::new(ComponentA(916)) as Box<dyn Reflect>;
|
||||
let boxed_reflect_component_a_clone = boxed_reflect_component_a.clone_value();
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert_reflect(boxed_reflect_component_a);
|
||||
commands
|
||||
.entity(entity2)
|
||||
.insert_reflect(boxed_reflect_component_a_clone);
|
||||
system_state.apply(&mut world);
|
||||
|
||||
assert_eq!(
|
||||
world.entity(entity).get::<ComponentA>(),
|
||||
Some(&ComponentA(916))
|
||||
world.entity(entity2).get::<ComponentA>()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,11 @@ struct CommandMeta {
|
||||
/// SAFETY: The `value` must point to a value of type `T: Command`,
|
||||
/// where `T` is some specific type that was used to produce this metadata.
|
||||
///
|
||||
/// `world` is optional to allow this one function pointer to perform double-duty as a drop.
|
||||
///
|
||||
/// Returns the size of `T` in bytes.
|
||||
apply_command_and_get_size: unsafe fn(value: OwningPtr<Unaligned>, world: &mut World) -> usize,
|
||||
consume_command_and_get_size:
|
||||
unsafe fn(value: OwningPtr<Unaligned>, world: &mut Option<&mut World>) -> usize,
|
||||
}
|
||||
|
||||
/// Densely and efficiently stores a queue of heterogenous types implementing [`Command`].
|
||||
@ -53,11 +56,16 @@ impl CommandQueue {
|
||||
}
|
||||
|
||||
let meta = CommandMeta {
|
||||
apply_command_and_get_size: |command, world| {
|
||||
// SAFETY: According to the invariants of `CommandMeta.apply_command_and_get_size`,
|
||||
consume_command_and_get_size: |command, world| {
|
||||
// SAFETY: According to the invariants of `CommandMeta.consume_command_and_get_size`,
|
||||
// `command` must point to a value of type `C`.
|
||||
let command: C = unsafe { command.read_unaligned() };
|
||||
command.apply(world);
|
||||
match world {
|
||||
// Apply command to the provided world...
|
||||
Some(world) => command.apply(world),
|
||||
// ...or discard it.
|
||||
None => drop(command),
|
||||
}
|
||||
std::mem::size_of::<C>()
|
||||
},
|
||||
};
|
||||
@ -97,18 +105,26 @@ impl CommandQueue {
|
||||
// flush the previously queued entities
|
||||
world.flush();
|
||||
|
||||
// Pointer that will iterate over the entries of the buffer.
|
||||
let mut cursor = self.bytes.as_mut_ptr();
|
||||
self.apply_or_drop_queued(Some(world));
|
||||
}
|
||||
|
||||
// The address of the end of the buffer.
|
||||
let end_addr = cursor as usize + self.bytes.len();
|
||||
/// If `world` is [`Some`], this will apply the queued [commands](`Command`).
|
||||
/// If `world` is [`None`], this will drop the queued [commands](`Command`) (without applying them).
|
||||
/// This clears the queue.
|
||||
#[inline]
|
||||
fn apply_or_drop_queued(&mut self, mut world: Option<&mut World>) {
|
||||
// The range of pointers of the filled portion of `self.bytes`.
|
||||
let bytes_range = self.bytes.as_mut_ptr_range();
|
||||
|
||||
// Pointer that will iterate over the entries of the buffer.
|
||||
let mut cursor = bytes_range.start;
|
||||
|
||||
// Reset the buffer, so it can be reused after this function ends.
|
||||
// In the loop below, ownership of each command will be transferred into user code.
|
||||
// SAFETY: `set_len(0)` is always valid.
|
||||
unsafe { self.bytes.set_len(0) };
|
||||
|
||||
while (cursor as usize) < end_addr {
|
||||
while cursor < bytes_range.end {
|
||||
// SAFETY: The cursor is either at the start of the buffer, or just after the previous command.
|
||||
// Since we know that the cursor is in bounds, it must point to the start of a new command.
|
||||
let meta = unsafe { cursor.cast::<CommandMeta>().read_unaligned() };
|
||||
@ -127,7 +143,7 @@ impl CommandQueue {
|
||||
// SAFETY: The data underneath the cursor must correspond to the type erased in metadata,
|
||||
// since they were stored next to each other by `.push()`.
|
||||
// For ZSTs, the type doesn't matter as long as the pointer is non-null.
|
||||
let size = unsafe { (meta.apply_command_and_get_size)(cmd, world) };
|
||||
let size = unsafe { (meta.consume_command_and_get_size)(cmd, &mut world) };
|
||||
// Advance the cursor past the command. For ZSTs, the cursor will not move.
|
||||
// At this point, it will either point to the next `CommandMeta`,
|
||||
// or the cursor will be out of bounds and the loop will end.
|
||||
@ -138,6 +154,12 @@ impl CommandQueue {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CommandQueue {
|
||||
fn drop(&mut self) {
|
||||
self.apply_or_drop_queued(None);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@ -188,6 +210,27 @@ mod test {
|
||||
assert_eq!(drops_b.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
/// Asserts that inner [commands](`Command`) are dropped on early drop of [`CommandQueue`].
|
||||
/// Originally identified as an issue in [#10676](https://github.com/bevyengine/bevy/issues/10676)
|
||||
#[test]
|
||||
fn test_command_queue_inner_drop_early() {
|
||||
let mut queue = CommandQueue::default();
|
||||
|
||||
let (dropcheck_a, drops_a) = DropCheck::new();
|
||||
let (dropcheck_b, drops_b) = DropCheck::new();
|
||||
|
||||
queue.push(dropcheck_a);
|
||||
queue.push(dropcheck_b);
|
||||
|
||||
assert_eq!(drops_a.load(Ordering::Relaxed), 0);
|
||||
assert_eq!(drops_b.load(Ordering::Relaxed), 0);
|
||||
|
||||
drop(queue);
|
||||
|
||||
assert_eq!(drops_a.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(drops_b.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
struct SpawnCommand;
|
||||
|
||||
impl Command for SpawnCommand {
|
||||
|
@ -55,10 +55,18 @@ impl World {
|
||||
&mut self,
|
||||
system: S,
|
||||
) -> SystemId {
|
||||
self.register_boxed_system(Box::new(IntoSystem::into_system(system)))
|
||||
}
|
||||
|
||||
/// Similar to [`Self::register_system`], but allows passing in a [`BoxedSystem`].
|
||||
///
|
||||
/// This is useful if the [`IntoSystem`] implementor has already been turned into a
|
||||
/// [`System`](crate::system::System) trait object and put in a [`Box`].
|
||||
pub fn register_boxed_system(&mut self, system: BoxedSystem) -> SystemId {
|
||||
SystemId(
|
||||
self.spawn(RegisteredSystem {
|
||||
initialized: false,
|
||||
system: Box::new(IntoSystem::into_system(system)),
|
||||
system,
|
||||
})
|
||||
.id(),
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_encase_derive"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Bevy derive macro for encase"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -12,5 +12,5 @@ keywords = ["bevy"]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.12.0" }
|
||||
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.12.1" }
|
||||
encase_derive_impl = "0.6.1"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_gilrs"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Gamepad system made using Gilrs for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -10,12 +10,12 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.12.0" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.12.1" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
gilrs = "0.10.1"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_gizmos"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides gizmos for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -13,15 +13,15 @@ webgl = []
|
||||
|
||||
[dependencies]
|
||||
# Bevy
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.12.0", optional = true }
|
||||
bevy_sprite = { path = "../bevy_sprite", version = "0.12.0", optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.12.1", optional = true }
|
||||
bevy_sprite = { path = "../bevy_sprite", version = "0.12.1", optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
|
@ -28,6 +28,8 @@ struct VertexOutput {
|
||||
@location(0) color: vec4<f32>,
|
||||
};
|
||||
|
||||
const EPSILON: f32 = 4.88e-04;
|
||||
|
||||
@vertex
|
||||
fn vertex(vertex: VertexInput) -> VertexOutput {
|
||||
var positions = array<vec3<f32>, 6>(
|
||||
@ -79,7 +81,6 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
|
||||
if line_gizmo.depth_bias >= 0. {
|
||||
depth = clip.z * (1. - line_gizmo.depth_bias);
|
||||
} else {
|
||||
let epsilon = 4.88e-04;
|
||||
// depth * (clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to clip.w
|
||||
// and when equal to 0.0, it is exactly equal to depth.
|
||||
// the epsilon is here to prevent the depth from exceeding clip.w when -depth_bias = 1.0
|
||||
@ -87,7 +88,7 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
|
||||
// of this value means nothing can be in front of this
|
||||
// The reason this uses an exponential function is that it makes it much easier for the
|
||||
// user to chose a value that is convenient for them
|
||||
depth = clip.z * exp2(-line_gizmo.depth_bias * log2(clip.w / clip.z - epsilon));
|
||||
depth = clip.z * exp2(-line_gizmo.depth_bias * log2(clip.w / clip.z - EPSILON));
|
||||
}
|
||||
|
||||
var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w);
|
||||
@ -101,8 +102,10 @@ fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
|
||||
// Interpolate a towards b until it's at the near plane.
|
||||
let distance_a = a.z - a.w;
|
||||
let distance_b = b.z - b.w;
|
||||
let t = distance_a / (distance_a - distance_b);
|
||||
return a + (b - a) * t;
|
||||
// Add an epsilon to the interpolator to ensure that the point is
|
||||
// not just behind the clip plane due to floating-point imprecision.
|
||||
let t = distance_a / (distance_a - distance_b) + EPSILON;
|
||||
return mix(a, b, t);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_gltf"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Bevy Engine GLTF loading"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -13,22 +13,22 @@ pbr_transmission_textures = []
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_animation = { path = "../bevy_animation", version = "0.12.0", optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.0" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.0" }
|
||||
bevy_scene = { path = "../bevy_scene", version = "0.12.0", features = ["bevy_render"] }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_animation = { path = "../bevy_animation", version = "0.12.1", optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.1" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.1" }
|
||||
bevy_scene = { path = "../bevy_scene", version = "0.12.1", features = ["bevy_render"] }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
gltf = { version = "1.3.0", default-features = false, features = [
|
||||
|
@ -545,7 +545,7 @@ async fn load_gltf<'a, 'b, 'c>(
|
||||
let mut world = World::default();
|
||||
let mut node_index_to_entity_map = HashMap::new();
|
||||
let mut entity_to_skin_index_map = HashMap::new();
|
||||
|
||||
let mut scene_load_context = load_context.begin_labeled_asset();
|
||||
world
|
||||
.spawn(SpatialBundle::INHERITED_IDENTITY)
|
||||
.with_children(|parent| {
|
||||
@ -554,6 +554,7 @@ async fn load_gltf<'a, 'b, 'c>(
|
||||
&node,
|
||||
parent,
|
||||
load_context,
|
||||
&mut scene_load_context,
|
||||
&mut node_index_to_entity_map,
|
||||
&mut entity_to_skin_index_map,
|
||||
&mut active_camera_found,
|
||||
@ -606,8 +607,8 @@ async fn load_gltf<'a, 'b, 'c>(
|
||||
joints: joint_entities,
|
||||
});
|
||||
}
|
||||
|
||||
let scene_handle = load_context.add_labeled_asset(scene_label(&scene), Scene::new(world));
|
||||
let loaded_scene = scene_load_context.finish(Scene::new(world), None);
|
||||
let scene_handle = load_context.add_loaded_labeled_asset(scene_label(&scene), loaded_scene);
|
||||
|
||||
if let Some(name) = scene.name() {
|
||||
named_scenes.insert(name.to_string(), scene_handle.clone());
|
||||
@ -853,9 +854,11 @@ fn load_material(
|
||||
}
|
||||
|
||||
/// Loads a glTF node.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn load_node(
|
||||
gltf_node: &gltf::Node,
|
||||
world_builder: &mut WorldChildBuilder,
|
||||
root_load_context: &LoadContext,
|
||||
load_context: &mut LoadContext,
|
||||
node_index_to_entity_map: &mut HashMap<usize, Entity>,
|
||||
entity_to_skin_index_map: &mut HashMap<Entity, usize>,
|
||||
@ -942,7 +945,9 @@ fn load_node(
|
||||
// added when iterating over all the gltf materials (since the default material is
|
||||
// not explicitly listed in the gltf).
|
||||
// It also ensures an inverted scale copy is instantiated if required.
|
||||
if !load_context.has_labeled_asset(&material_label) {
|
||||
if !root_load_context.has_labeled_asset(&material_label)
|
||||
&& !load_context.has_labeled_asset(&material_label)
|
||||
{
|
||||
load_material(&material, load_context, is_scale_inverted);
|
||||
}
|
||||
|
||||
@ -1074,6 +1079,7 @@ fn load_node(
|
||||
if let Err(err) = load_node(
|
||||
&child,
|
||||
parent,
|
||||
root_load_context,
|
||||
load_context,
|
||||
node_index_to_entity_map,
|
||||
entity_to_skin_index_map,
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_hierarchy"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides hierarchy functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -13,12 +13,12 @@ trace = []
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1", features = ["bevy_reflect"] }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
smallvec = { version = "1.6", features = ["serde", "union", "const_generics"] }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_input"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides input functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -14,11 +14,11 @@ serialize = ["serde"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = [
|
||||
"glam",
|
||||
] }
|
||||
|
||||
@ -27,4 +27,4 @@ serde = { version = "1", features = ["derive"], optional = true }
|
||||
thiserror = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
bevy = { path = "../../", version = "0.12.0" }
|
||||
bevy = { path = "../../", version = "0.12.1" }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_internal"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "An internal Bevy crate used to facilitate optional dynamic linking via the 'dynamic_linking' feature"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -120,36 +120,36 @@ embedded_watcher = ["bevy_asset?/embedded_watcher"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_a11y = { path = "../bevy_a11y", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.0" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.12.0" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_ptr = { path = "../bevy_ptr", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_time = { path = "../bevy_time", version = "0.12.0" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.0" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
|
||||
bevy_a11y = { path = "../bevy_a11y", version = "0.12.1" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.1" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.12.1" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_ptr = { path = "../bevy_ptr", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
bevy_time = { path = "../bevy_time", version = "0.12.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.1" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
|
||||
# bevy (optional)
|
||||
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.12.0" }
|
||||
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.12.0" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.12.0" }
|
||||
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.12.0" }
|
||||
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.12.0" }
|
||||
bevy_render = { path = "../bevy_render", optional = true, version = "0.12.0" }
|
||||
bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.12.0" }
|
||||
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.12.0" }
|
||||
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.12.0" }
|
||||
bevy_text = { path = "../bevy_text", optional = true, version = "0.12.0" }
|
||||
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.12.0" }
|
||||
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.12.0" }
|
||||
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.12.0" }
|
||||
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.12.0", default-features = false }
|
||||
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.12.1" }
|
||||
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.12.1" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.12.1" }
|
||||
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.12.1" }
|
||||
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.12.1" }
|
||||
bevy_render = { path = "../bevy_render", optional = true, version = "0.12.1" }
|
||||
bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.12.1" }
|
||||
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.12.1" }
|
||||
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.12.1" }
|
||||
bevy_text = { path = "../bevy_text", optional = true, version = "0.12.1" }
|
||||
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.12.1" }
|
||||
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.12.1" }
|
||||
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.12.1" }
|
||||
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.12.1", default-features = false }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_log"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides logging for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -13,9 +13,9 @@ trace = [ "tracing-error" ]
|
||||
trace_tracy_memory = ["dep:tracy-client"]
|
||||
|
||||
[dependencies]
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
|
||||
tracing-subscriber = {version = "0.3.1", features = ["registry", "env-filter"]}
|
||||
tracing-chrome = { version = "0.7.0", optional = true }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_macro_utils"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "A collection of utils for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_math"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides math functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_mikktspace"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
authors = ["Benjamin Wasty <benny.wasty@gmail.com>", "David Harvey-Macaulay <alteous@outlook.com>", "Layl Bongers <LaylConway@users.noreply.github.com>"]
|
||||
description = "Mikkelsen tangent space algorithm"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_pbr"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Adds PBR rendering to Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -14,17 +14,17 @@ pbr_transmission_textures = []
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.0" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.0" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.1" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
bitflags = "2.3"
|
||||
|
@ -1,5 +1,5 @@
|
||||
use bevy_asset::{Asset, Handle};
|
||||
use bevy_reflect::TypePath;
|
||||
use bevy_reflect::{impl_type_path, Reflect};
|
||||
use bevy_render::{
|
||||
mesh::MeshVertexBufferLayout,
|
||||
render_asset::RenderAssets,
|
||||
@ -97,12 +97,17 @@ pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized {
|
||||
/// When used with `StandardMaterial` as the base, all the standard material fields are
|
||||
/// present, so the `pbr_fragment` shader functions can be called from the extension shader (see
|
||||
/// the `extended_material` example).
|
||||
#[derive(Asset, Clone, TypePath)]
|
||||
#[derive(Asset, Clone, Reflect)]
|
||||
#[reflect(type_path = false)]
|
||||
pub struct ExtendedMaterial<B: Material, E: MaterialExtension> {
|
||||
pub base: B,
|
||||
pub extension: E,
|
||||
}
|
||||
|
||||
// We don't use the `TypePath` derive here due to a bug where `#[reflect(type_path = false)]`
|
||||
// causes the `TypePath` derive to not generate an implementation.
|
||||
impl_type_path!((in bevy_pbr::extended_material) ExtendedMaterial<B: Material, E: MaterialExtension>);
|
||||
|
||||
impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
|
||||
type Data = (<B as AsBindGroup>::Data, <E as AsBindGroup>::Data);
|
||||
|
||||
|
@ -39,7 +39,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
#ifdef MORPH_TARGETS
|
||||
var vertex = morph::morph_vertex(vertex_no_morph);
|
||||
var vertex = morph_vertex(vertex_no_morph);
|
||||
#else
|
||||
var vertex = vertex_no_morph;
|
||||
#endif
|
||||
@ -107,6 +107,12 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
||||
// See https://github.com/gfx-rs/naga/issues/2416
|
||||
out.instance_index = get_instance_index(vertex_no_morph.instance_index);
|
||||
#endif
|
||||
#ifdef BASE_INSTANCE_WORKAROUND
|
||||
// Hack: this ensures the push constant is always used, which works around this issue:
|
||||
// https://github.com/bevyengine/bevy/issues/10509
|
||||
// This can be removed when wgpu 0.19 is released
|
||||
out.position.x += min(f32(get_instance_index(0u)), 0.0);
|
||||
#endif
|
||||
|
||||
return out;
|
||||
}
|
||||
|
@ -88,6 +88,13 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
||||
out.instance_index = get_instance_index(vertex_no_morph.instance_index);
|
||||
#endif
|
||||
|
||||
#ifdef BASE_INSTANCE_WORKAROUND
|
||||
// Hack: this ensures the push constant is always used, which works around this issue:
|
||||
// https://github.com/bevyengine/bevy/issues/10509
|
||||
// This can be removed when wgpu 0.19 is released
|
||||
out.position.x += min(f32(get_instance_index(0u)), 0.0);
|
||||
#endif
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
@ -53,10 +53,11 @@ fn specular_transmissive_light(world_position: vec4<f32>, frag_coord: vec3<f32>,
|
||||
}
|
||||
|
||||
fn fetch_transmissive_background_non_rough(offset_position: vec2<f32>, frag_coord: vec3<f32>) -> vec4<f32> {
|
||||
var background_color = textureSample(
|
||||
var background_color = textureSampleLevel(
|
||||
view_bindings::view_transmission_texture,
|
||||
view_bindings::view_transmission_sampler,
|
||||
offset_position,
|
||||
0.0
|
||||
);
|
||||
|
||||
#ifdef DEPTH_PREPASS
|
||||
@ -152,10 +153,11 @@ fn fetch_transmissive_background(offset_position: vec2<f32>, frag_coord: vec3<f3
|
||||
let modified_offset_position = offset_position + rotated_spiral_offset * blur_intensity * (1.0 - f32(pixel_checkboard) * 0.1);
|
||||
|
||||
// Sample the view transmission texture at the offset position + noise offset, to get the background color
|
||||
var sample = textureSample(
|
||||
var sample = textureSampleLevel(
|
||||
view_bindings::view_transmission_texture,
|
||||
view_bindings::view_transmission_sampler,
|
||||
modified_offset_position,
|
||||
0.0
|
||||
);
|
||||
|
||||
#ifdef DEPTH_PREPASS
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_ptr"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Utilities for working with untyped pointers in a more safe way"
|
||||
homepage = "https://bevyengine.org"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_reflect"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Dynamically interact with rust types"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -18,12 +18,12 @@ documentation = ["bevy_reflect_derive/documentation"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0", features = [
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1", features = [
|
||||
"serialize",
|
||||
], optional = true }
|
||||
bevy_reflect_derive = { path = "bevy_reflect_derive", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_ptr = { path = "../bevy_ptr", version = "0.12.0" }
|
||||
bevy_reflect_derive = { path = "bevy_reflect_derive", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_ptr = { path = "../bevy_ptr", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
erased-serde = "0.3"
|
||||
@ -42,6 +42,8 @@ smol_str = { version = "0.2.0", optional = true }
|
||||
ron = "0.8.0"
|
||||
rmp-serde = "1.1"
|
||||
bincode = "1.3"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
[[example]]
|
||||
name = "reflect_docs"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_reflect_derive"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Derive implementations for bevy_reflect"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -17,7 +17,7 @@ default = []
|
||||
documentation = []
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.0" }
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.1" }
|
||||
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
proc-macro2 = "1.0"
|
||||
|
@ -132,9 +132,9 @@ impl WhereClauseOptions {
|
||||
let custom_bounds = active_bounds(field).map(|bounds| quote!(+ #bounds));
|
||||
|
||||
let bounds = if is_from_reflect {
|
||||
quote!(#bevy_reflect_path::FromReflect #custom_bounds)
|
||||
quote!(#bevy_reflect_path::FromReflect + #bevy_reflect_path::TypePath #custom_bounds)
|
||||
} else {
|
||||
quote!(#bevy_reflect_path::Reflect #custom_bounds)
|
||||
quote!(#bevy_reflect_path::Reflect + #bevy_reflect_path::TypePath #custom_bounds)
|
||||
};
|
||||
|
||||
(ty, bounds)
|
||||
|
@ -317,8 +317,8 @@ impl<'a> Serialize for EnumSerializer<'a> {
|
||||
|
||||
match variant_type {
|
||||
VariantType::Unit => {
|
||||
if self.enum_value.reflect_module_path() == Some("core::option")
|
||||
&& self.enum_value.reflect_type_ident() == Some("Option")
|
||||
if type_info.type_path_table().module_path() == Some("core::option")
|
||||
&& type_info.type_path_table().ident() == Some("Option")
|
||||
{
|
||||
serializer.serialize_none()
|
||||
} else {
|
||||
@ -352,10 +352,9 @@ impl<'a> Serialize for EnumSerializer<'a> {
|
||||
}
|
||||
VariantType::Tuple if field_len == 1 => {
|
||||
let field = self.enum_value.field_at(0).unwrap();
|
||||
if self
|
||||
.enum_value
|
||||
.reflect_type_path()
|
||||
.starts_with("core::option::Option")
|
||||
|
||||
if type_info.type_path_table().module_path() == Some("core::option")
|
||||
&& type_info.type_path_table().ident() == Some("Option")
|
||||
{
|
||||
serializer.serialize_some(&TypedReflectSerializer::new(field, self.registry))
|
||||
} else {
|
||||
@ -464,8 +463,8 @@ impl<'a> Serialize for ArraySerializer<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate as bevy_reflect;
|
||||
use crate::serde::ReflectSerializer;
|
||||
use crate::{self as bevy_reflect, Struct};
|
||||
use crate::{Reflect, ReflectSerialize, TypeRegistry};
|
||||
use bevy_utils::HashMap;
|
||||
use ron::extensions::Extensions;
|
||||
@ -881,4 +880,43 @@ mod tests {
|
||||
|
||||
assert_eq!(expected, bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serialize_dynamic_option() {
|
||||
#[derive(Default, Reflect)]
|
||||
struct OtherStruct {
|
||||
some: Option<SomeStruct>,
|
||||
none: Option<SomeStruct>,
|
||||
}
|
||||
|
||||
let value = OtherStruct {
|
||||
some: Some(SomeStruct { foo: 999999999 }),
|
||||
none: None,
|
||||
};
|
||||
let dynamic = value.clone_dynamic();
|
||||
let reflect = dynamic.as_reflect();
|
||||
|
||||
let registry = get_registry();
|
||||
|
||||
let serializer = ReflectSerializer::new(reflect, ®istry);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let format = serde_json::ser::PrettyFormatter::with_indent(b" ");
|
||||
let mut ser = serde_json::Serializer::with_formatter(&mut buf, format);
|
||||
|
||||
serializer.serialize(&mut ser).unwrap();
|
||||
|
||||
let output = std::str::from_utf8(&buf).unwrap();
|
||||
let expected = r#"{
|
||||
"bevy_reflect::serde::ser::tests::OtherStruct": {
|
||||
"some": {
|
||||
"foo": 999999999
|
||||
},
|
||||
"none": null
|
||||
}
|
||||
}"#;
|
||||
|
||||
assert_eq!(expected, output);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
use bevy_reflect::Reflect;
|
||||
|
||||
mod nested_generics {
|
||||
use super::*;
|
||||
|
||||
#[derive(Reflect)]
|
||||
struct Foo<T>(T);
|
||||
|
||||
#[derive(Reflect)]
|
||||
struct Bar<T>(Foo<T>);
|
||||
|
||||
#[derive(Reflect)]
|
||||
struct Baz<T>(Bar<Foo<T>>);
|
||||
}
|
||||
|
||||
fn main() {}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_render"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides rendering functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -35,23 +35,23 @@ webgl = ["wgpu/webgl"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.0" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.12.0" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_render_macros = { path = "macros", version = "0.12.0" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.12.0" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.12.1" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.12.1" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
bevy_render_macros = { path = "macros", version = "0.12.1" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.12.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
|
||||
|
||||
# rendering
|
||||
image = { version = "0.24", default-features = false }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_render_macros"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Derive implementations for bevy_render"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.0" }
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.1" }
|
||||
|
||||
syn = "2.0"
|
||||
proc-macro2 = "1.0"
|
||||
|
@ -259,30 +259,24 @@ impl AssetLoader for ShaderLoader {
|
||||
) -> BoxedFuture<'a, Result<Shader, Self::Error>> {
|
||||
Box::pin(async move {
|
||||
let ext = load_context.path().extension().unwrap().to_str().unwrap();
|
||||
|
||||
let path = load_context.asset_path().to_string();
|
||||
// On windows, the path will inconsistently use \ or /.
|
||||
// TODO: remove this once AssetPath forces cross-platform "slash" consistency. See #10511
|
||||
let path = path.replace(std::path::MAIN_SEPARATOR, "/");
|
||||
let mut bytes = Vec::new();
|
||||
reader.read_to_end(&mut bytes).await?;
|
||||
let mut shader = match ext {
|
||||
"spv" => Shader::from_spirv(bytes, load_context.path().to_string_lossy()),
|
||||
"wgsl" => Shader::from_wgsl(
|
||||
String::from_utf8(bytes)?,
|
||||
load_context.path().to_string_lossy(),
|
||||
),
|
||||
"vert" => Shader::from_glsl(
|
||||
String::from_utf8(bytes)?,
|
||||
naga::ShaderStage::Vertex,
|
||||
load_context.path().to_string_lossy(),
|
||||
),
|
||||
"frag" => Shader::from_glsl(
|
||||
String::from_utf8(bytes)?,
|
||||
naga::ShaderStage::Fragment,
|
||||
load_context.path().to_string_lossy(),
|
||||
),
|
||||
"comp" => Shader::from_glsl(
|
||||
String::from_utf8(bytes)?,
|
||||
naga::ShaderStage::Compute,
|
||||
load_context.path().to_string_lossy(),
|
||||
),
|
||||
"wgsl" => Shader::from_wgsl(String::from_utf8(bytes)?, path),
|
||||
"vert" => {
|
||||
Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Vertex, path)
|
||||
}
|
||||
"frag" => {
|
||||
Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Fragment, path)
|
||||
}
|
||||
"comp" => {
|
||||
Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Compute, path)
|
||||
}
|
||||
_ => panic!("unhandled extension: {ext}"),
|
||||
};
|
||||
|
||||
|
@ -269,7 +269,7 @@ pub fn calculate_bounds(
|
||||
for (entity, mesh_handle) in &without_aabb {
|
||||
if let Some(mesh) = meshes.get(mesh_handle) {
|
||||
if let Some(aabb) = mesh.compute_aabb() {
|
||||
commands.entity(entity).insert(aabb);
|
||||
commands.entity(entity).try_insert(aabb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_scene"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides scene functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -14,15 +14,15 @@ serialize = ["dep:serde", "uuid/serde"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.0", optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.1", optional = true }
|
||||
|
||||
# other
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{DynamicScene, Scene};
|
||||
use bevy_asset::{AssetEvent, AssetId, Assets};
|
||||
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
event::{Event, Events, ManualEventReader},
|
||||
@ -63,8 +63,8 @@ pub struct SceneSpawner {
|
||||
spawned_dynamic_scenes: HashMap<AssetId<DynamicScene>, Vec<InstanceId>>,
|
||||
spawned_instances: HashMap<InstanceId, InstanceInfo>,
|
||||
scene_asset_event_reader: ManualEventReader<AssetEvent<DynamicScene>>,
|
||||
dynamic_scenes_to_spawn: Vec<(AssetId<DynamicScene>, InstanceId)>,
|
||||
scenes_to_spawn: Vec<(AssetId<Scene>, InstanceId)>,
|
||||
dynamic_scenes_to_spawn: Vec<(Handle<DynamicScene>, InstanceId)>,
|
||||
scenes_to_spawn: Vec<(Handle<Scene>, InstanceId)>,
|
||||
scenes_to_despawn: Vec<AssetId<DynamicScene>>,
|
||||
instances_to_despawn: Vec<InstanceId>,
|
||||
scenes_with_parent: Vec<(InstanceId, Entity)>,
|
||||
@ -127,7 +127,7 @@ pub enum SceneSpawnError {
|
||||
|
||||
impl SceneSpawner {
|
||||
/// Schedule the spawn of a new instance of the provided dynamic scene.
|
||||
pub fn spawn_dynamic(&mut self, id: impl Into<AssetId<DynamicScene>>) -> InstanceId {
|
||||
pub fn spawn_dynamic(&mut self, id: impl Into<Handle<DynamicScene>>) -> InstanceId {
|
||||
let instance_id = InstanceId::new();
|
||||
self.dynamic_scenes_to_spawn.push((id.into(), instance_id));
|
||||
instance_id
|
||||
@ -136,7 +136,7 @@ impl SceneSpawner {
|
||||
/// Schedule the spawn of a new instance of the provided dynamic scene as a child of `parent`.
|
||||
pub fn spawn_dynamic_as_child(
|
||||
&mut self,
|
||||
id: impl Into<AssetId<DynamicScene>>,
|
||||
id: impl Into<Handle<DynamicScene>>,
|
||||
parent: Entity,
|
||||
) -> InstanceId {
|
||||
let instance_id = InstanceId::new();
|
||||
@ -146,14 +146,14 @@ impl SceneSpawner {
|
||||
}
|
||||
|
||||
/// Schedule the spawn of a new instance of the provided scene.
|
||||
pub fn spawn(&mut self, id: impl Into<AssetId<Scene>>) -> InstanceId {
|
||||
pub fn spawn(&mut self, id: impl Into<Handle<Scene>>) -> InstanceId {
|
||||
let instance_id = InstanceId::new();
|
||||
self.scenes_to_spawn.push((id.into(), instance_id));
|
||||
instance_id
|
||||
}
|
||||
|
||||
/// Schedule the spawn of a new instance of the provided scene as a child of `parent`.
|
||||
pub fn spawn_as_child(&mut self, id: impl Into<AssetId<Scene>>, parent: Entity) -> InstanceId {
|
||||
pub fn spawn_as_child(&mut self, id: impl Into<Handle<Scene>>, parent: Entity) -> InstanceId {
|
||||
let instance_id = InstanceId::new();
|
||||
self.scenes_to_spawn.push((id.into(), instance_id));
|
||||
self.scenes_with_parent.push((instance_id, parent));
|
||||
@ -296,21 +296,21 @@ impl SceneSpawner {
|
||||
pub fn spawn_queued_scenes(&mut self, world: &mut World) -> Result<(), SceneSpawnError> {
|
||||
let scenes_to_spawn = std::mem::take(&mut self.dynamic_scenes_to_spawn);
|
||||
|
||||
for (id, instance_id) in scenes_to_spawn {
|
||||
for (handle, instance_id) in scenes_to_spawn {
|
||||
let mut entity_map = HashMap::default();
|
||||
|
||||
match Self::spawn_dynamic_internal(world, id, &mut entity_map) {
|
||||
match Self::spawn_dynamic_internal(world, handle.id(), &mut entity_map) {
|
||||
Ok(_) => {
|
||||
self.spawned_instances
|
||||
.insert(instance_id, InstanceInfo { entity_map });
|
||||
let spawned = self
|
||||
.spawned_dynamic_scenes
|
||||
.entry(id)
|
||||
.entry(handle.id())
|
||||
.or_insert_with(Vec::new);
|
||||
spawned.push(instance_id);
|
||||
}
|
||||
Err(SceneSpawnError::NonExistentScene { .. }) => {
|
||||
self.dynamic_scenes_to_spawn.push((id, instance_id));
|
||||
self.dynamic_scenes_to_spawn.push((handle, instance_id));
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
@ -319,10 +319,10 @@ impl SceneSpawner {
|
||||
let scenes_to_spawn = std::mem::take(&mut self.scenes_to_spawn);
|
||||
|
||||
for (scene_handle, instance_id) in scenes_to_spawn {
|
||||
match self.spawn_sync_internal(world, scene_handle, instance_id) {
|
||||
match self.spawn_sync_internal(world, scene_handle.id(), instance_id) {
|
||||
Ok(_) => {}
|
||||
Err(SceneSpawnError::NonExistentRealScene { id: handle }) => {
|
||||
self.scenes_to_spawn.push((handle, instance_id));
|
||||
Err(SceneSpawnError::NonExistentRealScene { .. }) => {
|
||||
self.scenes_to_spawn.push((scene_handle, instance_id));
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_sprite"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides sprite functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -13,19 +13,19 @@ webgl = []
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = [
|
||||
"bevy",
|
||||
] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.0" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
bytemuck = { version = "1.5", features = ["derive"] }
|
||||
|
@ -131,7 +131,7 @@ pub fn calculate_bounds_2d(
|
||||
for (entity, mesh_handle) in &meshes_without_aabb {
|
||||
if let Some(mesh) = meshes.get(&mesh_handle.0) {
|
||||
if let Some(aabb) = mesh.compute_aabb() {
|
||||
commands.entity(entity).insert(aabb);
|
||||
commands.entity(entity).try_insert(aabb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,7 +144,7 @@ pub fn calculate_bounds_2d(
|
||||
center: (-sprite.anchor.as_vec() * size).extend(0.0).into(),
|
||||
half_extents: (0.5 * size).extend(0.0).into(),
|
||||
};
|
||||
commands.entity(entity).insert(aabb);
|
||||
commands.entity(entity).try_insert(aabb);
|
||||
}
|
||||
}
|
||||
for (entity, atlas_sprite, atlas_handle) in &atlases_without_aabb {
|
||||
@ -158,7 +158,7 @@ pub fn calculate_bounds_2d(
|
||||
center: (-atlas_sprite.anchor.as_vec() * size).extend(0.0).into(),
|
||||
half_extents: (0.5 * size).extend(0.0).into(),
|
||||
};
|
||||
commands.entity(entity).insert(aabb);
|
||||
commands.entity(entity).try_insert(aabb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_tasks"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "A task executor for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_text"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides text functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -14,16 +14,16 @@ default_font = []
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.0" }
|
||||
bevy_sprite = { path = "../bevy_sprite", version = "0.12.0" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.1" }
|
||||
bevy_sprite = { path = "../bevy_sprite", version = "0.12.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
ab_glyph = "0.2.6"
|
||||
|
@ -20,11 +20,15 @@ impl Font {
|
||||
|
||||
pub fn get_outlined_glyph_texture(outlined_glyph: OutlinedGlyph) -> Image {
|
||||
let bounds = outlined_glyph.px_bounds();
|
||||
let width = bounds.width() as usize;
|
||||
let height = bounds.height() as usize;
|
||||
// Increase the length of the glyph texture by 2-pixels on each axis to make space
|
||||
// for a pixel wide transparent border along its edges.
|
||||
let width = bounds.width() as usize + 2;
|
||||
let height = bounds.height() as usize + 2;
|
||||
let mut alpha = vec![0.0; width * height];
|
||||
outlined_glyph.draw(|x, y, v| {
|
||||
alpha[y as usize * width + x as usize] = v;
|
||||
// Displace the glyph by 1 pixel on each axis so that it is drawn in the center of the texture.
|
||||
// This leaves a pixel wide transparent border around the glyph.
|
||||
alpha[(y + 1) as usize * width + x as usize + 1] = v;
|
||||
});
|
||||
|
||||
// TODO: make this texture grayscale
|
||||
|
@ -65,7 +65,7 @@ impl FontAtlas {
|
||||
Self {
|
||||
texture_atlas: texture_atlases.add(texture_atlas),
|
||||
glyph_to_atlas_index: HashMap::default(),
|
||||
dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 1),
|
||||
dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 0),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_time"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides time functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -15,10 +15,10 @@ bevy_ci_testing = ["bevy_app/bevy_ci_testing"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1", features = ["bevy_reflect"] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
crossbeam-channel = "0.5.0"
|
||||
|
@ -19,11 +19,6 @@ pub use time::*;
|
||||
pub use timer::*;
|
||||
pub use virt::*;
|
||||
|
||||
use bevy_ecs::system::{Res, ResMut};
|
||||
use bevy_utils::{tracing::warn, Duration, Instant};
|
||||
pub use crossbeam_channel::TrySendError;
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
|
||||
pub mod prelude {
|
||||
//! The Bevy Time Prelude.
|
||||
#[doc(hidden)]
|
||||
@ -31,7 +26,11 @@ pub mod prelude {
|
||||
}
|
||||
|
||||
use bevy_app::{prelude::*, RunFixedUpdateLoop};
|
||||
use bevy_ecs::event::{event_queue_update_system, EventUpdateSignal};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_utils::{tracing::warn, Duration, Instant};
|
||||
pub use crossbeam_channel::TrySendError;
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
|
||||
/// Adds time functionality to Apps.
|
||||
#[derive(Default)]
|
||||
@ -61,6 +60,10 @@ impl Plugin for TimePlugin {
|
||||
)
|
||||
.add_systems(RunFixedUpdateLoop, run_fixed_update_schedule);
|
||||
|
||||
// ensure the events are not dropped until `FixedUpdate` systems can observe them
|
||||
app.init_resource::<EventUpdateSignal>()
|
||||
.add_systems(FixedUpdate, event_queue_update_system);
|
||||
|
||||
#[cfg(feature = "bevy_ci_testing")]
|
||||
if let Some(ci_testing_config) = app
|
||||
.world
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_transform"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides transform functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -10,16 +10,16 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1", features = ["bevy_reflect"] }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
thiserror = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
|
||||
approx = "0.5.1"
|
||||
glam = { version = "0.24", features = ["approx"] }
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_ui"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "A custom ECS-driven UI framework built specifically for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -10,25 +10,25 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_a11y = { path = "../bevy_a11y", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.12.0" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [
|
||||
bevy_a11y = { path = "../bevy_a11y", version = "0.12.1" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.1" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.12.1" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = [
|
||||
"bevy",
|
||||
] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.0" }
|
||||
bevy_sprite = { path = "../bevy_sprite", version = "0.12.0" }
|
||||
bevy_text = { path = "../bevy_text", version = "0.12.0", optional = true }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.12.1" }
|
||||
bevy_sprite = { path = "../bevy_sprite", version = "0.12.1" }
|
||||
bevy_text = { path = "../bevy_text", version = "0.12.1", optional = true }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
taffy = { version = "0.3.10" }
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::{camera_config::UiCameraConfig, CalculatedClip, Node, UiScale, UiStack};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
change_detection::DetectChangesMut,
|
||||
entity::Entity,
|
||||
@ -9,7 +8,7 @@ use bevy_ecs::{
|
||||
system::{Local, Query, Res},
|
||||
};
|
||||
use bevy_input::{mouse::MouseButton, touch::Touches, Input};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_math::{Rect, Vec2};
|
||||
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
|
||||
use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::ViewVisibility};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
@ -57,25 +56,16 @@ impl Default for Interaction {
|
||||
|
||||
/// A component storing the position of the mouse relative to the node, (0., 0.) being the top-left corner and (1., 1.) being the bottom-right
|
||||
/// If the mouse is not over the node, the value will go beyond the range of (0., 0.) to (1., 1.)
|
||||
/// A None value means that the cursor position is unknown.
|
||||
|
||||
///
|
||||
/// It can be used alongside interaction to get the position of the press.
|
||||
#[derive(
|
||||
Component,
|
||||
Deref,
|
||||
DerefMut,
|
||||
Copy,
|
||||
Clone,
|
||||
Default,
|
||||
PartialEq,
|
||||
Debug,
|
||||
Reflect,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[derive(Component, Copy, Clone, Default, PartialEq, Debug, Reflect, Serialize, Deserialize)]
|
||||
#[reflect(Component, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RelativeCursorPosition {
|
||||
/// Cursor position relative to size and position of the Node.
|
||||
/// Visible area of the Node relative to the size of the entire Node.
|
||||
pub normalized_visible_node_rect: Rect,
|
||||
/// Cursor position relative to the size and position of the Node.
|
||||
/// A None value indicates that the cursor position is unknown.
|
||||
pub normalized: Option<Vec2>,
|
||||
}
|
||||
|
||||
@ -83,7 +73,7 @@ impl RelativeCursorPosition {
|
||||
/// A helper function to check if the mouse is over the node
|
||||
pub fn mouse_over(&self) -> bool {
|
||||
self.normalized
|
||||
.map(|position| (0.0..1.).contains(&position.x) && (0.0..1.).contains(&position.y))
|
||||
.map(|position| self.normalized_visible_node_rect.contains(position))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
@ -216,22 +206,24 @@ pub fn ui_focus_system(
|
||||
}
|
||||
}
|
||||
|
||||
let position = node.global_transform.translation();
|
||||
let ui_position = position.truncate();
|
||||
let extents = node.node.size() / 2.0;
|
||||
let mut min = ui_position - extents;
|
||||
if let Some(clip) = node.calculated_clip {
|
||||
min = Vec2::max(min, clip.clip.min);
|
||||
}
|
||||
let node_rect = node.node.logical_rect(node.global_transform);
|
||||
|
||||
// Intersect with the calculated clip rect to find the bounds of the visible region of the node
|
||||
let visible_rect = node
|
||||
.calculated_clip
|
||||
.map(|clip| node_rect.intersect(clip.clip))
|
||||
.unwrap_or(node_rect);
|
||||
|
||||
// The mouse position relative to the node
|
||||
// (0., 0.) is the top-left corner, (1., 1.) is the bottom-right corner
|
||||
// Coordinates are relative to the entire node, not just the visible region.
|
||||
let relative_cursor_position = cursor_position
|
||||
.map(|cursor_position| (cursor_position - min) / node.node.size());
|
||||
.map(|cursor_position| (cursor_position - node_rect.min) / node_rect.size());
|
||||
|
||||
// If the current cursor position is within the bounds of the node, consider it for
|
||||
// If the current cursor position is within the bounds of the node's visible area, consider it for
|
||||
// clicking
|
||||
let relative_cursor_position_component = RelativeCursorPosition {
|
||||
normalized_visible_node_rect: visible_rect.normalize(node_rect),
|
||||
normalized: relative_cursor_position,
|
||||
};
|
||||
|
||||
|
@ -410,7 +410,7 @@ pub fn resolve_outlines_system(
|
||||
.max(0.);
|
||||
|
||||
node.outline_offset = outline
|
||||
.width
|
||||
.offset
|
||||
.resolve(node.size().x, viewport_size)
|
||||
.unwrap_or(0.)
|
||||
.max(0.);
|
||||
|
@ -344,6 +344,9 @@ impl Default for ButtonBundle {
|
||||
}
|
||||
|
||||
/// A UI node that is rendered using a [`UiMaterial`]
|
||||
///
|
||||
/// Adding a `BackgroundColor` component to an entity with this bundle will ignore the custom
|
||||
/// material and use the background color instead.
|
||||
#[derive(Bundle, Clone, Debug)]
|
||||
pub struct MaterialNodeBundle<M: UiMaterial> {
|
||||
/// Describes the logical size of the node
|
||||
|
@ -21,6 +21,7 @@ use crate::{
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::Vec3Swizzles;
|
||||
use bevy_math::{Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec4Swizzles};
|
||||
use bevy_render::{
|
||||
camera::Camera,
|
||||
@ -618,13 +619,14 @@ pub fn extract_text_uinodes(
|
||||
>,
|
||||
) {
|
||||
// TODO: Support window-independent UI scale: https://github.com/bevyengine/bevy/issues/5621
|
||||
let scale_factor = windows
|
||||
.get_single()
|
||||
.map(|window| window.resolution.scale_factor())
|
||||
.unwrap_or(1.0)
|
||||
* ui_scale.0;
|
||||
|
||||
let inverse_scale_factor = (scale_factor as f32).recip();
|
||||
let scale_factor = (windows
|
||||
.get_single()
|
||||
.map(|window| window.scale_factor())
|
||||
.unwrap_or(1.)
|
||||
* ui_scale.0) as f32;
|
||||
|
||||
let inverse_scale_factor = scale_factor.recip();
|
||||
|
||||
for (uinode, global_transform, text, text_layout_info, view_visibility, clip) in
|
||||
uinode_query.iter()
|
||||
@ -633,8 +635,20 @@ pub fn extract_text_uinodes(
|
||||
if !view_visibility.get() || uinode.size().x == 0. || uinode.size().y == 0. {
|
||||
continue;
|
||||
}
|
||||
let transform = global_transform.compute_matrix()
|
||||
* Mat4::from_translation(-0.5 * uinode.size().extend(0.));
|
||||
|
||||
let mut affine = global_transform.affine();
|
||||
|
||||
// Align the text to the nearest physical pixel:
|
||||
// * Translate by minus the text node's half-size
|
||||
// (The transform translates to the center of the node but the text coordinates are relative to the node's top left corner)
|
||||
// * Multiply the logical coordinates by the scale factor to get its position in physical coordinates
|
||||
// * Round the physical position to the nearest physical pixel
|
||||
// * Multiply by the rounded physical position by the inverse scale factor to return to logical coordinates
|
||||
let logical_top_left = affine.translation.xy() - 0.5 * uinode.size();
|
||||
let physical_nearest_pixel = (logical_top_left * scale_factor).round();
|
||||
let logical_top_left_nearest_pixel = physical_nearest_pixel * inverse_scale_factor;
|
||||
affine.translation = logical_top_left_nearest_pixel.extend(0.).into();
|
||||
let transform = Mat4::from(affine);
|
||||
|
||||
let mut color = Color::WHITE;
|
||||
let mut current_section = usize::MAX;
|
||||
|
@ -70,7 +70,7 @@ where
|
||||
.init_resource::<ExtractedUiMaterials<M>>()
|
||||
.init_resource::<ExtractedUiMaterialNodes<M>>()
|
||||
.init_resource::<RenderUiMaterials<M>>()
|
||||
.init_resource::<UiMaterialMeta>()
|
||||
.init_resource::<UiMaterialMeta<M>>()
|
||||
.init_resource::<SpecializedRenderPipelines<UiMaterialPipeline<M>>>()
|
||||
.add_systems(
|
||||
ExtractSchedule,
|
||||
@ -98,16 +98,18 @@ where
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct UiMaterialMeta {
|
||||
pub struct UiMaterialMeta<M: UiMaterial> {
|
||||
vertices: BufferVec<UiMaterialVertex>,
|
||||
view_bind_group: Option<BindGroup>,
|
||||
marker: PhantomData<M>,
|
||||
}
|
||||
|
||||
impl Default for UiMaterialMeta {
|
||||
impl<M: UiMaterial> Default for UiMaterialMeta<M> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
vertices: BufferVec::new(BufferUsages::VERTEX),
|
||||
view_bind_group: Default::default(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -261,7 +263,7 @@ pub type DrawUiMaterial<M> = (
|
||||
|
||||
pub struct SetMatUiViewBindGroup<M: UiMaterial, const I: usize>(PhantomData<M>);
|
||||
impl<P: PhaseItem, M: UiMaterial, const I: usize> RenderCommand<P> for SetMatUiViewBindGroup<M, I> {
|
||||
type Param = SRes<UiMaterialMeta>;
|
||||
type Param = SRes<UiMaterialMeta<M>>;
|
||||
type ViewWorldQuery = Read<ViewUniformOffset>;
|
||||
type ItemWorldQuery = ();
|
||||
|
||||
@ -296,10 +298,9 @@ impl<P: PhaseItem, M: UiMaterial, const I: usize> RenderCommand<P>
|
||||
materials: SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let material = materials
|
||||
.into_inner()
|
||||
.get(&material_handle.material)
|
||||
.unwrap();
|
||||
let Some(material) = materials.into_inner().get(&material_handle.material) else {
|
||||
return RenderCommandResult::Failure;
|
||||
};
|
||||
pass.set_bind_group(I, &material.bind_group, &[]);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
@ -307,7 +308,7 @@ impl<P: PhaseItem, M: UiMaterial, const I: usize> RenderCommand<P>
|
||||
|
||||
pub struct DrawUiMaterialNode<M>(PhantomData<M>);
|
||||
impl<P: PhaseItem, M: UiMaterial> RenderCommand<P> for DrawUiMaterialNode<M> {
|
||||
type Param = SRes<UiMaterialMeta>;
|
||||
type Param = SRes<UiMaterialMeta<M>>;
|
||||
type ViewWorldQuery = ();
|
||||
type ItemWorldQuery = Read<UiMaterialBatch<M>>;
|
||||
|
||||
@ -352,15 +353,18 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
||||
materials: Extract<Res<Assets<M>>>,
|
||||
ui_stack: Extract<Res<UiStack>>,
|
||||
uinode_query: Extract<
|
||||
Query<(
|
||||
Entity,
|
||||
&Node,
|
||||
&Style,
|
||||
&GlobalTransform,
|
||||
&Handle<M>,
|
||||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
)>,
|
||||
Query<
|
||||
(
|
||||
Entity,
|
||||
&Node,
|
||||
&Style,
|
||||
&GlobalTransform,
|
||||
&Handle<M>,
|
||||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
),
|
||||
Without<BackgroundColor>,
|
||||
>,
|
||||
>,
|
||||
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
@ -396,7 +400,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
||||
style.border.right,
|
||||
parent_width,
|
||||
ui_logical_viewport_size,
|
||||
) / uinode.size().y;
|
||||
) / uinode.size().x;
|
||||
let top =
|
||||
resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size)
|
||||
/ uinode.size().y;
|
||||
@ -429,7 +433,7 @@ pub fn prepare_uimaterial_nodes<M: UiMaterial>(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mut ui_meta: ResMut<UiMaterialMeta>,
|
||||
mut ui_meta: ResMut<UiMaterialMeta<M>>,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiMaterialNodes<M>>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
ui_material_pipeline: Res<UiMaterialPipeline<M>>,
|
||||
@ -727,7 +731,9 @@ pub fn queue_ui_material_nodes<M: UiMaterial>(
|
||||
let draw_function = draw_functions.read().id::<DrawUiMaterial<M>>();
|
||||
|
||||
for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() {
|
||||
let material = render_materials.get(&extracted_uinode.material).unwrap();
|
||||
let Some(material) = render_materials.get(&extracted_uinode.material) else {
|
||||
continue;
|
||||
};
|
||||
for (view, mut transparent_phase) in &mut views {
|
||||
let pipeline = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_utils"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "A collection of utils for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -17,7 +17,7 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
||||
uuid = { version = "1.1", features = ["v4", "serde"] }
|
||||
hashbrown = { version = "0.14", features = ["serde"] }
|
||||
bevy_utils_proc_macros = { version = "0.12.0", path = "macros" }
|
||||
bevy_utils_proc_macros = { version = "0.12.1", path = "macros" }
|
||||
petgraph = "0.6"
|
||||
thiserror = "1.0"
|
||||
nonmax = "0.5"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_utils_proc_macros"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
description = "Bevy Utils Proc Macros"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_window"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "Provides windowing functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -14,16 +14,16 @@ serialize = ["serde"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_a11y = { path = "../bevy_a11y", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [
|
||||
bevy_a11y = { path = "../bevy_a11y", version = "0.12.1" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = [
|
||||
"glam",
|
||||
] }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
# Used for close_on_esc
|
||||
bevy_input = { path = "../bevy_input", version = "0.12.0" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.12.1" }
|
||||
raw-window-handle = "0.5"
|
||||
|
||||
# other
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_winit"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "A winit window and input backend for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -16,16 +16,16 @@ accesskit_unix = ["accesskit_winit/accesskit_unix", "accesskit_winit/async-io"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_a11y = { path = "../bevy_a11y", version = "0.12.0" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.0" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.12.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.0" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
|
||||
bevy_a11y = { path = "../bevy_a11y", version = "0.12.1" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.12.1" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.12.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.12.1" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.12.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
|
||||
|
||||
# other
|
||||
winit = { version = "0.28.7", default-features = false }
|
||||
|
@ -349,6 +349,11 @@ impl Default for WinitAppRunnerState {
|
||||
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the
|
||||
/// `EventLoop`.
|
||||
pub fn winit_runner(mut app: App) {
|
||||
if app.plugins_state() == PluginsState::Ready {
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
}
|
||||
|
||||
let mut event_loop = app
|
||||
.world
|
||||
.remove_non_send_resource::<EventLoop<()>>()
|
||||
|
@ -2,12 +2,11 @@
|
||||
|
||||
use bevy::utils::thiserror;
|
||||
use bevy::{
|
||||
asset::{io::Reader, AssetLoader, LoadContext},
|
||||
asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext},
|
||||
prelude::*,
|
||||
reflect::TypePath,
|
||||
utils::BoxedFuture,
|
||||
};
|
||||
use futures_lite::AsyncReadExt;
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
|
||||
@ -24,7 +23,7 @@ pub struct CustomAssetLoader;
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CustomAssetLoaderError {
|
||||
/// An [IO](std::io) Error
|
||||
#[error("Could load shader: {0}")]
|
||||
#[error("Could not load asset: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
/// A [RON](ron) Error
|
||||
#[error("Could not parse RON: {0}")]
|
||||
|
@ -3,19 +3,16 @@
|
||||
//! It does not know anything about the asset formats, only how to talk to the underlying storage.
|
||||
|
||||
use bevy::{
|
||||
asset::io::{
|
||||
file::FileAssetReader, AssetReader, AssetReaderError, AssetSource, AssetSourceId,
|
||||
PathStream, Reader,
|
||||
},
|
||||
asset::io::{AssetReader, AssetReaderError, AssetSource, AssetSourceId, PathStream, Reader},
|
||||
prelude::*,
|
||||
utils::BoxedFuture,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
/// A custom asset reader implementation that wraps a given asset reader implementation
|
||||
struct CustomAssetReader<T: AssetReader>(T);
|
||||
struct CustomAssetReader(Box<dyn AssetReader>);
|
||||
|
||||
impl<T: AssetReader> AssetReader for CustomAssetReader<T> {
|
||||
impl AssetReader for CustomAssetReader {
|
||||
fn read<'a>(
|
||||
&'a self,
|
||||
path: &'a Path,
|
||||
@ -52,8 +49,12 @@ impl Plugin for CustomAssetReaderPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_asset_source(
|
||||
AssetSourceId::Default,
|
||||
AssetSource::build()
|
||||
.with_reader(|| Box::new(CustomAssetReader(FileAssetReader::new("assets")))),
|
||||
AssetSource::build().with_reader(|| {
|
||||
Box::new(CustomAssetReader(
|
||||
// This is the default reader for the current platform
|
||||
AssetSource::get_default_reader("assets".to_string())(),
|
||||
))
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
//! Demonstrates using a custom extension to the `StandardMaterial` to modify the results of the builtin pbr shader.
|
||||
|
||||
use bevy::reflect::TypePath;
|
||||
use bevy::{
|
||||
pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod},
|
||||
prelude::*,
|
||||
@ -73,7 +72,7 @@ fn rotate_things(mut q: Query<&mut Transform, With<Rotate>>, time: Res<Time>) {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Asset, AsBindGroup, TypePath, Debug, Clone)]
|
||||
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
|
||||
struct MyExtension {
|
||||
// We need to ensure that the bindings of the base material and the extension do not conflict,
|
||||
// so we start from binding slot 100, leaving slots 0-99 for the base material.
|
||||
|
@ -8,6 +8,7 @@ fn main() {
|
||||
// Only run the app when there is user input. This will significantly reduce CPU/GPU use.
|
||||
.insert_resource(WinitSettings::desktop_app())
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, update_outlines)
|
||||
.run();
|
||||
}
|
||||
|
||||
@ -86,18 +87,39 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
..Default::default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
parent.spawn(ImageBundle {
|
||||
image: UiImage::new(image.clone()),
|
||||
style: Style {
|
||||
min_width: Val::Px(100.),
|
||||
min_height: Val::Px(100.),
|
||||
parent.spawn((
|
||||
ImageBundle {
|
||||
image: UiImage::new(image.clone()),
|
||||
style: Style {
|
||||
min_width: Val::Px(100.),
|
||||
min_height: Val::Px(100.),
|
||||
..Default::default()
|
||||
},
|
||||
background_color: Color::WHITE.into(),
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
background_color: Color::WHITE.into(),
|
||||
..Default::default()
|
||||
});
|
||||
Interaction::default(),
|
||||
Outline {
|
||||
width: Val::Px(2.),
|
||||
offset: Val::Px(2.),
|
||||
color: Color::NONE,
|
||||
},
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn update_outlines(mut outlines_query: Query<(&mut Outline, Ref<Interaction>)>) {
|
||||
for (mut outline, interaction) in outlines_query.iter_mut() {
|
||||
if interaction.is_changed() {
|
||||
outline.color = match *interaction {
|
||||
Interaction::Pressed => Color::RED,
|
||||
Interaction::Hovered => Color::WHITE,
|
||||
Interaction::None => Color::NONE,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "build-templated-pages"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
edition = "2021"
|
||||
description = "handle templated pages in Bevy repository"
|
||||
publish = false
|
||||
|
@ -33,6 +33,10 @@ struct Args {
|
||||
#[arg(short, long)]
|
||||
/// Optimize the wasm file for size with wasm-opt
|
||||
optimize_size: bool,
|
||||
|
||||
#[arg(long)]
|
||||
/// Additional features to enable
|
||||
features: Vec<String>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@ -41,7 +45,7 @@ fn main() {
|
||||
assert!(!cli.examples.is_empty(), "must have at least one example");
|
||||
|
||||
let mut default_features = true;
|
||||
let mut features = vec![];
|
||||
let mut features: Vec<&str> = cli.features.iter().map(|f| f.as_str()).collect();
|
||||
if let Some(frames) = cli.frames {
|
||||
let mut file = File::create("ci_testing_config.ron").unwrap();
|
||||
file.write_fmt(format_args!("(exit_after: Some({frames}))"))
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "example-showcase"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
description = "Run examples"
|
||||
publish = false
|
||||
|
13
tools/example-showcase/asset-source-website.patch
Normal file
13
tools/example-showcase/asset-source-website.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs
|
||||
index 004f87a85..3c8656efc 100644
|
||||
--- a/crates/bevy_asset/src/lib.rs
|
||||
+++ b/crates/bevy_asset/src/lib.rs
|
||||
@@ -105,7 +105,7 @@ impl Default for AssetPlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: AssetMode::Unprocessed,
|
||||
- file_path: Self::DEFAULT_UNPROCESSED_FILE_PATH.to_string(),
|
||||
+ file_path: "/assets/examples".to_string(),
|
||||
processed_file_path: Self::DEFAULT_PROCESSED_FILE_PATH.to_string(),
|
||||
watch_for_changes_override: None,
|
||||
}
|
@ -562,11 +562,20 @@ header_message = \"Examples ({})\"
|
||||
let sh = Shell::new().unwrap();
|
||||
|
||||
// setting a canvas by default to help with integration
|
||||
cmd!(sh, "sed -i.bak 's/canvas: None,/canvas: Some(\"#bevy\".to_string()),/' crates/bevy_window/src/window.rs").run().unwrap();
|
||||
cmd!(sh, "sed -i.bak 's/fit_canvas_to_parent: false,/fit_canvas_to_parent: true,/' crates/bevy_window/src/window.rs").run().unwrap();
|
||||
cmd!(
|
||||
sh,
|
||||
"git apply --ignore-whitespace tools/example-showcase/window-settings-wasm.patch"
|
||||
)
|
||||
.run()
|
||||
.unwrap();
|
||||
|
||||
// setting the asset folder root to the root url of this domain
|
||||
cmd!(sh, "sed -i.bak 's/asset_folder: \"assets\"/asset_folder: \"\\/assets\\/examples\\/\"/' crates/bevy_asset/src/lib.rs").run().unwrap();
|
||||
cmd!(
|
||||
sh,
|
||||
"git apply --ignore-whitespace tools/example-showcase/asset-source-website.patch"
|
||||
)
|
||||
.run()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let work_to_do = || {
|
||||
@ -581,17 +590,26 @@ header_message = \"Examples ({})\"
|
||||
for to_build in work_to_do() {
|
||||
let sh = Shell::new().unwrap();
|
||||
let example = &to_build.technical_name;
|
||||
let required_features = if to_build.required_features.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
vec![
|
||||
"--features".to_string(),
|
||||
to_build.required_features.join(","),
|
||||
]
|
||||
};
|
||||
|
||||
if optimize_size {
|
||||
cmd!(
|
||||
sh,
|
||||
"cargo run -p build-wasm-example -- --api {api} {example} --optimize-size"
|
||||
"cargo run -p build-wasm-example -- --api {api} {example} --optimize-size {required_features...}"
|
||||
)
|
||||
.run()
|
||||
.unwrap();
|
||||
} else {
|
||||
cmd!(
|
||||
sh,
|
||||
"cargo run -p build-wasm-example -- --api {api} {example}"
|
||||
"cargo run -p build-wasm-example -- --api {api} {example} {required_features...}"
|
||||
)
|
||||
.run()
|
||||
.unwrap();
|
||||
|
16
tools/example-showcase/window-settings-wasm.patch
Normal file
16
tools/example-showcase/window-settings-wasm.patch
Normal file
@ -0,0 +1,16 @@
|
||||
diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs
|
||||
index 7b5c75d38..8e9404b93 100644
|
||||
--- a/crates/bevy_window/src/window.rs
|
||||
+++ b/crates/bevy_window/src/window.rs
|
||||
@@ -245,9 +245,9 @@ impl Default for Window {
|
||||
transparent: false,
|
||||
focused: true,
|
||||
window_level: Default::default(),
|
||||
- fit_canvas_to_parent: false,
|
||||
+ fit_canvas_to_parent: true,
|
||||
prevent_default_event_handling: true,
|
||||
- canvas: None,
|
||||
+ canvas: Some("#bevy".to_string()),
|
||||
window_theme: None,
|
||||
visible: true,
|
||||
}
|
Loading…
Reference in New Issue
Block a user