Cleanup observer system input: Trigger reborrowing - #25668
Conversation
4664ee0 to
16018f0
Compare
| pub unsafe trait Trigger<E: Event> { | ||
| /// For any `'long: 'short`, `State<'long>` must have the same layout as | ||
| /// `State<'short>` and must be valid to reinterpret as `State<'short>` for | ||
| /// the duration of the shorter borrow. |
There was a problem hiding this comment.
I can't review the PR right now, but I want to note that reinterpreting State<'long> as State<'short> also means that it cannot expose a way to get &mut Something<'short> (which includes public fields of type Something<'short>
I cannot check what this PR does, but that was the original issue and reason why On/Trigger had this lifetime hack.
(Also sorry for commenting only on this line, Github on mobile is terrible)
There was a problem hiding this comment.
I can't review the PR right now, but I want to note that reinterpreting
State<'long>asState<'short>also means that it cannot expose a way to get&mut Something<'short>(which includes public fields of typeSomething<'short>I cannot check what this PR does, but that was the original issue and reason why
On/Triggerhad this lifetime hack.
I think the fn reborrow() method having the extra lifetime parameter in Self::State<'_> is what addresses this issue. Reviewing your comments at #20731 (comment), you said:
Here however we're casting it to a
&mut E::Trigger<'w>for some arbitrary lifetime'w. This is fine only as long as nothing about the lifetime'wthat 's different from'ais ever exposed to safe code, effectively forcing the safe code to be universally quantifed over a set of lifetimes that must include the original'a.
I think that extra lifetime parameter gives us that universal quantification. The clever part is that the parameter is on a trait method on Trigger now, so it doesn't leak into On!
There was a problem hiding this comment.
I've been looking at the PR a bit more and I think it should be good (don't quote me on that yet!) but I think the motivation is not quite right.
I think what makes it safe is that On has only one lifetime thanks to reborrowing, and the trigger ref also has only one lifetime thanks to reborrowing.
I don't see a reason for requiring the State to be transmutable though. The reborrowing is done by reborrow, there seems to be no transmute going on.
| // - The implementation abides by the other safety constraints defined in [`Trigger`] | ||
| unsafe impl<E: AnimationEvent + for<'a> Event<Trigger<'a> = AnimationEventTrigger>> Trigger<E> | ||
| // SAFETY: `AnimationEventTrigger` has no lifetimes. | ||
| unsafe impl<E: AnimationEvent + Event<Trigger = AnimationEventTrigger>> Trigger<E> |
There was a problem hiding this comment.
While this does simplify the lifetimes in the top level API, it also complicates the "type story" for triggers generally. I don't fully understand the "trigger target queries" stuff. Do you have a proposal link and/or a practical example of the API you're aiming for?
| /// For any `'long: 'short`, `State<'long>` must have the same layout as | ||
| /// `State<'short>` and must be valid to reinterpret as `State<'short>` for | ||
| /// the duration of the shorter borrow. |
There was a problem hiding this comment.
I think you get this for free from parametricity, and don't need this to be a safety requirement. So Trigger can be a safe trait!
The only place the lifetime in the transmuted State<'_> is used is to call fn reborrow(). That's generic in the lifetime, and lifetime parameters are erased at runtime, so there is no way for it it depend on the particular lifetime. That is, you can declare that you are transmuting to the original 'long lifetime, since fn reborrow() has no way to tell it apart from any other lifetime.
The trouble with the old code was that On<...> as SystemInput had type Param<'i> = On<'i, 'i, E>, which wound up forcing that lifetime ('t) to equal a bunch of others ('w). Those other lifetimes should have been shorter, so code that relied on them being equal could be unsound. But this PR only uses the lifetime once, as a parameter to reborrow, so external code can't make any incorrect assumptions about it.
| T: Traversal<E>, | ||
| { | ||
| type State<'input> = PropagateEntityTrigger<AUTO_PROPAGATE, E, T>; | ||
| type View<'input> = PropagateEntityTriggerItem<'input, AUTO_PROPAGATE, E, T>; |
There was a problem hiding this comment.
This could alternately have been &'input mut PropagateEntityTrigger<AUTO_PROPAGATE, E, T> instead of a new type, right? That would have been slightly less breaking, since wouldn't need for the extra * when using trigger.propagate... although breaking the ability to modify original_event_target is probably for the best :).
| /// | ||
| /// This trait can be derived. | ||
| pub trait AnimationEvent: Clone + for<'a> Event<Trigger<'a> = AnimationEventTrigger> {} | ||
| pub trait AnimationEvent: Clone + Event<Trigger = AnimationEventTrigger> {} |
There was a problem hiding this comment.
I like all the places where we got to get rid of for<'a>!
| pub fn trigger<'a, E: Event<Trigger<'a>: Default>>(&mut self, mut event: E) { | ||
| pub fn trigger<E: Event>(&mut self, mut event: E) | ||
| where | ||
| for<'a> EventTriggerState<'a, E>: Default, |
There was a problem hiding this comment.
I don't love the places where we had to add a for<'a>, though.
... or, wait, could this be
| for<'a> EventTriggerState<'a, E>: Default, | |
| EventTriggerState<'static, E>: Default, |
here and elsewhere? The weaker constraint is still enough to call default(), and it avoids the for<'a>.
(I don't think it's even any less general than the old API. Event: 'static, so there aren't any lifetimes in E, which means any lifetime in an impl is either 'static or a free lifetime that can be substituted with it.)
There was a problem hiding this comment.
Yep that worked across the board pretty much!
| pub unsafe trait Trigger<E: Event> { | ||
| /// For any `'long: 'short`, `State<'long>` must have the same layout as | ||
| /// `State<'short>` and must be valid to reinterpret as `State<'short>` for | ||
| /// the duration of the shorter borrow. |
There was a problem hiding this comment.
I can't review the PR right now, but I want to note that reinterpreting
State<'long>asState<'short>also means that it cannot expose a way to get&mut Something<'short>(which includes public fields of typeSomething<'short>I cannot check what this PR does, but that was the original issue and reason why
On/Triggerhad this lifetime hack.
I think the fn reborrow() method having the extra lifetime parameter in Self::State<'_> is what addresses this issue. Reviewing your comments at #20731 (comment), you said:
Here however we're casting it to a
&mut E::Trigger<'w>for some arbitrary lifetime'w. This is fine only as long as nothing about the lifetime'wthat 's different from'ais ever exposed to safe code, effectively forcing the safe code to be universally quantifed over a set of lifetimes that must include the original'a.
I think that extra lifetime parameter gives us that universal quantification. The clever part is that the parameter is on a trait method on Trigger now, so it doesn't leak into On!
|
Updated based on feedback; I think it's much clearer now. |
Objective
Simplify the safety story of
OnandTrigger, and reduce the number of lifetimes onOnfrom two to one.This is a cleanup pass in preparation to support observer target queries,
On<'world, 'state, 'input, E: EventPattern, D: QueryData = (), F: QueryFilter = ()>.Solution
Add and require support for
Triggerreborrowing:Event::TriggerE: Event<Trigger = Self>requirement directly toTrigger's trait, removing theunsaferequirement for it.Trigger::State<'a>, which is the type that's passed toWorld::triggerandCommands::triggerTrigger::View<'a>, which is the type passed into observer systems as a mutable view of theTrigger::StateStateforPropagateEntityTrigger, in order to provide access to apropagate: &mut boolTrigger::reborrowto convert from a&'a mut Trigger::State<'_>to aTrigger::View<'a>(owned)EventTriggerStateandEventTriggerViewtype aliases for ease of access (replacingEventPatternTrigger)Testing
No new tests were added, suggestions welcome.