Top Related Projects
Quick Overview
Specs is a parallel Entity Component System (ECS) written in Rust. It's designed for game development and other performance-critical applications, offering a flexible and efficient way to manage game objects and their behaviors.
Pros
- High performance due to its parallel nature and cache-friendly data layout
- Flexible and extensible architecture allowing for easy addition of new components and systems
- Strong compile-time checks and safety guarantees thanks to Rust's type system
- Well-documented with comprehensive examples and tutorials
Cons
- Steep learning curve for developers new to ECS architecture
- Can be overkill for small projects or simple games
- Requires careful design to avoid common ECS pitfalls like over-componentization
- Limited ecosystem compared to more established game engines
Code Examples
Creating a basic world and entity:
use specs::prelude::*;
struct Position {
x: f32,
y: f32,
}
impl Component for Position {
type Storage = VecStorage<Self>;
}
let mut world = World::new();
world.register::<Position>();
let entity = world.create_entity()
.with(Position { x: 4.0, y: 7.0 })
.build();
Defining and running a system:
struct MovementSystem;
impl<'a> System<'a> for MovementSystem {
type SystemData = WriteStorage<'a, Position>;
fn run(&mut self, mut positions: Self::SystemData) {
for position in (&mut positions).join() {
position.x += 1.0;
position.y += 1.0;
}
}
}
let mut dispatcher = DispatcherBuilder::new()
.with(MovementSystem, "movement", &[])
.build();
dispatcher.dispatch(&world);
Using resources:
struct DeltaTime(f32);
let mut world = World::new();
world.insert(DeltaTime(0.05));
struct TimeScaledMovementSystem;
impl<'a> System<'a> for TimeScaledMovementSystem {
type SystemData = (WriteStorage<'a, Position>, Read<'a, DeltaTime>);
fn run(&mut self, (mut positions, delta): Self::SystemData) {
for position in (&mut positions).join() {
position.x += 10.0 * delta.0;
position.y += 10.0 * delta.0;
}
}
}
Getting Started
To use Specs in your Rust project, add it to your Cargo.toml
:
[dependencies]
specs = "0.18"
Then, in your Rust file:
use specs::prelude::*;
// Define your components, systems, and world setup here
fn main() {
let mut world = World::new();
// Register components, create entities, and set up systems
// Run your game loop
}
For more detailed instructions and examples, refer to the Specs Book.
Competitor Comparisons
A fast entity component system (ECS) for C & C++
Pros of flecs
- Written in C, offering better performance and lower-level control
- Supports both C and C++, providing flexibility for different projects
- More actively maintained with frequent updates and releases
Cons of flecs
- Steeper learning curve due to its lower-level nature
- Less integrated with a specific game engine, requiring more setup
Code Comparison
flecs:
ecs_world_t *world = ecs_init();
ECS_COMPONENT(world, Position);
ECS_COMPONENT(world, Velocity);
ecs_entity_t e = ecs_new(world, 0);
ecs_set(world, e, Position, {10, 20});
ecs_set(world, e, Velocity, {1, 1});
specs:
#[derive(Component)]
struct Position(f32, f32);
#[derive(Component)]
struct Velocity(f32, f32);
let mut world = World::new();
world.create_entity()
.with(Position(10.0, 20.0))
.with(Velocity(1.0, 1.0))
.build();
Both flecs and specs are Entity Component System (ECS) libraries, but they cater to different languages and use cases. flecs is more versatile and performant, while specs is more tightly integrated with the Rust ecosystem and the Amethyst game engine. The choice between them largely depends on the project's requirements and the developer's preferred language and ecosystem.
Gaming meets modern C++ - a fast and reliable entity component system (ECS) and much more
Pros of EnTT
- Header-only library, easier to integrate into projects
- More flexible and customizable ECS implementation
- Better performance in many scenarios, especially for large numbers of entities
Cons of EnTT
- Steeper learning curve due to more complex API
- Less documentation and community resources compared to Specs
- May require more manual memory management in some cases
Code Comparison
EnTT:
entt::registry registry;
auto entity = registry.create();
registry.emplace<Position>(entity, 0, 0);
registry.emplace<Velocity>(entity, 1, 1);
auto view = registry.view<Position, Velocity>();
for (auto [entity, pos, vel] : view.each()) {
pos.x += vel.x;
pos.y += vel.y;
}
Specs:
#[derive(Component)]
struct Position(f32, f32);
#[derive(Component)]
struct Velocity(f32, f32);
let mut world = World::new();
let mut dispatcher = DispatcherBuilder::new()
.with(MovementSystem, "movement", &[])
.build();
dispatcher.dispatch(&mut world);
struct MovementSystem;
impl<'a> System<'a> for MovementSystem {
type SystemData = (WriteStorage<'a, Position>, ReadStorage<'a, Velocity>);
fn run(&mut self, (mut pos, vel): Self::SystemData) {
for (pos, vel) in (&mut pos, &vel).join() {
pos.0 += vel.0;
pos.1 += vel.1;
}
}
}
A refreshingly simple data-driven game engine built in Rust
Pros of Bevy
- More active development and larger community support
- Integrated engine with built-in rendering, audio, and asset management
- Easier to get started with for beginners due to its all-in-one approach
Cons of Bevy
- Potentially higher resource usage due to its comprehensive nature
- Less flexibility for advanced users who want more control over individual components
- Steeper learning curve for those familiar with traditional ECS systems
Code Comparison
Bevy:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_system(hello_world)
.run();
}
Specs:
use specs::prelude::*;
fn main() {
let mut world = World::new();
let mut dispatcher = DispatcherBuilder::new()
.with(HelloWorld, "hello_world", &[])
.build();
dispatcher.dispatch(&mut world);
}
Both Bevy and Specs are Entity Component System (ECS) frameworks for Rust, but they differ in their approach and scope. Bevy is a more comprehensive game engine that includes ECS as part of its architecture, while Specs is a standalone ECS library. Bevy offers a more integrated experience with additional features like rendering and asset management, making it easier for beginners to get started. However, Specs provides more flexibility and control for advanced users who want to build custom game engines or integrate ECS into existing projects.
Rust bindings for Godot 4
Pros of gdext
- Integrates directly with the Godot game engine, providing access to its robust features and tools
- Offers a more user-friendly API for game development, especially for those familiar with Godot
- Provides better documentation and examples for game-specific use cases
Cons of gdext
- More tightly coupled to Godot, limiting flexibility for custom engine development
- Potentially slower performance due to the overhead of interfacing with Godot
- Smaller community and ecosystem compared to specs
Code Comparison
specs:
#[derive(Component)]
struct Position(f32, f32);
let mut world = World::new();
let entity = world.create_entity()
.with(Position(4.0, 7.0))
.build();
gdext:
#[derive(GodotClass)]
#[class(base=Node2D)]
struct Player {
#[export]
speed: f32,
}
#[godot_api]
impl Player {
fn new(base: &Node2D) -> Self {
Self { speed: 400.0 }
}
}
The specs code demonstrates its ECS-focused approach, while gdext shows integration with Godot's node system and export attributes.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Specs
Specs Parallel ECS
Specs is an Entity-Component System written in Rust. Unlike most other ECS libraries out there, it provides
- easy parallelism
- high flexibility
- contains 5 different storages for components, which can be extended by the user
- its types are mostly not coupled, so you can easily write some part yourself and still use Specs
System
s may read from and write to components and resources, can depend on each other and you can use barriers to force several stages in system execution
- high performance for real-world applications
Minimum Rust version: 1.70
Link to the book
Example
use specs::prelude::*;
// A component contains data
// which is associated with an entity.
#[derive(Debug)]
struct Vel(f32);
impl Component for Vel {
type Storage = VecStorage<Self>;
}
#[derive(Debug)]
struct Pos(f32);
impl Component for Pos {
type Storage = VecStorage<Self>;
}
struct SysA;
impl<'a> System<'a> for SysA {
// These are the resources required for execution.
// You can also define a struct and `#[derive(SystemData)]`,
// see the `full` example.
type SystemData = (WriteStorage<'a, Pos>, ReadStorage<'a, Vel>);
fn run(&mut self, (mut pos, vel): Self::SystemData) {
// The `.join()` combines multiple component storages,
// so we get access to all entities which have
// both a position and a velocity.
for (pos, vel) in (&mut pos, &vel).join() {
pos.0 += vel.0;
}
}
}
fn main() {
// The `World` is our
// container for components
// and other resources.
let mut world = World::new();
world.register::<Pos>();
world.register::<Vel>();
// An entity may or may not contain some component.
world.create_entity().with(Vel(2.0)).with(Pos(0.0)).build();
world.create_entity().with(Vel(4.0)).with(Pos(1.6)).build();
world.create_entity().with(Vel(1.5)).with(Pos(5.4)).build();
// This entity does not have `Vel`, so it won't be dispatched.
world.create_entity().with(Pos(2.0)).build();
// This builds a dispatcher.
// The third parameter of `with` specifies
// logical dependencies on other systems.
// Since we only have one, we don't depend on anything.
// See the `full` example for dependencies.
let mut dispatcher = DispatcherBuilder::new().with(SysA, "sys_a", &[]).build();
// This will call the `setup` function of every system.
// In this example this has no effect since we already registered our components.
dispatcher.setup(&mut world);
// This dispatches all the systems in parallel (but blocking).
dispatcher.dispatch(&mut world);
}
Please look into the examples directory for more.
Public dependencies
crate | version |
---|---|
hibitset | |
rayon | |
shred | |
shrev |
Contribution
Contribution is very welcome! If you didn't contribute before, just filter for issues with "easy" or "good first issue" label. Please note that your contributions are assumed to be dual-licensed under Apache-2.0/MIT.
Top Related Projects
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot