Crate stecs

Source
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.

  1. &mut. Each optic (except for id) must start by describing the reference type: either & or &mut.

  2. 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.

  3. .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§

get
Get components of a specific entity.
query
Query components from archetypes.

Derive Macros§

SplitFields
Derive macro for the static archetypes.