6 releases
0.2.2 | Sep 23, 2021 |
---|---|
0.2.1 | Sep 23, 2021 |
0.2.0 | Mar 20, 2021 |
0.1.1 | Mar 15, 2021 |
0.0.0 |
|
#913 in Development tools
33KB
696 lines
Genesis - an ECS generator library
Overview
genesis
provides an ECS-generating macro.
Unlike other ECS libraries and frameworks, which do dynamic borrow-checking at runtime,
you define all your components upfront and generate a completely statically typed ECS,
with borrow checking done at compile time.
Gone are the days of passing a World between functions, only to encounter a dynamic borrow checking problem!
genesis
is a lightweight ECS library that doesn't provide any scheduling capabilities.
Instead, you can query the storage for each component type directly.
use genesis::*;
#[derive(Clone, Debug, Eq, PartialEq)]
struct IndexComponent {
index: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct NameComponent {
name: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct RareComponent {
data: u32,
}
#[world(MyComponent, Template)]
#[derive(Clone, Debug, Eq, PartialEq)]
struct World {
#[component(vec)] //default, optional
#[template_name(index)]
indices: IndexComponent,
#[template_name(name)]
names: NameComponent,
#[component(map)]
rare_data: RareComponent,
}
fn main() -> Result<(), NoSuchEntity> {
let initial_capacity = 1024;
let mut world = World::new(initial_capacity);
// spawn an entity
let id_a = world.spawn();
// set the components directly on the corresponding storage
world.indices.set(id_a, IndexComponent { index: 42 })?;
// spawn another entity
let id_b = world.spawn();
// alternative way to set components: using the utility trait Register<T>.
world.register(id_b, IndexComponent { index: 0 })?;
world.register(
id_b,
NameComponent {
name: String::from("B"),
},
)?;
let id_c = world.spawn();
// third way of setting components: using the generated Template struct.
world.register(
id_c,
Template {
index: Some(IndexComponent { index: 100 }),
..Default::default()
},
)?;
if let Some(a_index) = world.indices.get(id_a) {
println!("first entity has index {:?}", a_index);
}
for id in world.entities.read().unwrap().iter() {
if let Some(index) = world.indices.get(id) {
println!("entity {:?} has index {:?}", id, index);
}
}
Ok(())
}
Goals
The main goal of genesis
is to provide a type-safe ECS with compile time borrow checking.
This can help avoid writing code where you pass an ECS world from one function to another
while looping over entities and holding onto mutable references in the calling function,
which results in a runtime borrow checking problems.
Other ECS
There are a lot of different ECS in Rust, all with their own pros and cons. Check out these in particular:
Benchmarks
Performance is not the primary goal of genesis
; the main benefit of an ECS is the data modelling enabled
by the paradigm.
If you have severe time constraints and need to iterate over hundreds of thousands of entities,
you should look into using an archetype ECS like hecs
for the better iteration speed gained through
better data locality and cache friendliness.
Note that archetype-based ECS generally trade off better iteration time for worse performance when it comes
to adding/removing components than ECS like specs
or genesis
, which have different storages.
For more information, check out benchmarks.
Licence
Licensed under
- MIT licence
- Apache Licence 2.0
Contributions
Contributions are welcome! Unless explicitly stated otherwise, your contribution is
assumed to be licensed under the same licences as genesis
(see above).
Dependencies
~2MB
~42K SLoC