mirror of
https://github.com/bevyengine/bevy
synced 2024-11-23 13:13:49 +00:00
Merge branch 'main' into transmission
This commit is contained in:
commit
eb41464ff9
51 changed files with 879 additions and 298 deletions
25
.github/workflows/dependencies.yml
vendored
25
.github/workflows/dependencies.yml
vendored
|
@ -29,11 +29,36 @@ jobs:
|
||||||
check-bans:
|
check-bans:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
# on main, prepare a new cargo tree output and cache it
|
||||||
|
- name: On main, prepare new cargo tree cache
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
run: cargo tree --depth 3 > cargo-tree-from-main
|
||||||
|
- name: On main, save the new cargo tree cache
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
with:
|
||||||
|
path: cargo-tree-from-main
|
||||||
|
key: cargo-tree-from-main
|
||||||
|
# on other branch, restore the cached cargo tree output
|
||||||
|
- name: On PR, restore cargo tree cache
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
if: github.ref != 'refs/heads/main'
|
||||||
|
with:
|
||||||
|
path: cargo-tree-from-main
|
||||||
|
key: cargo-tree-from-main
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
# if not on main, check that the cargo tree output is unchanged
|
||||||
|
- name: Check if the cargo tree changed from main
|
||||||
|
if: github.ref != 'refs/heads/main'
|
||||||
|
continue-on-error: true
|
||||||
|
id: cargo-tree-changed
|
||||||
|
run: diff cargo-tree-from-main <(cargo tree --depth 3)
|
||||||
- name: Install cargo-deny
|
- name: Install cargo-deny
|
||||||
run: cargo install cargo-deny
|
run: cargo install cargo-deny
|
||||||
|
# if the check was not a success (either skipped because on main or failed because of a change), run the check
|
||||||
- name: Check for banned and duplicated dependencies
|
- name: Check for banned and duplicated dependencies
|
||||||
|
if: steps.cargo-tree-changed.outcome != 'success'
|
||||||
run: cargo deny check bans
|
run: cargo deny check bans
|
||||||
|
|
||||||
check-licenses:
|
check-licenses:
|
||||||
|
|
|
@ -34,7 +34,7 @@ pub fn heavy_compute(c: &mut Criterion) {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fn sys(mut query: Query<(&mut Position, &mut Transform)>) {
|
fn sys(mut query: Query<(&mut Position, &mut Transform)>) {
|
||||||
query.par_iter_mut().for_each_mut(|(mut pos, mut mat)| {
|
query.par_iter_mut().for_each(|(mut pos, mut mat)| {
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
mat.0 = mat.0.inverse();
|
mat.0 = mat.0.inverse();
|
||||||
}
|
}
|
||||||
|
|
|
@ -364,7 +364,7 @@ pub fn animation_player(
|
||||||
) {
|
) {
|
||||||
animation_players
|
animation_players
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.for_each_mut(|(root, maybe_parent, mut player)| {
|
.for_each(|(root, maybe_parent, mut player)| {
|
||||||
update_transitions(&mut player, &time);
|
update_transitions(&mut player, &time);
|
||||||
run_animation_player(
|
run_animation_player(
|
||||||
root,
|
root,
|
||||||
|
|
|
@ -686,6 +686,7 @@ impl App {
|
||||||
/// Panics if one of the plugins was already added to the application.
|
/// Panics if one of the plugins was already added to the application.
|
||||||
///
|
///
|
||||||
/// [`PluginGroup`]:super::PluginGroup
|
/// [`PluginGroup`]:super::PluginGroup
|
||||||
|
#[track_caller]
|
||||||
pub fn add_plugins<M>(&mut self, plugins: impl Plugins<M>) -> &mut Self {
|
pub fn add_plugins<M>(&mut self, plugins: impl Plugins<M>) -> &mut Self {
|
||||||
plugins.add_to_app(self);
|
plugins.add_to_app(self);
|
||||||
self
|
self
|
||||||
|
|
|
@ -89,6 +89,7 @@ mod sealed {
|
||||||
pub struct PluginsTupleMarker;
|
pub struct PluginsTupleMarker;
|
||||||
|
|
||||||
impl<P: Plugin> Plugins<PluginMarker> for P {
|
impl<P: Plugin> Plugins<PluginMarker> for P {
|
||||||
|
#[track_caller]
|
||||||
fn add_to_app(self, app: &mut App) {
|
fn add_to_app(self, app: &mut App) {
|
||||||
if let Err(AppError::DuplicatePlugin { plugin_name }) =
|
if let Err(AppError::DuplicatePlugin { plugin_name }) =
|
||||||
app.add_boxed_plugin(Box::new(self))
|
app.add_boxed_plugin(Box::new(self))
|
||||||
|
@ -101,6 +102,7 @@ mod sealed {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: PluginGroup> Plugins<PluginGroupMarker> for P {
|
impl<P: PluginGroup> Plugins<PluginGroupMarker> for P {
|
||||||
|
#[track_caller]
|
||||||
fn add_to_app(self, app: &mut App) {
|
fn add_to_app(self, app: &mut App) {
|
||||||
self.build().finish(app);
|
self.build().finish(app);
|
||||||
}
|
}
|
||||||
|
@ -113,6 +115,7 @@ mod sealed {
|
||||||
$($plugins: Plugins<$param>),*
|
$($plugins: Plugins<$param>),*
|
||||||
{
|
{
|
||||||
#[allow(non_snake_case, unused_variables)]
|
#[allow(non_snake_case, unused_variables)]
|
||||||
|
#[track_caller]
|
||||||
fn add_to_app(self, app: &mut App) {
|
fn add_to_app(self, app: &mut App) {
|
||||||
let ($($plugins,)*) = self;
|
let ($($plugins,)*) = self;
|
||||||
$($plugins.add_to_app(app);)*
|
$($plugins.add_to_app(app);)*
|
||||||
|
|
|
@ -172,6 +172,7 @@ impl PluginGroupBuilder {
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if one of the plugin in the group was already added to the application.
|
/// Panics if one of the plugin in the group was already added to the application.
|
||||||
|
#[track_caller]
|
||||||
pub fn finish(mut self, app: &mut App) {
|
pub fn finish(mut self, app: &mut App) {
|
||||||
for ty in &self.order {
|
for ty in &self.order {
|
||||||
if let Some(entry) = self.plugins.remove(ty) {
|
if let Some(entry) = self.plugins.remove(ty) {
|
||||||
|
|
|
@ -237,6 +237,16 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
||||||
#marker_name: &'__w (),
|
#marker_name: &'__w (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl #user_impl_generics_with_world Clone for #fetch_struct_name #user_ty_generics_with_world
|
||||||
|
#user_where_clauses_with_world {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
#(#named_field_idents: self.#named_field_idents.clone(),)*
|
||||||
|
#marker_name: &(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field
|
// SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field
|
||||||
unsafe impl #user_impl_generics #path::query::WorldQuery
|
unsafe impl #user_impl_generics #path::query::WorldQuery
|
||||||
for #struct_name #user_ty_generics #user_where_clauses {
|
for #struct_name #user_ty_generics #user_where_clauses {
|
||||||
|
@ -275,17 +285,6 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'__w>(
|
|
||||||
_fetch: &<Self as #path::query::WorldQuery>::Fetch<'__w>
|
|
||||||
) -> <Self as #path::query::WorldQuery>::Fetch<'__w> {
|
|
||||||
#fetch_struct_name {
|
|
||||||
#(
|
|
||||||
#named_field_idents: <#field_types>::clone_fetch(& _fetch. #named_field_idents),
|
|
||||||
)*
|
|
||||||
#marker_name: &(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const IS_DENSE: bool = true #(&& <#field_types>::IS_DENSE)*;
|
const IS_DENSE: bool = true #(&& <#field_types>::IS_DENSE)*;
|
||||||
|
|
||||||
const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*;
|
const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*;
|
||||||
|
|
|
@ -735,6 +735,35 @@ impl<'a> MutUntyped<'a> {
|
||||||
self.value.as_ref()
|
self.value.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turn this [`MutUntyped`] into a [`Mut`] by mapping the inner [`PtrMut`] to another value,
|
||||||
|
/// without flagging a change.
|
||||||
|
/// This function is the untyped equivalent of [`Mut::map_unchanged`].
|
||||||
|
///
|
||||||
|
/// You should never modify the argument passed to the closure – if you want to modify the data without flagging a change, consider using [`bypass_change_detection`](DetectChangesMut::bypass_change_detection) to make your intent explicit.
|
||||||
|
///
|
||||||
|
/// If you know the type of the value you can do
|
||||||
|
/// ```no_run
|
||||||
|
/// # use bevy_ecs::change_detection::{Mut, MutUntyped};
|
||||||
|
/// # let mut_untyped: MutUntyped = unimplemented!();
|
||||||
|
/// // SAFETY: ptr is of type `u8`
|
||||||
|
/// mut_untyped.map_unchanged(|ptr| unsafe { ptr.deref_mut::<u8>() });
|
||||||
|
/// ```
|
||||||
|
/// If you have a [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr) that you know belongs to this [`MutUntyped`],
|
||||||
|
/// you can do
|
||||||
|
/// ```no_run
|
||||||
|
/// # use bevy_ecs::change_detection::{Mut, MutUntyped};
|
||||||
|
/// # let mut_untyped: MutUntyped = unimplemented!();
|
||||||
|
/// # let reflect_from_ptr: bevy_reflect::ReflectFromPtr = unimplemented!();
|
||||||
|
/// // SAFETY: from the context it is known that `ReflectFromPtr` was made for the type of the `MutUntyped`
|
||||||
|
/// mut_untyped.map_unchanged(|ptr| unsafe { reflect_from_ptr.as_reflect_ptr_mut(ptr) });
|
||||||
|
/// ```
|
||||||
|
pub fn map_unchanged<T: ?Sized>(self, f: impl FnOnce(PtrMut<'a>) -> &'a mut T) -> Mut<'a, T> {
|
||||||
|
Mut {
|
||||||
|
value: f(self.value),
|
||||||
|
ticks: self.ticks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Transforms this [`MutUntyped`] into a [`Mut<T>`] with the same lifetime.
|
/// Transforms this [`MutUntyped`] into a [`Mut<T>`] with the same lifetime.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -798,6 +827,8 @@ impl std::fmt::Debug for MutUntyped<'_> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use bevy_ecs_macros::Resource;
|
use bevy_ecs_macros::Resource;
|
||||||
|
use bevy_ptr::PtrMut;
|
||||||
|
use bevy_reflect::{FromType, ReflectFromPtr};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
self as bevy_ecs,
|
self as bevy_ecs,
|
||||||
|
@ -809,8 +840,7 @@ mod tests {
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::DetectChanges;
|
use super::{DetectChanges, DetectChangesMut, MutUntyped};
|
||||||
use super::DetectChangesMut;
|
|
||||||
|
|
||||||
#[derive(Component, PartialEq)]
|
#[derive(Component, PartialEq)]
|
||||||
struct C;
|
struct C;
|
||||||
|
@ -1034,4 +1064,40 @@ mod tests {
|
||||||
"Resource must be changed after setting to a different value."
|
"Resource must be changed after setting to a different value."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mut_untyped_to_reflect() {
|
||||||
|
let last_run = Tick::new(2);
|
||||||
|
let this_run = Tick::new(3);
|
||||||
|
let mut component_ticks = ComponentTicks {
|
||||||
|
added: Tick::new(1),
|
||||||
|
changed: Tick::new(2),
|
||||||
|
};
|
||||||
|
let ticks = TicksMut {
|
||||||
|
added: &mut component_ticks.added,
|
||||||
|
changed: &mut component_ticks.changed,
|
||||||
|
last_run,
|
||||||
|
this_run,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut value: i32 = 5;
|
||||||
|
let value = MutUntyped {
|
||||||
|
value: PtrMut::from(&mut value),
|
||||||
|
ticks,
|
||||||
|
};
|
||||||
|
|
||||||
|
let reflect_from_ptr = <ReflectFromPtr as FromType<i32>>::from_type();
|
||||||
|
|
||||||
|
let mut new = value.map_unchanged(|ptr| {
|
||||||
|
// SAFETY: The underlying type of `ptr` matches `reflect_from_ptr`.
|
||||||
|
let value = unsafe { reflect_from_ptr.as_reflect_ptr_mut(ptr) };
|
||||||
|
value
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(!new.is_changed());
|
||||||
|
|
||||||
|
new.reflect_mut();
|
||||||
|
|
||||||
|
assert!(new.is_changed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ pub mod prelude {
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::{Event, EventReader, EventWriter, Events},
|
event::{Event, EventReader, EventWriter, Events},
|
||||||
query::{Added, AnyOf, Changed, Or, QueryState, With, Without},
|
query::{Added, AnyOf, Changed, Has, Or, QueryState, With, Without},
|
||||||
removal_detection::RemovedComponents,
|
removal_detection::RemovedComponents,
|
||||||
schedule::{
|
schedule::{
|
||||||
apply_deferred, apply_state_transition, common_conditions::*, Condition,
|
apply_deferred, apply_state_transition, common_conditions::*, Condition,
|
||||||
|
|
|
@ -318,7 +318,7 @@ pub unsafe trait WorldQuery {
|
||||||
type Item<'a>;
|
type Item<'a>;
|
||||||
|
|
||||||
/// Per archetype/table state used by this [`WorldQuery`] to fetch [`Self::Item`](crate::query::WorldQuery::Item)
|
/// Per archetype/table state used by this [`WorldQuery`] to fetch [`Self::Item`](crate::query::WorldQuery::Item)
|
||||||
type Fetch<'a>;
|
type Fetch<'a>: Clone;
|
||||||
|
|
||||||
/// The read-only variant of this [`WorldQuery`], which satisfies the [`ReadOnlyWorldQuery`] trait.
|
/// The read-only variant of this [`WorldQuery`], which satisfies the [`ReadOnlyWorldQuery`] trait.
|
||||||
type ReadOnly: ReadOnlyWorldQuery<State = Self::State>;
|
type ReadOnly: ReadOnlyWorldQuery<State = Self::State>;
|
||||||
|
@ -345,14 +345,6 @@ pub unsafe trait WorldQuery {
|
||||||
this_run: Tick,
|
this_run: Tick,
|
||||||
) -> Self::Fetch<'w>;
|
) -> Self::Fetch<'w>;
|
||||||
|
|
||||||
/// While this function can be called for any query, it is always safe to call if `Self: ReadOnlyWorldQuery` holds.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// While calling this method on its own cannot cause UB it is marked `unsafe` as the caller must ensure
|
|
||||||
/// that the returned value is not used in any way that would cause two `QueryItem<Self>` for the same
|
|
||||||
/// `archetype_row` or `table_row` to be alive at the same time.
|
|
||||||
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w>;
|
|
||||||
|
|
||||||
/// Returns true if (and only if) every table of every archetype matched by this fetch contains
|
/// Returns true if (and only if) every table of every archetype matched by this fetch contains
|
||||||
/// all of the matched components. This is used to select a more efficient "table iterator"
|
/// all of the matched components. This is used to select a more efficient "table iterator"
|
||||||
/// for "dense" queries. If this returns true, [`WorldQuery::set_table`] must be used before
|
/// for "dense" queries. If this returns true, [`WorldQuery::set_table`] must be used before
|
||||||
|
@ -404,6 +396,10 @@ pub unsafe trait WorldQuery {
|
||||||
///
|
///
|
||||||
/// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and
|
/// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and
|
||||||
/// `table_row` must be in the range of the current table and archetype.
|
/// `table_row` must be in the range of the current table and archetype.
|
||||||
|
///
|
||||||
|
/// If `update_component_access` includes any mutable accesses, then the caller must ensure
|
||||||
|
/// that `fetch` is called no more than once for each `entity`/`table_row` in each archetype.
|
||||||
|
/// If `Self` implements [`ReadOnlyWorldQuery`], then this can safely be called multiple times.
|
||||||
unsafe fn fetch<'w>(
|
unsafe fn fetch<'w>(
|
||||||
fetch: &mut Self::Fetch<'w>,
|
fetch: &mut Self::Fetch<'w>,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
|
@ -483,8 +479,6 @@ unsafe impl WorldQuery for Entity {
|
||||||
) -> Self::Fetch<'w> {
|
) -> Self::Fetch<'w> {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn set_archetype<'w>(
|
unsafe fn set_archetype<'w>(
|
||||||
_fetch: &mut Self::Fetch<'w>,
|
_fetch: &mut Self::Fetch<'w>,
|
||||||
|
@ -554,10 +548,6 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> {
|
||||||
world.world()
|
world.world()
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(world: &Self::Fetch<'w>) -> Self::Fetch<'w> {
|
|
||||||
world
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn set_archetype<'w>(
|
unsafe fn set_archetype<'w>(
|
||||||
_fetch: &mut Self::Fetch<'w>,
|
_fetch: &mut Self::Fetch<'w>,
|
||||||
|
@ -620,6 +610,13 @@ pub struct ReadFetch<'w, T> {
|
||||||
sparse_set: Option<&'w ComponentSparseSet>,
|
sparse_set: Option<&'w ComponentSparseSet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for ReadFetch<'_, T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Copy for ReadFetch<'_, T> {}
|
||||||
|
|
||||||
/// SAFETY: `Self` is the same as `Self::ReadOnly`
|
/// SAFETY: `Self` is the same as `Self::ReadOnly`
|
||||||
unsafe impl<T: Component> WorldQuery for &T {
|
unsafe impl<T: Component> WorldQuery for &T {
|
||||||
type Fetch<'w> = ReadFetch<'w, T>;
|
type Fetch<'w> = ReadFetch<'w, T>;
|
||||||
|
@ -663,13 +660,6 @@ unsafe impl<T: Component> WorldQuery for &T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
|
|
||||||
ReadFetch {
|
|
||||||
table_components: fetch.table_components,
|
|
||||||
sparse_set: fetch.sparse_set,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn set_archetype<'w>(
|
unsafe fn set_archetype<'w>(
|
||||||
fetch: &mut ReadFetch<'w, T>,
|
fetch: &mut ReadFetch<'w, T>,
|
||||||
|
@ -770,6 +760,13 @@ pub struct RefFetch<'w, T> {
|
||||||
this_run: Tick,
|
this_run: Tick,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for RefFetch<'_, T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Copy for RefFetch<'_, T> {}
|
||||||
|
|
||||||
/// SAFETY: `Self` is the same as `Self::ReadOnly`
|
/// SAFETY: `Self` is the same as `Self::ReadOnly`
|
||||||
unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
||||||
type Fetch<'w> = RefFetch<'w, T>;
|
type Fetch<'w> = RefFetch<'w, T>;
|
||||||
|
@ -812,15 +809,6 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
|
|
||||||
RefFetch {
|
|
||||||
table_data: fetch.table_data,
|
|
||||||
sparse_set: fetch.sparse_set,
|
|
||||||
last_run: fetch.last_run,
|
|
||||||
this_run: fetch.this_run,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn set_archetype<'w>(
|
unsafe fn set_archetype<'w>(
|
||||||
fetch: &mut RefFetch<'w, T>,
|
fetch: &mut RefFetch<'w, T>,
|
||||||
|
@ -933,6 +921,13 @@ pub struct WriteFetch<'w, T> {
|
||||||
this_run: Tick,
|
this_run: Tick,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for WriteFetch<'_, T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Copy for WriteFetch<'_, T> {}
|
||||||
|
|
||||||
/// SAFETY: access of `&T` is a subset of `&mut T`
|
/// SAFETY: access of `&T` is a subset of `&mut T`
|
||||||
unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
||||||
type Fetch<'w> = WriteFetch<'w, T>;
|
type Fetch<'w> = WriteFetch<'w, T>;
|
||||||
|
@ -975,15 +970,6 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
|
|
||||||
WriteFetch {
|
|
||||||
table_data: fetch.table_data,
|
|
||||||
sparse_set: fetch.sparse_set,
|
|
||||||
last_run: fetch.last_run,
|
|
||||||
this_run: fetch.this_run,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn set_archetype<'w>(
|
unsafe fn set_archetype<'w>(
|
||||||
fetch: &mut WriteFetch<'w, T>,
|
fetch: &mut WriteFetch<'w, T>,
|
||||||
|
@ -1084,6 +1070,15 @@ pub struct OptionFetch<'w, T: WorldQuery> {
|
||||||
matches: bool,
|
matches: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: WorldQuery> Clone for OptionFetch<'_, T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
fetch: self.fetch.clone(),
|
||||||
|
matches: self.matches,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SAFETY: defers to soundness of `T: WorldQuery` impl
|
// SAFETY: defers to soundness of `T: WorldQuery` impl
|
||||||
unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
|
unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
|
||||||
type Fetch<'w> = OptionFetch<'w, T>;
|
type Fetch<'w> = OptionFetch<'w, T>;
|
||||||
|
@ -1112,13 +1107,6 @@ unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
|
|
||||||
OptionFetch {
|
|
||||||
fetch: T::clone_fetch(&fetch.fetch),
|
|
||||||
matches: fetch.matches,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn set_archetype<'w>(
|
unsafe fn set_archetype<'w>(
|
||||||
fetch: &mut OptionFetch<'w, T>,
|
fetch: &mut OptionFetch<'w, T>,
|
||||||
|
@ -1271,10 +1259,6 @@ unsafe impl<T: Component> WorldQuery for Has<T> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
|
|
||||||
*fetch
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn set_archetype<'w>(
|
unsafe fn set_archetype<'w>(
|
||||||
fetch: &mut Self::Fetch<'w>,
|
fetch: &mut Self::Fetch<'w>,
|
||||||
|
@ -1351,13 +1335,6 @@ macro_rules! impl_tuple_fetch {
|
||||||
($($name::init_fetch(_world, $name, _last_run, _this_run),)*)
|
($($name::init_fetch(_world, $name, _last_run, _this_run),)*)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(
|
|
||||||
fetch: &Self::Fetch<'w>,
|
|
||||||
) -> Self::Fetch<'w> {
|
|
||||||
let ($($name,)*) = &fetch;
|
|
||||||
($($name::clone_fetch($name),)*)
|
|
||||||
}
|
|
||||||
|
|
||||||
const IS_DENSE: bool = true $(&& $name::IS_DENSE)*;
|
const IS_DENSE: bool = true $(&& $name::IS_DENSE)*;
|
||||||
|
|
||||||
const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*;
|
const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*;
|
||||||
|
@ -1461,13 +1438,6 @@ macro_rules! impl_anytuple_fetch {
|
||||||
($(($name::init_fetch(_world, $name, _last_run, _this_run), false),)*)
|
($(($name::init_fetch(_world, $name, _last_run, _this_run), false),)*)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(
|
|
||||||
fetch: &Self::Fetch<'w>,
|
|
||||||
) -> Self::Fetch<'w> {
|
|
||||||
let ($($name,)*) = &fetch;
|
|
||||||
($(($name::clone_fetch(& $name.0), $name.1),)*)
|
|
||||||
}
|
|
||||||
|
|
||||||
const IS_DENSE: bool = true $(&& $name::IS_DENSE)*;
|
const IS_DENSE: bool = true $(&& $name::IS_DENSE)*;
|
||||||
|
|
||||||
const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*;
|
const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*;
|
||||||
|
@ -1588,8 +1558,6 @@ unsafe impl<Q: WorldQuery> WorldQuery for NopWorldQuery<Q> {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {}
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
unsafe fn set_archetype(
|
unsafe fn set_archetype(
|
||||||
_fetch: &mut (),
|
_fetch: &mut (),
|
||||||
|
@ -1651,8 +1619,6 @@ unsafe impl<T: ?Sized> WorldQuery for PhantomData<T> {
|
||||||
) -> Self::Fetch<'w> {
|
) -> Self::Fetch<'w> {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {}
|
|
||||||
|
|
||||||
// `PhantomData` does not match any components, so all components it matches
|
// `PhantomData` does not match any components, so all components it matches
|
||||||
// are stored in a Table (vacuous truth).
|
// are stored in a Table (vacuous truth).
|
||||||
const IS_DENSE: bool = true;
|
const IS_DENSE: bool = true;
|
||||||
|
|
|
@ -59,8 +59,6 @@ unsafe impl<T: Component> WorldQuery for With<T> {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {}
|
|
||||||
|
|
||||||
const IS_DENSE: bool = {
|
const IS_DENSE: bool = {
|
||||||
match T::Storage::STORAGE_TYPE {
|
match T::Storage::STORAGE_TYPE {
|
||||||
StorageType::Table => true,
|
StorageType::Table => true,
|
||||||
|
@ -162,8 +160,6 @@ unsafe impl<T: Component> WorldQuery for Without<T> {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {}
|
|
||||||
|
|
||||||
const IS_DENSE: bool = {
|
const IS_DENSE: bool = {
|
||||||
match T::Storage::STORAGE_TYPE {
|
match T::Storage::STORAGE_TYPE {
|
||||||
StorageType::Table => true,
|
StorageType::Table => true,
|
||||||
|
@ -259,6 +255,15 @@ pub struct OrFetch<'w, T: WorldQuery> {
|
||||||
matches: bool,
|
matches: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: WorldQuery> Clone for OrFetch<'_, T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
fetch: self.fetch.clone(),
|
||||||
|
matches: self.matches,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_query_filter_tuple {
|
macro_rules! impl_query_filter_tuple {
|
||||||
($(($filter: ident, $state: ident)),*) => {
|
($(($filter: ident, $state: ident)),*) => {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
|
@ -288,18 +293,6 @@ macro_rules! impl_query_filter_tuple {
|
||||||
},)*)
|
},)*)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(
|
|
||||||
fetch: &Self::Fetch<'w>,
|
|
||||||
) -> Self::Fetch<'w> {
|
|
||||||
let ($($filter,)*) = &fetch;
|
|
||||||
($(
|
|
||||||
OrFetch {
|
|
||||||
fetch: $filter::clone_fetch(&$filter.fetch),
|
|
||||||
matches: $filter.matches,
|
|
||||||
},
|
|
||||||
)*)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
|
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
|
||||||
let ($($filter,)*) = fetch;
|
let ($($filter,)*) = fetch;
|
||||||
|
@ -403,10 +396,10 @@ macro_rules! impl_tick_filter {
|
||||||
pub struct $name<T>(PhantomData<T>);
|
pub struct $name<T>(PhantomData<T>);
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
#[derive(Clone)]
|
||||||
$(#[$fetch_meta])*
|
$(#[$fetch_meta])*
|
||||||
pub struct $fetch_name<'w, T> {
|
pub struct $fetch_name<'w> {
|
||||||
table_ticks: Option< ThinSlicePtr<'w, UnsafeCell<Tick>>>,
|
table_ticks: Option<ThinSlicePtr<'w, UnsafeCell<Tick>>>,
|
||||||
marker: PhantomData<T>,
|
|
||||||
sparse_set: Option<&'w ComponentSparseSet>,
|
sparse_set: Option<&'w ComponentSparseSet>,
|
||||||
last_run: Tick,
|
last_run: Tick,
|
||||||
this_run: Tick,
|
this_run: Tick,
|
||||||
|
@ -414,7 +407,7 @@ macro_rules! impl_tick_filter {
|
||||||
|
|
||||||
// SAFETY: `Self::ReadOnly` is the same as `Self`
|
// SAFETY: `Self::ReadOnly` is the same as `Self`
|
||||||
unsafe impl<T: Component> WorldQuery for $name<T> {
|
unsafe impl<T: Component> WorldQuery for $name<T> {
|
||||||
type Fetch<'w> = $fetch_name<'w, T>;
|
type Fetch<'w> = $fetch_name<'w>;
|
||||||
type Item<'w> = bool;
|
type Item<'w> = bool;
|
||||||
type ReadOnly = Self;
|
type ReadOnly = Self;
|
||||||
type State = ComponentId;
|
type State = ComponentId;
|
||||||
|
@ -440,24 +433,11 @@ macro_rules! impl_tick_filter {
|
||||||
.get(id)
|
.get(id)
|
||||||
.debug_checked_unwrap()
|
.debug_checked_unwrap()
|
||||||
}),
|
}),
|
||||||
marker: PhantomData,
|
|
||||||
last_run,
|
last_run,
|
||||||
this_run,
|
this_run,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clone_fetch<'w>(
|
|
||||||
fetch: &Self::Fetch<'w>,
|
|
||||||
) -> Self::Fetch<'w> {
|
|
||||||
$fetch_name {
|
|
||||||
table_ticks: fetch.table_ticks,
|
|
||||||
sparse_set: fetch.sparse_set,
|
|
||||||
last_run: fetch.last_run,
|
|
||||||
this_run: fetch.this_run,
|
|
||||||
marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const IS_DENSE: bool = {
|
const IS_DENSE: bool = {
|
||||||
match T::Storage::STORAGE_TYPE {
|
match T::Storage::STORAGE_TYPE {
|
||||||
StorageType::Table => true,
|
StorageType::Table => true,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
storage::{TableId, TableRow, Tables},
|
storage::{TableId, TableRow, Tables},
|
||||||
world::unsafe_world_cell::UnsafeWorldCell,
|
world::unsafe_world_cell::UnsafeWorldCell,
|
||||||
};
|
};
|
||||||
use std::{borrow::Borrow, iter::FusedIterator, marker::PhantomData, mem::MaybeUninit};
|
use std::{borrow::Borrow, iter::FusedIterator, mem::MaybeUninit};
|
||||||
|
|
||||||
use super::ReadOnlyWorldQuery;
|
use super::ReadOnlyWorldQuery;
|
||||||
|
|
||||||
|
@ -163,7 +163,9 @@ where
|
||||||
// SAFETY: set_archetype was called prior.
|
// SAFETY: set_archetype was called prior.
|
||||||
// `location.archetype_row` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d
|
// `location.archetype_row` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d
|
||||||
if F::filter_fetch(&mut self.filter, entity, location.table_row) {
|
if F::filter_fetch(&mut self.filter, entity, location.table_row) {
|
||||||
// SAFETY: set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype
|
// SAFETY:
|
||||||
|
// - set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype
|
||||||
|
// - fetch is only called once for each entity.
|
||||||
return Some(Q::fetch(&mut self.fetch, entity, location.table_row));
|
return Some(Q::fetch(&mut self.fetch, entity, location.table_row));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,7 +346,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery, const K: usize>
|
||||||
match self.cursors[i].next(self.tables, self.archetypes, self.query_state) {
|
match self.cursors[i].next(self.tables, self.archetypes, self.query_state) {
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
for j in (i + 1)..K {
|
for j in (i + 1)..K {
|
||||||
self.cursors[j] = self.cursors[j - 1].clone_cursor();
|
self.cursors[j] = self.cursors[j - 1].clone();
|
||||||
match self.cursors[j].next(self.tables, self.archetypes, self.query_state) {
|
match self.cursors[j].next(self.tables, self.archetypes, self.query_state) {
|
||||||
Some(_) => {}
|
Some(_) => {}
|
||||||
None if i > 0 => continue 'outer,
|
None if i > 0 => continue 'outer,
|
||||||
|
@ -453,28 +455,19 @@ struct QueryIterationCursor<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> {
|
||||||
current_len: usize,
|
current_len: usize,
|
||||||
// either table row or archetype index, depending on whether both `Q`'s and `F`'s fetches are dense
|
// either table row or archetype index, depending on whether both `Q`'s and `F`'s fetches are dense
|
||||||
current_row: usize,
|
current_row: usize,
|
||||||
phantom: PhantomData<Q>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, Q, F> {
|
impl<Q: WorldQuery, F: ReadOnlyWorldQuery> Clone for QueryIterationCursor<'_, '_, Q, F> {
|
||||||
/// This function is safe to call if `(Q, F): ReadOnlyWorldQuery` holds.
|
fn clone(&self) -> Self {
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// While calling this method on its own cannot cause UB it is marked `unsafe` as the caller must ensure
|
|
||||||
/// that the returned value is not used in any way that would cause two `QueryItem<Q>` for the same
|
|
||||||
/// `archetype_row` or `table_row` to be alive at the same time.
|
|
||||||
unsafe fn clone_cursor(&self) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
table_id_iter: self.table_id_iter.clone(),
|
table_id_iter: self.table_id_iter.clone(),
|
||||||
archetype_id_iter: self.archetype_id_iter.clone(),
|
archetype_id_iter: self.archetype_id_iter.clone(),
|
||||||
table_entities: self.table_entities,
|
table_entities: self.table_entities,
|
||||||
archetype_entities: self.archetype_entities,
|
archetype_entities: self.archetype_entities,
|
||||||
// SAFETY: upheld by caller invariants
|
fetch: self.fetch.clone(),
|
||||||
fetch: Q::clone_fetch(&self.fetch),
|
filter: self.filter.clone(),
|
||||||
filter: F::clone_fetch(&self.filter),
|
|
||||||
current_len: self.current_len,
|
current_len: self.current_len,
|
||||||
current_row: self.current_row,
|
current_row: self.current_row,
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -515,7 +508,6 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
|
||||||
archetype_id_iter: query_state.matched_archetype_ids.iter(),
|
archetype_id_iter: query_state.matched_archetype_ids.iter(),
|
||||||
current_len: 0,
|
current_len: 0,
|
||||||
current_row: 0,
|
current_row: 0,
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -593,8 +585,11 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: set_table was called prior.
|
// SAFETY:
|
||||||
// `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed.
|
// - set_table was called prior.
|
||||||
|
// - `current_row` must be a table row in range of the current table,
|
||||||
|
// because if it was not, then the if above would have been executed.
|
||||||
|
// - fetch is only called once for each `entity`.
|
||||||
let item = Q::fetch(&mut self.fetch, *entity, row);
|
let item = Q::fetch(&mut self.fetch, *entity, row);
|
||||||
|
|
||||||
self.current_row += 1;
|
self.current_row += 1;
|
||||||
|
@ -633,8 +628,11 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: set_archetype was called prior, `current_row` is an archetype index in range of the current archetype
|
// SAFETY:
|
||||||
// `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed.
|
// - set_archetype was called prior.
|
||||||
|
// - `current_row` must be an archetype index row in range of the current archetype,
|
||||||
|
// because if it was not, then the if above would have been executed.
|
||||||
|
// - fetch is only called once for each `archetype_entity`.
|
||||||
let item = Q::fetch(
|
let item = Q::fetch(
|
||||||
&mut self.fetch,
|
&mut self.fetch,
|
||||||
archetype_entity.entity(),
|
archetype_entity.entity(),
|
||||||
|
|
|
@ -778,7 +778,7 @@ mod tests {
|
||||||
world.spawn((A(1), B(1)));
|
world.spawn((A(1), B(1)));
|
||||||
|
|
||||||
fn propagate_system(mut query: Query<(&A, &mut B), Changed<A>>) {
|
fn propagate_system(mut query: Query<(&A, &mut B), Changed<A>>) {
|
||||||
query.par_iter_mut().for_each_mut(|(a, mut b)| {
|
query.par_iter_mut().for_each(|(a, mut b)| {
|
||||||
b.0 = a.0;
|
b.0 = a.0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{component::Tick, world::unsafe_world_cell::UnsafeWorldCell};
|
use crate::{component::Tick, world::unsafe_world_cell::UnsafeWorldCell};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use super::{QueryItem, QueryState, ROQueryItem, ReadOnlyWorldQuery, WorldQuery};
|
use super::{QueryItem, QueryState, ReadOnlyWorldQuery, WorldQuery};
|
||||||
|
|
||||||
/// Dictates how a parallel query chunks up large tables/archetypes
|
/// Dictates how a parallel query chunks up large tables/archetypes
|
||||||
/// during iteration.
|
/// during iteration.
|
||||||
|
@ -90,26 +90,6 @@ pub struct QueryParIter<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> {
|
||||||
pub(crate) batching_strategy: BatchingStrategy,
|
pub(crate) batching_strategy: BatchingStrategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> {
|
|
||||||
/// Runs `func` on each query result in parallel.
|
|
||||||
///
|
|
||||||
/// This can only be called for read-only queries, see [`Self::for_each_mut`] for
|
|
||||||
/// write-queries.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being
|
|
||||||
/// initialized and run from the ECS scheduler, this should never panic.
|
|
||||||
///
|
|
||||||
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
|
|
||||||
#[inline]
|
|
||||||
pub fn for_each<FN: Fn(ROQueryItem<'w, Q>) + Send + Sync + Clone>(&self, func: FN) {
|
|
||||||
// SAFETY: query is read only
|
|
||||||
unsafe {
|
|
||||||
self.for_each_unchecked(func);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> {
|
impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> {
|
||||||
/// Changes the batching strategy used when iterating.
|
/// Changes the batching strategy used when iterating.
|
||||||
///
|
///
|
||||||
|
@ -123,50 +103,47 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> {
|
||||||
/// Runs `func` on each query result in parallel.
|
/// Runs `func` on each query result in parallel.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being
|
/// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being
|
||||||
/// initialized and run from the ECS scheduler, this should never panic.
|
/// initialized and run from the ECS scheduler, this should never panic.
|
||||||
///
|
///
|
||||||
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
|
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn for_each_mut<FN: Fn(QueryItem<'w, Q>) + Send + Sync + Clone>(&mut self, func: FN) {
|
pub fn for_each<FN: Fn(QueryItem<'w, Q>) + Send + Sync + Clone>(self, func: FN) {
|
||||||
// SAFETY: query has unique world access
|
|
||||||
unsafe {
|
|
||||||
self.for_each_unchecked(func);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs `func` on each query result in parallel.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being
|
|
||||||
/// initialized and run from the ECS scheduler, this should never panic.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
|
|
||||||
/// have unique access to the components they query.
|
|
||||||
///
|
|
||||||
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
|
|
||||||
#[inline]
|
|
||||||
unsafe fn for_each_unchecked<FN: Fn(QueryItem<'w, Q>) + Send + Sync + Clone>(&self, func: FN) {
|
|
||||||
#[cfg(any(target = "wasm32", not(feature = "multi-threaded")))]
|
#[cfg(any(target = "wasm32", not(feature = "multi-threaded")))]
|
||||||
{
|
{
|
||||||
self.state
|
// SAFETY:
|
||||||
.for_each_unchecked_manual(self.world, func, self.last_run, self.this_run);
|
// This method can only be called once per instance of QueryParIter,
|
||||||
}
|
// which ensures that mutable queries cannot be executed multiple times at once.
|
||||||
#[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))]
|
// Mutable instances of QueryParIter can only be created via an exclusive borrow of a
|
||||||
{
|
// Query or a World, which ensures that multiple aliasing QueryParIters cannot exist
|
||||||
let thread_count = bevy_tasks::ComputeTaskPool::get().thread_num();
|
// at the same time.
|
||||||
if thread_count <= 1 {
|
unsafe {
|
||||||
self.state.for_each_unchecked_manual(
|
self.state.for_each_unchecked_manual(
|
||||||
self.world,
|
self.world,
|
||||||
func,
|
func,
|
||||||
self.last_run,
|
self.last_run,
|
||||||
self.this_run,
|
self.this_run,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))]
|
||||||
|
{
|
||||||
|
let thread_count = bevy_tasks::ComputeTaskPool::get().thread_num();
|
||||||
|
if thread_count <= 1 {
|
||||||
|
// SAFETY: See the safety comment above.
|
||||||
|
unsafe {
|
||||||
|
self.state.for_each_unchecked_manual(
|
||||||
|
self.world,
|
||||||
|
func,
|
||||||
|
self.last_run,
|
||||||
|
self.this_run,
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Need a batch size of at least 1.
|
// Need a batch size of at least 1.
|
||||||
let batch_size = self.get_batch_size(thread_count).max(1);
|
let batch_size = self.get_batch_size(thread_count).max(1);
|
||||||
|
// SAFETY: See the safety comment above.
|
||||||
|
unsafe {
|
||||||
self.state.par_for_each_unchecked_manual(
|
self.state.par_for_each_unchecked_manual(
|
||||||
self.world,
|
self.world,
|
||||||
batch_size,
|
batch_size,
|
||||||
|
@ -177,6 +154,20 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs `func` on each query result in parallel.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being
|
||||||
|
/// initialized and run from the ECS scheduler, this should never panic.
|
||||||
|
///
|
||||||
|
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
|
||||||
|
#[inline]
|
||||||
|
#[deprecated = "use `.for_each(...)` instead."]
|
||||||
|
pub fn for_each_mut<FN: Fn(QueryItem<'w, Q>) + Send + Sync + Clone>(self, func: FN) {
|
||||||
|
self.for_each(func);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))]
|
#[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))]
|
||||||
fn get_batch_size(&self, thread_count: usize) -> usize {
|
fn get_batch_size(&self, thread_count: usize) -> usize {
|
||||||
|
|
|
@ -235,13 +235,13 @@ use std::{any::TypeId, borrow::Borrow, fmt::Debug};
|
||||||
///
|
///
|
||||||
/// |Query methods|Effect|
|
/// |Query methods|Effect|
|
||||||
/// |:---:|---|
|
/// |:---:|---|
|
||||||
/// |[`iter`]\([`_mut`][`iter_mut`])|Returns an iterator over all query items.|
|
/// |[`iter`]\[[`_mut`][`iter_mut`]]|Returns an iterator over all query items.|
|
||||||
/// |[`for_each`]\([`_mut`][`for_each_mut`]),<br>[`par_iter`]\([`_mut`][`par_iter_mut`])|Runs a specified function for each query item.|
|
/// |[`for_each`]\[[`_mut`][`for_each_mut`]],<br>[`par_iter`]\[[`_mut`][`par_iter_mut`]]|Runs a specified function for each query item.|
|
||||||
/// |[`iter_many`]\([`_mut`][`iter_many_mut`])|Iterates or runs a specified function over query items generated by a list of entities.|
|
/// |[`iter_many`]\[[`_mut`][`iter_many_mut`]]|Iterates or runs a specified function over query items generated by a list of entities.|
|
||||||
/// |[`iter_combinations`]\([`_mut`][`iter_combinations_mut`])|Returns an iterator over all combinations of a specified number of query items.|
|
/// |[`iter_combinations`]\[[`_mut`][`iter_combinations_mut`]]|Returns an iterator over all combinations of a specified number of query items.|
|
||||||
/// |[`get`]\([`_mut`][`get_mut`])|Returns the query item for the specified entity.|
|
/// |[`get`]\[[`_mut`][`get_mut`]]|Returns the query item for the specified entity.|
|
||||||
/// |[`many`]\([`_mut`][`many_mut`]),<br>[`get_many`]\([`_mut`][`get_many_mut`])|Returns the query items for the specified entities.|
|
/// |[`many`]\[[`_mut`][`many_mut`]],<br>[`get_many`]\[[`_mut`][`get_many_mut`]]|Returns the query items for the specified entities.|
|
||||||
/// |[`single`]\([`_mut`][`single_mut`]),<br>[`get_single`]\([`_mut`][`get_single_mut`])|Returns the query item while verifying that there aren't others.|
|
/// |[`single`]\[[`_mut`][`single_mut`]],<br>[`get_single`]\[[`_mut`][`get_single_mut`]]|Returns the query item while verifying that there aren't others.|
|
||||||
///
|
///
|
||||||
/// There are two methods for each type of query operation: immutable and mutable (ending with `_mut`).
|
/// There are two methods for each type of query operation: immutable and mutable (ending with `_mut`).
|
||||||
/// When using immutable methods, the query items returned are of type [`ROQueryItem`], a read-only version of the query item.
|
/// When using immutable methods, the query items returned are of type [`ROQueryItem`], a read-only version of the query item.
|
||||||
|
@ -271,14 +271,14 @@ use std::{any::TypeId, borrow::Borrow, fmt::Debug};
|
||||||
///
|
///
|
||||||
/// |Query operation|Computational complexity|
|
/// |Query operation|Computational complexity|
|
||||||
/// |:---:|:---:|
|
/// |:---:|:---:|
|
||||||
/// |[`iter`]\([`_mut`][`iter_mut`])|O(n)|
|
/// |[`iter`]\[[`_mut`][`iter_mut`]]|O(n)|
|
||||||
/// |[`for_each`]\([`_mut`][`for_each_mut`]),<br>[`par_iter`]\([`_mut`][`par_iter_mut`])|O(n)|
|
/// |[`for_each`]\[[`_mut`][`for_each_mut`]],<br>[`par_iter`]\[[`_mut`][`par_iter_mut`]]|O(n)|
|
||||||
/// |[`iter_many`]\([`_mut`][`iter_many_mut`])|O(k)|
|
/// |[`iter_many`]\[[`_mut`][`iter_many_mut`]]|O(k)|
|
||||||
/// |[`iter_combinations`]\([`_mut`][`iter_combinations_mut`])|O(<sub>n</sub>C<sub>r</sub>)|
|
/// |[`iter_combinations`]\[[`_mut`][`iter_combinations_mut`]]|O(<sub>n</sub>C<sub>r</sub>)|
|
||||||
/// |[`get`]\([`_mut`][`get_mut`])|O(1)|
|
/// |[`get`]\[[`_mut`][`get_mut`]]|O(1)|
|
||||||
/// |([`get_`][`get_many`])[`many`]|O(k)|
|
/// |([`get_`][`get_many`])[`many`]|O(k)|
|
||||||
/// |([`get_`][`get_many_mut`])[`many_mut`]|O(k<sup>2</sup>)|
|
/// |([`get_`][`get_many_mut`])[`many_mut`]|O(k<sup>2</sup>)|
|
||||||
/// |[`single`]\([`_mut`][`single_mut`]),<br>[`get_single`]\([`_mut`][`get_single_mut`])|O(a)|
|
/// |[`single`]\[[`_mut`][`single_mut`]],<br>[`get_single`]\[[`_mut`][`get_single_mut`]]|O(a)|
|
||||||
/// |Archetype based filtering ([`With`], [`Without`], [`Or`])|O(a)|
|
/// |Archetype based filtering ([`With`], [`Without`], [`Or`])|O(a)|
|
||||||
/// |Change detection filtering ([`Added`], [`Changed`])|O(a + n)|
|
/// |Change detection filtering ([`Added`], [`Changed`])|O(a + n)|
|
||||||
///
|
///
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use bevy_app::{Last, Plugin, Update};
|
use bevy_app::{Last, Plugin, PostUpdate};
|
||||||
use bevy_asset::{load_internal_asset, AddAsset, Assets, Handle, HandleUntyped};
|
use bevy_asset::{load_internal_asset, AddAsset, Assets, Handle, HandleUntyped};
|
||||||
use bevy_core::cast_slice;
|
use bevy_core::cast_slice;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
|
@ -50,7 +50,10 @@ use bevy_render::{
|
||||||
view::RenderLayers,
|
view::RenderLayers,
|
||||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||||
};
|
};
|
||||||
use bevy_transform::components::{GlobalTransform, Transform};
|
use bevy_transform::{
|
||||||
|
components::{GlobalTransform, Transform},
|
||||||
|
TransformSystem,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod gizmos;
|
pub mod gizmos;
|
||||||
|
|
||||||
|
@ -85,11 +88,12 @@ impl Plugin for GizmoPlugin {
|
||||||
.init_resource::<GizmoStorage>()
|
.init_resource::<GizmoStorage>()
|
||||||
.add_systems(Last, update_gizmo_meshes)
|
.add_systems(Last, update_gizmo_meshes)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
PostUpdate,
|
||||||
(
|
(
|
||||||
draw_aabbs,
|
draw_aabbs,
|
||||||
draw_all_aabbs.run_if(|config: Res<GizmoConfig>| config.aabb.draw_all),
|
draw_all_aabbs.run_if(|config: Res<GizmoConfig>| config.aabb.draw_all),
|
||||||
),
|
)
|
||||||
|
.after(TransformSystem::TransformPropagate),
|
||||||
);
|
);
|
||||||
|
|
||||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
|
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
|
||||||
|
@ -233,13 +237,17 @@ fn draw_all_aabbs(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color_from_entity(entity: Entity) -> Color {
|
fn color_from_entity(entity: Entity) -> Color {
|
||||||
use bevy_utils::RandomState;
|
let index = entity.index();
|
||||||
const U64_TO_DEGREES: f32 = 360.0 / u64::MAX as f32;
|
|
||||||
const STATE: RandomState =
|
// from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
|
||||||
RandomState::with_seeds(5952553601252303067, 16866614500153072625, 0, 0);
|
//
|
||||||
|
// See https://en.wikipedia.org/wiki/Low-discrepancy_sequence
|
||||||
|
// Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range,
|
||||||
|
// so that the closer the numbers are, the larger the difference of their image.
|
||||||
|
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
|
||||||
|
const RATIO_360: f32 = 360.0 / u32::MAX as f32;
|
||||||
|
let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
|
||||||
|
|
||||||
let hash = STATE.hash_one(entity);
|
|
||||||
let hue = hash as f32 * U64_TO_DEGREES;
|
|
||||||
Color::hsl(hue, 1., 0.5)
|
Color::hsl(hue, 1., 0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,21 +40,24 @@ impl GltfPlugin {
|
||||||
|
|
||||||
impl Plugin for GltfPlugin {
|
impl Plugin for GltfPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
let supported_compressed_formats = match app.world.get_resource::<RenderDevice>() {
|
app.register_type::<GltfExtras>()
|
||||||
Some(render_device) => CompressedImageFormats::from_features(render_device.features()),
|
|
||||||
|
|
||||||
None => CompressedImageFormats::all(),
|
|
||||||
};
|
|
||||||
app.add_asset_loader::<GltfLoader>(GltfLoader {
|
|
||||||
supported_compressed_formats,
|
|
||||||
custom_vertex_attributes: self.custom_vertex_attributes.clone(),
|
|
||||||
})
|
|
||||||
.register_type::<GltfExtras>()
|
|
||||||
.add_asset::<Gltf>()
|
.add_asset::<Gltf>()
|
||||||
.add_asset::<GltfNode>()
|
.add_asset::<GltfNode>()
|
||||||
.add_asset::<GltfPrimitive>()
|
.add_asset::<GltfPrimitive>()
|
||||||
.add_asset::<GltfMesh>();
|
.add_asset::<GltfMesh>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finish(&self, app: &mut App) {
|
||||||
|
let supported_compressed_formats = match app.world.get_resource::<RenderDevice>() {
|
||||||
|
Some(render_device) => CompressedImageFormats::from_features(render_device.features()),
|
||||||
|
|
||||||
|
None => CompressedImageFormats::NONE,
|
||||||
|
};
|
||||||
|
app.add_asset_loader::<GltfLoader>(GltfLoader {
|
||||||
|
supported_compressed_formats,
|
||||||
|
custom_vertex_attributes: self.custom_vertex_attributes.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of a loaded glTF file.
|
/// Representation of a loaded glTF file.
|
||||||
|
|
|
@ -28,10 +28,20 @@ impl Default for BevyManifest {
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.map(|mut path| {
|
.map(|mut path| {
|
||||||
path.push("Cargo.toml");
|
path.push("Cargo.toml");
|
||||||
let manifest = std::fs::read_to_string(path).unwrap();
|
if !path.exists() {
|
||||||
manifest.parse::<Document>().unwrap()
|
panic!(
|
||||||
|
"No Cargo manifest found for crate. Expected: {}",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let manifest = std::fs::read_to_string(path.clone()).unwrap_or_else(|_| {
|
||||||
|
panic!("Unable to read cargo manifest: {}", path.display())
|
||||||
|
});
|
||||||
|
manifest.parse::<Document>().unwrap_or_else(|_| {
|
||||||
|
panic!("Failed to parse cargo manifest: {}", path.display())
|
||||||
})
|
})
|
||||||
.unwrap(),
|
})
|
||||||
|
.expect("CARGO_MANIFEST_DIR is not defined."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::{IVec2, Rect, URect};
|
||||||
/// methods instead, which will ensure this invariant is met, unless you already have
|
/// methods instead, which will ensure this invariant is met, unless you already have
|
||||||
/// the minimum and maximum corners.
|
/// the minimum and maximum corners.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Default, Clone, Copy, Debug, PartialEq)]
|
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct IRect {
|
pub struct IRect {
|
||||||
/// The minimum corner point of the rect.
|
/// The minimum corner point of the rect.
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::{IRect, Rect, UVec2};
|
||||||
/// methods instead, which will ensure this invariant is met, unless you already have
|
/// methods instead, which will ensure this invariant is met, unless you already have
|
||||||
/// the minimum and maximum corners.
|
/// the minimum and maximum corners.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Default, Clone, Copy, Debug, PartialEq)]
|
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct URect {
|
pub struct URect {
|
||||||
/// The minimum corner point of the rect.
|
/// The minimum corner point of the rect.
|
||||||
|
|
|
@ -166,6 +166,7 @@ impl Plugin for PbrPlugin {
|
||||||
);
|
);
|
||||||
|
|
||||||
app.register_asset_reflect::<StandardMaterial>()
|
app.register_asset_reflect::<StandardMaterial>()
|
||||||
|
.register_type::<AlphaMode>()
|
||||||
.register_type::<AmbientLight>()
|
.register_type::<AmbientLight>()
|
||||||
.register_type::<Cascade>()
|
.register_type::<Cascade>()
|
||||||
.register_type::<CascadeShadowConfig>()
|
.register_type::<CascadeShadowConfig>()
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
use crate as bevy_reflect;
|
use crate as bevy_reflect;
|
||||||
use crate::prelude::ReflectDefault;
|
use crate::prelude::ReflectDefault;
|
||||||
use crate::{ReflectDeserialize, ReflectSerialize};
|
use crate::{ReflectDeserialize, ReflectSerialize};
|
||||||
use bevy_math::{Rect, Vec2};
|
use bevy_math::{IRect, IVec2, Rect, URect, UVec2, Vec2};
|
||||||
use bevy_reflect_derive::impl_reflect_struct;
|
use bevy_reflect_derive::impl_reflect_struct;
|
||||||
|
|
||||||
|
impl_reflect_struct!(
|
||||||
|
#[reflect(Debug, PartialEq, Hash, Serialize, Deserialize, Default)]
|
||||||
|
#[type_path = "bevy_math"]
|
||||||
|
struct IRect {
|
||||||
|
min: IVec2,
|
||||||
|
max: IVec2,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
impl_reflect_struct!(
|
impl_reflect_struct!(
|
||||||
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
|
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
|
||||||
#[type_path = "bevy_math"]
|
#[type_path = "bevy_math"]
|
||||||
|
@ -12,3 +21,12 @@ impl_reflect_struct!(
|
||||||
max: Vec2,
|
max: Vec2,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
impl_reflect_struct!(
|
||||||
|
#[reflect(Debug, PartialEq, Hash, Serialize, Deserialize, Default)]
|
||||||
|
#[type_path = "bevy_math"]
|
||||||
|
struct URect {
|
||||||
|
min: UVec2,
|
||||||
|
max: UVec2,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -58,7 +58,6 @@ image = { version = "0.24", default-features = false }
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
wgpu = { version = "0.16.0", features=["naga"] }
|
wgpu = { version = "0.16.0", features=["naga"] }
|
||||||
wgpu-hal = "0.16.0"
|
|
||||||
codespan-reporting = "0.11.0"
|
codespan-reporting = "0.11.0"
|
||||||
naga = { version = "0.12.0", features = ["wgsl-in"] }
|
naga = { version = "0.12.0", features = ["wgsl-in"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
55
crates/bevy_render/src/gpu_component_array_buffer.rs
Normal file
55
crates/bevy_render/src/gpu_component_array_buffer.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use crate::{
|
||||||
|
render_resource::{GpuArrayBuffer, GpuArrayBufferable},
|
||||||
|
renderer::{RenderDevice, RenderQueue},
|
||||||
|
Render, RenderApp, RenderSet,
|
||||||
|
};
|
||||||
|
use bevy_app::{App, Plugin};
|
||||||
|
use bevy_ecs::{
|
||||||
|
prelude::{Component, Entity},
|
||||||
|
schedule::IntoSystemConfigs,
|
||||||
|
system::{Commands, Query, Res, ResMut},
|
||||||
|
};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
/// This plugin prepares the components of the corresponding type for the GPU
|
||||||
|
/// by storing them in a [`GpuArrayBuffer`].
|
||||||
|
pub struct GpuComponentArrayBufferPlugin<C: Component + GpuArrayBufferable>(PhantomData<C>);
|
||||||
|
|
||||||
|
impl<C: Component + GpuArrayBufferable> Plugin for GpuComponentArrayBufferPlugin<C> {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
|
render_app
|
||||||
|
.insert_resource(GpuArrayBuffer::<C>::new(
|
||||||
|
render_app.world.resource::<RenderDevice>(),
|
||||||
|
))
|
||||||
|
.add_systems(
|
||||||
|
Render,
|
||||||
|
prepare_gpu_component_array_buffers::<C>.in_set(RenderSet::Prepare),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Component + GpuArrayBufferable> Default for GpuComponentArrayBufferPlugin<C> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(PhantomData::<C>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_gpu_component_array_buffers<C: Component + GpuArrayBufferable>(
|
||||||
|
mut commands: Commands,
|
||||||
|
render_device: Res<RenderDevice>,
|
||||||
|
render_queue: Res<RenderQueue>,
|
||||||
|
mut gpu_array_buffer: ResMut<GpuArrayBuffer<C>>,
|
||||||
|
components: Query<(Entity, &C)>,
|
||||||
|
) {
|
||||||
|
gpu_array_buffer.clear();
|
||||||
|
|
||||||
|
let entities = components
|
||||||
|
.iter()
|
||||||
|
.map(|(entity, component)| (entity, gpu_array_buffer.push(component.clone())))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
commands.insert_or_spawn_batch(entities);
|
||||||
|
|
||||||
|
gpu_array_buffer.write_buffer(&render_device, &render_queue);
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ pub mod extract_component;
|
||||||
mod extract_param;
|
mod extract_param;
|
||||||
pub mod extract_resource;
|
pub mod extract_resource;
|
||||||
pub mod globals;
|
pub mod globals;
|
||||||
|
pub mod gpu_component_array_buffer;
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
pub mod pipelined_rendering;
|
pub mod pipelined_rendering;
|
||||||
pub mod primitives;
|
pub mod primitives;
|
||||||
|
|
|
@ -103,21 +103,29 @@ impl Plugin for PipelinedRenderingPlugin {
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
let _span = bevy_utils::tracing::info_span!("render thread").entered();
|
let _span = bevy_utils::tracing::info_span!("render thread").entered();
|
||||||
|
|
||||||
|
let compute_task_pool = ComputeTaskPool::get();
|
||||||
loop {
|
loop {
|
||||||
// run a scope here to allow main world to use this thread while it's waiting for the render app
|
// run a scope here to allow main world to use this thread while it's waiting for the render app
|
||||||
let mut render_app = ComputeTaskPool::get()
|
let sent_app = compute_task_pool
|
||||||
.scope(|s| {
|
.scope(|s| {
|
||||||
s.spawn(async { app_to_render_receiver.recv().await.unwrap() });
|
s.spawn(async { app_to_render_receiver.recv().await });
|
||||||
})
|
})
|
||||||
.pop()
|
.pop();
|
||||||
.unwrap();
|
let Some(Ok(mut render_app)) = sent_app else { break };
|
||||||
|
|
||||||
|
{
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
let _sub_app_span =
|
let _sub_app_span =
|
||||||
bevy_utils::tracing::info_span!("sub app", name = ?RenderApp).entered();
|
bevy_utils::tracing::info_span!("sub app", name = ?RenderApp).entered();
|
||||||
render_app.run();
|
render_app.run();
|
||||||
render_to_app_sender.send_blocking(render_app).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if render_to_app_sender.send_blocking(render_app).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bevy_utils::tracing::debug!("exiting pipelined rendering thread");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
152
crates/bevy_render/src/render_resource/batched_uniform_buffer.rs
Normal file
152
crates/bevy_render/src/render_resource/batched_uniform_buffer.rs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
use super::{GpuArrayBufferIndex, GpuArrayBufferable};
|
||||||
|
use crate::{
|
||||||
|
render_resource::DynamicUniformBuffer,
|
||||||
|
renderer::{RenderDevice, RenderQueue},
|
||||||
|
};
|
||||||
|
use encase::{
|
||||||
|
private::{ArrayMetadata, BufferMut, Metadata, RuntimeSizedArray, WriteInto, Writer},
|
||||||
|
ShaderType,
|
||||||
|
};
|
||||||
|
use std::{marker::PhantomData, num::NonZeroU64};
|
||||||
|
use wgpu::{BindingResource, Limits};
|
||||||
|
|
||||||
|
// 1MB else we will make really large arrays on macOS which reports very large
|
||||||
|
// `max_uniform_buffer_binding_size`. On macOS this ends up being the minimum
|
||||||
|
// size of the uniform buffer as well as the size of each chunk of data at a
|
||||||
|
// dynamic offset.
|
||||||
|
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
|
||||||
|
const MAX_REASONABLE_UNIFORM_BUFFER_BINDING_SIZE: u32 = 1 << 20;
|
||||||
|
|
||||||
|
// WebGL2 quirk: using uniform buffers larger than 4KB will cause extremely
|
||||||
|
// long shader compilation times, so the limit needs to be lower on WebGL2.
|
||||||
|
// This is due to older shader compilers/GPUs that don't support dynamically
|
||||||
|
// indexing uniform buffers, and instead emulate it with large switch statements
|
||||||
|
// over buffer indices that take a long time to compile.
|
||||||
|
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
|
||||||
|
const MAX_REASONABLE_UNIFORM_BUFFER_BINDING_SIZE: u32 = 1 << 12;
|
||||||
|
|
||||||
|
/// Similar to [`DynamicUniformBuffer`], except every N elements (depending on size)
|
||||||
|
/// are grouped into a batch as an `array<T, N>` in WGSL.
|
||||||
|
///
|
||||||
|
/// This reduces the number of rebindings required due to having to pass dynamic
|
||||||
|
/// offsets to bind group commands, and if indices into the array can be passed
|
||||||
|
/// in via other means, it enables batching of draw commands.
|
||||||
|
pub struct BatchedUniformBuffer<T: GpuArrayBufferable> {
|
||||||
|
// Batches of fixed-size arrays of T are written to this buffer so that
|
||||||
|
// each batch in a fixed-size array can be bound at a dynamic offset.
|
||||||
|
uniforms: DynamicUniformBuffer<MaxCapacityArray<Vec<T>>>,
|
||||||
|
// A batch of T are gathered into this `MaxCapacityArray` until it is full,
|
||||||
|
// then it is written into the `DynamicUniformBuffer`, cleared, and new T
|
||||||
|
// are gathered here, and so on for each batch.
|
||||||
|
temp: MaxCapacityArray<Vec<T>>,
|
||||||
|
current_offset: u32,
|
||||||
|
dynamic_offset_alignment: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: GpuArrayBufferable> BatchedUniformBuffer<T> {
|
||||||
|
pub fn batch_size(limits: &Limits) -> usize {
|
||||||
|
(limits
|
||||||
|
.max_uniform_buffer_binding_size
|
||||||
|
.min(MAX_REASONABLE_UNIFORM_BUFFER_BINDING_SIZE) as u64
|
||||||
|
/ T::min_size().get()) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(limits: &Limits) -> Self {
|
||||||
|
let capacity = Self::batch_size(limits);
|
||||||
|
let alignment = limits.min_uniform_buffer_offset_alignment;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
uniforms: DynamicUniformBuffer::new_with_alignment(alignment as u64),
|
||||||
|
temp: MaxCapacityArray(Vec::with_capacity(capacity), capacity),
|
||||||
|
current_offset: 0,
|
||||||
|
dynamic_offset_alignment: alignment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn size(&self) -> NonZeroU64 {
|
||||||
|
self.temp.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.uniforms.clear();
|
||||||
|
self.current_offset = 0;
|
||||||
|
self.temp.0.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, component: T) -> GpuArrayBufferIndex<T> {
|
||||||
|
let result = GpuArrayBufferIndex {
|
||||||
|
index: self.temp.0.len() as u32,
|
||||||
|
dynamic_offset: Some(self.current_offset),
|
||||||
|
element_type: PhantomData,
|
||||||
|
};
|
||||||
|
self.temp.0.push(component);
|
||||||
|
if self.temp.0.len() == self.temp.1 {
|
||||||
|
self.flush();
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flush(&mut self) {
|
||||||
|
self.uniforms.push(self.temp.clone());
|
||||||
|
|
||||||
|
self.current_offset +=
|
||||||
|
align_to_next(self.temp.size().get(), self.dynamic_offset_alignment as u64) as u32;
|
||||||
|
|
||||||
|
self.temp.0.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
|
||||||
|
if !self.temp.0.is_empty() {
|
||||||
|
self.flush();
|
||||||
|
}
|
||||||
|
self.uniforms.write_buffer(device, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn binding(&self) -> Option<BindingResource> {
|
||||||
|
let mut binding = self.uniforms.binding();
|
||||||
|
if let Some(BindingResource::Buffer(binding)) = &mut binding {
|
||||||
|
// MaxCapacityArray is runtime-sized so can't use T::min_size()
|
||||||
|
binding.size = Some(self.size());
|
||||||
|
}
|
||||||
|
binding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn align_to_next(value: u64, alignment: u64) -> u64 {
|
||||||
|
debug_assert!(alignment & (alignment - 1) == 0);
|
||||||
|
((value - 1) | (alignment - 1)) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// MaxCapacityArray was implemented by Teodor Tanasoaia for encase. It was
|
||||||
|
// copied here as it was not yet included in an encase release and it is
|
||||||
|
// unclear if it is the correct long-term solution for encase.
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct MaxCapacityArray<T>(T, usize);
|
||||||
|
|
||||||
|
impl<T> ShaderType for MaxCapacityArray<T>
|
||||||
|
where
|
||||||
|
T: ShaderType<ExtraMetadata = ArrayMetadata>,
|
||||||
|
{
|
||||||
|
type ExtraMetadata = ArrayMetadata;
|
||||||
|
|
||||||
|
const METADATA: Metadata<Self::ExtraMetadata> = T::METADATA;
|
||||||
|
|
||||||
|
fn size(&self) -> ::core::num::NonZeroU64 {
|
||||||
|
Self::METADATA.stride().mul(self.1.max(1) as u64).0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> WriteInto for MaxCapacityArray<T>
|
||||||
|
where
|
||||||
|
T: WriteInto + RuntimeSizedArray,
|
||||||
|
{
|
||||||
|
fn write_into<B: BufferMut>(&self, writer: &mut Writer<B>) {
|
||||||
|
debug_assert!(self.0.len() <= self.1);
|
||||||
|
self.0.write_into(writer);
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,9 +21,11 @@ use wgpu::BufferUsages;
|
||||||
/// from system RAM to VRAM.
|
/// from system RAM to VRAM.
|
||||||
///
|
///
|
||||||
/// Other options for storing GPU-accessible data are:
|
/// Other options for storing GPU-accessible data are:
|
||||||
|
/// * [`StorageBuffer`](crate::render_resource::StorageBuffer)
|
||||||
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
||||||
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
||||||
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
||||||
|
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
|
||||||
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
||||||
/// * [`Texture`](crate::render_resource::Texture)
|
/// * [`Texture`](crate::render_resource::Texture)
|
||||||
pub struct BufferVec<T: Pod> {
|
pub struct BufferVec<T: Pod> {
|
||||||
|
|
129
crates/bevy_render/src/render_resource/gpu_array_buffer.rs
Normal file
129
crates/bevy_render/src/render_resource/gpu_array_buffer.rs
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
use super::StorageBuffer;
|
||||||
|
use crate::{
|
||||||
|
render_resource::batched_uniform_buffer::BatchedUniformBuffer,
|
||||||
|
renderer::{RenderDevice, RenderQueue},
|
||||||
|
};
|
||||||
|
use bevy_ecs::{prelude::Component, system::Resource};
|
||||||
|
use encase::{private::WriteInto, ShaderSize, ShaderType};
|
||||||
|
use std::{marker::PhantomData, mem};
|
||||||
|
use wgpu::{BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType, ShaderStages};
|
||||||
|
|
||||||
|
/// Trait for types able to go in a [`GpuArrayBuffer`].
|
||||||
|
pub trait GpuArrayBufferable: ShaderType + ShaderSize + WriteInto + Clone {}
|
||||||
|
impl<T: ShaderType + ShaderSize + WriteInto + Clone> GpuArrayBufferable for T {}
|
||||||
|
|
||||||
|
/// Stores an array of elements to be transferred to the GPU and made accessible to shaders as a read-only array.
|
||||||
|
///
|
||||||
|
/// On platforms that support storage buffers, this is equivalent to [`StorageBuffer<Vec<T>>`].
|
||||||
|
/// Otherwise, this falls back to a dynamic offset uniform buffer with the largest
|
||||||
|
/// array of T that fits within a uniform buffer binding (within reasonable limits).
|
||||||
|
///
|
||||||
|
/// Other options for storing GPU-accessible data are:
|
||||||
|
/// * [`StorageBuffer`](crate::render_resource::StorageBuffer)
|
||||||
|
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
||||||
|
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
||||||
|
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
||||||
|
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
||||||
|
/// * [`Texture`](crate::render_resource::Texture)
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub enum GpuArrayBuffer<T: GpuArrayBufferable> {
|
||||||
|
Uniform(BatchedUniformBuffer<T>),
|
||||||
|
Storage((StorageBuffer<Vec<T>>, Vec<T>)),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: GpuArrayBufferable> GpuArrayBuffer<T> {
|
||||||
|
pub fn new(device: &RenderDevice) -> Self {
|
||||||
|
let limits = device.limits();
|
||||||
|
if limits.max_storage_buffers_per_shader_stage == 0 {
|
||||||
|
GpuArrayBuffer::Uniform(BatchedUniformBuffer::new(&limits))
|
||||||
|
} else {
|
||||||
|
GpuArrayBuffer::Storage((StorageBuffer::default(), Vec::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
match self {
|
||||||
|
GpuArrayBuffer::Uniform(buffer) => buffer.clear(),
|
||||||
|
GpuArrayBuffer::Storage((_, buffer)) => buffer.clear(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, value: T) -> GpuArrayBufferIndex<T> {
|
||||||
|
match self {
|
||||||
|
GpuArrayBuffer::Uniform(buffer) => buffer.push(value),
|
||||||
|
GpuArrayBuffer::Storage((_, buffer)) => {
|
||||||
|
let index = buffer.len() as u32;
|
||||||
|
buffer.push(value);
|
||||||
|
GpuArrayBufferIndex {
|
||||||
|
index,
|
||||||
|
dynamic_offset: None,
|
||||||
|
element_type: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
|
||||||
|
match self {
|
||||||
|
GpuArrayBuffer::Uniform(buffer) => buffer.write_buffer(device, queue),
|
||||||
|
GpuArrayBuffer::Storage((buffer, vec)) => {
|
||||||
|
buffer.set(mem::take(vec));
|
||||||
|
buffer.write_buffer(device, queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn binding_layout(
|
||||||
|
binding: u32,
|
||||||
|
visibility: ShaderStages,
|
||||||
|
device: &RenderDevice,
|
||||||
|
) -> BindGroupLayoutEntry {
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding,
|
||||||
|
visibility,
|
||||||
|
ty: if device.limits().max_storage_buffers_per_shader_stage == 0 {
|
||||||
|
BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: true,
|
||||||
|
// BatchedUniformBuffer uses a MaxCapacityArray that is runtime-sized, so we use
|
||||||
|
// None here and let wgpu figure out the size.
|
||||||
|
min_binding_size: None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Storage { read_only: true },
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: Some(T::min_size()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn binding(&self) -> Option<BindingResource> {
|
||||||
|
match self {
|
||||||
|
GpuArrayBuffer::Uniform(buffer) => buffer.binding(),
|
||||||
|
GpuArrayBuffer::Storage((buffer, _)) => buffer.binding(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn batch_size(device: &RenderDevice) -> Option<u32> {
|
||||||
|
let limits = device.limits();
|
||||||
|
if limits.max_storage_buffers_per_shader_stage == 0 {
|
||||||
|
Some(BatchedUniformBuffer::<T>::batch_size(&limits) as u32)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An index into a [`GpuArrayBuffer`] for a given element.
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct GpuArrayBufferIndex<T: GpuArrayBufferable> {
|
||||||
|
/// The index to use in a shader into the array.
|
||||||
|
pub index: u32,
|
||||||
|
/// The dynamic offset to use when setting the bind group in a pass.
|
||||||
|
/// Only used on platforms that don't support storage buffers.
|
||||||
|
pub dynamic_offset: Option<u32>,
|
||||||
|
pub element_type: PhantomData<T>,
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
|
mod batched_uniform_buffer;
|
||||||
mod bind_group;
|
mod bind_group;
|
||||||
mod bind_group_layout;
|
mod bind_group_layout;
|
||||||
mod buffer;
|
mod buffer;
|
||||||
mod buffer_vec;
|
mod buffer_vec;
|
||||||
|
mod gpu_array_buffer;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
mod pipeline_cache;
|
mod pipeline_cache;
|
||||||
mod pipeline_specializer;
|
mod pipeline_specializer;
|
||||||
|
@ -15,6 +17,7 @@ pub use bind_group::*;
|
||||||
pub use bind_group_layout::*;
|
pub use bind_group_layout::*;
|
||||||
pub use buffer::*;
|
pub use buffer::*;
|
||||||
pub use buffer_vec::*;
|
pub use buffer_vec::*;
|
||||||
|
pub use gpu_array_buffer::*;
|
||||||
pub use pipeline::*;
|
pub use pipeline::*;
|
||||||
pub use pipeline_cache::*;
|
pub use pipeline_cache::*;
|
||||||
pub use pipeline_specializer::*;
|
pub use pipeline_specializer::*;
|
||||||
|
|
|
@ -25,6 +25,7 @@ use wgpu::{util::BufferInitDescriptor, BindingResource, BufferBinding, BufferUsa
|
||||||
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
||||||
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
||||||
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
||||||
|
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
|
||||||
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
||||||
/// * [`Texture`](crate::render_resource::Texture)
|
/// * [`Texture`](crate::render_resource::Texture)
|
||||||
///
|
///
|
||||||
|
@ -154,6 +155,7 @@ impl<T: ShaderType + WriteInto> StorageBuffer<T> {
|
||||||
/// * [`StorageBuffer`](crate::render_resource::StorageBuffer)
|
/// * [`StorageBuffer`](crate::render_resource::StorageBuffer)
|
||||||
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
||||||
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
||||||
|
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
|
||||||
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
||||||
/// * [`Texture`](crate::render_resource::Texture)
|
/// * [`Texture`](crate::render_resource::Texture)
|
||||||
///
|
///
|
||||||
|
|
|
@ -22,9 +22,10 @@ use wgpu::{util::BufferInitDescriptor, BindingResource, BufferBinding, BufferUsa
|
||||||
/// (vectors), or structures with fields that are vectors.
|
/// (vectors), or structures with fields that are vectors.
|
||||||
///
|
///
|
||||||
/// Other options for storing GPU-accessible data are:
|
/// Other options for storing GPU-accessible data are:
|
||||||
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
|
||||||
/// * [`StorageBuffer`](crate::render_resource::StorageBuffer)
|
/// * [`StorageBuffer`](crate::render_resource::StorageBuffer)
|
||||||
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
||||||
|
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
||||||
|
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
|
||||||
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
||||||
/// * [`Texture`](crate::render_resource::Texture)
|
/// * [`Texture`](crate::render_resource::Texture)
|
||||||
///
|
///
|
||||||
|
@ -151,6 +152,8 @@ impl<T: ShaderType + WriteInto> UniformBuffer<T> {
|
||||||
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
||||||
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
||||||
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
||||||
|
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
|
||||||
|
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
||||||
/// * [`Texture`](crate::render_resource::Texture)
|
/// * [`Texture`](crate::render_resource::Texture)
|
||||||
///
|
///
|
||||||
/// [std140 alignment/padding requirements]: https://www.w3.org/TR/WGSL/#address-spaces-uniform
|
/// [std140 alignment/padding requirements]: https://www.w3.org/TR/WGSL/#address-spaces-uniform
|
||||||
|
@ -177,6 +180,17 @@ impl<T: ShaderType> Default for DynamicUniformBuffer<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ShaderType + WriteInto> DynamicUniformBuffer<T> {
|
impl<T: ShaderType + WriteInto> DynamicUniformBuffer<T> {
|
||||||
|
pub fn new_with_alignment(alignment: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
scratch: DynamicUniformBufferWrapper::new_with_alignment(Vec::new(), alignment),
|
||||||
|
buffer: None,
|
||||||
|
label: None,
|
||||||
|
changed: false,
|
||||||
|
buffer_usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn buffer(&self) -> Option<&Buffer> {
|
pub fn buffer(&self) -> Option<&Buffer> {
|
||||||
self.buffer.as_ref()
|
self.buffer.as_ref()
|
||||||
|
|
|
@ -82,7 +82,7 @@ impl FromWorld for ImageTextureLoader {
|
||||||
let supported_compressed_formats = match world.get_resource::<RenderDevice>() {
|
let supported_compressed_formats = match world.get_resource::<RenderDevice>() {
|
||||||
Some(render_device) => CompressedImageFormats::from_features(render_device.features()),
|
Some(render_device) => CompressedImageFormats::from_features(render_device.features()),
|
||||||
|
|
||||||
None => CompressedImageFormats::all(),
|
None => CompressedImageFormats::NONE,
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
supported_compressed_formats,
|
supported_compressed_formats,
|
||||||
|
|
|
@ -70,19 +70,6 @@ impl ImagePlugin {
|
||||||
|
|
||||||
impl Plugin for ImagePlugin {
|
impl Plugin for ImagePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
#[cfg(any(
|
|
||||||
feature = "png",
|
|
||||||
feature = "dds",
|
|
||||||
feature = "tga",
|
|
||||||
feature = "jpeg",
|
|
||||||
feature = "bmp",
|
|
||||||
feature = "basis-universal",
|
|
||||||
feature = "ktx2",
|
|
||||||
))]
|
|
||||||
{
|
|
||||||
app.init_asset_loader::<ImageTextureLoader>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "exr")]
|
#[cfg(feature = "exr")]
|
||||||
{
|
{
|
||||||
app.init_asset_loader::<ExrTextureLoader>();
|
app.init_asset_loader::<ExrTextureLoader>();
|
||||||
|
@ -112,6 +99,19 @@ impl Plugin for ImagePlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, app: &mut App) {
|
fn finish(&self, app: &mut App) {
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "png",
|
||||||
|
feature = "dds",
|
||||||
|
feature = "tga",
|
||||||
|
feature = "jpeg",
|
||||||
|
feature = "bmp",
|
||||||
|
feature = "basis-universal",
|
||||||
|
feature = "ktx2",
|
||||||
|
))]
|
||||||
|
{
|
||||||
|
app.init_asset_loader::<ImageTextureLoader>();
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
let default_sampler = {
|
let default_sampler = {
|
||||||
let device = render_app.world.resource::<RenderDevice>();
|
let device = render_app.world.resource::<RenderDevice>();
|
||||||
|
|
|
@ -367,7 +367,7 @@ pub fn check_visibility(
|
||||||
let view_mask = maybe_view_mask.copied().unwrap_or_default();
|
let view_mask = maybe_view_mask.copied().unwrap_or_default();
|
||||||
|
|
||||||
visible_entities.entities.clear();
|
visible_entities.entities.clear();
|
||||||
visible_aabb_query.par_iter_mut().for_each_mut(
|
visible_aabb_query.par_iter_mut().for_each(
|
||||||
|(
|
|(
|
||||||
entity,
|
entity,
|
||||||
mut computed_visibility,
|
mut computed_visibility,
|
||||||
|
@ -412,7 +412,7 @@ pub fn check_visibility(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
visible_no_aabb_query.par_iter_mut().for_each_mut(
|
visible_no_aabb_query.par_iter_mut().for_each(
|
||||||
|(entity, mut computed_visibility, maybe_entity_mask)| {
|
|(entity, mut computed_visibility, maybe_entity_mask)| {
|
||||||
// skip computing visibility for entities that are configured to be hidden. is_visible_in_view has already been set to false
|
// skip computing visibility for entities that are configured to be hidden. is_visible_in_view has already been set to false
|
||||||
// in visibility_propagate_system
|
// in visibility_propagate_system
|
||||||
|
|
|
@ -266,6 +266,7 @@ pub fn prepare_windows(
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
present_mode: match window.present_mode {
|
present_mode: match window.present_mode {
|
||||||
PresentMode::Fifo => wgpu::PresentMode::Fifo,
|
PresentMode::Fifo => wgpu::PresentMode::Fifo,
|
||||||
|
PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed,
|
||||||
PresentMode::Mailbox => wgpu::PresentMode::Mailbox,
|
PresentMode::Mailbox => wgpu::PresentMode::Mailbox,
|
||||||
PresentMode::Immediate => wgpu::PresentMode::Immediate,
|
PresentMode::Immediate => wgpu::PresentMode::Immediate,
|
||||||
PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync,
|
PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync,
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub fn sync_simple_transforms(
|
||||||
query
|
query
|
||||||
.p0()
|
.p0()
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.for_each_mut(|(transform, mut global_transform)| {
|
.for_each(|(transform, mut global_transform)| {
|
||||||
*global_transform = GlobalTransform::from(*transform);
|
*global_transform = GlobalTransform::from(*transform);
|
||||||
});
|
});
|
||||||
// Update orphaned entities.
|
// Update orphaned entities.
|
||||||
|
@ -59,7 +59,7 @@ pub fn propagate_transforms(
|
||||||
orphaned_entities.clear();
|
orphaned_entities.clear();
|
||||||
orphaned_entities.extend(orphaned.iter());
|
orphaned_entities.extend(orphaned.iter());
|
||||||
orphaned_entities.sort_unstable();
|
orphaned_entities.sort_unstable();
|
||||||
root_query.par_iter_mut().for_each_mut(
|
root_query.par_iter_mut().for_each(
|
||||||
|(entity, children, transform, mut global_transform)| {
|
|(entity, children, transform, mut global_transform)| {
|
||||||
let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
|
let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
|
||||||
if changed {
|
if changed {
|
||||||
|
|
|
@ -58,6 +58,7 @@ pub const UI_SHADER_HANDLE: HandleUntyped =
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||||
pub enum RenderUiSystem {
|
pub enum RenderUiSystem {
|
||||||
ExtractNode,
|
ExtractNode,
|
||||||
|
ExtractAtlasNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_ui_render(app: &mut App) {
|
pub fn build_ui_render(app: &mut App) {
|
||||||
|
@ -81,10 +82,12 @@ pub fn build_ui_render(app: &mut App) {
|
||||||
extract_default_ui_camera_view::<Camera2d>,
|
extract_default_ui_camera_view::<Camera2d>,
|
||||||
extract_default_ui_camera_view::<Camera3d>,
|
extract_default_ui_camera_view::<Camera3d>,
|
||||||
extract_uinodes.in_set(RenderUiSystem::ExtractNode),
|
extract_uinodes.in_set(RenderUiSystem::ExtractNode),
|
||||||
extract_atlas_uinodes.after(RenderUiSystem::ExtractNode),
|
extract_atlas_uinodes
|
||||||
extract_uinode_borders.after(RenderUiSystem::ExtractNode),
|
.in_set(RenderUiSystem::ExtractAtlasNode)
|
||||||
|
.after(RenderUiSystem::ExtractNode),
|
||||||
|
extract_uinode_borders.after(RenderUiSystem::ExtractAtlasNode),
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
extract_text_uinodes.after(RenderUiSystem::ExtractNode),
|
extract_text_uinodes.after(RenderUiSystem::ExtractAtlasNode),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
|
|
|
@ -141,7 +141,8 @@ impl Plugin for WindowPlugin {
|
||||||
.register_type::<InternalWindowState>()
|
.register_type::<InternalWindowState>()
|
||||||
.register_type::<MonitorSelection>()
|
.register_type::<MonitorSelection>()
|
||||||
.register_type::<WindowResizeConstraints>()
|
.register_type::<WindowResizeConstraints>()
|
||||||
.register_type::<WindowTheme>();
|
.register_type::<WindowTheme>()
|
||||||
|
.register_type::<EnabledButtons>();
|
||||||
|
|
||||||
// Register `PathBuf` as it's used by `FileDragAndDrop`
|
// Register `PathBuf` as it's used by `FileDragAndDrop`
|
||||||
app.register_type::<PathBuf>();
|
app.register_type::<PathBuf>();
|
||||||
|
|
|
@ -126,13 +126,21 @@ pub struct Window {
|
||||||
/// Note: This does not stop the program from fullscreening/setting
|
/// Note: This does not stop the program from fullscreening/setting
|
||||||
/// the size programmatically.
|
/// the size programmatically.
|
||||||
pub resizable: bool,
|
pub resizable: bool,
|
||||||
|
/// Specifies which window control buttons should be enabled.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// **`iOS`**, **`Android`**, and the **`Web`** do not have window control buttons.
|
||||||
|
///
|
||||||
|
/// On some **`Linux`** environments these values have no effect.
|
||||||
|
pub enabled_buttons: EnabledButtons,
|
||||||
/// Should the window have decorations enabled?
|
/// Should the window have decorations enabled?
|
||||||
///
|
///
|
||||||
/// (Decorations are the minimize, maximize, and close buttons on desktop apps)
|
/// (Decorations are the minimize, maximize, and close buttons on desktop apps)
|
||||||
///
|
///
|
||||||
// ## Platform-specific
|
/// ## Platform-specific
|
||||||
//
|
///
|
||||||
// **`iOS`**, **`Android`**, and the **`Web`** do not have decorations.
|
/// **`iOS`**, **`Android`**, and the **`Web`** do not have decorations.
|
||||||
pub decorations: bool,
|
pub decorations: bool,
|
||||||
/// Should the window be transparent?
|
/// Should the window be transparent?
|
||||||
///
|
///
|
||||||
|
@ -221,6 +229,7 @@ impl Default for Window {
|
||||||
ime_enabled: Default::default(),
|
ime_enabled: Default::default(),
|
||||||
ime_position: Default::default(),
|
ime_position: Default::default(),
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
enabled_buttons: Default::default(),
|
||||||
decorations: true,
|
decorations: true,
|
||||||
transparent: false,
|
transparent: false,
|
||||||
focused: true,
|
focused: true,
|
||||||
|
@ -801,6 +810,7 @@ pub enum MonitorSelection {
|
||||||
/// [`Immediate`] or [`Mailbox`] will panic if not supported by the platform.
|
/// [`Immediate`] or [`Mailbox`] will panic if not supported by the platform.
|
||||||
///
|
///
|
||||||
/// [`Fifo`]: PresentMode::Fifo
|
/// [`Fifo`]: PresentMode::Fifo
|
||||||
|
/// [`FifoRelaxed`]: PresentMode::FifoRelaxed
|
||||||
/// [`Immediate`]: PresentMode::Immediate
|
/// [`Immediate`]: PresentMode::Immediate
|
||||||
/// [`Mailbox`]: PresentMode::Mailbox
|
/// [`Mailbox`]: PresentMode::Mailbox
|
||||||
/// [`AutoVsync`]: PresentMode::AutoVsync
|
/// [`AutoVsync`]: PresentMode::AutoVsync
|
||||||
|
@ -819,30 +829,67 @@ pub enum PresentMode {
|
||||||
/// Chooses FifoRelaxed -> Fifo based on availability.
|
/// Chooses FifoRelaxed -> Fifo based on availability.
|
||||||
///
|
///
|
||||||
/// Because of the fallback behavior, it is supported everywhere.
|
/// Because of the fallback behavior, it is supported everywhere.
|
||||||
AutoVsync = 0,
|
AutoVsync = 0, // NOTE: The explicit ordinal values mirror wgpu.
|
||||||
/// Chooses Immediate -> Mailbox -> Fifo (on web) based on availability.
|
/// Chooses Immediate -> Mailbox -> Fifo (on web) based on availability.
|
||||||
///
|
///
|
||||||
/// Because of the fallback behavior, it is supported everywhere.
|
/// Because of the fallback behavior, it is supported everywhere.
|
||||||
AutoNoVsync = 1,
|
AutoNoVsync = 1,
|
||||||
/// The presentation engine does **not** wait for a vertical blanking period and
|
/// Presentation frames are kept in a First-In-First-Out queue approximately 3 frames
|
||||||
/// the request is presented immediately. This is a low-latency presentation mode,
|
/// long. Every vertical blanking period, the presentation engine will pop a frame
|
||||||
/// but visible tearing may be observed. Not optimal for mobile.
|
/// off the queue to display. If there is no frame to display, it will present the same
|
||||||
|
/// frame again until the next vblank.
|
||||||
///
|
///
|
||||||
/// Selecting this variant will panic if not supported, it is preferred to use
|
/// When a present command is executed on the gpu, the presented image is added on the queue.
|
||||||
/// [`PresentMode::AutoNoVsync`].
|
|
||||||
Immediate = 2,
|
|
||||||
/// The presentation engine waits for the next vertical blanking period to update
|
|
||||||
/// the current image, but frames may be submitted without delay. This is a low-latency
|
|
||||||
/// presentation mode and visible tearing will **not** be observed. Not optimal for mobile.
|
|
||||||
///
|
///
|
||||||
/// Selecting this variant will panic if not supported, it is preferred to use
|
/// No tearing will be observed.
|
||||||
/// [`PresentMode::AutoNoVsync`].
|
///
|
||||||
Mailbox = 3,
|
/// Calls to get_current_texture will block until there is a spot in the queue.
|
||||||
/// The presentation engine waits for the next vertical blanking period to update
|
///
|
||||||
/// the current image. The framerate will be capped at the display refresh rate,
|
/// Supported on all platforms.
|
||||||
/// corresponding to the `VSync`. Tearing cannot be observed. Optimal for mobile.
|
///
|
||||||
|
/// If you don't know what mode to choose, choose this mode. This is traditionally called "Vsync On".
|
||||||
#[default]
|
#[default]
|
||||||
Fifo = 4, // NOTE: The explicit ordinal values mirror wgpu.
|
Fifo = 2,
|
||||||
|
/// Presentation frames are kept in a First-In-First-Out queue approximately 3 frames
|
||||||
|
/// long. Every vertical blanking period, the presentation engine will pop a frame
|
||||||
|
/// off the queue to display. If there is no frame to display, it will present the
|
||||||
|
/// same frame until there is a frame in the queue. The moment there is a frame in the
|
||||||
|
/// queue, it will immediately pop the frame off the queue.
|
||||||
|
///
|
||||||
|
/// When a present command is executed on the gpu, the presented image is added on the queue.
|
||||||
|
///
|
||||||
|
/// Tearing will be observed if frames last more than one vblank as the front buffer.
|
||||||
|
///
|
||||||
|
/// Calls to get_current_texture will block until there is a spot in the queue.
|
||||||
|
///
|
||||||
|
/// Supported on AMD on Vulkan.
|
||||||
|
///
|
||||||
|
/// This is traditionally called "Adaptive Vsync"
|
||||||
|
FifoRelaxed = 3,
|
||||||
|
/// Presentation frames are not queued at all. The moment a present command
|
||||||
|
/// is executed on the GPU, the presented image is swapped onto the front buffer
|
||||||
|
/// immediately.
|
||||||
|
///
|
||||||
|
/// Tearing can be observed.
|
||||||
|
///
|
||||||
|
/// Supported on most platforms except older DX12 and Wayland.
|
||||||
|
///
|
||||||
|
/// This is traditionally called "Vsync Off".
|
||||||
|
Immediate = 4,
|
||||||
|
/// Presentation frames are kept in a single-frame queue. Every vertical blanking period,
|
||||||
|
/// the presentation engine will pop a frame from the queue. If there is no frame to display,
|
||||||
|
/// it will present the same frame again until the next vblank.
|
||||||
|
///
|
||||||
|
/// When a present command is executed on the gpu, the frame will be put into the queue.
|
||||||
|
/// If there was already a frame in the queue, the new frame will _replace_ the old frame
|
||||||
|
/// on the queue.
|
||||||
|
///
|
||||||
|
/// No tearing will be observed.
|
||||||
|
///
|
||||||
|
/// Supported on DX11/12 on Windows 10, NVidia on Vulkan and Wayland on Vulkan.
|
||||||
|
///
|
||||||
|
/// This is traditionally called "Fast Vsync"
|
||||||
|
Mailbox = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specifies how the alpha channel of the textures should be handled during compositing, for a [`Window`].
|
/// Specifies how the alpha channel of the textures should be handled during compositing, for a [`Window`].
|
||||||
|
@ -963,3 +1010,42 @@ pub enum WindowTheme {
|
||||||
/// Use the dark variant.
|
/// Use the dark variant.
|
||||||
Dark,
|
Dark,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specifies which [`Window`] control buttons should be enabled.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// **`iOS`**, **`Android`**, and the **`Web`** do not have window control buttons.
|
||||||
|
///
|
||||||
|
/// On some **`Linux`** environments these values have no effect.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Reflect)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serialize",
|
||||||
|
derive(serde::Serialize, serde::Deserialize),
|
||||||
|
reflect(Serialize, Deserialize)
|
||||||
|
)]
|
||||||
|
#[reflect(Debug, PartialEq, Default)]
|
||||||
|
pub struct EnabledButtons {
|
||||||
|
/// Enables the functionality of the minimize button.
|
||||||
|
pub minimize: bool,
|
||||||
|
/// Enables the functionality of the maximize button.
|
||||||
|
///
|
||||||
|
/// macOS note: When [`Window`] `resizable` member is set to `false`
|
||||||
|
/// the maximize button will be disabled regardless of this value.
|
||||||
|
/// Additionaly, when `resizable` is set to `true` the window will
|
||||||
|
/// be maximized when its bar is double-clicked regardless of whether
|
||||||
|
/// the maximize button is enabled or not.
|
||||||
|
pub maximize: bool,
|
||||||
|
/// Enables the functionality of the close button.
|
||||||
|
pub close: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EnabledButtons {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
minimize: true,
|
||||||
|
maximize: true,
|
||||||
|
close: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use bevy_input::{
|
||||||
ButtonState,
|
ButtonState,
|
||||||
};
|
};
|
||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
use bevy_window::{CursorIcon, WindowLevel, WindowTheme};
|
use bevy_window::{CursorIcon, EnabledButtons, WindowLevel, WindowTheme};
|
||||||
|
|
||||||
pub fn convert_keyboard_input(
|
pub fn convert_keyboard_input(
|
||||||
keyboard_input: &winit::event::KeyboardInput,
|
keyboard_input: &winit::event::KeyboardInput,
|
||||||
|
@ -293,3 +293,17 @@ pub fn convert_window_theme(theme: WindowTheme) -> winit::window::Theme {
|
||||||
WindowTheme::Dark => winit::window::Theme::Dark,
|
WindowTheme::Dark => winit::window::Theme::Dark,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn convert_enabled_buttons(enabled_buttons: EnabledButtons) -> winit::window::WindowButtons {
|
||||||
|
let mut window_buttons = winit::window::WindowButtons::empty();
|
||||||
|
if enabled_buttons.minimize {
|
||||||
|
window_buttons.insert(winit::window::WindowButtons::MINIMIZE);
|
||||||
|
}
|
||||||
|
if enabled_buttons.maximize {
|
||||||
|
window_buttons.insert(winit::window::WindowButtons::MAXIMIZE);
|
||||||
|
}
|
||||||
|
if enabled_buttons.close {
|
||||||
|
window_buttons.insert(winit::window::WindowButtons::CLOSE);
|
||||||
|
}
|
||||||
|
window_buttons
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,10 @@ use winit::{
|
||||||
use crate::web_resize::{CanvasParentResizeEventChannel, WINIT_CANVAS_SELECTOR};
|
use crate::web_resize::{CanvasParentResizeEventChannel, WINIT_CANVAS_SELECTOR};
|
||||||
use crate::{
|
use crate::{
|
||||||
accessibility::{AccessKitAdapters, WinitActionHandlers},
|
accessibility::{AccessKitAdapters, WinitActionHandlers},
|
||||||
converters::{self, convert_window_level, convert_window_theme, convert_winit_theme},
|
converters::{
|
||||||
|
self, convert_enabled_buttons, convert_window_level, convert_window_theme,
|
||||||
|
convert_winit_theme,
|
||||||
|
},
|
||||||
get_best_videomode, get_fitting_videomode, WinitWindows,
|
get_best_videomode, get_fitting_videomode, WinitWindows,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -222,6 +225,10 @@ pub(crate) fn changed_window(
|
||||||
winit_window.set_resizable(window.resizable);
|
winit_window.set_resizable(window.resizable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if window.enabled_buttons != cache.window.enabled_buttons {
|
||||||
|
winit_window.set_enabled_buttons(convert_enabled_buttons(window.enabled_buttons));
|
||||||
|
}
|
||||||
|
|
||||||
if window.resize_constraints != cache.window.resize_constraints {
|
if window.resize_constraints != cache.window.resize_constraints {
|
||||||
let constraints = window.resize_constraints.check_constraints();
|
let constraints = window.resize_constraints.check_constraints();
|
||||||
let min_inner_size = LogicalSize {
|
let min_inner_size = LogicalSize {
|
||||||
|
|
|
@ -18,7 +18,7 @@ use winit::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
accessibility::{AccessKitAdapters, WinitActionHandler, WinitActionHandlers},
|
accessibility::{AccessKitAdapters, WinitActionHandler, WinitActionHandlers},
|
||||||
converters::{convert_window_level, convert_window_theme},
|
converters::{convert_enabled_buttons, convert_window_level, convert_window_theme},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A resource which maps window entities to [`winit`] library windows.
|
/// A resource which maps window entities to [`winit`] library windows.
|
||||||
|
@ -94,6 +94,7 @@ impl WinitWindows {
|
||||||
.with_window_level(convert_window_level(window.window_level))
|
.with_window_level(convert_window_level(window.window_level))
|
||||||
.with_theme(window.window_theme.map(convert_window_theme))
|
.with_theme(window.window_theme.map(convert_window_theme))
|
||||||
.with_resizable(window.resizable)
|
.with_resizable(window.resizable)
|
||||||
|
.with_enabled_buttons(convert_enabled_buttons(window.enabled_buttons))
|
||||||
.with_decorations(window.decorations)
|
.with_decorations(window.decorations)
|
||||||
.with_transparent(window.transparent);
|
.with_transparent(window.transparent);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//! Note that this uses [`Text2dBundle`] to display text alongside your other entities in a 2D scene.
|
//! Note that this uses [`Text2dBundle`] to display text alongside your other entities in a 2D scene.
|
||||||
//!
|
//!
|
||||||
//! For an example on how to render text as part of a user interface, independent from the world
|
//! For an example on how to render text as part of a user interface, independent from the world
|
||||||
//! viewport, you may want to look at `2d/contributors.rs` or `ui/text.rs`.
|
//! viewport, you may want to look at `games/contributors.rs` or `ui/text.rs`.
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
|
|
@ -34,7 +34,7 @@ fn move_system(mut sprites: Query<(&mut Transform, &Velocity)>) {
|
||||||
// to use or not use ParallelIterator over a normal Iterator.
|
// to use or not use ParallelIterator over a normal Iterator.
|
||||||
sprites
|
sprites
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.for_each_mut(|(mut transform, velocity)| {
|
.for_each(|(mut transform, velocity)| {
|
||||||
transform.translation += velocity.extend(0.0);
|
transform.translation += velocity.extend(0.0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ fn bounce_system(windows: Query<&Window>, mut sprites: Query<(&Transform, &mut V
|
||||||
sprites
|
sprites
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.batching_strategy(BatchingStrategy::fixed(32))
|
.batching_strategy(BatchingStrategy::fixed(32))
|
||||||
.for_each_mut(|(transform, mut v)| {
|
.for_each(|(transform, mut v)| {
|
||||||
if !(left < transform.translation.x
|
if !(left < transform.translation.x
|
||||||
&& transform.translation.x < right
|
&& transform.translation.x < right
|
||||||
&& bottom < transform.translation.y
|
&& bottom < transform.translation.y
|
||||||
|
|
|
@ -318,8 +318,8 @@ impl FromWorld for PostProcessPipeline {
|
||||||
write_mask: ColorWrites::ALL,
|
write_mask: ColorWrites::ALL,
|
||||||
})],
|
})],
|
||||||
}),
|
}),
|
||||||
// All of the following property are not important for this effect so just use the default values.
|
// All of the following properties are not important for this effect so just use the default values.
|
||||||
// This struct doesn't have the Default trai implemented because not all field can have a default value.
|
// This struct doesn't have the Default trait implemented because not all field can have a default value.
|
||||||
primitive: PrimitiveState::default(),
|
primitive: PrimitiveState::default(),
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
multisample: MultisampleState::default(),
|
multisample: MultisampleState::default(),
|
||||||
|
|
|
@ -17,7 +17,7 @@ fn main() {
|
||||||
app.add_plugins((
|
app.add_plugins((
|
||||||
DefaultPlugins.set(WindowPlugin {
|
DefaultPlugins.set(WindowPlugin {
|
||||||
primary_window: Some(Window {
|
primary_window: Some(Window {
|
||||||
present_mode: PresentMode::Immediate,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
..default()
|
..default()
|
||||||
|
|
|
@ -14,7 +14,7 @@ fn main() {
|
||||||
.add_plugins((
|
.add_plugins((
|
||||||
DefaultPlugins.set(WindowPlugin {
|
DefaultPlugins.set(WindowPlugin {
|
||||||
primary_window: Some(Window {
|
primary_window: Some(Window {
|
||||||
present_mode: PresentMode::Immediate,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
..default()
|
..default()
|
||||||
|
|
|
@ -34,6 +34,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
// Accepts a `String` or any type that converts into a `String`, such as `&str`
|
// Accepts a `String` or any type that converts into a `String`, such as `&str`
|
||||||
"hello\nbevy!",
|
"hello\nbevy!",
|
||||||
TextStyle {
|
TextStyle {
|
||||||
|
// This font is loaded and will be used instead of the default font.
|
||||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
font_size: 100.0,
|
font_size: 100.0,
|
||||||
color: Color::WHITE,
|
color: Color::WHITE,
|
||||||
|
@ -56,15 +57,17 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
TextSection::new(
|
TextSection::new(
|
||||||
"FPS: ",
|
"FPS: ",
|
||||||
TextStyle {
|
TextStyle {
|
||||||
|
// This font is loaded and will be used instead of the default font.
|
||||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
font_size: 60.0,
|
font_size: 60.0,
|
||||||
color: Color::WHITE,
|
color: Color::WHITE,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
TextSection::from_style(TextStyle {
|
TextSection::from_style(TextStyle {
|
||||||
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
|
||||||
font_size: 60.0,
|
font_size: 60.0,
|
||||||
color: Color::GOLD,
|
color: Color::GOLD,
|
||||||
|
// If no font is specified, it will use the default font.
|
||||||
|
..default()
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
FpsText,
|
FpsText,
|
||||||
|
|
|
@ -20,6 +20,10 @@ fn main() {
|
||||||
// Tells wasm not to override default event handling, like F5, Ctrl+R etc.
|
// Tells wasm not to override default event handling, like F5, Ctrl+R etc.
|
||||||
prevent_default_event_handling: false,
|
prevent_default_event_handling: false,
|
||||||
window_theme: Some(WindowTheme::Dark),
|
window_theme: Some(WindowTheme::Dark),
|
||||||
|
enabled_buttons: bevy::window::EnabledButtons {
|
||||||
|
maximize: false,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
..default()
|
..default()
|
||||||
|
@ -34,6 +38,7 @@ fn main() {
|
||||||
toggle_theme,
|
toggle_theme,
|
||||||
toggle_cursor,
|
toggle_cursor,
|
||||||
toggle_vsync,
|
toggle_vsync,
|
||||||
|
toggle_window_controls,
|
||||||
cycle_cursor_icon,
|
cycle_cursor_icon,
|
||||||
switch_level,
|
switch_level,
|
||||||
),
|
),
|
||||||
|
@ -76,6 +81,31 @@ fn switch_level(input: Res<Input<KeyCode>>, mut windows: Query<&mut Window>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This system toggles the window controls when pressing buttons 1, 2 and 3
|
||||||
|
///
|
||||||
|
/// This feature only works on some platforms. Please check the
|
||||||
|
/// [documentation](https://docs.rs/bevy/latest/bevy/prelude/struct.Window.html#structfield.enabled_buttons)
|
||||||
|
/// for more details.
|
||||||
|
fn toggle_window_controls(input: Res<Input<KeyCode>>, mut windows: Query<&mut Window>) {
|
||||||
|
let toggle_minimize = input.just_pressed(KeyCode::Key1);
|
||||||
|
let toggle_maximize = input.just_pressed(KeyCode::Key2);
|
||||||
|
let toggle_close = input.just_pressed(KeyCode::Key3);
|
||||||
|
|
||||||
|
if toggle_minimize || toggle_maximize || toggle_close {
|
||||||
|
let mut window = windows.single_mut();
|
||||||
|
|
||||||
|
if toggle_minimize {
|
||||||
|
window.enabled_buttons.minimize = !window.enabled_buttons.minimize;
|
||||||
|
}
|
||||||
|
if toggle_maximize {
|
||||||
|
window.enabled_buttons.maximize = !window.enabled_buttons.maximize;
|
||||||
|
}
|
||||||
|
if toggle_close {
|
||||||
|
window.enabled_buttons.close = !window.enabled_buttons.close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This system will then change the title during execution
|
/// This system will then change the title during execution
|
||||||
fn change_title(mut windows: Query<&mut Window>, time: Res<Time>) {
|
fn change_title(mut windows: Query<&mut Window>, time: Res<Time>) {
|
||||||
let mut window = windows.single_mut();
|
let mut window = windows.single_mut();
|
||||||
|
|
|
@ -53,7 +53,7 @@ enum Action {
|
||||||
/// Path to the folder where the content should be created
|
/// Path to the folder where the content should be created
|
||||||
content_folder: String,
|
content_folder: String,
|
||||||
|
|
||||||
#[arg(value_enum, long, default_value_t = WebApi::Webgl2)]
|
#[arg(value_enum, long, default_value_t = WebApi::Webgpu)]
|
||||||
/// Which API to use for rendering
|
/// Which API to use for rendering
|
||||||
api: WebApi,
|
api: WebApi,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue