Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge dev #6

Merged
merged 52 commits into from
Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
20711bb
wip: fetch transforms
ten3roberts Jun 26, 2023
e5e9f7e
feat: implement modified transform for tuples
ten3roberts Jun 27, 2023
1a66530
chore: split filters into more modules
ten3roberts Jun 27, 2023
6e42b93
chore: move union to filter modules
ten3roberts Jun 27, 2023
03573b6
fix!: reduce `And` nesting in query filter parameter
ten3roberts Jun 27, 2023
a566303
fix: typos in README.md
ten3roberts Jun 27, 2023
ce9b9d3
feat: use trait to support Union filter for foreign types
ten3roberts Jun 29, 2023
41a5347
feat: make derive support generics
ten3roberts Jul 1, 2023
a01b58e
fix: warnings
ten3roberts Jul 1, 2023
82fd6a8
feat: derive modified transform
ten3roberts Jul 1, 2023
1daf43d
fix: warnings
ten3roberts Jul 1, 2023
1ab5d84
feat: generic fetch transforms
ten3roberts Jul 2, 2023
53ea861
chore: attempt to use GAT
ten3roberts Jul 2, 2023
df198d2
Revert "chore: attempt to use GAT"
ten3roberts Jul 2, 2023
582f9f6
feat: fetch map
ten3roberts Jul 2, 2023
64f79ab
chore: use fully qualified syntax for derive
ten3roberts Jul 2, 2023
8e1031e
fix: no std tests
ten3roberts Jul 2, 2023
a7268b1
chore: implement transform for Opt, Cloned, Copied
ten3roberts Jul 4, 2023
6b549e5
chore: remove adjacent atomic ref cell borrowing
ten3roberts Jul 8, 2023
57b5912
chore: use entity slice directly
ten3roberts Jul 8, 2023
71b06fe
wip: generalize set
ten3roberts Jul 9, 2023
b4c3505
chore: cleanup
ten3roberts Jul 10, 2023
07c3507
chore: make set use writer abstraction
ten3roberts Jul 10, 2023
bc20d1e
feat: buffer component writer
ten3roberts Jul 10, 2023
a3a0579
feat: CellMutGuard and CellGuard mapping
ten3roberts Jul 12, 2023
44685fb
chore: make set_with use new ComponentWriter
ten3roberts Jul 13, 2023
60bcd15
test: entity builder relations
ten3roberts Jul 16, 2023
ce7cdec
test: replace existing relation on entity using builder
ten3roberts Jul 16, 2023
df2eb2d
wip: invalid archetype
ten3roberts Jul 19, 2023
8c4cf0e
fix: invalid archetype
ten3roberts Jul 21, 2023
0c96098
refactor: simplify writer traits
ten3roberts Jul 22, 2023
e4c93fa
refactor: remove set_inner
ten3roberts Jul 23, 2023
56d4efa
feat: EntityRefMut::set_dedup
ten3roberts Jul 23, 2023
d42bfc8
feat: update_dedup
ten3roberts Jul 23, 2023
084c818
chore: cleanup
ten3roberts Jul 23, 2023
b81cb96
feat: inserted transform
ten3roberts Jul 30, 2023
73302d6
fix: rename inserted to added
ten3roberts Jul 30, 2023
d77418e
feat: make entity errors more specific
ten3roberts Jul 30, 2023
251a28b
chore: sync readme
ten3roberts Jul 30, 2023
77c09e8
chore: ComponentInfo => ComponentDesc
ten3roberts Jul 30, 2023
677ffdc
fix: use of std
ten3roberts Jul 30, 2023
e032971
wip: fetch rework
ten3roberts Aug 2, 2023
2434b32
wip: batch fetching
ten3roberts Aug 5, 2023
07b7e9a
wip: batch fetching
ten3roberts Aug 5, 2023
26862ab
wip: batch fetching
ten3roberts Aug 5, 2023
8b7b5e6
chore: cleanup
ten3roberts Aug 6, 2023
4521906
fix: source
ten3roberts Aug 6, 2023
411d3fd
fix: remaining queries
ten3roberts Aug 6, 2023
be90484
chore: batch => chunk
ten3roberts Aug 6, 2023
babbf8e
chore: improve miri speed
ten3roberts Aug 6, 2023
cced257
fix: no-std
ten3roberts Aug 6, 2023
2c9223e
chore: use advancing ptr
ten3roberts Aug 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .nvim-settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"lsp": {
"rust-analyzer": {
"cargo": {
"features": "all"
}
}
}
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"rust-analyzer.cargo.features": "all"
}
23 changes: 11 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

<!-- cargo-sync-readme start -->

Flax is a performant and easy to use Entity Component System.
Flax is a performant and ergonomic Entity Component System.

The world is organized by simple identifiers known as an [Entity](https://docs.rs/flax/latest/flax/entity/struct.Entity.html), which can have any number of components attached to them.
The world is organized by simple identifiers known as an [`Entity`](https://docs.rs/flax/latest/flax/entity/struct.Entity.html), which can have any number of components attached to them.

Systems operate on the world's entities and provide the application logic.

Expand Down Expand Up @@ -64,7 +64,7 @@ See a live demo of asteroids using wasm [here](https://ten3roberts.github.io/fla

let mut query = Query::new((health().as_mut(), regen()));

// Apply health regen for all match entites
// Apply health regeneration for all matched entites
for (health, regen) in &mut query.borrow(&world) {
*health = (*health + regen).min(100.0);
}
Expand Down Expand Up @@ -134,16 +134,16 @@ let child1 = Entity::builder()

## Comparison to other ECS

Compared to other ecs implementations, a component is simply another `Entity`
Compared to other ECS implementations, a component is simply another `Entity`
identifier to which data is attached. This means the same "type" can be added to
an entity multiple times.

A limitation of existing implementations such as [specs](https://github.com/amethyst/specs), [planck](https://github.com/jojolepro/planck_ecs/), or [hecs](https://github.com/Ralith/hecs) is that newtype wrappers need to be created to allow components of the same inner type to coexist.

This leads to having to forward all trait implementations trough e.g
`derive-more` or dereferencing the newtypes during usage.
`derive-more` or dereferencing the *newtypes* during usage.

By making components separate from the type the components can work together without deref or
By making components separate from the type the components can work together without `deref` or
newtype construction.

```rust
Expand All @@ -159,13 +159,12 @@ let dt = 0.1;
*pos += *vel * dt;
```

On a further note, since the components have to be declared beforehand (not
always true, more on that later), it limits the amount of types which can be
On a further note, since the components have to be declared beforehand, it limits the amount of types which can be
inserted as components. This fixes subtle bugs which come by having the type
dictate the component, such as inserting an `Arc<Type>` instead of just `Type`,
dictate the component, such as using an `Arc<Type>` instead of just `Type`,
which leads to subsequent systems not finding the `Type` on the entity.

Having statically declared componenents makes the rust type system disallow
Using statically declared components makes the rust type system disallow
these cases and catches these bugs earlier.

## Motivation
Expand All @@ -175,12 +174,12 @@ library, and the author [Ralith](https://github.com/Ralith) has been wonderful i
contributions and inquiries.

Despite this, I often made subtle bugs with *similar* types. The game engine was
cluttered with gigantic newtypes for `Velocity`, `Position` with many deref
cluttered with gigantic newtypes for `Velocity`, `Position` with many *deref*
coercions in order to coexist.

## Unsafe
This library makes use of unsafe for type erasure and the allocation in storage
of ComponentBuffers and Archetypes.
of `ComponentBuffer`s and `Archetype`s.

<!-- cargo-sync-readme end -->

Expand Down
117 changes: 70 additions & 47 deletions asteroids/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use anyhow::{Context, Result};
use itertools::Itertools;
use std::f32::consts::TAU;
use tracing_subscriber::{prelude::*, registry};

use flax::{
component, entity_ids,
events::{EventKind, EventSubscriber},
filter::{All, And, With},
*,
BoxedSystem, CommandBuffer, Component, Debuggable, Entity, EntityBorrow, EntityBuilder,
EntityIds, Fetch, FetchExt, Mutable, Opt, OptOr, Query, QueryBorrow, Schedule, SharedResource,
System, World,
};
use itertools::Itertools;
use macroquad::{
color::hsl_to_rgb,
math::*,
Expand All @@ -18,8 +17,11 @@ use macroquad::{
window::{clear_background, next_frame, screen_height, screen_width},
};
use rand::{rngs::StdRng, Rng, SeedableRng};
use std::f32::consts::TAU;
use tracing_subscriber::{prelude::*, registry};
use tracing_tree::HierarchicalLayer;

// Declare the components that will be used.
component! {
position: Vec2 => [ Debuggable ],
rotation: f32 => [ Debuggable ],
Expand Down Expand Up @@ -57,45 +59,6 @@ component! {

}

/// Macroquad has unsound race conditions, as such, use a mock shared
/// context
#[derive(Hash, Debug, Clone)]
struct GraphicsContext;

#[derive(Debug, Clone)]
enum Shape {
Polygon { radius: f32, sides: u8 },
Circle { radius: f32 },
Triangle(Vec2, Vec2, Vec2),
}

impl Shape {
pub fn draw(&self, view: &Mat3, pos: Vec2, rot: f32, color: Color) {
match *self {
Shape::Circle { radius } => {
let pos = view.transform_point2(pos);
let radius = view.transform_vector2(Vec2::splat(radius)).x;
draw_circle(pos.x, pos.y, radius, color)
}
Shape::Polygon { radius, sides } => {
let pos = view.transform_point2(pos);
let radius = view.transform_vector2(Vec2::splat(radius)).x;

draw_poly(pos.x, pos.y, sides, radius, rot, color)
}
Shape::Triangle(v1, v2, v3) => {
let transform = *view * Mat3::from_scale_angle_translation(Vec2::ONE, rot, pos);

let v1 = transform.transform_point2(v1);
let v2 = transform.transform_point2(v2);
let v3 = transform.transform_point2(v3);

draw_triangle(v1, v2, v3, color)
}
}
}
}

#[macroquad::main("Asteroids")]
async fn main() -> Result<()> {
registry().with(HierarchicalLayer::default()).init();
Expand All @@ -115,6 +78,8 @@ async fn main() -> Result<()> {
);

// Setup everything required for the game logic and physics
//
// Two different schedules will run independently of each other at different rates.
let mut physics_schedule = Schedule::builder()
.with_system(player_system(dt))
.with_system(camera_system(dt))
Expand Down Expand Up @@ -148,7 +113,7 @@ async fn main() -> Result<()> {

while acc > 0.0 {
acc -= dt;
let batches = physics_schedule.batch_info(&mut world);
let batches = physics_schedule.batch_info(&world);
tracing::debug!(
"Batches: {:#?}",
batches
Expand All @@ -169,6 +134,7 @@ async fn main() -> Result<()> {

const ASTEROID_SIZE: f32 = 40.0;

/// Create the central player ship
fn create_player() -> EntityBuilder {
Entity::builder()
.set_default(position())
Expand Down Expand Up @@ -284,6 +250,7 @@ fn create_explosion(
})
}

/// Updates each particle in the world
fn particle_system() -> BoxedSystem {
System::builder()
.with_name("particle_system")
Expand All @@ -301,6 +268,9 @@ fn particle_system() -> BoxedSystem {
.boxed()
}

/// System which makes the camera track the player smoothly.
///
/// Uses two different queries, one for the player and one for the camera.
fn camera_system(dt: f32) -> BoxedSystem {
System::builder()
.with(Query::new((position(), velocity())).with(player()))
Expand All @@ -310,7 +280,7 @@ fn camera_system(dt: f32) -> BoxedSystem {
camera().as_mut(),
)))
.build(
move |mut players: QueryBorrow<(Component<Vec2>, Component<Vec2>), And<All, With>>,
move |mut players: QueryBorrow<(Component<Vec2>, Component<Vec2>), _>,
mut cameras: QueryBorrow<(Mutable<Vec2>, Mutable<Vec2>, Mutable<Mat3>)>|
-> Result<()> {
if let Some((player_pos, player_vel)) = players.first() {
Expand All @@ -336,6 +306,44 @@ fn camera_system(dt: f32) -> BoxedSystem {
.boxed()
}

/// Macroquad has unsound race conditions, as such, use a mock shared
/// context
#[derive(Hash, Debug, Clone)]
struct GraphicsContext;

#[derive(Debug, Clone)]
enum Shape {
Polygon { radius: f32, sides: u8 },
Circle { radius: f32 },
Triangle(Vec2, Vec2, Vec2),
}

impl Shape {
pub fn draw(&self, view: &Mat3, pos: Vec2, rot: f32, color: Color) {
match *self {
Shape::Circle { radius } => {
let pos = view.transform_point2(pos);
let radius = view.transform_vector2(Vec2::splat(radius)).x;
draw_circle(pos.x, pos.y, radius, color)
}
Shape::Polygon { radius, sides } => {
let pos = view.transform_point2(pos);
let radius = view.transform_vector2(Vec2::splat(radius)).x;

draw_poly(pos.x, pos.y, sides, radius, rot, color)
}
Shape::Triangle(v1, v2, v3) => {
let transform = *view * Mat3::from_scale_angle_translation(Vec2::ONE, rot, pos);

let v1 = transform.transform_point2(v1);
let v2 = transform.transform_point2(v2);
let v3 = transform.transform_point2(v3);

draw_triangle(v1, v2, v3, color)
}
}
}
}
struct Collision {
a: Entity,
b: Entity,
Expand Down Expand Up @@ -383,6 +391,7 @@ fn lifetime_system(dt: f32) -> BoxedSystem {
.boxed()
}

/// N-body collision system
fn collision_system() -> BoxedSystem {
System::builder()
.with_name("collision_system")
Expand Down Expand Up @@ -468,7 +477,11 @@ const SHIP_TURN: f32 = 2.0;
const WEAPON_COOLDOWN: f32 = 0.2;
const PLUME_COOLDOWN: f32 = 0.02;

/// Sometimes a query can grow to a very large tuple. Using a struct helps with naming the fields
/// and refactoring.
#[derive(Fetch)]
// Ensures the fetch item is debuggable
#[fetch(item_derives = [Debug])]
struct PlayerQuery {
id: EntityIds,
player: Component<()>,
Expand Down Expand Up @@ -509,6 +522,7 @@ fn player_system(dt: f32) -> BoxedSystem {
current_plume_cooldown -= dt;

for player in &mut q {
dbg!(&player);
*player.invincibility = (*player.invincibility - 0.02).max(0.0);

*player.difficulty = (*player.material * 0.001).max(1.0);
Expand Down Expand Up @@ -552,6 +566,8 @@ fn player_system(dt: f32) -> BoxedSystem {
)
.boxed()
}

/// Kill of out of bounds entities relative to the player
fn despawn_out_of_bounds() -> BoxedSystem {
System::builder()
.with_name("despawn_out_of_bounds")
Expand All @@ -572,6 +588,7 @@ fn despawn_out_of_bounds() -> BoxedSystem {
.boxed()
}

/// Deferred despawn dead entities (including players)
fn despawn_dead() -> BoxedSystem {
System::builder()
.with_name("despawn_dead")
Expand Down Expand Up @@ -609,6 +626,7 @@ fn despawn_dead() -> BoxedSystem {
.boxed()
}

/// Spawn random asteroids near the player up to a maximum concurrent count
fn spawn_asteroids(max_count: usize) -> BoxedSystem {
System::builder()
.with_name("spawn_asteroids")
Expand Down Expand Up @@ -711,6 +729,7 @@ impl TransformQuery {
}
}

/// Draw each entity with a shape on the screen
fn draw_shapes() -> BoxedSystem {
System::builder()
.with_name("draw_asteroids")
Expand All @@ -734,6 +753,10 @@ fn draw_shapes() -> BoxedSystem {
.boxed()
}

/// Draws the score board by querying the ecs world for the data it needs.
///
/// For more complex Uis, consider having a look at [`violet`](https://github.com/ten3roberts/violet)
/// which uses `flax`
fn draw_ui() -> BoxedSystem {
System::builder()
.with_name("draw_ui")
Expand Down
9 changes: 6 additions & 3 deletions examples/guide/change_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,19 @@ fn main() {

// ANCHOR: cleanup_system

let query = Query::new((entity_ids(), player().satisfied())).filter(health().le(0.0));
let query = Query::new((name().opt(), entity_ids(), player().satisfied()))
.filter(health().le(0.0).modified());

let cleanup = System::builder()
.with_name("cleanup")
.with(query)
.write::<CommandBuffer>()
.build(|mut q: QueryBorrow<_, _>, cmd: &mut CommandBuffer| {
for (id, is_player) in &mut q {
for (name, id, is_player) in &mut q {
if is_player {
tracing::info!("Player died");
}
tracing::info!(is_player, "Despawning {id}");
tracing::info!(name, is_player, "Despawning {id}");
cmd.despawn(id);
}
});
Expand All @@ -115,13 +116,15 @@ fn main() {
.with_system(damage_random)
.with_system(update_poison)
.with_system(health_changes)
.flush()
.with_system(cleanup)
.flush();

while world.is_alive(player_id) {
schedule
.execute_par(&mut world)
.expect("Failed to run schedule");

sleep(Duration::from_millis(1000));
}

Expand Down
8 changes: 4 additions & 4 deletions examples/guide/dynamic_components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ fn main() -> anyhow::Result<()> {

let mut world = World::new();

let position: Component<Vec2> = world.spawn_component("position", |info| {
let position: Component<Vec2> = world.spawn_component("position", |desc| {
let mut buf = ComponentBuffer::new();
<Debug as MetaData<Vec2>>::attach(info, &mut buf);
<Debug as MetaData<Vec2>>::attach(desc, &mut buf);
buf
});

Expand All @@ -39,9 +39,9 @@ fn main() -> anyhow::Result<()> {
distance: f32,
}

let child_of = world.spawn_relation::<RelationData>("child_of", |info| {
let child_of = world.spawn_relation::<RelationData>("child_of", |desc| {
let mut buf = ComponentBuffer::new();
<Debug as MetaData<RelationData>>::attach(info, &mut buf);
<Debug as MetaData<RelationData>>::attach(desc, &mut buf);
buf
});

Expand Down
Loading
Loading