2 releases
0.0.2 | May 21, 2021 |
---|---|
0.0.1 | Jan 5, 2020 |
#839 in Game dev
68KB
1.5K
SLoC
ecstatic - statically typed ECS
lib.rs
:
Library for implementing the entity-component-system (ECS) pattern.
The API is very loosely based on specs
, but with an
emphasis on statically validating the usage of the library (instead of dynamically, as specs
does). This comes at the cost of some flexibility, but almost all logic errors are detected at
compile time.
It's also not as optimized as specs
is (yet), since it's designed for roguelikes.
Usage
Implementing an ECS requires the following:
- Define the components and resources you need to store using the
define_world!
macro. This generates a struct calledWorld
, along with trait implementations necessary for the library to interact with it - Implement one or more
System
s - Run your
System
s on the World using the (run_system
)traits/trait.WorldInterface.html#method.run_system method.
Peculiarities
This library uses Rust's type system in a somewhat advanced manner. In the
traits
module you will find the Nest
and
Flatten
traits, which allow flat tuples (such as (A, B, C)
) to
be converted to a nested representation (A, (B, (C, ())))
and back again. These traits are
implemented for tuples up to length 32, which ought to be enough for most use cases.
Converting flat tuples to nested tuples at the API boundary allows us to implement certain
traits recursively, rather than needing to write macros for each trait to implement them for
flat tuple types. As a result, you will see type parameters that have Nest
/Flatten
trait
bounds all over the code base. Because there's no way to tell the compiler that Nest
and
Flatten
are inverse operations, occasionally you will see bounds that specify that the nested
represenation is also flattenable.
Additionally, we have some type-level metaprogramming traits that provide some amount of compile-time invariant checking.
In general, client code shouldn't need to worry about these too much, but it does have the unfortunate side effect of making compiler error messages less helpful.
Examples
#[derive(Debug, PartialEq)]
pub struct Data {
x: u32,
}
// `Default` impl that isn't the additive identity.
impl Default for Data {
fn default() -> Data {
Data { x: 128 }
}
}
#[derive(Debug, Default, PartialEq)]
pub struct MoreData {
y: u32,
}
define_world!(
#[derive(Default)]
pub world {
components {
test1: BasicVecStorage<Data>,
test2: BasicVecStorage<MoreData>,
}
resources {}
}
);
let mut w = World::default();
w.new_entity().with(Data { x: 1 }).build();
w.new_entity().with(Data { x: 1 }).build();
let md = w
.new_entity()
.with(Data { x: 2 })
.with(MoreData { y: 42 })
.build();
w.new_entity().with(Data { x: 3 }).build();
w.new_entity().with(Data { x: 5 }).build();
w.new_entity().with(Data { x: 8 }).build();
/// `TestSystem` adds up the values in every `Data` component (storing the result in `total`),
/// and multiplies every `MoreData` by the `Data` in the same component.
#[derive(Default)]
struct TestSystem {
total: u32,
}
impl<'a> System<'a> for TestSystem {
type Dependencies = (
ReadComponent<'a, Data>,
WriteComponent<'a, MoreData>,
);
fn run(&'a mut self, (data, mut more_data): Self::Dependencies) {
self.total = 0;
(&data,).for_each(|_, (d,)| {
self.total += d.x;
});
(&data, &mut more_data).for_each(|_, (d, md)| {
md.y *= d.x;
});
}
}
let mut system = TestSystem::default();
w.run_system(&mut system);
assert_eq!(system.total, 20);
assert_eq!(
<World as GetComponent<'_, MoreData>>::get(&w).get(md),
Some(&MoreData { y: 84 })
);
Components accessed via ReadComponent
cannot be iterated over mutably:
#[derive(Debug, PartialEq)]
pub struct Data {
x: u32,
}
define_world!(
pub world {
components {
test1: BasicVecStorage<Data>,
}
resources {}
}
);
#[derive(Default)]
struct TestSystem {}
impl<'a> System<'a> for TestSystem {
type Dependencies = (
ReadComponent<'a, Data>,
);
fn run(&'a mut self, (data,): Self::Dependencies) {
(&mut data,).for_each(|(d,)| {
// do something
});
}
}