forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Solves bevyengine#5069. `AssetChanged<T>` is just a thin wrapper around the `&Handle<T>` query, with additional logic to check in `Assets<T>` if the corresponding `HandleId` was changed between the last and current runs of the system. This adds some overhead in the `asset_event_system` for keeping track of when which handle got updated. Otherwise, the overhead is constrained to the `AssetChanged` query, which is ripe for optimizations. NOTE: We chose to update the `change_ticks` hash map during asset event propagation rather than when accessing the asset, because we need access somehow to the current `change_tick`. Updating the tick hash map during modification would require passing the `change_tick` to all methods of `Assets` that accepts a `&mut self`. Which would be a huge ergonomic loss. As a result, the asset change expressed by `AssetChanged` is only effective after `asset_event_system` ran. This is in line with how `AssetEvents` currently works, but might cause a 1 frame lag for asset change detection. A potential future update could implement some sort of specialization so that when `Assets` is accessed through a `ResMut`, the called methods are called with the `ResMut`'s change_tick. But with current rust, this is either impossible or causing huge ergonomic loss on the `Assets` API.
- Loading branch information
Showing
5 changed files
with
205 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
//! Filter query to limit iteration over [`Handle`]s which content was updated recently. | ||
use std::marker::PhantomData; | ||
|
||
use bevy_ecs::{ | ||
archetype::{Archetype, ArchetypeComponentId}, | ||
change_detection::MAX_CHANGE_AGE, | ||
component::ComponentId, | ||
prelude::World, | ||
query::{ | ||
Access, ComponentIdState, Fetch, FetchState, FilteredAccess, QueryItem, ReadFetch, | ||
ReadOnlyWorldQuery, WorldQuery, WorldQueryGats, | ||
}, | ||
storage::{Table, Tables}, | ||
}; | ||
use bevy_utils::HashMap; | ||
|
||
use crate::{Asset, Assets, Handle, HandleId}; | ||
|
||
struct AssetChangeCheck<'w> { | ||
handle_change_map: Option<&'w HashMap<HandleId, u32>>, | ||
prev_system_tick: u32, | ||
current_tick: u32, | ||
} | ||
impl<'w> AssetChangeCheck<'w> { | ||
fn new<T: Asset>(assets: Option<&'w Assets<T>>, last_change: u32, current: u32) -> Self { | ||
let handle_change_map = assets.map(|a| &a.change_ticks); | ||
Self { | ||
handle_change_map, | ||
prev_system_tick: last_change, | ||
current_tick: current, | ||
} | ||
} | ||
fn ticks_since(&self, event_tick: u32) -> u32 { | ||
self.current_tick | ||
.wrapping_sub(event_tick) | ||
.min(MAX_CHANGE_AGE) | ||
} | ||
// TODO: some sort of caching? Each check has two levels of indirection, | ||
// which is not optimal. | ||
fn has_changed<T: Asset>(&self, handle: &Handle<T>) -> bool { | ||
let is_changed = || { | ||
let map = self.handle_change_map?; | ||
let handle_change_tick = *map.get(&handle.id)?; | ||
// This replicates the logic used in `ComponentTicks::is_changed` | ||
let ticks_since_change = self.ticks_since(handle_change_tick); | ||
let ticks_since_system = self.ticks_since(self.prev_system_tick); | ||
Some(ticks_since_system >= ticks_since_change) | ||
}; | ||
is_changed().unwrap_or(true) | ||
} | ||
} | ||
|
||
/// Query filter to get all entities which `Handle<T>` component underlying asset changed. | ||
pub struct AssetChanged<T>(PhantomData<T>); | ||
|
||
/// Fetch for [`AssetChanged`] | ||
#[doc(hidden)] | ||
pub struct AssetChangedFetch<'w, T: Asset> { | ||
inner: ReadFetch<'w, Handle<T>>, | ||
check: AssetChangeCheck<'w>, | ||
} | ||
|
||
/// State for [`AssetChanged`] | ||
pub struct AssetChangedState<T: Asset> { | ||
inner: ComponentIdState<Handle<T>>, | ||
} | ||
|
||
// SAFETY: `ROQueryFetch<Self>` is the same as `QueryFetch<Self>` | ||
unsafe impl<T: Asset> WorldQuery for AssetChanged<T> { | ||
type ReadOnly = Self; | ||
type State = AssetChangedState<T>; | ||
|
||
fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { | ||
item | ||
} | ||
} | ||
|
||
impl<T: Asset> FetchState for AssetChangedState<T> { | ||
fn init(world: &mut World) -> Self { | ||
Self { | ||
inner: ComponentIdState::init(world), | ||
} | ||
} | ||
|
||
fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { | ||
self.inner.matches_component_set(set_contains_id) | ||
} | ||
} | ||
|
||
impl<'w, T: Asset> WorldQueryGats<'w> for AssetChanged<T> { | ||
type Fetch = AssetChangedFetch<'w, T>; | ||
type _State = AssetChangedState<T>; | ||
} | ||
|
||
// SAFETY: this reads the T component. archetype component access and component access are updated to reflect that | ||
unsafe impl<'w, T: Asset> Fetch<'w> for AssetChangedFetch<'w, T> { | ||
type State = AssetChangedState<T>; | ||
type Item = bool; | ||
|
||
unsafe fn init( | ||
world: &'w World, | ||
state: &AssetChangedState<T>, | ||
last_change_tick: u32, | ||
change_tick: u32, | ||
) -> Self { | ||
Self { | ||
inner: ReadFetch::init(world, &state.inner, last_change_tick, change_tick), | ||
check: AssetChangeCheck::new::<T>(world.get_resource(), last_change_tick, change_tick), | ||
} | ||
} | ||
|
||
const IS_DENSE: bool = <ReadFetch<'w, Handle<T>>>::IS_DENSE; | ||
|
||
// This Fetch filters more than just the archetype. | ||
const IS_ARCHETYPAL: bool = false; | ||
|
||
unsafe fn set_table(&mut self, state: &Self::State, table: &'w Table) { | ||
self.inner.set_table(&state.inner, table); | ||
} | ||
|
||
unsafe fn set_archetype( | ||
&mut self, | ||
state: &Self::State, | ||
archetype: &'w Archetype, | ||
tables: &'w Tables, | ||
) { | ||
self.inner.set_archetype(&state.inner, archetype, tables); | ||
} | ||
|
||
unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item { | ||
let handle = self.inner.table_fetch(table_row); | ||
self.check.has_changed(handle) | ||
} | ||
|
||
unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { | ||
let handle = self.inner.archetype_fetch(archetype_index); | ||
self.check.has_changed(handle) | ||
} | ||
|
||
#[inline] | ||
unsafe fn table_filter_fetch(&mut self, table_row: usize) -> bool { | ||
self.table_fetch(table_row) | ||
} | ||
|
||
#[inline] | ||
unsafe fn archetype_filter_fetch(&mut self, archetype_index: usize) -> bool { | ||
self.archetype_fetch(archetype_index) | ||
} | ||
|
||
#[inline] | ||
fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>) { | ||
ReadFetch::<Handle<T>>::update_component_access(&state.inner, access); | ||
} | ||
|
||
#[inline] | ||
fn update_archetype_component_access( | ||
state: &Self::State, | ||
archetype: &Archetype, | ||
access: &mut Access<ArchetypeComponentId>, | ||
) { | ||
ReadFetch::<Handle<T>>::update_archetype_component_access(&state.inner, archetype, access); | ||
} | ||
} | ||
|
||
/// SAFETY: read-only access | ||
unsafe impl<T: Asset> ReadOnlyWorldQuery for AssetChanged<T> {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters