0

I'm trying to write an entity-component system in Rust (version rustc 1.19.0-nightly (557967766 2017-05-26)) without the use of downcasting, and preferably also without macros. The general architecture is a loose amalgamation of specs and calx-ecs.

I'm struggling with the definition of a working run method that executes system actions based on their selected components. The requirement is that systems may borrow components of each relevant entity mutably. However, I'm really fighting with the borrow checker, and I'm a bit lost.

The run method is implemented by the Scheduler struct, which owns a World struct and a vector of boxed system trait objects.

pub type ComponentMask = u8;
pub trait AssemblyTrait {
    fn new() -> Self;
    fn match_mask(&self, entity: &Entity, mask: ComponentMask) -> bool;
    fn remove(&mut self, entity: &Entity);
}
pub trait ViewTrait<'a> {
    type View;
    fn get_view(&'a mut self, entity: &Entity) -> Self::View;
}
pub struct World<'a, A: 'a + AssemblyTrait + ViewTrait<'a>> {
    next_idx: usize,
    next_uuid:  UUID,
    free_indices: Vec<usize>,
    entities: ComponentContainer<bool>,
    pub components: A,
    phantom: marker::PhantomData<&'a A>,
}
pub struct Scheduler<'a, A> where A: 'a + AssemblyTrait + ViewTrait<'a> {
    world: World<'a, A>,
    systems: Vec<Box<'static + SystemTrait<'a, A>>>,
}
impl<'a, A> Scheduler<'a, A> where A: 'a + AssemblyTrait + ViewTrait<'a> {
    // Some methods ommitted.
    pub fn run(&'a mut self) {
        for system in self.systems.iter() {
            let mask = system.get_mask();
            let mut components: Vec<A::View> = self.world.iter()
                .filter(|e| self.world.match_mask(e, mask))
                .map(|e| self.world.components.get_view(e))
                .collect();
            if components.len() > 0 {
                system.run(&components);
            }
        }
    }
}

The system trait is defined as follows:

pub trait SystemTrait<'a, A> where A: AssemblyTrait + ViewTrait<'a> {
    fn get_name(&self) -> &'static str;
    fn get_mask(&self) -> ComponentMask;
    fn run(&mut self, components: &mut [A::View]);
}

I've posted the full implementation as well.

The definition of the ViewTrait can be found in this minimal working example, which I managed to get working thanks to Lifetime parameters in associated type.

While the above example works properly, I continue to get lifetime errors for the Scheduler::run method:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
   --> src\lib.rs:261:48
    |
261 |                 .map(|e| self.world.components.get_view(e))
    |                                                ^^^^^^^^
    |
note: first, the lifetime cannot outlive the lifetime  as defined on the body at 261:22...
   --> src\lib.rs:261:22
    |
261 |                 .map(|e| self.world.components.get_view(e))
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that closure can access `self`
   --> src\lib.rs:261:26
    |
261 |                 .map(|e| self.world.components.get_view(e))
    |                          ^^^^^^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 244:1...
   --> src\lib.rs:244:1
    |
244 | / impl<'a, A> Scheduler<'a, A> where A: 'a + AssemblyTrait + ViewTrait<'a> {
245 | |     pub fn new(world: World<'a, A>) -> Scheduler<'a, A> {
246 | |         Scheduler {
247 | |             world: world,
...   |
267 | |     }
268 | | }
    | |_^
note: ...so that types are compatible (expected &'a mut A, found &mut A)
   --> src\lib.rs:261:48
    |
261 |                 .map(|e| self.world.components.get_view(e))
    |                                                ^^^^^^^^
E Y
  • 281
  • 2
  • 11
  • Can you also put the definition of the remaining relevant types in the question? In particular, `Scheduler`, `AssemblyTrait` and `ViewTrait` should be here. That issue looks very similar to https://stackoverflow.com/q/24574741/1233251 (although not with iterators but your own trait): the lifetime of `&mut self` is incompatible with lifetime `'a`. – E_net4 May 27 '17 at 11:11
  • @E_net4: I see the similarities to the linked question. Although I don't quite know how I would apply its solution to my problem. In the meantime, I've added the definitions you've asked for. – E Y May 27 '17 at 12:08
  • I notice you used `'a ViewTrait<'a>` which is probably overspecifying lifetimes, as in this question: https://stackoverflow.com/questions/44081272. – zstewart May 27 '17 at 12:24
  • I actually introduced the additional lifetime bound to the generic type `A` because the struct `World` contains a `std::marker::PhantomData` field which in turn consumes a falsely unused lifetime parameter. I'll update the question to include the definition of the World struct. – E Y May 27 '17 at 12:36
  • I have to run; but [I've started an answer which contains some MCVEs](https://gist.github.com/shepmaster/74523a8ccf7167822570df9e26764b99) if anyone wants to pick it up. – Shepmaster May 27 '17 at 15:38
  • 1
    @Shepmaster, thanks for the in-progress answer. Based on yours, I recreated an [MCVE](https://gist.github.com/youngec/3063d6688b7061d21f2a8107ca333a48) on my own, and realized that I have multiple simultaneous issues with my code: not only do I have a lifetime issue with `ViewTrait::View` (i.e. mutable references as return type) but I also mutably borrow `World` multiple times. I guess, what I'm asking is; how do I sort this all out? – E Y May 27 '17 at 16:50
  • Can you explain why you've chosen to define `'a` at the trait level? – Shepmaster May 28 '17 at 14:04
  • I followed the suggestions made by [Lifetime parameters in associated type](https://stackoverflow.com/questions/33734640/lifetime-parameters-in-associated-type#33756123). The option to include it at the trait level seemed to work more easily than implementing the trait for a reference type. I'm open to alternative suggestions! – E Y May 28 '17 at 16:32
  • Finally, I did manage to solve the multiple mutable borrow issue of `World` (and its members). But I still don't know how to solve the issue of the interaction between `SystemTrait` and `ViewTrait` with regards to lifetimes. For the purpose of better detailing the actual issue, should I modify my original question or ask a new one? – E Y May 31 '17 at 08:04

0 Answers0