feather icon indicating copy to clipboard operation
feather copied to clipboard

HList and Plucker for components

Open Defman opened this issue 3 years ago • 5 comments

pub trait SendMessage<'a, Index> {
    fn send_message(&'a self, message: &str);
}

impl<'a, T, Index> SendMessage<'a, Index> for (&'a Entity, T)
where
    T: TupleRef<'a>,
    <T as TupleRef<'a>>::HList: PluckerRef<Player, Index>,
{
    fn send_message(&'a self, _message: &str) {
        let _player: &Player = PluckerRef::<Player, Index>::pluck(&foo);
    }
}

for (entity, (player,)) in game.query::<(&Player,)>() {
      (&entity, (&player,)).send_message("Hello world!");
      (&entity, (&player,)).send_message("Hello world!");
}

It's still missing support for mutable references and in addition it might also be possible to change the signature a such that &(Entity, (Player,)) also works.

Defman avatar Mar 07 '21 11:03 Defman

My implementation can most likely be made more generic. We can maybe get away with not having TupleRef and only Tuple.

Defman avatar Mar 07 '21 20:03 Defman

I'm still concerned about the complexity this introduces for Quill users and developers. It's not intuitive to call methods on tuples, especially when the tuples require trailing commas, and forgetting to insert that comma gives an unhelpful compile error:

error[E0599]: no method named `send_message` found for tuple `(&Entity, &quill::entities::Player)` in the current scope
  --> quill/example-plugins/query-entities/src/lib.rs:56:28
   |
56 |         (&entity, &player).send_message("foo");
   |                            ^^^^^^^^^^^^ method not found in `(&Entity, &quill::entities::Player)`
   |
   = note: the method `send_message` exists but the following trait bounds were not satisfied:
           `&quill::entities::Player: Tuple`
           which is required by `(&Entity, &quill::entities::Player): SendMessage<_>`

Also, Feather developers wanting to add new functionality need to figure out the HList system, which is difficult to understand unless you come from a Haskell background.

A solution similar to the current state of this PR can work, but in my opinion, we need better ergonomics and less type-system magic.

caelunshun avatar Mar 08 '21 00:03 caelunshun

My intentions are not for the user to write (&entity, (&component,)) instead

let query = world.query::<(Player, Position)>();
for player in query.iter() {
  player.send_message("");
}
// Or when it becomes stable
for player @ (entity, (_, Position)) in query.iter() {
  player.send_message("");
}

Also this is more of an experiment to see what can be done, this is far from final and I'm open for suggestions.

Defman avatar Mar 08 '21 09:03 Defman

An alternative approach would be make query! macro which generates a QueryResult struct and implement AsRef and AsMut depending on access level.

#[macro_export]
macro_rules! query {
    {$(
        $ident:ident: $type:ty
    ),* $(,)?} => {{
        struct QueryResult {
            $($ident: $type),*
        }

        $(
            impl ::std::convert::AsRef<$type> for QueryResult {
                fn as_ref(&self) -> &$type {
                    &self.$ident
                }
            }
        )*

        $(
            impl ::std::convert::AsMut<$type> for QueryResult {
                fn as_mut(&mut self) -> &mut $type {
                    &mut self.$ident
                }
            }
        )*

        struct Query;

        impl Query {
            #[allow(dead_code)]
            fn iter(&self, game: &mut ::quill::Game) -> impl ::std::iter::Iterator<Item = QueryResult> {
                game.query::<($(&$type,)*)>().map(|(_, ($($ident),*))| QueryResult {
                    $($ident),*
                })
            }
        }

        Query
    }};
}

I kind of like this approach and using it would look something like

pub trait SendMessage {
    fn send_message(&self, message: &str);
}

impl<T> SendMessage for T
where
    T: AsRef<Player>
{
    fn send_message(&self, _message: &str) {
        ...
    }
}

let query = query! {
    player: Player,
    position: Position,
};

for entity in query.iter(game) {
    entity.send_message("test");
    entity.position.x += 10;
}

I can expand this to deal with lifetimes, when support is added. Also mutable references are going to be **** since extracting each field would require a mutable lock reference to the entire T.

Defman avatar Mar 08 '21 19:03 Defman

My intentions are not for the user to write (&entity, (&component,)) instead

let query = world.query::<(Player, Position)>();
for player in query.iter() {
  player.send_message("");
}
// Or when it becomes stable
for player @ (entity, (_, Position)) in query.iter() {
  player.send_message("");
}

Also this is more of an experiment to see what can be done, this is far from final and I'm open for suggestions.

The tracking issue for the relevant bindings_after_at feature for reference: https://github.com/rust-lang/rust/issues/65490 It seems to be close to stabilisation.

Schuwi avatar Mar 12 '21 13:03 Schuwi