Expand description
§stecs - static compiler-checked ECS*.
For an introduction into the idea, see this blogpost.
This library attempts to bridge the gap between
- compile-time guarantees, that are one of the important points of Rust;
- performance benefits of SoA (Struct of Array);
- and ease of use of ECS libraries
*Note: technically this library likely does not qualify as a proper ECS. What this library actually is, is a generalized SoA derive (For an example of a non-general one, see soa_derive or soa-rs).
This library contains and also generates snippets of unsafe code to support mutable querying. It could be avoided by using lending iterators, but they are much less convenient to use in for-loops. However, if possible, the goal is for the library to contain no unsafe code.
§Example
See the GitHub repository for more examples.
#[derive(SplitFields)]
struct Player {
position: f64,
health: Option<i64>,
}
struct World {
players: StructOf<Vec<Player>>,
}
let mut world = World { players: Default::default() };
world.players.insert(Player {
position: 1.0,
health: Some(5),
});
for (pos, health) in query!(world.players, (&position, &mut health.Get.Some)) {
println!("player at {}; health: {}", pos, health);
*health -= 1;
}
§Archetypes
Archetypes are entity types stored together for efficient access. Here, the archetypes are static and defined by the user as regular structs with a derive macro.
#[derive(SplitFields)]
struct Monster {
position: (f32, f32),
health: f32,
tick: usize,
damage: Option<f32>,
}
The main thing SplitFields
macro generates is an analogous struct where each field is inside an abstract Storage
(for example, Vec).
// Generated struct
struct MonsterStructOf<F: StorageFamily> {
position: F::Storage<(f32, f32)>,
health: F::Storage<f32>,
tick: F::Storage<usize>,
damage: F::Storage<Option<f32>>,
}
§Querying
Archetypes form the basis of the library and can be used by themselves. Though, accessing the required components manually may be inconvenient, so we offer more macros.
Both query!
and get!
have almost identical syntax, and work by providing an archetype (or multiple, for a query), and then providing the target component view.
The target view can be either a tuple or a struct (user-defined) with regular instantiation syntax, except for value expressions, which use optics.
// Get position of a specific entity
let pos = get!(world.units, id, (&position)).unwrap();
// Querying into a tuple
for (id, pos, vel) in query!(world.units, (id, &mut position, &velocity)) { }
// Equivalent query into a struct
for view in query!(world.units, TargetView { id, position: &mut position, velocity }) { }
§Optics
For an overview of optics in general, see this tutorial or this Rust library. This library provides only a very limited version of optics applicable to ECS component access.
An optic has 3 distinguishable parts: reference type, storage access, and component access (optional).
The storage and component parts are separated by a .Get
indicating access to a specific entity’s component inside a storage, but can be omitted when not using the component part.
Note: there’s a special optic id
(without &
) that returns the id of the entity being queried.
Take, for example, &mut body.health.Get.Some
.
-
&mut
. Each optic (except forid
) must start by describing the reference type: either&
or&mut
. -
body.health
. The storage optic provides the path to the component storage. It is usually a single identifier that is the name of the component. But it can also be multiple dot-separated identifiers when querying inside a nested storage. -
.Some
. The component optic describes manipulations on the component value and starts after the.Get
. Typically, the component optic is either omitted or used to filter out optional components:.Some
.
In general, there are 3 things you can do in an optic:
- access a field, like in normal Rust:
position.x
- get the component from the storage:
.Get
- filter out optional components:
.Some
Modules§
- archetype
- The traits for describing archetypes and split storages.
- prelude
- use
stecs::prelude::*;
to import all necessary traits, types, and macros. - storage
- The
Storage
trait and basic implementors.
Macros§
Derive Macros§
- Split
Fields - Derive macro for the static archetypes.