#Wrapper trait around SystemParam with mutable references (invariant)

6 messages · Page 1 of 1 (latest)

runic scarab
#

I want to have nodes which behave similar to systems. Each entity can have a graph instance containing the nodes.
It does make sense to run all equivalent nodes on different entities in one wrapper system.
A node looks like

fn node(slot_a: Slot<0, T>, world_query: World<Query<&U>>, mut world_query_mut: World<Query<&mut V>>) -> (Slot0Val, Slot1Val) {
  (Slot0Val, Slot1Val)
}

Here World is clashingly/badly named and means it wraps a SystemParam to act like a NodeParam.
The params of a node function impl the trait NodeParam.
A wrapper system for a node looks something like

fn into_system<F>(f: F) where F: NodeFunction {
.
.
.

let system = move |query: Query<QueryForAllEquivalentNodes>, param: <F::NodeParam as NodeParam>::SystemParam| {
  for node in &query {
    let param = F::NodeParam::get_param(node, param);
    let output = f.run(param);
    .
    .
    .
  }
}

}

where NodeParam is also implemented for tuples of NodeParam - not important but may be a detail you scratch your head around.
This all already works great with immutable SystemParams, but when mutability is desired all lifetime annotations just explode because of invariation.
I have reduced it to a simpler example.

#

Here is what I am currently trying:

use bevy::ecs::{
    entity::Entity,
    system::{Query, SystemParam, SystemParamItem},
};

struct World<'n, T>(&'n mut T)
where
    T: SystemParam;

trait NodeParam {
    type SystemParam: SystemParam;
    type Item<'n>
    where
        Self: 'n;

    fn get_param<'n>(param: &'n mut SystemParamItem<Self::SystemParam>) -> Self::Item<'n>
    where
        Self: 'n;
}

impl<T> NodeParam for World<'_, T>
where
    T: for<'w, 's> SystemParam<Item<'w, 's> = T>,
{
    type SystemParam = T;
    type Item<'n> = World<'n, T> where Self: 'n;

    fn get_param<'n>(param: &'n mut SystemParamItem<Self::SystemParam>) -> Self::Item<'n>
    where
        Self: 'n,
    {
        World(param)
    }
}

fn is_node_param<P: NodeParam>(param: P) {}

fn system(mut query: Query<Entity>) {
    is_node_param(World(&mut query));
}

fn main() {}
#

This errors with

error[E0521]: borrowed data escapes outside of function
  --> src/main.rs:39:5
   |
38 | fn system(mut query: Query<Entity>) {
   |           ---------
   |           |
   |           `query` is a reference that is only valid in the function body
   |           has type `Query<'1, '_, bevy::prelude::Entity>`
39 |     is_node_param(World(&mut query));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |     |
   |     `query` escapes the function body here
   |     argument requires that `'1` must outlive `'static`
   |
   = note: requirement occurs because of the type `World<'_, Query<'_, '_, bevy::prelude::Entity>>`, which makes the generic argument `Query<'_, '_, bevy::prelude::Entity>` invariant
   = note: the struct `World<'n, T>` is invariant over the parameter `T`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
note: due to current limitations in the borrow checker, this implies a `'static` lifetime
  --> src/main.rs:36:21
   |
36 | fn is_node_param<P: NodeParam>(param: P) {}
   |                     ^^^^^^^^^

#
error[E0521]: borrowed data escapes outside of function
  --> src/main.rs:39:5
   |
38 | fn system(mut query: Query<Entity>) {
   |           ---------
   |           |
   |           `query` is a reference that is only valid in the function body
   |           has type `Query<'_, '2, bevy::prelude::Entity>`
39 |     is_node_param(World(&mut query));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |     |
   |     `query` escapes the function body here
   |     argument requires that `'2` must outlive `'static`
   |
   = note: requirement occurs because of the type `World<'_, Query<'_, '_, bevy::prelude::Entity>>`, which makes the generic argument `Query<'_, '_, bevy::prelude::Entity>` invariant
   = note: the struct `World<'n, T>` is invariant over the parameter `T`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
note: due to current limitations in the borrow checker, this implies a `'static` lifetime
  --> src/main.rs:36:21
   |
36 | fn is_node_param<P: NodeParam>(param: P) {}
   |                     ^^^^^^^^^

error[E0308]: mismatched types
  --> src/main.rs:39:5
   |
39 |     is_node_param(World(&mut query));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected struct `Query<'w, 's, _, _>`
              found struct `Query<'_, '_, _, _>`
note: the lifetime requirement is introduced here
  --> src/main.rs:36:21
   |
36 | fn is_node_param<P: NodeParam>(param: P) {}
   |                     ^^^^^^^^^

Some errors have detailed explanations: E0308, E0521.
For more information about an error, try `rustc --explain E0308`.
warning: `bevy-node-test` (bin "bevy-node-test") generated 1 warning
#

To summarize: the goal is to grant every node the possibility to mutably reference system params.

#

I also tried something along these lines

impl<'w, 's, Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> NodeParam for &mut Query<'w, 's, Q, F> {
  type SystemParam = Query<'w, 's, Q, F>;
  type Item<'n, 'world, 'state> = &'n Query<'world, 'state, Q, F>;

  fn get_param<'n, 'world, 'state>(param: &'n mut SystemParamItem<Self::SystemParam>) -> Self::Item<'n, 'world, 'state> {
    param
  }
}

Where I impl NodeParam directly for SystemParams.
But this also didnt get me far, because of the pain to annotate everything everywhere
e.g. must uphold 'world: 'n, 'state: 'n
and then it is borrowed for multiple iterations and logically unwrapping so many lifetimes logically is just unmaintainable.