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))
| ^^^^^^^^