sky_ecs 0.1.2

High-performance typed chunk-based ECS for Rust
Documentation

Sky ECS

Sky ECS is a typed, chunk-based Entity Component System for Rust. It is the ECS used by SkyEngine, but the crate can be used independently.

Features

  • Chunk-columnar archetype storage
  • Generational entity identifiers
  • Bundle-based spawning and cached structural transitions
  • World-bound typed queries and reusable PreparedQuery
  • Optional query parameters and With, Without, and Any filters
  • Entity and chunk iteration, with sequential and parallel variants
  • Named query items through #[derive(QueryData)]
  • Typed resources, systems, stages, fixed steps, and deferred commands
  • Runtime-typed access under sky_ecs::dynamic
  • Low-level storage APIs under sky_ecs::expert

Usage

[dependencies]
sky_ecs = "0.1.2"
use sky_ecs::World;

#[derive(Clone, Copy)]
struct Position { x: f32, y: f32 }

#[derive(Clone, Copy)]
struct Velocity { x: f32, y: f32 }

fn main() {
    let mut world = World::new();

    world.spawn_batch((0..10_000).map(|i| (
        Position { x: i as f32, y: 0.0 },
        Velocity { x: 80.0, y: 30.0 },
    )));

    world
        .query_mut::<(&mut Position, &Velocity)>()
        .for_each(|(position, velocity)| {
            position.x += velocity.x / 60.0;
            position.y += velocity.y / 60.0;
        });
}

For repeated random access to one component type, bind a component accessor to the world before entering the hot loop:

let positions = world.accessor::<Position>();

for entity in entities {
    if let Some(position) = positions.get(entity) {
        println!("({}, {})", position.x, position.y);
    }
}

The accessor resolves matching component columns once and keeps a read-only borrow of the world while it is alive. Use World::get for occasional lookups, or when the world must be structurally changed between accesses.

Repeated random updates use an exclusive accessor:

let mut positions = world.accessor_mut::<Position>();

for entity in entities {
    if let Some(position) = positions.get_mut(entity) {
        position.x += 1.0;
    }
}

Queries

Queries are created from a World. Matching archetype plans are cached by the world and refreshed after structural changes.

use sky_ecs::{With, Without};

struct Player;
struct Disabled;

let query = world
    .query::<(&Position, Option<&Velocity>)>()
    .filter::<(With<Player>, Without<Disabled>)>();

Use query_mut when a query contains mutable component references. Use PreparedQuery when the query plan needs to be stored explicitly or reused across worlds.

Named query items are available for wider queries:

use sky_ecs::QueryData;

#[derive(QueryData)]
struct Movement<'w> {
    position: &'w mut Position,
    velocity: &'w Velocity,
}

world.query_mut::<Movement>().for_each(|body| {
    body.position.x += body.velocity.x;
    body.position.y += body.velocity.y;
});

Parallel iteration uses the same query types:

world
    .query_mut::<(&mut Position, &Velocity)>()
    .par_for_each(|(position, velocity)| {
        position.x += velocity.x;
        position.y += velocity.y;
    });

Small workloads fall back to sequential iteration. par_for_each_chunk is available when the inner loop benefits from component slices.

Systems

System access is inferred from typed parameters. Compatible systems may run in parallel, while conflicting systems keep registration order.

use sky_ecs::{Res, Time, Update, View, World};

fn movement(bodies: View<(&mut Position, &Velocity)>, time: Res<Time>) {
    bodies.for_each(|(position, velocity)| {
        position.x += velocity.x * time.delta;
        position.y += velocity.y * time.delta;
    });
}

let mut world = World::new();
world.stage(Update).add(movement);
world.tick_with_delta(1.0 / 60.0).unwrap();

Structural changes made by systems can be deferred through Commands. Outside the scheduler, use an owned CommandBuffer.

Benchmarks

The repository includes a Criterion comparison against hecs, bevy_ecs, flecs_ecs, freecs, and shipyard. Compare-ECS v2 limits conclusions to its single-threaded safe-public-API workloads, uses each library's recommended reusable query or view state, and validates all adapters before measurement.

In the complete six-rotation snapshot recorded on 2026-07-14, Sky has the lowest cross-run median for bulk and single insertion, steady 10k iteration, spawn/despawn, and the mixed-frame scenario. Shipyard leads prepared random access and add/remove transitions; Flecs and Sky are effectively tied for steady iteration at 100k entities. See the benchmark guide for the full table and measurement boundaries.

cargo compare-ecs
cargo compare-ecs-publish

The methodology and recorded results are kept in the benchmark guide. Internal Sky ECS benchmarks are kept separate and run with cargo bench.

API overview

Need API
Read-only query World::query
Mutable query World::query_mut
Repeated random component access World::accessor, World::accessor_mut
Explicit reusable query plan PreparedQuery
Parallel entity iteration par_for_each
Parallel chunk iteration par_for_each_chunk
Archetype filters With<T>, Without<T>, Any<(...)>
Deferred structural changes Commands, CommandBuffer
Runtime-known component types sky_ecs::dynamic
Low-level storage access sky_ecs::expert

Sky ECS requires Rust 1.85 or newer. The crate is currently versioned as 0.x.

License

MIT. See LICENSE.