bevy icon indicating copy to clipboard operation
bevy copied to clipboard

Add `get_ref` methods for Queries

Open Adamkob12 opened this issue 1 year ago • 9 comments

Objective

  • fix #11517 by adding a get_ref method for Query and QueryState

Solution

  • Add get_ref method for QueryState
  • Add get_ref method for Query

Changelog

  • The QueryData trait has another associated type - ReadOnlySmartRef. ReadOnlySmartRef is the type we get when we request read-only, change-detection-enabled, shared access to the data. For example: access of &T is Ref<T>, the same is true for &mut T, because this access is explicitly read-only, and as such is analogous to first converting a QueryData to its read-only form (QueryData::ReadOnly) and then to its QueryData::ReadOnlySmartRef form.
  • The QueryData derive macro changed a bit in its implementation to reflect the above changes.
  • The QueryData implementations of many types have changed to reflect the above changes.
  • get_ref , get_component_ref, iter_ref, as_readonly_smart_ref methods were added to QueryState
  • get_ref, iter_ref, iter_combinations_ref, iter_many_ref, par_iter_ref, get_component_ref, component_ref, single_ref, get_single_ref, methods was added to Query
  • system_query_get_ref test was added to make sure Query<Ref<T>> and Query<&T> + get_ref produce the same result.

Migration guide

  • An associated type was added to the public QueryData trait: ReadOnlySmartRef. If the type you're implementing QueryData for can be accessed using some change-detection-enabled, read-only way, for example: the Ref smart pointer - then define ReadOnlySmartRef to be that type. If it can't be accessed that way, then keep ReadOnlySmartRef the same as ReadOnly. For example:
// Before:
impl<'a, C: Component> QueryData for &'a C {
    type ReadOnly = &'a C; // Regular read-only access
}

impl QueryData for Entity {
    type ReadOnly = Entity; // Standard read-only access of a Copy type
}

// After:
impl<'a, C: Component> QueryData for &'a C {
    type ReadOnly = &'a C; // Regular read-only access
    type ReadOnlySmartRef = Ref<'a, C>; // We can use `Ref` for change detection
}

impl QueryData for Entity {
    type ReadOnly = Entity; // Standard read-only access of a Copy type
    type ReadOnlySmartRef = Entity; // No option for change-detection, so keep it the same as `Self::ReadOnly`
}

Adamkob12 avatar Jan 26 '24 15:01 Adamkob12

@alice-i-cecile What do I do about that one failing CI that expects a certain error message? (because the derive macro changed a bit and that messed with the error)

Adamkob12 avatar Jan 26 '24 16:01 Adamkob12

@alice-i-cecile What do I do about that one failing CI that expects a certain error message? (because the derive macro changed a bit and that messed with the error)

The error message should be updated to match in this PR. IIRC there's a "bless" command to do it automatically if you prefer: check the docs of the crate we're using if you want to try it out :)

alice-i-cecile avatar Jan 26 '24 18:01 alice-i-cecile

It looks like your PR is a breaking change, but you didn't provide a migration guide.

Could you add some context on what users should update when this change get released in a new version of Bevy? It will be used to help writing the migration guide for the version. Putting it after a ## Migration Guide will help it get automatically picked up by our tooling.

github-actions[bot] avatar Jan 26 '24 23:01 github-actions[bot]

Naming suggestions:

  • QueryItemRef (Mut is often used as a suffix, so why not Ref?)
  • ValueRef
  • WithRef
  • RefWrapper
  • RefAccessor
  • RefAccess
  • Reference

viridia avatar Jan 27 '24 01:01 viridia

Is there a reason it's not possible for the "reffed" form of &'a mut C to be Mut<'a, C>? That would be my intuitive expectation, to just add change-detection without changing the rest of the semantics.

The default form of &'a mut C already is Mut<'a, C> 🤔 I think that "the reffed form is always read-only" is more clear, and works better with the rest of the API.

alice-i-cecile avatar Jan 27 '24 18:01 alice-i-cecile

I settled on ROQueryItemRef and ReadOnlySmartRef, I think these are consistent and straightforward. I implemented many of your suggestions in the documentation, and added a migration guide. Should I add the missing "get_ref API" such as iter_ref etc. here or in another PR? or not at all?

Adamkob12 avatar Jan 27 '24 21:01 Adamkob12

I'm unsure of the utility of this PR. I feel like if you have a Query<&T> it's easy enough to change that to a Query<Ref<T>>. So the only time this comes in handy is when you have a Query<&mut T>. I feel like it would be easier to change the ReadOnly associated type to a Ref<T> for impl WorldData for &mut T. It would be a breaking change, but I think it makes sense for Mut<T> to go to a Ref<T> when it's read only instead of a &T. This would make query.get return a Ref<T> for Query<&mut T>.

hymm avatar Feb 11 '24 06:02 hymm

I'm also going to voice my opposition to this, quite frankly, significant complexity of the implementation and how much utility it provides. Regarding access to Ref<T> from Query<&mut T>, another option would be to allow Mut<T> to downgrade into a Ref<T> for a similar effect.

james7132 avatar Feb 11 '24 22:02 james7132

I agree, I've felt this is too much for too little.

Adamkob12 avatar Feb 11 '24 23:02 Adamkob12