https://github.com/sunsided/sillyecs
A silly little compile-time generated archetype ECS in Rust
https://github.com/sunsided/sillyecs
archetype archetype-ecs ecs game-development game-engine rust
Last synced: about 1 year ago
JSON representation
A silly little compile-time generated archetype ECS in Rust
- Host: GitHub
- URL: https://github.com/sunsided/sillyecs
- Owner: sunsided
- License: eupl-1.2
- Created: 2025-04-01T22:49:12.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-04-01T23:26:11.000Z (about 1 year ago)
- Last Synced: 2025-04-01T23:32:54.183Z (about 1 year ago)
- Topics: archetype, archetype-ecs, ecs, game-development, game-engine, rust
- Language: Rust
- Homepage: https://crates.io/crates/sillyecs
- Size: 0 Bytes
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# sillyecs
A silly little compile-time generated archetype ECS in Rust.
[](https://crates.io/crates/sillyecs)
[](LICENSE)
## Installation
`sillyecs` is a build-time dependency. To use it, add this to your `Cargo.toml`:
```toml
[build-dependencies]
sillyecs = "0.0.2"
```
## Usage
Use `sillyecs` in your `build.rs`:
```rust
use sillyecs::EcsCode;
use std::fs::File;
use std::io::BufReader;
fn main() -> eyre::Result<()> {
println!("cargo:rerun-if-changed=ecs.yaml");
let file = File::open("ecs.yaml").expect("Failed to open ecs.yaml");
let reader = BufReader::new(file);
EcsCode::generate(reader)?.write_files()?;
Ok(())
}
```
Define your ECS components and systems in a YAML file:
```yaml
# ecs.yaml
states:
- name: WgpuRender
description: The WGPU render state; will be initialized in the Render phase hooks
components:
- name: Position
- name: Velocity
- name: Health
- name: Collider
archetypes:
- name: Particle
description: A particle system particle.
components:
- Position
- Velocity
- name: Player
components:
- Position
- Velocity
- Health
- name: ForegroundObject
components:
- Position
- Collider
promotions:
- BackgroundObject
- name: BackgroundObject
components:
- Position
promotions:
- ForegroundObject
phases:
- name: Startup
- name: FixedUpdate
fixed: 60 Hz # or "0.01666 s"
- name: Update
- name: Render
states:
- use: WgpuRender # Use state in phase begin/end hooks
write: true
systems:
- name: Physics
phase: FixedUpdate
context: true
run_after: [] # optional
inputs:
- Velocity
outputs:
- Position
- name: Render
phase: Render
manual: true # Require manual call to world.apply_system_phase_render()
# on_request: true # call world.request_render_phase() to allow execution (resets atomically)
states:
- use: WgpuRender
write: false # optional
inputs:
- Position
worlds:
- name: Main
archetypes:
- Particle
- Player
- ForegroundObject
- BackgroundObject
```
Include the compile-time generated files:
```rust
include!(concat!(env!("OUT_DIR"), "/components_gen.rs"));
include!(concat!(env!("OUT_DIR"), "/archetypes_gen.rs"));
include!(concat!(env!("OUT_DIR"), "/systems_gen.rs"));
include!(concat!(env!("OUT_DIR"), "/world_gen.rs"));
```
The compiler will tell you which traits and functions to implement.
### Command Queue
You will have to implement a command queue. Below is an example for a queue based on
[`crossbeam-channel`](https://docs.rs/crossbeam/latest/crossbeam/channel):
```rust
struct CommandQueue {
sender: crossbeam_channel::Sender,
receiver: crossbeam_channel::Receiver,
}
impl CommandQueue {
pub fn new() -> Self {
let (sender, receiver) = crossbeam_channel::unbounded();
Self {
sender,
receiver,
}
}
}
impl WorldCommandReceiver for CommandQueue {
type Error = TryRecvError;
fn recv(&self) -> Result, Self::Error> {
match self.receiver.try_recv() {
Ok(cmd) => Ok(Some(cmd)),
Err(TryRecvError::Empty) => Ok(None),
Err(err) => Err(err),
}
}
}
impl WorldCommandSender for CommandQueue {
type Error = crossbeam_channel::SendError;
fn send(&self, command: WorldCommand) -> Result<(), Self::Error> {
self.sender.send(command)
}
}
```