mirror of
https://github.com/bevyengine/bevy
synced 2024-11-23 05:03:47 +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:
|
||||
runs-on: ubuntu-latest
|
||||
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: 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
|
||||
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
|
||||
if: steps.cargo-tree-changed.outcome != 'success'
|
||||
run: cargo deny check bans
|
||||
|
||||
check-licenses:
|
||||
|
|
|
@ -34,7 +34,7 @@ pub fn heavy_compute(c: &mut Criterion) {
|
|||
}));
|
||||
|
||||
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 {
|
||||
mat.0 = mat.0.inverse();
|
||||
}
|
||||
|
|
|
@ -364,7 +364,7 @@ pub fn animation_player(
|
|||
) {
|
||||
animation_players
|
||||
.par_iter_mut()
|
||||
.for_each_mut(|(root, maybe_parent, mut player)| {
|
||||
.for_each(|(root, maybe_parent, mut player)| {
|
||||
update_transitions(&mut player, &time);
|
||||
run_animation_player(
|
||||
root,
|
||||
|
|
|
@ -686,6 +686,7 @@ impl App {
|
|||
/// Panics if one of the plugins was already added to the application.
|
||||
///
|
||||
/// [`PluginGroup`]:super::PluginGroup
|
||||
#[track_caller]
|
||||
pub fn add_plugins<M>(&mut self, plugins: impl Plugins<M>) -> &mut Self {
|
||||
plugins.add_to_app(self);
|
||||
self
|
||||
|
|
|
@ -89,6 +89,7 @@ mod sealed {
|
|||
pub struct PluginsTupleMarker;
|
||||
|
||||
impl<P: Plugin> Plugins<PluginMarker> for P {
|
||||
#[track_caller]
|
||||
fn add_to_app(self, app: &mut App) {
|
||||
if let Err(AppError::DuplicatePlugin { plugin_name }) =
|
||||
app.add_boxed_plugin(Box::new(self))
|
||||
|
@ -101,6 +102,7 @@ mod sealed {
|
|||
}
|
||||
|
||||
impl<P: PluginGroup> Plugins<PluginGroupMarker> for P {
|
||||
#[track_caller]
|
||||
fn add_to_app(self, app: &mut App) {
|
||||
self.build().finish(app);
|
||||
}
|
||||
|
@ -113,6 +115,7 @@ mod sealed {
|
|||
$($plugins: Plugins<$param>),*
|
||||
{
|
||||
#[allow(non_snake_case, unused_variables)]
|
||||
#[track_caller]
|
||||
fn add_to_app(self, app: &mut App) {
|
||||
let ($($plugins,)*) = self;
|
||||
$($plugins.add_to_app(app);)*
|
||||
|
|
|
@ -172,6 +172,7 @@ impl PluginGroupBuilder {
|
|||
/// # Panics
|
||||
///
|
||||
/// 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) {
|
||||
for ty in &self.order {
|
||||
if let Some(entry) = self.plugins.remove(ty) {
|
||||
|
|
|
@ -237,6 +237,16 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
|||
#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
|
||||
unsafe impl #user_impl_generics #path::query::WorldQuery
|
||||
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_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*;
|
||||
|
|
|
@ -735,6 +735,35 @@ impl<'a> MutUntyped<'a> {
|
|||
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.
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -798,6 +827,8 @@ impl std::fmt::Debug for MutUntyped<'_> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_ecs_macros::Resource;
|
||||
use bevy_ptr::PtrMut;
|
||||
use bevy_reflect::{FromType, ReflectFromPtr};
|
||||
|
||||
use crate::{
|
||||
self as bevy_ecs,
|
||||
|
@ -809,8 +840,7 @@ mod tests {
|
|||
world::World,
|
||||
};
|
||||
|
||||
use super::DetectChanges;
|
||||
use super::DetectChangesMut;
|
||||
use super::{DetectChanges, DetectChangesMut, MutUntyped};
|
||||
|
||||
#[derive(Component, PartialEq)]
|
||||
struct C;
|
||||
|
@ -1034,4 +1064,40 @@ mod tests {
|
|||
"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,
|
||||
entity::Entity,
|
||||
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,
|
||||
schedule::{
|
||||
apply_deferred, apply_state_transition, common_conditions::*, Condition,
|
||||
|
|
|
@ -318,7 +318,7 @@ pub unsafe trait WorldQuery {
|
|||
type Item<'a>;
|
||||
|
||||
/// 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.
|
||||
type ReadOnly: ReadOnlyWorldQuery<State = Self::State>;
|
||||
|
@ -345,14 +345,6 @@ pub unsafe trait WorldQuery {
|
|||
this_run: Tick,
|
||||
) -> 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
|
||||
/// 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
|
||||
|
@ -404,6 +396,10 @@ pub unsafe trait WorldQuery {
|
|||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
entity: Entity,
|
||||
|
@ -483,8 +479,6 @@ unsafe impl WorldQuery for Entity {
|
|||
) -> Self::Fetch<'w> {
|
||||
}
|
||||
|
||||
unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype<'w>(
|
||||
_fetch: &mut Self::Fetch<'w>,
|
||||
|
@ -554,10 +548,6 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> {
|
|||
world.world()
|
||||
}
|
||||
|
||||
unsafe fn clone_fetch<'w>(world: &Self::Fetch<'w>) -> Self::Fetch<'w> {
|
||||
world
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype<'w>(
|
||||
_fetch: &mut Self::Fetch<'w>,
|
||||
|
@ -620,6 +610,13 @@ pub struct ReadFetch<'w, T> {
|
|||
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`
|
||||
unsafe impl<T: Component> WorldQuery for &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]
|
||||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut ReadFetch<'w, T>,
|
||||
|
@ -770,6 +760,13 @@ pub struct RefFetch<'w, T> {
|
|||
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`
|
||||
unsafe impl<'__w, T: Component> WorldQuery for Ref<'__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]
|
||||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut RefFetch<'w, T>,
|
||||
|
@ -933,6 +921,13 @@ pub struct WriteFetch<'w, T> {
|
|||
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`
|
||||
unsafe impl<'__w, T: Component> WorldQuery for &'__w mut 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]
|
||||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut WriteFetch<'w, T>,
|
||||
|
@ -1084,6 +1070,15 @@ pub struct OptionFetch<'w, T: WorldQuery> {
|
|||
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
|
||||
unsafe impl<T: WorldQuery> WorldQuery for Option<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]
|
||||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut OptionFetch<'w, T>,
|
||||
|
@ -1271,10 +1259,6 @@ unsafe impl<T: Component> WorldQuery for Has<T> {
|
|||
false
|
||||
}
|
||||
|
||||
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
|
||||
*fetch
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
|
@ -1351,13 +1335,6 @@ macro_rules! impl_tuple_fetch {
|
|||
($($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_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),)*)
|
||||
}
|
||||
|
||||
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_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)]
|
||||
unsafe fn set_archetype(
|
||||
_fetch: &mut (),
|
||||
|
@ -1651,8 +1619,6 @@ unsafe impl<T: ?Sized> WorldQuery for PhantomData<T> {
|
|||
) -> 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
|
||||
// are stored in a Table (vacuous truth).
|
||||
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 = {
|
||||
match T::Storage::STORAGE_TYPE {
|
||||
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 = {
|
||||
match T::Storage::STORAGE_TYPE {
|
||||
StorageType::Table => true,
|
||||
|
@ -259,6 +255,15 @@ pub struct OrFetch<'w, T: WorldQuery> {
|
|||
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 {
|
||||
($(($filter: ident, $state: ident)),*) => {
|
||||
#[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]
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
|
||||
let ($($filter,)*) = fetch;
|
||||
|
@ -403,10 +396,10 @@ macro_rules! impl_tick_filter {
|
|||
pub struct $name<T>(PhantomData<T>);
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
$(#[$fetch_meta])*
|
||||
pub struct $fetch_name<'w, T> {
|
||||
table_ticks: Option< ThinSlicePtr<'w, UnsafeCell<Tick>>>,
|
||||
marker: PhantomData<T>,
|
||||
pub struct $fetch_name<'w> {
|
||||
table_ticks: Option<ThinSlicePtr<'w, UnsafeCell<Tick>>>,
|
||||
sparse_set: Option<&'w ComponentSparseSet>,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
|
@ -414,7 +407,7 @@ macro_rules! impl_tick_filter {
|
|||
|
||||
// SAFETY: `Self::ReadOnly` is the same as `Self`
|
||||
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 ReadOnly = Self;
|
||||
type State = ComponentId;
|
||||
|
@ -440,24 +433,11 @@ macro_rules! impl_tick_filter {
|
|||
.get(id)
|
||||
.debug_checked_unwrap()
|
||||
}),
|
||||
marker: PhantomData,
|
||||
last_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 = {
|
||||
match T::Storage::STORAGE_TYPE {
|
||||
StorageType::Table => true,
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
storage::{TableId, TableRow, Tables},
|
||||
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;
|
||||
|
||||
|
@ -163,7 +163,9 @@ where
|
|||
// 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
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
Some(_) => {
|
||||
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) {
|
||||
Some(_) => {}
|
||||
None if i > 0 => continue 'outer,
|
||||
|
@ -453,28 +455,19 @@ struct QueryIterationCursor<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> {
|
|||
current_len: usize,
|
||||
// either table row or archetype index, depending on whether both `Q`'s and `F`'s fetches are dense
|
||||
current_row: usize,
|
||||
phantom: PhantomData<Q>,
|
||||
}
|
||||
|
||||
impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, Q, F> {
|
||||
/// This function is safe to call if `(Q, F): 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<Q>` for the same
|
||||
/// `archetype_row` or `table_row` to be alive at the same time.
|
||||
unsafe fn clone_cursor(&self) -> Self {
|
||||
impl<Q: WorldQuery, F: ReadOnlyWorldQuery> Clone for QueryIterationCursor<'_, '_, Q, F> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
table_id_iter: self.table_id_iter.clone(),
|
||||
archetype_id_iter: self.archetype_id_iter.clone(),
|
||||
table_entities: self.table_entities,
|
||||
archetype_entities: self.archetype_entities,
|
||||
// SAFETY: upheld by caller invariants
|
||||
fetch: Q::clone_fetch(&self.fetch),
|
||||
filter: F::clone_fetch(&self.filter),
|
||||
fetch: self.fetch.clone(),
|
||||
filter: self.filter.clone(),
|
||||
current_len: self.current_len,
|
||||
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(),
|
||||
current_len: 0,
|
||||
current_row: 0,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -593,8 +585,11 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
|
|||
continue;
|
||||
}
|
||||
|
||||
// SAFETY: set_table was called prior.
|
||||
// `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.
|
||||
// SAFETY:
|
||||
// - 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);
|
||||
|
||||
self.current_row += 1;
|
||||
|
@ -633,8 +628,11 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
|
|||
continue;
|
||||
}
|
||||
|
||||
// SAFETY: set_archetype was called prior, `current_row` is an archetype index in range of the current archetype
|
||||
// `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.
|
||||
// SAFETY:
|
||||
// - 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(
|
||||
&mut self.fetch,
|
||||
archetype_entity.entity(),
|
||||
|
|
|
@ -778,7 +778,7 @@ mod tests {
|
|||
world.spawn((A(1), B(1)));
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{component::Tick, world::unsafe_world_cell::UnsafeWorldCell};
|
||||
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
|
||||
/// during iteration.
|
||||
|
@ -90,26 +90,6 @@ pub struct QueryParIter<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> {
|
|||
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> {
|
||||
/// Changes the batching strategy used when iterating.
|
||||
///
|
||||
|
@ -123,61 +103,72 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> {
|
|||
/// Runs `func` on each query result in parallel.
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
|
||||
#[inline]
|
||||
pub fn for_each_mut<FN: Fn(QueryItem<'w, Q>) + Send + Sync + Clone>(&mut 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) {
|
||||
pub fn for_each<FN: Fn(QueryItem<'w, Q>) + Send + Sync + Clone>(self, func: FN) {
|
||||
#[cfg(any(target = "wasm32", not(feature = "multi-threaded")))]
|
||||
{
|
||||
self.state
|
||||
.for_each_unchecked_manual(self.world, func, self.last_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:
|
||||
// This method can only be called once per instance of QueryParIter,
|
||||
// which ensures that mutable queries cannot be executed multiple times at once.
|
||||
// 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
|
||||
// at the same time.
|
||||
unsafe {
|
||||
self.state.for_each_unchecked_manual(
|
||||
self.world,
|
||||
func,
|
||||
self.last_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 {
|
||||
// Need a batch size of at least 1.
|
||||
let batch_size = self.get_batch_size(thread_count).max(1);
|
||||
self.state.par_for_each_unchecked_manual(
|
||||
self.world,
|
||||
batch_size,
|
||||
func,
|
||||
self.last_run,
|
||||
self.this_run,
|
||||
);
|
||||
// SAFETY: See the safety comment above.
|
||||
unsafe {
|
||||
self.state.par_for_each_unchecked_manual(
|
||||
self.world,
|
||||
batch_size,
|
||||
func,
|
||||
self.last_run,
|
||||
self.this_run,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
fn get_batch_size(&self, thread_count: usize) -> usize {
|
||||
if self.batching_strategy.batch_size_limits.is_empty() {
|
||||
|
|
|
@ -235,13 +235,13 @@ use std::{any::TypeId, borrow::Borrow, fmt::Debug};
|
|||
///
|
||||
/// |Query methods|Effect|
|
||||
/// |:---:|---|
|
||||
/// |[`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.|
|
||||
/// |[`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.|
|
||||
/// |[`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.|
|
||||
/// |[`single`]\([`_mut`][`single_mut`]),<br>[`get_single`]\([`_mut`][`get_single_mut`])|Returns the query item while verifying that there aren't others.|
|
||||
/// |[`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.|
|
||||
/// |[`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.|
|
||||
/// |[`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.|
|
||||
/// |[`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`).
|
||||
/// 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|
|
||||
/// |:---:|:---:|
|
||||
/// |[`iter`]\([`_mut`][`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_combinations`]\([`_mut`][`iter_combinations_mut`])|O(<sub>n</sub>C<sub>r</sub>)|
|
||||
/// |[`get`]\([`_mut`][`get_mut`])|O(1)|
|
||||
/// |[`iter`]\[[`_mut`][`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_combinations`]\[[`_mut`][`iter_combinations_mut`]]|O(<sub>n</sub>C<sub>r</sub>)|
|
||||
/// |[`get`]\[[`_mut`][`get_mut`]]|O(1)|
|
||||
/// |([`get_`][`get_many`])[`many`]|O(k)|
|
||||
/// |([`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)|
|
||||
/// |Change detection filtering ([`Added`], [`Changed`])|O(a + n)|
|
||||
///
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
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_core::cast_slice;
|
||||
use bevy_ecs::{
|
||||
|
@ -50,7 +50,10 @@ use bevy_render::{
|
|||
view::RenderLayers,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
use bevy_transform::{
|
||||
components::{GlobalTransform, Transform},
|
||||
TransformSystem,
|
||||
};
|
||||
|
||||
pub mod gizmos;
|
||||
|
||||
|
@ -85,11 +88,12 @@ impl Plugin for GizmoPlugin {
|
|||
.init_resource::<GizmoStorage>()
|
||||
.add_systems(Last, update_gizmo_meshes)
|
||||
.add_systems(
|
||||
Update,
|
||||
PostUpdate,
|
||||
(
|
||||
draw_aabbs,
|
||||
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; };
|
||||
|
@ -233,13 +237,17 @@ fn draw_all_aabbs(
|
|||
}
|
||||
|
||||
fn color_from_entity(entity: Entity) -> Color {
|
||||
use bevy_utils::RandomState;
|
||||
const U64_TO_DEGREES: f32 = 360.0 / u64::MAX as f32;
|
||||
const STATE: RandomState =
|
||||
RandomState::with_seeds(5952553601252303067, 16866614500153072625, 0, 0);
|
||||
let index = entity.index();
|
||||
|
||||
// from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
|
|
@ -40,20 +40,23 @@ impl GltfPlugin {
|
|||
|
||||
impl Plugin for GltfPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_type::<GltfExtras>()
|
||||
.add_asset::<Gltf>()
|
||||
.add_asset::<GltfNode>()
|
||||
.add_asset::<GltfPrimitive>()
|
||||
.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::all(),
|
||||
None => CompressedImageFormats::NONE,
|
||||
};
|
||||
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::<GltfNode>()
|
||||
.add_asset::<GltfPrimitive>()
|
||||
.add_asset::<GltfMesh>();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,10 +28,20 @@ impl Default for BevyManifest {
|
|||
.map(PathBuf::from)
|
||||
.map(|mut path| {
|
||||
path.push("Cargo.toml");
|
||||
let manifest = std::fs::read_to_string(path).unwrap();
|
||||
manifest.parse::<Document>().unwrap()
|
||||
if !path.exists() {
|
||||
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
|
||||
/// the minimum and maximum corners.
|
||||
#[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))]
|
||||
pub struct IRect {
|
||||
/// 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
|
||||
/// the minimum and maximum corners.
|
||||
#[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))]
|
||||
pub struct URect {
|
||||
/// The minimum corner point of the rect.
|
||||
|
|
|
@ -166,6 +166,7 @@ impl Plugin for PbrPlugin {
|
|||
);
|
||||
|
||||
app.register_asset_reflect::<StandardMaterial>()
|
||||
.register_type::<AlphaMode>()
|
||||
.register_type::<AmbientLight>()
|
||||
.register_type::<Cascade>()
|
||||
.register_type::<CascadeShadowConfig>()
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
use crate as bevy_reflect;
|
||||
use crate::prelude::ReflectDefault;
|
||||
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;
|
||||
|
||||
impl_reflect_struct!(
|
||||
#[reflect(Debug, PartialEq, Hash, Serialize, Deserialize, Default)]
|
||||
#[type_path = "bevy_math"]
|
||||
struct IRect {
|
||||
min: IVec2,
|
||||
max: IVec2,
|
||||
}
|
||||
);
|
||||
|
||||
impl_reflect_struct!(
|
||||
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[type_path = "bevy_math"]
|
||||
|
@ -12,3 +21,12 @@ impl_reflect_struct!(
|
|||
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
|
||||
wgpu = { version = "0.16.0", features=["naga"] }
|
||||
wgpu-hal = "0.16.0"
|
||||
codespan-reporting = "0.11.0"
|
||||
naga = { version = "0.12.0", features = ["wgsl-in"] }
|
||||
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;
|
||||
pub mod extract_resource;
|
||||
pub mod globals;
|
||||
pub mod gpu_component_array_buffer;
|
||||
pub mod mesh;
|
||||
pub mod pipelined_rendering;
|
||||
pub mod primitives;
|
||||
|
|
|
@ -103,21 +103,29 @@ impl Plugin for PipelinedRenderingPlugin {
|
|||
#[cfg(feature = "trace")]
|
||||
let _span = bevy_utils::tracing::info_span!("render thread").entered();
|
||||
|
||||
let compute_task_pool = ComputeTaskPool::get();
|
||||
loop {
|
||||
// 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| {
|
||||
s.spawn(async { app_to_render_receiver.recv().await.unwrap() });
|
||||
s.spawn(async { app_to_render_receiver.recv().await });
|
||||
})
|
||||
.pop()
|
||||
.unwrap();
|
||||
.pop();
|
||||
let Some(Ok(mut render_app)) = sent_app else { break };
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
let _sub_app_span =
|
||||
bevy_utils::tracing::info_span!("sub app", name = ?RenderApp).entered();
|
||||
render_app.run();
|
||||
render_to_app_sender.send_blocking(render_app).unwrap();
|
||||
{
|
||||
#[cfg(feature = "trace")]
|
||||
let _sub_app_span =
|
||||
bevy_utils::tracing::info_span!("sub app", name = ?RenderApp).entered();
|
||||
render_app.run();
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// 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)
|
||||
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
|
||||
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
||||
/// * [`Texture`](crate::render_resource::Texture)
|
||||
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_layout;
|
||||
mod buffer;
|
||||
mod buffer_vec;
|
||||
mod gpu_array_buffer;
|
||||
mod pipeline;
|
||||
mod pipeline_cache;
|
||||
mod pipeline_specializer;
|
||||
|
@ -15,6 +17,7 @@ pub use bind_group::*;
|
|||
pub use bind_group_layout::*;
|
||||
pub use buffer::*;
|
||||
pub use buffer_vec::*;
|
||||
pub use gpu_array_buffer::*;
|
||||
pub use pipeline::*;
|
||||
pub use pipeline_cache::*;
|
||||
pub use pipeline_specializer::*;
|
||||
|
|
|
@ -25,6 +25,7 @@ use wgpu::{util::BufferInitDescriptor, BindingResource, BufferBinding, BufferUsa
|
|||
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
||||
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
||||
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
||||
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
|
||||
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
||||
/// * [`Texture`](crate::render_resource::Texture)
|
||||
///
|
||||
|
@ -154,6 +155,7 @@ impl<T: ShaderType + WriteInto> StorageBuffer<T> {
|
|||
/// * [`StorageBuffer`](crate::render_resource::StorageBuffer)
|
||||
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
||||
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
||||
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
|
||||
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
||||
/// * [`Texture`](crate::render_resource::Texture)
|
||||
///
|
||||
|
|
|
@ -22,9 +22,10 @@ use wgpu::{util::BufferInitDescriptor, BindingResource, BufferBinding, BufferUsa
|
|||
/// (vectors), or structures with fields that are vectors.
|
||||
///
|
||||
/// Other options for storing GPU-accessible data are:
|
||||
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
||||
/// * [`StorageBuffer`](crate::render_resource::StorageBuffer)
|
||||
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
||||
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
||||
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
|
||||
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
||||
/// * [`Texture`](crate::render_resource::Texture)
|
||||
///
|
||||
|
@ -151,6 +152,8 @@ impl<T: ShaderType + WriteInto> UniformBuffer<T> {
|
|||
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
|
||||
/// * [`UniformBuffer`](crate::render_resource::UniformBuffer)
|
||||
/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer)
|
||||
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
|
||||
/// * [`BufferVec`](crate::render_resource::BufferVec)
|
||||
/// * [`Texture`](crate::render_resource::Texture)
|
||||
///
|
||||
/// [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> {
|
||||
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]
|
||||
pub fn buffer(&self) -> Option<&Buffer> {
|
||||
self.buffer.as_ref()
|
||||
|
|
|
@ -82,7 +82,7 @@ impl FromWorld for ImageTextureLoader {
|
|||
let supported_compressed_formats = match world.get_resource::<RenderDevice>() {
|
||||
Some(render_device) => CompressedImageFormats::from_features(render_device.features()),
|
||||
|
||||
None => CompressedImageFormats::all(),
|
||||
None => CompressedImageFormats::NONE,
|
||||
};
|
||||
Self {
|
||||
supported_compressed_formats,
|
||||
|
|
|
@ -70,19 +70,6 @@ impl ImagePlugin {
|
|||
|
||||
impl Plugin for ImagePlugin {
|
||||
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")]
|
||||
{
|
||||
app.init_asset_loader::<ExrTextureLoader>();
|
||||
|
@ -112,6 +99,19 @@ impl Plugin for ImagePlugin {
|
|||
}
|
||||
|
||||
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) {
|
||||
let default_sampler = {
|
||||
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();
|
||||
|
||||
visible_entities.entities.clear();
|
||||
visible_aabb_query.par_iter_mut().for_each_mut(
|
||||
visible_aabb_query.par_iter_mut().for_each(
|
||||
|(
|
||||
entity,
|
||||
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)| {
|
||||
// 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
|
||||
|
|
|
@ -266,6 +266,7 @@ pub fn prepare_windows(
|
|||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
present_mode: match window.present_mode {
|
||||
PresentMode::Fifo => wgpu::PresentMode::Fifo,
|
||||
PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed,
|
||||
PresentMode::Mailbox => wgpu::PresentMode::Mailbox,
|
||||
PresentMode::Immediate => wgpu::PresentMode::Immediate,
|
||||
PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync,
|
||||
|
|
|
@ -29,7 +29,7 @@ pub fn sync_simple_transforms(
|
|||
query
|
||||
.p0()
|
||||
.par_iter_mut()
|
||||
.for_each_mut(|(transform, mut global_transform)| {
|
||||
.for_each(|(transform, mut global_transform)| {
|
||||
*global_transform = GlobalTransform::from(*transform);
|
||||
});
|
||||
// Update orphaned entities.
|
||||
|
@ -59,7 +59,7 @@ pub fn propagate_transforms(
|
|||
orphaned_entities.clear();
|
||||
orphaned_entities.extend(orphaned.iter());
|
||||
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)| {
|
||||
let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
|
||||
if changed {
|
||||
|
|
|
@ -58,6 +58,7 @@ pub const UI_SHADER_HANDLE: HandleUntyped =
|
|||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
pub enum RenderUiSystem {
|
||||
ExtractNode,
|
||||
ExtractAtlasNode,
|
||||
}
|
||||
|
||||
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::<Camera3d>,
|
||||
extract_uinodes.in_set(RenderUiSystem::ExtractNode),
|
||||
extract_atlas_uinodes.after(RenderUiSystem::ExtractNode),
|
||||
extract_uinode_borders.after(RenderUiSystem::ExtractNode),
|
||||
extract_atlas_uinodes
|
||||
.in_set(RenderUiSystem::ExtractAtlasNode)
|
||||
.after(RenderUiSystem::ExtractNode),
|
||||
extract_uinode_borders.after(RenderUiSystem::ExtractAtlasNode),
|
||||
#[cfg(feature = "bevy_text")]
|
||||
extract_text_uinodes.after(RenderUiSystem::ExtractNode),
|
||||
extract_text_uinodes.after(RenderUiSystem::ExtractAtlasNode),
|
||||
),
|
||||
)
|
||||
.add_systems(
|
||||
|
|
|
@ -141,7 +141,8 @@ impl Plugin for WindowPlugin {
|
|||
.register_type::<InternalWindowState>()
|
||||
.register_type::<MonitorSelection>()
|
||||
.register_type::<WindowResizeConstraints>()
|
||||
.register_type::<WindowTheme>();
|
||||
.register_type::<WindowTheme>()
|
||||
.register_type::<EnabledButtons>();
|
||||
|
||||
// Register `PathBuf` as it's used by `FileDragAndDrop`
|
||||
app.register_type::<PathBuf>();
|
||||
|
|
|
@ -126,13 +126,21 @@ pub struct Window {
|
|||
/// Note: This does not stop the program from fullscreening/setting
|
||||
/// the size programmatically.
|
||||
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?
|
||||
///
|
||||
/// (Decorations are the minimize, maximize, and close buttons on desktop apps)
|
||||
///
|
||||
// ## Platform-specific
|
||||
//
|
||||
// **`iOS`**, **`Android`**, and the **`Web`** do not have decorations.
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **`iOS`**, **`Android`**, and the **`Web`** do not have decorations.
|
||||
pub decorations: bool,
|
||||
/// Should the window be transparent?
|
||||
///
|
||||
|
@ -221,6 +229,7 @@ impl Default for Window {
|
|||
ime_enabled: Default::default(),
|
||||
ime_position: Default::default(),
|
||||
resizable: true,
|
||||
enabled_buttons: Default::default(),
|
||||
decorations: true,
|
||||
transparent: false,
|
||||
focused: true,
|
||||
|
@ -801,6 +810,7 @@ pub enum MonitorSelection {
|
|||
/// [`Immediate`] or [`Mailbox`] will panic if not supported by the platform.
|
||||
///
|
||||
/// [`Fifo`]: PresentMode::Fifo
|
||||
/// [`FifoRelaxed`]: PresentMode::FifoRelaxed
|
||||
/// [`Immediate`]: PresentMode::Immediate
|
||||
/// [`Mailbox`]: PresentMode::Mailbox
|
||||
/// [`AutoVsync`]: PresentMode::AutoVsync
|
||||
|
@ -819,30 +829,67 @@ pub enum PresentMode {
|
|||
/// Chooses FifoRelaxed -> Fifo based on availability.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// Because of the fallback behavior, it is supported everywhere.
|
||||
AutoNoVsync = 1,
|
||||
/// The presentation engine does **not** wait for a vertical blanking period and
|
||||
/// the request is presented immediately. This is a low-latency presentation mode,
|
||||
/// but visible tearing may be observed. Not optimal for mobile.
|
||||
/// 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 again until the next vblank.
|
||||
///
|
||||
/// Selecting this variant will panic if not supported, it is preferred to use
|
||||
/// [`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.
|
||||
/// When a present command is executed on the gpu, the presented image is added on the queue.
|
||||
///
|
||||
/// Selecting this variant will panic if not supported, it is preferred to use
|
||||
/// [`PresentMode::AutoNoVsync`].
|
||||
Mailbox = 3,
|
||||
/// 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,
|
||||
/// corresponding to the `VSync`. Tearing cannot be observed. Optimal for mobile.
|
||||
/// No tearing will be observed.
|
||||
///
|
||||
/// Calls to get_current_texture will block until there is a spot in the queue.
|
||||
///
|
||||
/// Supported on all platforms.
|
||||
///
|
||||
/// If you don't know what mode to choose, choose this mode. This is traditionally called "Vsync On".
|
||||
#[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`].
|
||||
|
@ -963,3 +1010,42 @@ pub enum WindowTheme {
|
|||
/// Use the dark variant.
|
||||
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,
|
||||
};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_window::{CursorIcon, WindowLevel, WindowTheme};
|
||||
use bevy_window::{CursorIcon, EnabledButtons, WindowLevel, WindowTheme};
|
||||
|
||||
pub fn convert_keyboard_input(
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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::{
|
||||
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,
|
||||
};
|
||||
|
||||
|
@ -222,6 +225,10 @@ pub(crate) fn changed_window(
|
|||
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 {
|
||||
let constraints = window.resize_constraints.check_constraints();
|
||||
let min_inner_size = LogicalSize {
|
||||
|
|
|
@ -18,7 +18,7 @@ use winit::{
|
|||
|
||||
use crate::{
|
||||
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.
|
||||
|
@ -94,6 +94,7 @@ impl WinitWindows {
|
|||
.with_window_level(convert_window_level(window.window_level))
|
||||
.with_theme(window.window_theme.map(convert_window_theme))
|
||||
.with_resizable(window.resizable)
|
||||
.with_enabled_buttons(convert_enabled_buttons(window.enabled_buttons))
|
||||
.with_decorations(window.decorations)
|
||||
.with_transparent(window.transparent);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
//! 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
|
||||
//! 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::{
|
||||
prelude::*,
|
||||
|
|
|
@ -34,7 +34,7 @@ fn move_system(mut sprites: Query<(&mut Transform, &Velocity)>) {
|
|||
// to use or not use ParallelIterator over a normal Iterator.
|
||||
sprites
|
||||
.par_iter_mut()
|
||||
.for_each_mut(|(mut transform, velocity)| {
|
||||
.for_each(|(mut transform, velocity)| {
|
||||
transform.translation += velocity.extend(0.0);
|
||||
});
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ fn bounce_system(windows: Query<&Window>, mut sprites: Query<(&Transform, &mut V
|
|||
sprites
|
||||
.par_iter_mut()
|
||||
.batching_strategy(BatchingStrategy::fixed(32))
|
||||
.for_each_mut(|(transform, mut v)| {
|
||||
.for_each(|(transform, mut v)| {
|
||||
if !(left < transform.translation.x
|
||||
&& transform.translation.x < right
|
||||
&& bottom < transform.translation.y
|
||||
|
|
|
@ -318,8 +318,8 @@ impl FromWorld for PostProcessPipeline {
|
|||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
// All of the following property 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.
|
||||
// All of the following properties are not important for this effect so just use the default values.
|
||||
// This struct doesn't have the Default trait implemented because not all field can have a default value.
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
|
|
|
@ -17,7 +17,7 @@ fn main() {
|
|||
app.add_plugins((
|
||||
DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
present_mode: PresentMode::Immediate,
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
|
|
|
@ -14,7 +14,7 @@ fn main() {
|
|||
.add_plugins((
|
||||
DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
present_mode: PresentMode::Immediate,
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
..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`
|
||||
"hello\nbevy!",
|
||||
TextStyle {
|
||||
// This font is loaded and will be used instead of the default font.
|
||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||
font_size: 100.0,
|
||||
color: Color::WHITE,
|
||||
|
@ -56,15 +57,17 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
TextSection::new(
|
||||
"FPS: ",
|
||||
TextStyle {
|
||||
// This font is loaded and will be used instead of the default font.
|
||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||
font_size: 60.0,
|
||||
color: Color::WHITE,
|
||||
},
|
||||
),
|
||||
TextSection::from_style(TextStyle {
|
||||
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||
font_size: 60.0,
|
||||
color: Color::GOLD,
|
||||
// If no font is specified, it will use the default font.
|
||||
..default()
|
||||
}),
|
||||
]),
|
||||
FpsText,
|
||||
|
|
|
@ -20,6 +20,10 @@ fn main() {
|
|||
// Tells wasm not to override default event handling, like F5, Ctrl+R etc.
|
||||
prevent_default_event_handling: false,
|
||||
window_theme: Some(WindowTheme::Dark),
|
||||
enabled_buttons: bevy::window::EnabledButtons {
|
||||
maximize: false,
|
||||
..Default::default()
|
||||
},
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
|
@ -34,6 +38,7 @@ fn main() {
|
|||
toggle_theme,
|
||||
toggle_cursor,
|
||||
toggle_vsync,
|
||||
toggle_window_controls,
|
||||
cycle_cursor_icon,
|
||||
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
|
||||
fn change_title(mut windows: Query<&mut Window>, time: Res<Time>) {
|
||||
let mut window = windows.single_mut();
|
||||
|
|
|
@ -53,7 +53,7 @@ enum Action {
|
|||
/// Path to the folder where the content should be created
|
||||
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
|
||||
api: WebApi,
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue