Repository: joseph-gio/bevy-trait-query (language: rust)

Issue:
Add support for change detection filters `Changed<>`, `Added<>`, etc
TODO: figure out exact semantics


Discussion / hints:

Fix patch:
diff --git a/proc-macro/src/lib.rs b/proc-macro/src/lib.rs
index da1fdac..0fbefdc 100644
--- a/proc-macro/src/lib.rs
+++ b/proc-macro/src/lib.rs
@@ -114,7 +114,7 @@ fn impl_trait_query(arg: TokenStream, item: TokenStream) -> Result<TokenStream2>
 
     let my_crate = proc_macro_crate::crate_name("bevy-trait-query").unwrap();
     let my_crate = match my_crate {
-        proc_macro_crate::FoundCrate::Itself => quote! { crate },
+        proc_macro_crate::FoundCrate::Itself => quote! { bevy_trait_query },
         proc_macro_crate::FoundCrate::Name(x) => {
             let ident = quote::format_ident!("{x}");
             quote! { #ident }
@@ -156,7 +156,7 @@ fn impl_trait_query(arg: TokenStream, item: TokenStream) -> Result<TokenStream2>
         #where_clause
         {
             type Item<'__w> = #my_crate::ReadTraits<'__w, #trait_object>;
-            type Fetch<'__w> = #my_crate::ReadAllTraitsFetch<'__w, #trait_object>;
+            type Fetch<'__w> = <#my_crate::All<&'__a #trait_object> as #imports::WorldQuery>::Fetch<'__w>;
             type ReadOnly = Self;
             type State = #my_crate::TraitQueryState<#trait_object>;
 
@@ -264,7 +264,7 @@ fn impl_trait_query(arg: TokenStream, item: TokenStream) -> Result<TokenStream2>
         #where_clause
         {
             type Item<'__w> = #my_crate::WriteTraits<'__w, #trait_object>;
-            type Fetch<'__w> = #my_crate::WriteAllTraitsFetch<'__w, #trait_object>;
+            type Fetch<'__w> = <#my_crate::All<&'__a #trait_object> as #imports::WorldQuery>::Fetch<'__w>;
             type ReadOnly = &'__a #trait_object;
             type State = #my_crate::TraitQueryState<#trait_object>;
 
diff --git a/src/all.rs b/src/all.rs
index cfaa84d..f1f191f 100644
--- a/src/all.rs
+++ b/src/all.rs
@@ -1,15 +1,17 @@
+use bevy::ecs::{
+    change_detection::{DetectChanges, Mut, Ref},
+    component::{ComponentId, Tick},
+    entity::Entity,
+    query::{QueryItem, ReadOnlyWorldQuery, WorldQuery},
+    storage::{SparseSets, Table, TableRow},
+    world::{unsafe_world_cell::UnsafeWorldCell, World},
+};
+use bevy::ptr::UnsafeCellDeref;
+
 use crate::{
     debug_unreachable, trait_registry_error, zip_exact, TraitImplMeta, TraitImplRegistry,
     TraitQuery, TraitQueryState,
 };
-use bevy::ecs::change_detection::Mut;
-use bevy::ecs::component::{ComponentId, Tick};
-use bevy::ecs::entity::Entity;
-use bevy::ecs::query::{QueryItem, ReadOnlyWorldQuery, WorldQuery};
-use bevy::ecs::storage::{SparseSets, Table, TableRow};
-use bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell;
-use bevy::ecs::world::World;
-use bevy::ptr::UnsafeCellDeref;
 
 /// Read-access to all components implementing a trait for a given entity.
 pub struct ReadTraits<'a, Trait: ?Sized + TraitQuery> {
@@ -24,6 +26,8 @@ pub struct ReadTraits<'a, Trait: ?Sized + TraitQuery> {
     /// The fetch impl registers read-access for all of these components,
     /// so there will be no runtime conflicts.
     sparse_sets: &'a SparseSets,
+    last_run: Tick,
+    this_run: Tick,
 }
 
 #[doc(hidden)]
@@ -39,10 +43,12 @@ pub struct ReadTableTraitsIter<'a, Trait: ?Sized> {
     // Grants shared access to the components corresponding to `components` in this table.
     // Not all components are guaranteed to exist in the table.
     table: &'a Table,
+    last_run: Tick,
+    this_run: Tick,
 }
 
 impl<'a, Trait: ?Sized + TraitQuery> Iterator for ReadTableTraitsIter<'a, Trait> {
-    type Item = &'a Trait;
+    type Item = Ref<'a, Trait>;
     fn next(&mut self) -> Option<Self::Item> {
         // Iterate the remaining table components that are registered,
         // until we find one that exists in the table.
@@ -55,7 +61,19 @@ impl<'a, Trait: ?Sized + TraitQuery> Iterator for ReadTableTraitsIter<'a, Trait>
                 .byte_add(self.table_row.index() * meta.size_bytes)
         };
         let trait_object = unsafe { meta.dyn_ctor.cast(ptr) };
-        Some(trait_object)
+
+        // SAFETY: we know that the `table_row` is a valid index.
+        // Read access has been registered, so we can dereference it immutably.
+        let added_tick = unsafe { column.get_added_ticks_unchecked(self.table_row).deref() };
+        let changed_tick = unsafe { column.get_changed_ticks_unchecked(self.table_row).deref() };
+
+        Some(Ref::new(
+            trait_object,
+            added_tick,
+            changed_tick,
+            self.last_run,
+            self.this_run,
+        ))
     }
 }
 
@@ -67,28 +85,37 @@ pub struct ReadSparseTraitsIter<'a, Trait: ?Sized> {
     entity: Entity,
     // Grants shared access to the components corresponding to both `components` and `entity`.
     sparse_sets: &'a SparseSets,
+    last_run: Tick,
+    this_run: Tick,
 }
 
 impl<'a, Trait: ?Sized + TraitQuery> Iterator for ReadSparseTraitsIter<'a, Trait> {
-    type Item = &'a Trait;
+    type Item = Ref<'a, Trait>;
     fn next(&mut self) -> Option<Self::Item> {
         // Iterate the remaining sparse set components that are registered,
         // until we find one that exists in the archetype.
-        let (ptr, meta) = unsafe { zip_exact(&mut self.components, &mut self.meta) }.find_map(
-            |(&component, meta)| {
-                self.sparse_sets
-                    .get(component)
-                    .and_then(|set| set.get(self.entity))
-                    .zip(Some(meta))
-            },
-        )?;
+        let ((ptr, ticks_ptr), meta) = unsafe { zip_exact(&mut self.components, &mut self.meta) }
+            .find_map(|(&component, meta)| {
+            self.sparse_sets
+                .get(component)
+                .and_then(|set| set.get_with_ticks(self.entity))
+                .zip(Some(meta))
+        })?;
         let trait_object = unsafe { meta.dyn_ctor.cast(ptr) };
-        Some(trait_object)
+        let added_tick = unsafe { ticks_ptr.added.deref() };
+        let changed_tick = unsafe { ticks_ptr.changed.deref() };
+        Some(Ref::new(
+            trait_object,
+            added_tick,
+            changed_tick,
+            self.last_run,
+            self.this_run,
+        ))
     }
 }
 
 impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for ReadTraits<'w, Trait> {
-    type Item = &'w Trait;
+    type Item = Ref<'w, Trait>;
     type IntoIter = CombinedReadTraitsIter<'w, Trait>;
     #[inline]
     fn into_iter(self) -> Self::IntoIter {
@@ -97,19 +124,23 @@ impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for ReadTraits<'w, Trait> {
             meta: self.registry.table_meta.iter(),
             table: self.table,
             table_row: self.table_row,
+            last_run: self.last_run,
+            this_run: self.this_run,
         };
         let sparse = ReadSparseTraitsIter {
             components: self.registry.sparse_components.iter(),
             meta: self.registry.sparse_meta.iter(),
             entity: self.table.entities()[self.table_row.index()],
             sparse_sets: self.sparse_sets,
+            last_run: self.last_run,
+            this_run: self.this_run,
         };
         table.chain(sparse)
     }
 }
 
 impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for &ReadTraits<'w, Trait> {
-    type Item = &'w Trait;
+    type Item = Ref<'w, Trait>;
     type IntoIter = CombinedReadTraitsIter<'w, Trait>;
     #[inline]
     fn into_iter(self) -> Self::IntoIter {
@@ -118,12 +149,16 @@ impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for &ReadTraits<'w, Trait> {
             meta: self.registry.table_meta.iter(),
             table: self.table,
             table_row: self.table_row,
+            last_run: self.last_run,
+            this_run: self.this_run,
         };
         let sparse = ReadSparseTraitsIter {
             components: self.registry.sparse_components.iter(),
             meta: self.registry.sparse_meta.iter(),
             entity: self.table.entities()[self.table_row.index()],
             sparse_sets: self.sparse_sets,
+            last_run: self.last_run,
+            this_run: self.this_run,
         };
         table.chain(sparse)
     }
@@ -134,13 +169,27 @@ impl<'w, Trait: ?Sized + TraitQuery> ReadTraits<'w, Trait> {
     pub fn iter(&self) -> CombinedReadTraitsIter<'w, Trait> {
         self.into_iter()
     }
+
+    /// Returns an iterator over the components implementing `Trait` for the current entity
+    /// that were added since the last time the system was run.
+    pub fn iter_added(&self) -> impl Iterator<Item = Ref<'w, Trait>> {
+        self.iter().filter(DetectChanges::is_added)
+    }
+
+    /// Returns an iterator over the components implementing `Trait` for the current entity
+    /// whose values were changed since the last time the system was run.
+    pub fn iter_changed(&self) -> impl Iterator<Item = Ref<'w, Trait>> {
+        self.iter().filter(DetectChanges::is_changed)
+    }
 }
 
 #[doc(hidden)]
-pub struct ReadAllTraitsFetch<'w, Trait: ?Sized> {
+pub struct AllTraitsFetch<'w, Trait: ?Sized> {
     registry: &'w TraitImplRegistry<Trait>,
     table: Option<&'w Table>,
     sparse_sets: &'w SparseSets,
+    last_run: Tick,
+    this_run: Tick,
 }
 
 /// Write-access to all components implementing a trait for a given entity.
@@ -267,15 +316,40 @@ impl<'a, Trait: ?Sized + TraitQuery> Iterator for WriteSparseTraitsIter<'a, Trai
     }
 }
 
-impl<'w, Trait: ?Sized + TraitQuery> WriteTraits<'w, Trait> {
+impl<Trait: ?Sized + TraitQuery> WriteTraits<'_, Trait> {
     /// Returns an iterator over the components implementing `Trait` for the current entity.
     pub fn iter(&self) -> CombinedReadTraitsIter<'_, Trait> {
         self.into_iter()
     }
+
     /// Returns a mutable iterator over the components implementing `Trait` for the current entity.
     pub fn iter_mut(&mut self) -> CombinedWriteTraitsIter<'_, Trait> {
         self.into_iter()
     }
+
+    /// Returns an iterator over the components implementing `Trait` for the current entity
+    /// that were added since the last time the system was run.
+    pub fn iter_added(&self) -> impl Iterator<Item = Ref<'_, Trait>> {
+        self.iter().filter(DetectChanges::is_added)
+    }
+
+    /// Returns an iterator over the components implementing `Trait` for the current entity
+    /// whose values were changed since the last time the system was run.
+    pub fn iter_changed(&self) -> impl Iterator<Item = Ref<'_, Trait>> {
+        self.iter().filter(DetectChanges::is_changed)
+    }
+
+    /// Returns a mutable iterator over the components implementing `Trait` for the current entity
+    /// that were added since the last time the system was run.
+    pub fn iter_added_mut(&mut self) -> impl Iterator<Item = Mut<'_, Trait>> {
+        self.iter_mut().filter(DetectChanges::is_added)
+    }
+
+    /// Returns a mutable iterator over the components implementing `Trait` for the current entity
+    /// whose values were changed since the last time the system was run.
+    pub fn iter_changed_mut(&mut self) -> impl Iterator<Item = Mut<'_, Trait>> {
+        self.iter_mut().filter(DetectChanges::is_changed)
+    }
 }
 
 impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for WriteTraits<'w, Trait> {
@@ -306,7 +380,7 @@ impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for WriteTraits<'w, Trait> {
 impl<'world, 'local, Trait: ?Sized + TraitQuery> IntoIterator
     for &'local WriteTraits<'world, Trait>
 {
-    type Item = &'local Trait;
+    type Item = Ref<'local, Trait>;
     type IntoIter = CombinedReadTraitsIter<'local, Trait>;
     #[inline]
     fn into_iter(self) -> Self::IntoIter {
@@ -315,12 +389,16 @@ impl<'world, 'local, Trait: ?Sized + TraitQuery> IntoIterator
             meta: self.registry.table_meta.iter(),
             table: self.table,
             table_row: self.table_row,
+            last_run: self.last_run,
+            this_run: self.this_run,
         };
         let sparse = ReadSparseTraitsIter {
             components: self.registry.sparse_components.iter(),
             meta: self.registry.sparse_meta.iter(),
             entity: self.table.entities()[self.table_row.index()],
             sparse_sets: self.sparse_sets,
+            last_run: self.last_run,
+            this_run: self.this_run,
         };
         table.chain(sparse)
     }
@@ -353,36 +431,19 @@ impl<'world, 'local, Trait: ?Sized + TraitQuery> IntoIterator
     }
 }
 
-#[doc(hidden)]
-pub struct WriteAllTraitsFetch<'w, Trait: ?Sized + TraitQuery> {
-    registry: &'w TraitImplRegistry<Trait>,
-    table: Option<&'w Table>,
-    sparse_sets: &'w SparseSets,
-    last_run: Tick,
-    this_run: Tick,
-}
-
 /// `WorldQuery` adapter that fetches all implementations of a given trait for an entity.
 ///
 /// You can usually just use `&dyn Trait` or `&mut dyn Trait` as a `WorldQuery` directly.
 pub struct All<T: ?Sized>(T);
 
-/// `WorldQuery` adapter that fetches all implementations of a given trait for an entity, with
-/// the additional condition that they have also changed since the last tick.
-pub struct ChangedAll<T: ?Sized>(T);
-
-/// `WorldQuery` adapter that fetches all implementations of a given trait for an entity, with
-/// the additional condition that they have been added since the last tick.
-pub struct AddedAll<T: ?Sized>(T);
-
 unsafe impl<'a, Trait: ?Sized + TraitQuery> ReadOnlyWorldQuery for All<&'a Trait> {}
 
-/// SAFETY: We only access the components registered in the trait registry.
-/// This is known to match the set of components in the `DynQueryState`,
-/// which is used to match archetypes and register world access.
+// SAFETY: We only access the components registered in the trait registry.
+// This is known to match the set of components in the TraitQueryState,
+// which is used to match archetypes and register world access.
 unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> {
     type Item<'w> = ReadTraits<'w, Trait>;
-    type Fetch<'w> = ReadAllTraitsFetch<'w, Trait>;
+    type Fetch<'w> = AllTraitsFetch<'w, Trait>;
     type ReadOnly = Self;
     type State = TraitQueryState<Trait>;
 
@@ -395,24 +456,28 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> {
     unsafe fn init_fetch<'w>(
         world: UnsafeWorldCell<'w>,
         _state: &Self::State,
-        _last_run: Tick,
-        _this_run: Tick,
-    ) -> ReadAllTraitsFetch<'w, Trait> {
-        ReadAllTraitsFetch {
+        last_run: Tick,
+        this_run: Tick,
+    ) -> Self::Fetch<'w> {
+        AllTraitsFetch {
             registry: world
                 .get_resource()
                 .unwrap_or_else(|| trait_registry_error()),
             table: None,
             sparse_sets: &world.storages().sparse_sets,
+            last_run,
+            this_run,
         }
     }
 
     #[inline]
     unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
-        ReadAllTraitsFetch {
+        AllTraitsFetch {
             registry: fetch.registry,
             table: fetch.table,
             sparse_sets: fetch.sparse_sets,
+            last_run: fetch.last_run,
+            this_run: fetch.this_run,
         }
     }
 
@@ -421,7 +486,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> {
 
     #[inline]
     unsafe fn set_archetype<'w>(
-        fetch: &mut ReadAllTraitsFetch<'w, Trait>,
+        fetch: &mut Self::Fetch<'w>,
         _state: &Self::State,
         _archetype: &'w bevy::ecs::archetype::Archetype,
         table: &'w bevy::ecs::storage::Table,
@@ -430,7 +495,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> {
     }
 
     unsafe fn set_table<'w>(
-        fetch: &mut ReadAllTraitsFetch<'w, Trait>,
+        fetch: &mut Self::Fetch<'w>,
         _state: &Self::State,
         table: &'w bevy::ecs::storage::Table,
     ) {
@@ -450,6 +515,8 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> {
             table,
             table_row,
             sparse_sets: fetch.sparse_sets,
+            last_run: fetch.last_run,
+            this_run: fetch.this_run,
         }
     }
 
@@ -494,12 +561,12 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> {
     }
 }
 
-/// SAFETY: We only access the components registered in the trait registry.
-/// This is known to match the set of components in the `DynQueryState`,
-/// which is used to match archetypes and register world access.
+// SAFETY: We only access the components registered in the trait registry.
+// This is known to match the set of components in the TraitQueryState,
+// which is used to match archetypes and register world access.
 unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a mut Trait> {
     type Item<'w> = WriteTraits<'w, Trait>;
-    type Fetch<'w> = WriteAllTraitsFetch<'w, Trait>;
+    type Fetch<'w> = AllTraitsFetch<'w, Trait>;
     type ReadOnly = All<&'a Trait>;
     type State = TraitQueryState<Trait>;
 
@@ -514,8 +581,8 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a mut Trait> {
         _state: &Self::State,
         last_run: Tick,
         this_run: Tick,
-    ) -> WriteAllTraitsFetch<'w, Trait> {
-        WriteAllTraitsFetch {
+    ) -> Self::Fetch<'w> {
+        AllTraitsFetch {
             registry: world
                 .get_resource()
                 .unwrap_or_else(|| trait_registry_error()),
@@ -528,7 +595,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a mut Trait> {
 
     #[inline]
     unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
-        WriteAllTraitsFetch {
+        AllTraitsFetch {
             registry: fetch.registry,
             table: fetch.table,
             sparse_sets: fetch.sparse_sets,
@@ -542,7 +609,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a mut Trait> {
 
     #[inline]
     unsafe fn set_archetype<'w>(
-        fetch: &mut WriteAllTraitsFetch<'w, Trait>,
+        fetch: &mut Self::Fetch<'w>,
         _state: &Self::State,
         _archetype: &'w bevy::ecs::archetype::Archetype,
         table: &'w bevy::ecs::storage::Table,
@@ -552,7 +619,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a mut Trait> {
 
     #[inline]
     unsafe fn set_table<'w>(
-        fetch: &mut WriteAllTraitsFetch<'w, Trait>,
+        fetch: &mut Self::Fetch<'w>,
         _state: &Self::State,
         table: &'w bevy::ecs::storage::Table,
     ) {
diff --git a/src/lib.rs b/src/lib.rs
index 87cda94..0319a0c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,7 +12,7 @@
 //!
 //! In order to be useful within bevy, you'll want to be able to query for this trait.
 //!
-//! ```ignore
+//! ```
 //! # use bevy::prelude::*;
 //! # // Required to make the macro work, because cargo thinks
 //! # // we are in `bevy_trait_query` when compiling this example.
@@ -167,7 +167,7 @@
 //! you can use the filter [`One`](crate::one::One). This has significantly better performance than iterating
 //! over all trait impls.
 //!
-//! ```ignore
+//! ```
 //! # use bevy::prelude::*;
 //! # use bevy_trait_query::*;
 //! #
@@ -189,6 +189,58 @@
 //! # bevy::ecs::system::assert_is_system(show_tooltips);
 //! ```
 //!
+//! Trait queries support basic change detection filtration. So to get all the components that
+//! implement the target trait, and have also changed in some way since the last tick, you can:
+//! ```no_run
+//! # use bevy::prelude::*;
+//! # use bevy_trait_query::*;
+//! #
+//! # #[bevy_trait_query::queryable]
+//! # pub trait Tooltip {
+//! #     fn tooltip(&self) -> &str;
+//! # }
+//! #
+//! fn show_tooltips(
+//!     tooltips_query: Query<All<&dyn Tooltip>>
+//!     // ...
+//! ) {
+//!     // Iterate over all entities with at least one component implementing `Tooltip`
+//!     for entity_tooltips in &tooltips_query {
+//!         // Iterate over each component for the current entity that changed since the last time the system was run.
+//!         for tooltip in entity_tooltips.iter_changed() {
+//!             println!("Changed Tooltip: {}", tooltip.tooltip());
+//!         }
+//!     }
+//! }
+//! ```
+//!
+//! Similar to [`iter_changed`](crate::all::All::iter_changed), we have [`iter_added`](crate::all::All::iter_added)
+//! to detect entities which have had a trait-implementing component added since the last tick.
+//!
+//! If you know you have only one component that implements the target trait,
+//! you can use `OneAdded` or `OneChanged` which behave more like the typical
+//! `bevy` `Added/Changed` filters:
+//! ```no_run
+//! # use bevy::prelude::*;
+//! # use bevy_trait_query::*;
+//! #
+//! # #[bevy_trait_query::queryable]
+//! # pub trait Tooltip {
+//! #     fn tooltip(&self) -> &str;
+//! # }
+//! #
+//! fn show_tooltips(
+//!     tooltips_query: Query<One<&dyn Tooltip>, OneChanged<dyn Tooltip>>
+//!     // ...
+//! ) {
+//!     // Iterate over each entity that has one tooltip implementing component that has also changed
+//!     for tooltip in &tooltips_query {
+//!         println!("Changed Tooltip: {}", tooltip.tooltip());
+//!     }
+//! }
+//! ```
+//! Note in the above example how `OneChanged` does *not* take a reference to the trait object!
+//!
 //! # Performance
 //!
 //! The performance of trait queries is quite competitive. Here are some benchmarks for simple cases:
@@ -199,6 +251,7 @@
 //! | 2 matches         | 17.501 µs      | -                 | 102.83 µs       |
 //! | 1-2 matches       | -              | 16.959 µs         | 82.179 µs       |
 //!
+
 use bevy::{
     ecs::{
         component::{ComponentId, StorageType},
diff --git a/src/one.rs b/src/one.rs
index 56758cb..b3d3d91 100644
--- a/src/one.rs
+++ b/src/one.rs
@@ -1,65 +1,39 @@
-use crate::{debug_unreachable, zip_exact, TraitImplMeta, TraitQuery, TraitQueryState};
-use bevy::ecs::change_detection::Mut;
-use bevy::ecs::component::{ComponentId, ComponentTicks, Tick};
-use bevy::ecs::entity::Entity;
-use bevy::ecs::query::{QueryItem, ReadOnlyWorldQuery, WorldQuery};
-use bevy::ecs::storage::{ComponentSparseSet, SparseSets, TableRow};
-use bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell;
-use bevy::ecs::world::World;
+use std::{cell::UnsafeCell, marker::PhantomData};
+
+use bevy::ecs::{
+    archetype::{Archetype, ArchetypeComponentId},
+    change_detection::{Mut, Ref},
+    component::{ComponentId, Tick},
+    entity::Entity,
+    query::{Access, FilteredAccess, QueryItem, ReadOnlyWorldQuery, WorldQuery},
+    storage::{ComponentSparseSet, SparseSets, Table, TableRow},
+    world::{unsafe_world_cell::UnsafeWorldCell, World},
+};
 use bevy::ptr::{Ptr, ThinSlicePtr, UnsafeCellDeref};
-use std::cell::UnsafeCell;
 
-pub struct ReadTraitFetch<'w, Trait: ?Sized> {
-    // While we have shared access to all sparse set components,
-    // in practice we will only read the components specified in the `FetchState`.
-    // These accesses have been registered, which prevents runtime conflicts.
-    sparse_sets: &'w SparseSets,
-    // After `Fetch::set_archetype` or `set_table` has been called,
-    // this will carry the component data and metadata for the first trait impl found in the archetype.
-    storage: ReadStorage<'w, Trait>,
-}
+use crate::{
+    debug_unreachable, trait_registry_error, zip_exact, TraitImplMeta, TraitImplRegistry,
+    TraitQuery, TraitQueryState,
+};
 
-enum ReadStorage<'w, Trait: ?Sized> {
-    Uninit,
-    Table {
-        /// This points to one of the component table columns,
-        /// corresponding to one of the `ComponentId`s in the fetch state.
-        /// The fetch impl registers read access for all of these components,
-        /// so there will be no runtime conflicts.
-        column: Ptr<'w>,
-        ticks: Option<ThinSlicePtr<'w, UnsafeCell<ComponentTicks>>>,
-        meta: TraitImplMeta<Trait>,
-    },
-    SparseSet {
-        /// This gives us access to one of the components implementing the trait.
-        /// The fetch impl registers read access for all components implementing the trait,
-        /// so there will not be any runtime conflicts.
-        components: &'w ComponentSparseSet,
-        meta: TraitImplMeta<Trait>,
-    },
-}
-
-#[doc(hidden)]
-pub struct WriteTraitFetch<'w, Trait: ?Sized> {
-    // While we have shared mutable access to all sparse set components,
-    // in practice we will only modify the components specified in the `FetchState`.
+pub struct OneTraitFetch<'w, Trait: ?Sized> {
+    // While we have shared access to all sparse set components,
+    // in practice we will only access the components specified in the `FetchState`.
     // These accesses have been registered, which prevents runtime conflicts.
     sparse_sets: &'w SparseSets,
-
     // After `Fetch::set_archetype` or `set_table` has been called,
     // this will carry the component data and metadata for the first trait impl found in the archetype.
-    storage: WriteStorage<'w, Trait>,
-
+    storage: FetchStorage<'w, Trait>,
     last_run: Tick,
     this_run: Tick,
 }
 
-enum WriteStorage<'w, Trait: ?Sized> {
+enum FetchStorage<'w, Trait: ?Sized> {
     Uninit,
     Table {
-        /// This is a shared mutable pointer to one of the component table columns,
+        /// This points to one of the component table columns,
         /// corresponding to one of the `ComponentId`s in the fetch state.
-        /// The fetch impl registers write access for all of these components,
+        /// The fetch impl registers access for all of these components,
         /// so there will be no runtime conflicts.
         column: Ptr<'w>,
         added_ticks: ThinSlicePtr<'w, UnsafeCell<Tick>>,
@@ -67,8 +41,9 @@ enum WriteStorage<'w, Trait: ?Sized> {
         meta: TraitImplMeta<Trait>,
     },
     SparseSet {
-        /// This gives us shared mutable access to one of the components implementing the trait.
-        /// The fetch impl registers write access for all components implementing the trait, so there will be no runtime conflicts.
+        /// This gives us access to one of the components implementing the trait.
+        /// The fetch impl registers access for all components implementing the trait,
+        /// so there will not be any runtime conflicts.
         components: &'w ComponentSparseSet,
         meta: TraitImplMeta<Trait>,
     },
@@ -79,11 +54,11 @@ pub struct One<T>(pub T);
 
 unsafe impl<'a, T: ?Sized + TraitQuery> ReadOnlyWorldQuery for One<&'a T> {}
 
-/// SAFETY: We only access the components registered in `DynQueryState`.
-/// This same set of components is used to match archetypes, and used to register world access.
+// SAFETY: We only access the components registered in TraitQueryState.
+// This same set of components is used to match archetypes, and used to register world access.
 unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> {
-    type Item<'w> = &'w Trait;
-    type Fetch<'w> = ReadTraitFetch<'w, Trait>;
+    type Item<'w> = Ref<'w, Trait>;
+    type Fetch<'w> = OneTraitFetch<'w, Trait>;
     type ReadOnly = Self;
     type State = TraitQueryState<Trait>;
 
@@ -98,32 +73,38 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> {
         _state: &Self::State,
         _last_run: Tick,
         _this_run: Tick,
-    ) -> ReadTraitFetch<'w, Trait> {
-        ReadTraitFetch {
-            storage: ReadStorage::Uninit,
+    ) -> OneTraitFetch<'w, Trait> {
+        OneTraitFetch {
+            storage: FetchStorage::Uninit,
+            last_run: Tick::new(0),
             sparse_sets: &world.storages().sparse_sets,
+            this_run: Tick::new(0),
         }
     }
 
     #[inline]
     unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
-        ReadTraitFetch {
+        OneTraitFetch {
             storage: match fetch.storage {
-                ReadStorage::Uninit => ReadStorage::Uninit,
-                ReadStorage::Table {
+                FetchStorage::Uninit => FetchStorage::Uninit,
+                FetchStorage::Table {
                     column,
-                    ticks,
+                    added_ticks,
+                    changed_ticks,
                     meta,
-                } => ReadStorage::Table {
+                } => FetchStorage::Table {
                     column,
-                    ticks,
+                    added_ticks,
+                    changed_ticks,
                     meta,
                 },
-                ReadStorage::SparseSet { components, meta } => {
-                    ReadStorage::SparseSet { components, meta }
+                FetchStorage::SparseSet { components, meta } => {
+                    FetchStorage::SparseSet { components, meta }
                 }
             },
+            last_run: Tick::new(0),
             sparse_sets: fetch.sparse_sets,
+            this_run: Tick::new(0),
         }
     }
 
@@ -132,7 +113,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> {
 
     #[inline]
     unsafe fn set_archetype<'w>(
-        fetch: &mut ReadTraitFetch<'w, Trait>,
+        fetch: &mut OneTraitFetch<'w, Trait>,
         state: &Self::State,
         _archetype: &'w bevy::ecs::archetype::Archetype,
         table: &'w bevy::ecs::storage::Table,
@@ -141,9 +122,10 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> {
         // We check the table components first since it is faster to retrieve data of this type.
         for (&component, &meta) in zip_exact(&*state.components, &*state.meta) {
             if let Some(column) = table.get_column(component) {
-                fetch.storage = ReadStorage::Table {
+                fetch.storage = FetchStorage::Table {
                     column: column.get_data_ptr(),
-                    ticks: None,
+                    added_ticks: column.get_added_ticks_slice().into(),
+                    changed_ticks: column.get_changed_ticks_slice().into(),
                     meta,
                 };
                 return;
@@ -151,7 +133,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> {
         }
         for (&component, &meta) in zip_exact(&*state.components, &*state.meta) {
             if let Some(sparse_set) = fetch.sparse_sets.get(component) {
-                fetch.storage = ReadStorage::SparseSet {
+                fetch.storage = FetchStorage::SparseSet {
                     components: sparse_set,
                     meta,
                 };
@@ -164,16 +146,17 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> {
 
     #[inline]
     unsafe fn set_table<'w>(
-        fetch: &mut ReadTraitFetch<'w, Trait>,
+        fetch: &mut OneTraitFetch<'w, Trait>,
         state: &Self::State,
         table: &'w bevy::ecs::storage::Table,
     ) {
         // Search for a registered trait impl that is present in the table.
         for (&component, &meta) in std::iter::zip(&*state.components, &*state.meta) {
             if let Some(column) = table.get_column(component) {
-                fetch.storage = ReadStorage::Table {
+                fetch.storage = FetchStorage::Table {
                     column: column.get_data_ptr(),
-                    ticks: None,
+                    added_ticks: column.get_added_ticks_slice().into(),
+                    changed_ticks: column.get_changed_ticks_slice().into(),
                     meta,
                 }
             }
@@ -189,21 +172,49 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> {
         table_row: TableRow,
     ) -> Self::Item<'w> {
         let table_row = table_row.index();
-        match fetch.storage {
+        let dyn_ctor;
+        let (ptr, added, changed) = match fetch.storage {
             // SAFETY: This function must have been called after `set_archetype`,
             // so we know that `self.storage` has been initialized.
-            ReadStorage::Uninit => debug_unreachable(),
-            ReadStorage::Table { column, meta, .. } => {
+            FetchStorage::Uninit => debug_unreachable(),
+            FetchStorage::Table {
+                column,
+                added_ticks,
+                changed_ticks,
+                meta,
+            } => {
+                dyn_ctor = meta.dyn_ctor;
                 let ptr = column.byte_add(table_row * meta.size_bytes);
-                meta.dyn_ctor.cast(ptr)
+                (
+                    ptr,
+                    // SAFETY: We have read access to the component, so by extension
+                    // we have access to the corresponding `ComponentTicks`.
+                    added_ticks.get(table_row).deref(),
+                    changed_ticks.get(table_row).deref(),
+                )
             }
-            ReadStorage::SparseSet { components, meta } => {
-                let ptr = components
-                    .get(entity)
+            FetchStorage::SparseSet { components, meta } => {
+                dyn_ctor = meta.dyn_ctor;
+                let (ptr, ticks) = components
+                    .get_with_ticks(entity)
                     .unwrap_or_else(|| debug_unreachable());
-                meta.dyn_ctor.cast(ptr)
+                (
+                    ptr,
+                    // SAFETY: We have read access to the component, so by extension
+                    // we have access to the corresponding `ComponentTicks`.
+                    ticks.added.deref(),
+                    ticks.changed.deref(),
+                )
             }
-        }
+        };
+
+        Ref::new(
+            dyn_ctor.cast(ptr),
+            added,
+            changed,
+            fetch.last_run,
+            fetch.this_run,
+        )
     }
 
     #[inline]
@@ -248,11 +259,11 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> {
     }
 }
 
-/// SAFETY: We only access the components registered in `DynQueryState`.
-/// This same set of components is used to match archetypes, and used to register world access.
+// SAFETY: We only access the components registered in TraitQueryState.
+// This same set of components is used to match archetypes, and used to register world access.
 unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> {
     type Item<'w> = Mut<'w, Trait>;
-    type Fetch<'w> = WriteTraitFetch<'w, Trait>;
+    type Fetch<'w> = OneTraitFetch<'w, Trait>;
     type ReadOnly = One<&'a Trait>;
     type State = TraitQueryState<Trait>;
 
@@ -267,9 +278,9 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> {
         _state: &Self::State,
         last_run: Tick,
         this_run: Tick,
-    ) -> WriteTraitFetch<'w, Trait> {
-        WriteTraitFetch {
-            storage: WriteStorage::Uninit,
+    ) -> OneTraitFetch<'w, Trait> {
+        OneTraitFetch {
+            storage: FetchStorage::Uninit,
             sparse_sets: &world.storages().sparse_sets,
             last_run,
             this_run,
@@ -278,22 +289,22 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> {
 
     #[inline]
     unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
-        WriteTraitFetch {
+        OneTraitFetch {
             storage: match fetch.storage {
-                WriteStorage::Uninit => WriteStorage::Uninit,
-                WriteStorage::Table {
+                FetchStorage::Uninit => FetchStorage::Uninit,
+                FetchStorage::Table {
                     column,
                     meta,
                     added_ticks,
                     changed_ticks,
-                } => WriteStorage::Table {
+                } => FetchStorage::Table {
                     column,
                     meta,
                     added_ticks,
                     changed_ticks,
                 },
-                WriteStorage::SparseSet { components, meta } => {
-                    WriteStorage::SparseSet { components, meta }
+                FetchStorage::SparseSet { components, meta } => {
+                    FetchStorage::SparseSet { components, meta }
                 }
             },
             sparse_sets: fetch.sparse_sets,
@@ -307,7 +318,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> {
 
     #[inline]
     unsafe fn set_archetype<'w>(
-        fetch: &mut WriteTraitFetch<'w, Trait>,
+        fetch: &mut OneTraitFetch<'w, Trait>,
         state: &Self::State,
         _archetype: &'w bevy::ecs::archetype::Archetype,
         table: &'w bevy::ecs::storage::Table,
@@ -315,7 +326,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> {
         // Search for a registered trait impl that is present in the archetype.
         for (&component, &meta) in zip_exact(&*state.components, &*state.meta) {
             if let Some(column) = table.get_column(component) {
-                fetch.storage = WriteStorage::Table {
+                fetch.storage = FetchStorage::Table {
                     column: column.get_data_ptr(),
                     added_ticks: column.get_added_ticks_slice().into(),
                     changed_ticks: column.get_changed_ticks_slice().into(),
@@ -326,7 +337,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> {
         }
         for (&component, &meta) in zip_exact(&*state.components, &*state.meta) {
             if let Some(sparse_set) = fetch.sparse_sets.get(component) {
-                fetch.storage = WriteStorage::SparseSet {
+                fetch.storage = FetchStorage::SparseSet {
                     components: sparse_set,
                     meta,
                 };
@@ -339,14 +350,14 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> {
 
     #[inline]
     unsafe fn set_table<'w>(
-        fetch: &mut WriteTraitFetch<'w, Trait>,
+        fetch: &mut OneTraitFetch<'w, Trait>,
         state: &Self::State,
         table: &'w bevy::ecs::storage::Table,
     ) {
         // Search for a registered trait impl that is present in the table.
         for (&component, &meta) in std::iter::zip(&*state.components, &*state.meta) {
             if let Some(column) = table.get_column(component) {
-                fetch.storage = WriteStorage::Table {
+                fetch.storage = FetchStorage::Table {
                     column: column.get_data_ptr(),
                     added_ticks: column.get_added_ticks_slice().into(),
                     changed_ticks: column.get_changed_ticks_slice().into(),
@@ -370,8 +381,8 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> {
         let (ptr, added, changed) = match fetch.storage {
             // SAFETY: This function must have been called after `set_archetype`,
             // so we know that `self.storage` has been initialized.
-            WriteStorage::Uninit => debug_unreachable(),
-            WriteStorage::Table {
+            FetchStorage::Uninit => debug_unreachable(),
+            FetchStorage::Table {
                 column,
                 added_ticks,
                 changed_ticks,
@@ -390,7 +401,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> {
                     changed_ticks.get(table_row).deref_mut(),
                 )
             }
-            WriteStorage::SparseSet { components, meta } => {
+            FetchStorage::SparseSet { components, meta } => {
                 dyn_ctor = meta.dyn_ctor;
                 let (ptr, ticks) = components
                     .get_with_ticks(entity)
@@ -457,3 +468,344 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> {
         state.matches_component_set_one(set_contains_id)
     }
 }
+
+enum ChangeDetectionStorage<'w> {
+    Uninit,
+    Table {
+        /// This points to one of the component table columns,
+        /// corresponding to one of the `ComponentId`s in the fetch state.
+        /// The fetch impl registers read access for all of these components,
+        /// so there will be no runtime conflicts.
+        ticks: ThinSlicePtr<'w, UnsafeCell<Tick>>,
+    },
+    SparseSet {
+        /// This gives us access to one of the components implementing the trait.
+        /// The fetch impl registers read access for all components implementing the trait,
+        /// so there will not be any runtime conflicts.
+        components: &'w ComponentSparseSet,
+    },
+}
+
+/// [`WorldQuery`] filter for entities with exactly [one](crate::One) component
+/// implementing a trait, whose value has changed since the last time the system ran.
+pub struct OneAdded<Trait: ?Sized + TraitQuery> {
+    marker: PhantomData<&'static Trait>,
+}
+
+pub struct ChangeDetectionFetch<'w, Trait: ?Sized + TraitQuery> {
+    registry: &'w TraitImplRegistry<Trait>,
+    storage: ChangeDetectionStorage<'w>,
+    sparse_sets: &'w SparseSets,
+    last_run: Tick,
+    this_run: Tick,
+}
+
+unsafe impl<Trait: ?Sized + TraitQuery> WorldQuery for OneAdded<Trait> {
+    type Item<'w> = bool;
+    type Fetch<'w> = ChangeDetectionFetch<'w, Trait>;
+    type ReadOnly = Self;
+    type State = TraitQueryState<Trait>;
+
+    fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
+        item
+    }
+
+    unsafe fn init_fetch<'w>(
+        world: UnsafeWorldCell<'w>,
+        _state: &Self::State,
+        last_run: Tick,
+        this_run: Tick,
+    ) -> Self::Fetch<'w> {
+        Self::Fetch::<'w> {
+            registry: world
+                .get_resource()
+                .unwrap_or_else(|| trait_registry_error()),
+            storage: ChangeDetectionStorage::Uninit,
+            sparse_sets: &world.storages().sparse_sets,
+            last_run,
+            this_run,
+        }
+    }
+
+    unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
+        Self::Fetch {
+            registry: fetch.registry,
+            storage: match fetch.storage {
+                ChangeDetectionStorage::Uninit => ChangeDetectionStorage::Uninit,
+                ChangeDetectionStorage::Table { ticks } => ChangeDetectionStorage::Table { ticks },
+                ChangeDetectionStorage::SparseSet { components } => {
+                    ChangeDetectionStorage::SparseSet { components }
+                }
+            },
+            sparse_sets: fetch.sparse_sets,
+            last_run: fetch.last_run,
+            this_run: fetch.this_run,
+        }
+    }
+
+    // This will always be false for us, as we (so far) do not know at compile time whether the
+    // components our trait has been impl'd for are stored in table or in sparse set
+    const IS_DENSE: bool = false;
+    const IS_ARCHETYPAL: bool = false;
+
+    #[inline]
+    unsafe fn set_archetype<'w>(
+        fetch: &mut Self::Fetch<'w>,
+        state: &Self::State,
+        _archetype: &'w Archetype,
+        table: &'w Table,
+    ) {
+        // Search for a registered trait impl that is present in the archetype.
+        // We check the table components first since it is faster to retrieve data of this type.
+        for &component in &*state.components {
+            if let Some(column) = table.get_column(component) {
+                fetch.storage = ChangeDetectionStorage::Table {
+                    ticks: ThinSlicePtr::from(column.get_added_ticks_slice()),
+                };
+                return;
+            }
+        }
+        for &component in &*state.components {
+            if let Some(components) = fetch.sparse_sets.get(component) {
+                fetch.storage = ChangeDetectionStorage::SparseSet { components };
+                return;
+            }
+        }
+        // At least one of the components must be present in the table/sparse set.
+        debug_unreachable()
+    }
+
+    #[inline]
+    unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
+        // only gets called if IS_DENSE == true, which does not hold for us
+        debug_unreachable()
+    }
+
+    #[inline(always)]
+    unsafe fn fetch<'w>(
+        fetch: &mut Self::Fetch<'w>,
+        entity: Entity,
+        table_row: TableRow,
+    ) -> Self::Item<'w> {
+        let ticks_ptr = match fetch.storage {
+            ChangeDetectionStorage::Uninit => {
+                // set_archetype must have been called already
+                debug_unreachable()
+            }
+            ChangeDetectionStorage::Table { ticks } => ticks.get(table_row.index()),
+            ChangeDetectionStorage::SparseSet { components } => components
+                .get_added_ticks(entity)
+                .unwrap_or_else(|| debug_unreachable()),
+        };
+
+        (*ticks_ptr)
+            .deref()
+            .is_newer_than(fetch.last_run, fetch.this_run)
+    }
+
+    #[inline(always)]
+    unsafe fn filter_fetch(
+        fetch: &mut Self::Fetch<'_>,
+        entity: Entity,
+        table_row: TableRow,
+    ) -> bool {
+        Self::fetch(fetch, entity, table_row)
+    }
+
+    #[inline]
+    fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>) {
+        for &component in &*state.components {
+            assert!(
+                !access.access().has_write(component),
+                "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
+                std::any::type_name::<Trait>(),
+            );
+            access.add_read(component);
+        }
+    }
+
+    #[inline]
+    fn update_archetype_component_access(
+        state: &Self::State,
+        archetype: &Archetype,
+        access: &mut Access<ArchetypeComponentId>,
+    ) {
+        for &component in &*state.components {
+            if let Some(archetype_component_id) = archetype.get_archetype_component_id(component) {
+                access.add_read(archetype_component_id);
+            }
+        }
+    }
+
+    fn init_state(world: &mut World) -> Self::State {
+        TraitQueryState::init(world)
+    }
+
+    fn matches_component_set(
+        state: &Self::State,
+        set_contains_id: &impl Fn(ComponentId) -> bool,
+    ) -> bool {
+        state.matches_component_set_one(set_contains_id)
+    }
+}
+
+/// SAFETY: read-only access
+unsafe impl<Trait: ?Sized + TraitQuery> ReadOnlyWorldQuery for OneAdded<Trait> {}
+
+/// [`WorldQuery`] filter for entities with exactly [one](crate::One) component
+/// implementing a trait, which was added since the last time the system ran.
+pub struct OneChanged<Trait: ?Sized + TraitQuery> {
+    marker: PhantomData<&'static Trait>,
+}
+
+unsafe impl<Trait: ?Sized + TraitQuery> WorldQuery for OneChanged<Trait> {
+    type Item<'w> = bool;
+    type Fetch<'w> = ChangeDetectionFetch<'w, Trait>;
+    type ReadOnly = Self;
+    type State = TraitQueryState<Trait>;
+
+    fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
+        item
+    }
+
+    unsafe fn init_fetch<'w>(
+        world: UnsafeWorldCell<'w>,
+        _state: &Self::State,
+        last_run: Tick,
+        this_run: Tick,
+    ) -> Self::Fetch<'w> {
+        Self::Fetch::<'w> {
+            registry: world
+                .get_resource()
+                .unwrap_or_else(|| trait_registry_error()),
+            storage: ChangeDetectionStorage::Uninit,
+            sparse_sets: &world.storages().sparse_sets,
+            last_run,
+            this_run,
+        }
+    }
+
+    unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
+        Self::Fetch {
+            registry: fetch.registry,
+            storage: match fetch.storage {
+                ChangeDetectionStorage::Uninit => ChangeDetectionStorage::Uninit,
+                ChangeDetectionStorage::Table { ticks } => ChangeDetectionStorage::Table { ticks },
+                ChangeDetectionStorage::SparseSet { components } => {
+                    ChangeDetectionStorage::SparseSet { components }
+                }
+            },
+            sparse_sets: fetch.sparse_sets,
+            last_run: fetch.last_run,
+            this_run: fetch.this_run,
+        }
+    }
+
+    // This will always be false for us, as we (so far) do not know at compile time whether the
+    // components our trait has been impl'd for are stored in table or in sparse set
+    const IS_DENSE: bool = false;
+    const IS_ARCHETYPAL: bool = false;
+
+    #[inline]
+    unsafe fn set_archetype<'w>(
+        fetch: &mut Self::Fetch<'w>,
+        state: &Self::State,
+        _archetype: &'w Archetype,
+        table: &'w Table,
+    ) {
+        // Search for a registered trait impl that is present in the archetype.
+        // We check the table components first since it is faster to retrieve data of this type.
+        for &component in &*state.components {
+            if let Some(column) = table.get_column(component) {
+                fetch.storage = ChangeDetectionStorage::Table {
+                    ticks: column.get_changed_ticks_slice().into(),
+                };
+                return;
+            }
+        }
+        for &component in &*state.components {
+            if let Some(components) = fetch.sparse_sets.get(component) {
+                fetch.storage = ChangeDetectionStorage::SparseSet { components };
+                return;
+            }
+        }
+        // At least one of the components must be present in the table/sparse set.
+        debug_unreachable()
+    }
+
+    #[inline]
+    unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
+        // only gets called if IS_DENSE == true, which does not hold for us
+        debug_unreachable()
+    }
+
+    #[inline(always)]
+    unsafe fn fetch<'w>(
+        fetch: &mut Self::Fetch<'w>,
+        entity: Entity,
+        table_row: TableRow,
+    ) -> Self::Item<'w> {
+        let ticks_ptr = match fetch.storage {
+            ChangeDetectionStorage::Uninit => {
+                // set_archetype must have been called already
+                debug_unreachable()
+            }
+            ChangeDetectionStorage::Table { ticks } => ticks.get(table_row.index()),
+            ChangeDetectionStorage::SparseSet { components } => components
+                .get_changed_ticks(entity)
+                .unwrap_or_else(|| debug_unreachable()),
+        };
+
+        (*ticks_ptr)
+            .deref()
+            .is_newer_than(fetch.last_run, fetch.this_run)
+    }
+
+    #[inline(always)]
+    unsafe fn filter_fetch(
+        fetch: &mut Self::Fetch<'_>,
+        entity: Entity,
+        table_row: TableRow,
+    ) -> bool {
+        Self::fetch(fetch, entity, table_row)
+    }
+
+    #[inline]
+    fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>) {
+        for &component in &*state.components {
+            assert!(
+                !access.access().has_write(component),
+                "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
+                std::any::type_name::<Trait>(),
+            );
+            access.add_read(component);
+        }
+    }
+
+    #[inline]
+    fn update_archetype_component_access(
+        state: &Self::State,
+        archetype: &Archetype,
+        access: &mut Access<ArchetypeComponentId>,
+    ) {
+        for &component in &*state.components {
+            if let Some(archetype_component_id) = archetype.get_archetype_component_id(component) {
+                access.add_read(archetype_component_id);
+            }
+        }
+    }
+
+    fn init_state(world: &mut World) -> Self::State {
+        TraitQueryState::init(world)
+    }
+
+    fn matches_component_set(
+        state: &Self::State,
+        set_contains_id: &impl Fn(ComponentId) -> bool,
+    ) -> bool {
+        state.matches_component_set_one(set_contains_id)
+    }
+}
+
+/// SAFETY: read-only access
+unsafe impl<Trait: ?Sized + TraitQuery> ReadOnlyWorldQuery for OneChanged<Trait> {}


Test patch:
diff --git a/src/tests.rs b/src/tests.rs
index 69e91a2..cf47323 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -1,6 +1,9 @@
 use super::*;
 use std::fmt::{Debug, Display};
 
+// Required for proc macros.
+use crate as bevy_trait_query;
+
 #[derive(Resource, Default)]
 pub struct Output(Vec<String>);
 
@@ -181,6 +184,315 @@ fn age_up_not(mut q: Query<&mut dyn Person, Without<Fem>>) {
     }
 }
 
+#[test]
+fn added_all() {
+    let mut world = World::new();
+    world.init_resource::<Output>();
+    world
+        .register_component_as::<dyn Person, Human>()
+        .register_component_as::<dyn Person, Dolphin>();
+
+    world.spawn(Human("Henry".to_owned(), 22));
+
+    let mut schedule = Schedule::new();
+    schedule.add_systems((print_added_all_info, age_up_fem).chain());
+
+    schedule.run(&mut world);
+
+    world.spawn((Human("Garbanzo".to_owned(), 17), Fem, Dolphin(17)));
+
+    schedule.run(&mut world);
+
+    // only changes will occur now to the ages of Garbanzo/Reginald, so nothing should be printed
+
+    schedule.run(&mut world);
+
+    println!("{:?}", world.resource::<Output>().0);
+
+    assert_eq!(
+        world.resource::<Output>().0,
+        &[
+            "Added people:",
+            "Henry: 22",
+            "",
+            "Added people:",
+            "Garbanzo: 17",
+            "Reginald: 17",
+            "",
+            "Added people:",
+            ""
+        ]
+    );
+}
+
+// Prints the name and age of every newly added `Person`.
+fn print_added_all_info(people: Query<All<&dyn Person>>, mut output: ResMut<Output>) {
+    output.0.push("Added people:".to_string());
+    for person in people.iter().flat_map(|p| p.iter_added()) {
+        output
+            .0
+            .push(format!("{}: {}", person.name(), person.age()));
+    }
+    output.0.push(default());
+}
+
+#[test]
+fn changed_all() {
+    let mut world = World::new();
+    world.init_resource::<Output>();
+    world
+        .register_component_as::<dyn Person, Human>()
+        .register_component_as::<dyn Person, Dolphin>();
+
+    let mut schedule = Schedule::new();
+    schedule.add_systems((print_changed_all_info, age_up_fem).chain());
+
+    // Henry is newly added, so we expect him to be printed
+    world.spawn(Human("Henry".to_owned(), 22));
+
+    schedule.run(&mut world);
+
+    // Garbanzo and Dolphin (Reginald) are newly added, so we expect them to be printed
+    world.spawn((Human("Garbanzo".to_owned(), 17), Fem, Dolphin(17)));
+
+    schedule.run(&mut world);
+
+    // Garbanzo and Dolphin (Reginald) will both be incremented in age by one by `age_up_fem`, so
+    // they should be printed again
+
+    schedule.run(&mut world);
+
+    assert_eq!(
+        world.resource::<Output>().0,
+        &[
+            "Changed people:",
+            "Henry: 22",
+            "",
+            "Changed people:",
+            "Garbanzo: 17",
+            "Reginald: 17",
+            "",
+            "Changed people:",
+            "Garbanzo: 18",
+            "Reginald: 18",
+            "",
+        ]
+    );
+}
+
+// Prints the name and age of every `Person` whose info has changed in some way
+fn print_changed_all_info(people: Query<All<&dyn Person>>, mut output: ResMut<Output>) {
+    output.0.push("Changed people:".to_string());
+    for person in people.iter().flat_map(|p| p.iter_changed()) {
+        output
+            .0
+            .push(format!("{}: {}", person.name(), person.age()));
+    }
+    output.0.push(default());
+}
+
+#[test]
+fn added_one() {
+    let mut world = World::new();
+    world.init_resource::<Output>();
+    world
+        .register_component_as::<dyn Person, Human>()
+        .register_component_as::<dyn Person, Dolphin>();
+
+    world.spawn(Human("Henry".to_owned(), 22));
+
+    let mut schedule = Schedule::new();
+    schedule.add_systems((print_added_one_info, (age_up_fem, age_up_not)).chain());
+
+    schedule.run(&mut world);
+
+    world.spawn((Dolphin(27), Fem));
+
+    schedule.run(&mut world);
+
+    schedule.run(&mut world);
+
+    assert_eq!(
+        world.resource::<Output>().0,
+        &[
+            "Added people:",
+            "Henry: 22",
+            "",
+            "Added people:",
+            "Reginald: 27",
+            "",
+            "Added people:",
+            "",
+        ]
+    );
+}
+
+// Prints the name and age of every newly added `Person`.
+fn print_added_one_info(
+    people: Query<One<&dyn Person>, OneAdded<dyn Person>>,
+    mut output: ResMut<Output>,
+) {
+    output.0.push("Added people:".to_string());
+    for person in &people {
+        output
+            .0
+            .push(format!("{}: {}", person.name(), person.age()));
+    }
+    output.0.push(default());
+}
+
+#[test]
+fn changed_one() {
+    let mut world = World::new();
+    world.init_resource::<Output>();
+    world
+        .register_component_as::<dyn Person, Human>()
+        .register_component_as::<dyn Person, Dolphin>();
+
+    let mut schedule = Schedule::new();
+    schedule.add_systems((print_changed_one_info, age_up_fem).chain());
+
+    world.spawn(Human("Henry".to_owned(), 22));
+
+    schedule.run(&mut world);
+
+    world.spawn((Dolphin(27), Fem));
+
+    schedule.run(&mut world);
+
+    schedule.run(&mut world);
+
+    assert_eq!(
+        world.resource::<Output>().0,
+        &[
+            "Changed people:",
+            "Henry: 22",
+            "",
+            "Changed people:",
+            "Reginald: 27",
+            "",
+            "Changed people:",
+            "Reginald: 28",
+            ""
+        ]
+    );
+}
+
+// Prints the name and age of every `Person` whose info has changed in some way
+fn print_changed_one_info(
+    people: Query<One<&dyn Person>, OneChanged<dyn Person>>,
+    mut output: ResMut<Output>,
+) {
+    output.0.push("Changed people:".to_string());
+    for person in &people {
+        output
+            .0
+            .push(format!("{}: {}", person.name(), person.age()));
+    }
+    output.0.push(default());
+}
+
+#[test]
+fn one_added_filter() {
+    let mut world = World::new();
+    world.init_resource::<Output>();
+    world
+        .register_component_as::<dyn Person, Human>()
+        .register_component_as::<dyn Person, Dolphin>();
+
+    world.spawn(Human("Henry".to_owned(), 22));
+
+    let mut schedule = Schedule::new();
+    schedule.add_systems((print_one_added_filter_info, (age_up_fem, age_up_not)).chain());
+
+    schedule.run(&mut world);
+
+    world.spawn((Dolphin(27), Fem));
+
+    schedule.run(&mut world);
+
+    schedule.run(&mut world);
+
+    assert_eq!(
+        world.resource::<Output>().0,
+        &[
+            "Added people:",
+            "Henry: 22",
+            "",
+            "Added people:",
+            "Reginald: 27",
+            "",
+            "Added people:",
+            "",
+        ]
+    );
+}
+
+// Prints the name and age of every newly added `Person`.
+fn print_one_added_filter_info(
+    people: Query<One<&dyn Person>, OneAdded<dyn Person>>,
+    mut output: ResMut<Output>,
+) {
+    output.0.push("Added people:".to_string());
+    for person in (&people).into_iter() {
+        output
+            .0
+            .push(format!("{}: {}", person.name(), person.age()));
+    }
+    output.0.push(default());
+}
+
+#[test]
+fn one_changed_filter() {
+    let mut world = World::new();
+    world.init_resource::<Output>();
+    world
+        .register_component_as::<dyn Person, Human>()
+        .register_component_as::<dyn Person, Dolphin>();
+
+    world.spawn(Human("Henry".to_owned(), 22));
+
+    let mut schedule = Schedule::new();
+    schedule.add_systems((print_one_changed_filter_info, age_up_fem).chain());
+
+    schedule.run(&mut world);
+
+    world.spawn((Dolphin(27), Fem));
+
+    schedule.run(&mut world);
+
+    schedule.run(&mut world);
+
+    assert_eq!(
+        world.resource::<Output>().0,
+        &[
+            "Changed people:",
+            "Henry: 22",
+            "",
+            "Changed people:",
+            "Reginald: 27",
+            "",
+            "Changed people:",
+            "Reginald: 28",
+            ""
+        ]
+    );
+}
+
+// Prints the name and age of every `Person` whose info has changed in some way
+fn print_one_changed_filter_info(
+    people: Query<One<&dyn Person>, OneChanged<dyn Person>>,
+    mut output: ResMut<Output>,
+) {
+    output.0.push("Changed people:".to_string());
+    for person in (&people).into_iter() {
+        output
+            .0
+            .push(format!("{}: {}", person.name(), person.age()));
+    }
+    output.0.push(default());
+}
+
 #[queryable]
 pub trait Messages {
     fn send(&mut self, _: &dyn Display);