{"id":17791231,"url":"https://github.com/schell/apecs","last_synced_at":"2025-03-21T17:15:02.931Z","repository":{"id":41454848,"uuid":"469533347","full_name":"schell/apecs","owner":"schell","description":"An asyncronous and pleasant entity-component system for Rust","archived":false,"fork":false,"pushed_at":"2024-09-20T21:03:03.000Z","size":654,"stargazers_count":63,"open_issues_count":3,"forks_count":3,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-09T17:50:14.560Z","etag":null,"topics":["app-engine","ecs","game-development","game-engine","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/schell.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-14T00:43:26.000Z","updated_at":"2025-03-04T15:30:10.000Z","dependencies_parsed_at":"2024-08-28T10:27:13.214Z","dependency_job_id":"af05dcc7-b3bb-4c5b-85b5-ed4963f38d41","html_url":"https://github.com/schell/apecs","commit_stats":{"total_commits":136,"total_committers":1,"mean_commits":136.0,"dds":0.0,"last_synced_commit":"5a1c10929143a551a7650381202f34183b628027"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schell%2Fapecs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schell%2Fapecs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schell%2Fapecs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schell%2Fapecs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/schell","download_url":"https://codeload.github.com/schell/apecs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244835575,"owners_count":20518263,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["app-engine","ecs","game-development","game-engine","rust"],"created_at":"2024-10-27T10:50:19.915Z","updated_at":"2025-03-21T17:15:02.905Z","avatar_url":"https://github.com/schell.png","language":"Rust","readme":"# apecs\n**A**sync-friendly and **P**leasant **E**ntity **C**omponent **S**ystem\n\n`apecs` is an entity-component system written in Rust that can share world resources with \nfutures run in any async runtime. This makes it great for general applications, \nquick game prototypes, DIY engines and any simulation that has discrete steps in time.\n\n## Why\n\nMost ECS libraries (and game main-loops in general) are polling based. \nThis is great for certain tasks, but things get complicated when programming in the time domain.\nAsync / await is great for programming in the time domain without explicitly spawning new threads \nor blocking, but it isn't supported by ECS libraries. \n\n`apecs` was designed to to be an ECS that plays nice with async / await. \n\n## What and How\n\nAt its core `apecs` is a library for sharing resources across disparate polling and async loops. \nIt uses derivable traits and channels to orchestrate systems' access to resources and uses rayon \n(where available) for concurrency.\n\n## Goals\n* productivity\n* flexibility\n* observability\n* very well rounded performance, competitive with inspirational ECS libraries\n  - like `specs`, `bevy_ecs`, `hecs`, `legion`, `shipyard`, `planck_ecs`\n  - backed by criterion benchmarks\n\n## Features\n\nHere is a quick table of features compared to other ECSs.\n\n| Feature           | apecs    | bevy_ecs | hecs     | legion   | planck_ecs | shipyard | specs     |\n|-------------------|----------|----------|----------|----------|------------|----------|-----------|\n| storage           |archetypal|  hybrid  |archetypal|archetypal| separated  |  sparse  | separated |\n| system scheduling | ✔️        | ✔️       |          | ✔️        | ✔️          | ✔️        | ✔️         |\n| early exit systems| ✔️        |          |          |          |            |          |           |\n| parallel systems  | ✔️        | ✔️       | ✔️        | ✔️        |            | ✔️        | ✔️         |\n| change tracking   | ✔️        | ✔️       |          | kinda    |            | ✔️        | ✔️         |\n| async support     | ✔️        |          |          |          |            |          |           |\n\n### Feature examples\n\n- systems with early exit and failure\n```rust\n  use apecs::*;\n\n  #[derive(Clone, Copy, Debug, Default, PartialEq)]\n  struct Number(u32);\n\n  fn demo_system(mut u32_number: ViewMut\u003cNumber\u003e) -\u003e Result\u003c(), GraphError\u003e {\n      u32_number.0 += 1;\n      if u32_number.0 == 3 {\n          end()\n      } else {\n          ok()\n      }\n  }\n\n  let mut world = World::default();\n  world.add_subgraph(graph!(demo_system));\n  world.run().unwrap();\n  assert_eq!(Number(3), *world.get_resource::\u003cNumber\u003e().unwrap());\n  ```\n\n- async support \n  - futures visit world resources through `Facade` using a closure. \n  - resources are acquired without lifetimes\n  - plays well with any async runtime\n```rust\n  use apecs::*;\n\n  #[derive(Clone, Copy, Debug, Default, PartialEq)]\n  struct Number(u32);\n\n  let mut world = World::default();\n  let mut facade = world.facade();\n\n  let task = smol::spawn(async move {\n      loop {\n          let i = facade\n              .visit(|mut u32_number: ViewMut\u003cNumber\u003e| {\n                  u32_number.0 += 1;\n                  u32_number.0\n              })\n              .await\n              .unwrap();\n          if i \u003e 5 {\n              break;\n          }\n      }\n  });\n\n  while !task.is_finished() {\n      world.tick().unwrap();\n      world.get_facade_schedule().unwrap().run().unwrap();\n  }\n\n  assert_eq!(Number(6), *world.get_resource::\u003cNumber\u003e().unwrap());\n```\n\n- system data derive macros\n```rust\n  use apecs::*;\n\n  #[derive(Edges)]\n  struct MyData {\n      entities: View\u003cEntities\u003e,\n      u32_number: ViewMut\u003cu32\u003e,\n  }\n\n  let mut world = World::default();\n  world\n      .visit(|mut my_data: MyData| {\n          *my_data.u32_number = 1;\n      })\n      .unwrap();\n```\n\n- system scheduling\n  - compatible systems are placed in parallel batches (a batch is a group of systems\n    that can run in parallel, ie they don't have conflicting borrows)\n  - systems may depend on other systems running before or after\n  - barriers\n  ```rust\n    use apecs::*;\n\n    fn one(mut u32_number: ViewMut\u003cu32\u003e) -\u003e Result\u003c(), GraphError\u003e {\n        *u32_number += 1;\n        end()\n    }\n\n    fn two(mut u32_number: ViewMut\u003cu32\u003e) -\u003e Result\u003c(), GraphError\u003e {\n        *u32_number += 1;\n        end()\n    }\n\n    fn exit_on_three(mut f32_number: ViewMut\u003cf32\u003e) -\u003e Result\u003c(), GraphError\u003e {\n        *f32_number += 1.0;\n        if *f32_number == 3.0 {\n            end()\n        } else {\n            ok()\n        }\n    }\n\n    fn lastly((u32_number, f32_number): (View\u003cu32\u003e, View\u003cf32\u003e)) -\u003e Result\u003c(), GraphError\u003e {\n        if *u32_number == 2 \u0026\u0026 *f32_number == 3.0 {\n            end()\n        } else {\n            ok()\n        }\n    }\n\n    let mut world = World::default();\n    world.add_subgraph(\n        graph!(\n            // one should run before two\n            one \u003c two,\n            // exit_on_three has no dependencies\n            exit_on_three\n        )\n        // add a barrier\n        .with_barrier()\n        .with_subgraph(\n            // all systems after a barrier run after the systems before a barrier\n            graph!(lastly),\n        ),\n    );\n\n    assert_eq!(\n        vec![vec![\"exit_on_three\", \"one\"], vec![\"two\"], vec![\"lastly\"]],\n        world.get_schedule_names()\n    );\n\n    world.tick().unwrap();\n\n    assert_eq!(\n        vec![vec![\"exit_on_three\"], vec![\"lastly\"]],\n        world.get_schedule_names()\n    );\n\n    world.tick().unwrap();\n    world.tick().unwrap();\n    assert!(world.get_schedule_names().is_empty());\n  ```\n- component storage\n  - optimized for space and iteration time as archetypes\n  - queries with \"maybe\" and \"without\" semantics\n  - queries can find a single entity without iteration or filtering\n  - add and modified time tracking\n  - parallel queries (inner parallelism)\n  ```rust\n    use apecs::*;\n\n    // Make a type for tracking changes\n    #[derive(Default)]\n    struct MyTracker(u64);\n\n    fn create(mut entities: ViewMut\u003cEntities\u003e) -\u003e Result\u003c(), GraphError\u003e {\n        for mut entity in (0..100).map(|_| entities.create()) {\n            entity.insert_bundle((0.0f32, 0u32, format!(\"{}:0\", entity.id())));\n        }\n        end()\n    }\n\n    fn progress(q_f32s: Query\u003c\u0026mut f32\u003e) -\u003e Result\u003c(), GraphError\u003e {\n        for f32 in q_f32s.query().iter_mut() {\n            **f32 += 1.0;\n        }\n        ok()\n    }\n\n    fn sync(\n        (q_others, mut tracker): (Query\u003c(\u0026f32, \u0026mut String, \u0026mut u32)\u003e, ViewMut\u003cMyTracker\u003e),\n    ) -\u003e Result\u003c(), GraphError\u003e {\n        for (f32, string, u32) in q_others.query().iter_mut() {\n            if f32.was_modified_since(tracker.0) {\n                **u32 = **f32 as u32;\n                **string = format!(\"{}:{}\", f32.id(), **u32);\n            }\n        }\n        tracker.0 = apecs::current_iteration();\n        ok()\n    }\n\n    // Entities and Components (which stores components) are default\n    // resources\n    let mut world = World::default();\n    world.add_subgraph(graph!(\n        create \u003c progress \u003c sync\n    ));\n\n    assert_eq!(\n        vec![vec![\"create\"], vec![\"progress\"], vec![\"sync\"]],\n        world.get_schedule_names()\n    );\n\n    world.tick().unwrap(); // entities are created, components applied lazily\n    world.tick().unwrap(); // f32s are modified, u32s and strings are synced\n    world.tick().unwrap(); // f32s are modified, u32s and strings are synced\n\n    world\n        .visit(|q_bundle: Query\u003c(\u0026f32, \u0026u32, \u0026String)\u003e| {\n            assert_eq!(\n                (2.0f32, 2u32, \"13:2\".to_string()),\n                q_bundle\n                    .query()\n                    .find_one(13)\n                    .map(|(f, u, s)| (**f, **u, s.to_string()))\n                    .unwrap()\n            );\n        })\n        .unwrap();\n  ```\n- outer parallelism (running systems in parallel)\n  - parallel system scheduling\n  - parallel execution of async futures\n  - parallelism is configurable (can be automatic or a requested number of threads, including 1)\n```rust\n    use apecs::*;\n\n    #[derive(Default)]\n    struct F32(f32);\n\n    let mut world = World::default();\n\n    fn one(mut f32_number: ViewMut\u003cF32\u003e) -\u003e Result\u003c(), GraphError\u003e {\n        f32_number.0 += 1.0;\n        ok()\n    }\n\n    fn two(f32_number: View\u003cF32\u003e) -\u003e Result\u003c(), GraphError\u003e {\n        println!(\"system two reads {}\", f32_number.0);\n        ok()\n    }\n\n    fn three(f32_number: View\u003cF32\u003e) -\u003e Result\u003c(), GraphError\u003e {\n        println!(\"system three reads {}\", f32_number.0);\n        ok()\n    }\n\n    world\n        .add_subgraph(graph!(one, two, three))\n        .with_parallelism(Parallelism::Automatic);\n\n    world.tick().unwrap();\n```\n\n- fully compatible with WASM and runs in the browser\n\n## Roadmap\n- your ideas go here\n\n## Tests\n```bash\ncargo test\nwasm-pack test --firefox crates/apecs\n```\n\nI like firefox, but you can use different browsers for the wasm tests. The tests\nmake sure apecs works on wasm.\n\n## Benchmarks\nThe `apecs` benchmarks measure itself against my favorite ECS libs:\n`specs`, `bevy`, `hecs`, `legion`, `shipyard` and `planck_ecs`.\n\n```bash\ncargo bench -p benchmarks\n```\n\n# Minimum supported Rust version 1.65\n`apecs` uses generic associated types for its component iteration traits.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschell%2Fapecs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fschell%2Fapecs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschell%2Fapecs/lists"}