Entity Component System focused on usability and speed.

Overview

Shipyard

Shipyard is an Entity Component System focused on usability and speed.

If you have any question or want to follow the development more closely Chat.

Crates.io Documentation LICENSE

Guide Master | Guide 0.5 | Demo

Basic Example

use shipyard::{IntoIter, View, ViewMut, World};

struct Health(u32);
struct Position {
    x: f32,
    y: f32,
}

fn in_acid(positions: View<Position>, mut healths: ViewMut<Health>) {
    for (_, mut health) in (&positions, &mut healths)
        .iter()
        .filter(|(pos, _)| is_in_acid(pos))
    {
        health.0 -= 1;
    }
}

fn is_in_acid(_: &Position) -> bool {
    // it's wet season
    true
}

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

    world.add_entity((Position { x: 0.0, y: 0.0 }, Health(1000)));

    world.run(in_acid).unwrap();
}

Small Game Example

Inspired by Erik Hazzard's Rectangle Eater.

Play Source

Table of Contents

Origin of the name

Assembly lines take input, process it at each step, and output a result. You can have multiple lines working in parallel as long as they don't bother each other.

Shipyards such as the Venetian Arsenal are some of the oldest examples of successful, large-scale, industrial assembly lines. So successful that it could output a fully-finished ship every day.

Shipyard is a project you can use to build your own highly-parallel software processes.

Motivation

I initially wanted to make an ECS to learn how it works. After a failed attempt and some research, I started working on Shipyard.

Specs was already well established as the go-to Rust ECS but I thought I could do better and went with EnTT's core data-structure (SparseSet) and grouping model. A very flexible combo.

Cargo Features

  • parallel (default) — enables workload threading and add parallel iterators
  • serde1 — adds (de)serialization support with serde
  • std (default) — lets Shipyard use the standard library
  • thread_local — adds methods and types required to work with !Send and !Sync components

License

Licensed under either of

at your option.

Contributing

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Comments
  • View/ViewMut (and probably others) `get` is not public

    View/ViewMut (and probably others) `get` is not public

    Currently attempting to call get on a View/ViewMut storage (and probably other types) fails due it being pub(crate), yet the Index/IndexMut implementations just call them straight, except they .unwrap() the value. I can test with .contains first but that's just doing a double lookup needlessly. Is there a reason that .get and so forth are not public even though Index lookups just call them and work fine other then they .unwrap() it?

    opened by OvermindDL1 19
  • Alternative low boilerplate system syntax

    Alternative low boilerplate system syntax

    First: this is a really cool project and I've enjoyed playing with it so far :)

    Current system declarations

    Right now there are three ways to declare systems and each has its own tradeoffs:

    Struct with System trait impl

    struct DoThing;
    impl<'a> System<'a> for DoThing {
        type Data = (&'a Position, &'a mut Health);
        fn run((positions, mut healths): <Self::Data as SystemData>::View) {
            (&positions, &mut healths).iter().for_each(|(position, mut health)| {
                // do stuff here
            });
        }
    }
    
    // later
    world.run_system::<DoThing>();
    
    • Pros: Pure rust. Portable. Extends nicely to other scenarios like #34
    • Cons: Lots of boilerplate

    Function with macro

    #[system(DoThing)]
    fn run(position: &Position, mut health: &mut Health) {
        (&position, &mut health).iter().for_each(|(position, mut health)| {
            // do stuff here
        });
    }
    
    // later
    world.run_system::<DoThing>();
    
    • Pros: Low boilerplate. Portable
    • Cons: Domain specific language that isn't fully clear on types and breaks IDEs

    Anonymous function

    world.run::<(&Position, &mut Health),_,_>(|(positions, healths)| {
        (&positions, &mut healths).iter().for_each(|(position, mut health)| {
            // do thing
        });
    });
    
    • Pros: Low boilerplate. Pure rust
    • Cons: Needs to be declared within the context of a world / less portable. Requires extra type annotations. Nameless

    Proposed system declaration

    fn do_thing<'a>((positions, mut healths): (View<'a, Position>, ViewMut<'a, Health>)) {
        (&positions, &mut healths).iter().for_each(|(position, mut health)| {
            // do thing
        });
    }
    
    // later
    world.run_fn(do_thing);
    

    Additionally I think the lifetime could be elided in this case:

    fn do_thing((positions, mut healths): (View<Position>, ViewMut<Health>)) {
        (&positions, &mut healths).iter().for_each(|position, mut health| {
            // do thing
        });
    }
    
    // later
    world.run_fn(do_thing);
    
    • Pros: Dead simple and very low boilerplate (especially with elided lifetime). Pure rust. No type abstractions or DSLs. Portable
    • Cons: Works with views (View<Positition>) instead of type references (&Position). But I view this as a win because its honest about the types being used / has parity with other storages like Unique<T>.

    I personally think this the most ergonomic / straightforward solution (provided its actually possible).

    It also looks like this couldn't build off of the existing Run trait because it relies on T::SystemData, which assumes types are declared as (&Position, &mut Health) instead of (View<Position, ViewMut<Health>). But implementing this seems doable because it is actually less abstraction over the types used to run the system.

    Does this seem doable? Is it desirable?

    opened by cart 18
  • WIP World::take_unique()

    WIP World::take_unique()

    I'm trying to add support for moving a unique component out of the world:

    struct MyComponent;
    
    impl MyComponent {
        fn cleanup(self) {}
    }
    
    world.add_unique(MyComponent);
    world.take_unique::<MyComponent>().cleanup();
    assert!(world.try_borrow::<UniqueView<MyComponent>>().is_err());
    

    and having trouble coming up with a clean Storage interface since we can't consume the storage while holding a RefMut. One naive idea would be adding Storage::try_lock(&self) -> bool and Storage::take<T>(self) -> T and just

    if storage.try_lock() {
        // we got an exclusive lock
        Ok(storage.take())
    }
    

    but that interface doesn't enforce that try_lock() was actually called. Is there some pattern that could be used here, or some totally different way of going about it?

    Thanks!

    opened by poconn 14
  • Feedback after doing a refactor from Legion to Shipyard

    Feedback after doing a refactor from Legion to Shipyard

    Hi,

    I wanted to give some (hopefully constructive) feedback about your ECS shipyard. Currently I'm writting an alternative server implementation for a MMORPG that uses multiple ECS to handle the game logic (One variable tick ECS for the "global" server wide events like trading, chat, login handling etc and multiple high tick ECS for the local fields and dungeon instances that handle combat, player movement, colloision detection, view curling etc).

    Since I work with legacy software which network code I can't change, I'm stuck with the logic that the client is dictating in some way. So my client needs to know the 64bit EntityId to desribe in world objects (like players, NPCs, projectiles etc). I first chosed Legion as the ECS to use, but soon found out that my above requirement of serializing/deserializing the EntityId wasn't possible with Legion.

    So I had to rip out the ECS code and replace it with Shipyard. After around 12 hours of refactoring and getting all my tests running again, I think I have to share some valuable feedback:

    The positives:

    • The documentation is quite good. Especially the API documentation is very helpful (something were Legion is lacking in comparison) and also the guide helped a lot to understand the implementation. I would maybe suggest a glossary. In my first discovery I dismissed Shipyard as not ready because I thought it would lack "Resources". I now know that Unique provides this feature, but it wasn't apparent right away.
    • The demonstration code was very helpful to understand on how to use the API.
    • Writing Systems and Components is quite hassle free. I easily could transfer my ECS code from Legion to Shipyard, so they are pretty comparable in the feature set, I guess.
    • World.run() is a life safer when writing tests. It helps a lot to contain the borrowing and no needing to wrap every code with blocks or use drop(). I kind wish that AllStorages would have a comparable method.

    The negatives:

    • I was caught offguard that the Shiperators are not normal std iterators.
    • Deletions of entities are a hassle to do in systems. Since you have to borrow from AllStorages, your code gets more complicates. I solves this problem, by queuing my entities in a unique vector and have a special "cleaner" system to delete them at the end of a tick. Legion has command buffers which are quite useful in these cases.
    • Since I use an event as a component messaging system, I delete all of my events at each tick. A "delete all entities with this component" method would be helpful (maybe it could also be implemented more efficient?).
    • The system procedural macro has too much magic. Since it shadows the real type of the storages, it makes reading the code much harder then it needs to be. My IDE also gets confused by this. Let me elaborate:
    shipyard-1

    Somebody who doesn't know how the system macro works in depth would assume that the above storages are somehow just a reference / mutable reference of the component. But actually mut outgoing_events: &mut OutgoingEvents is a mut outgoing_events: ViewMut<&OutgoingEvents> and if you would want to declare the signature of a method that would like to use that View, you would need to declare it like this:

    shipyard-3

    My IDE also can't handle this (which isn't my primary concern):

    shipyard-2

    I personaly think that code should be easy to read and easy to understand. This kind of magic might saves some characters to type, but makes understanding the code much harder. This also makes the API look inconsistent, since you have to use write Unique<&Component> when you want to use Unique components anyway.

    Other things I would like to see:

    • I really like the idea to have an "universe" from which you could create multiple worlds that share the EntityId space between them. Especially in a multi-instances environment like my project it would help simplify some logic.

    So much for my feedback. Hopefully you find it helpful. You can close this issue on your own discretion.

    Please keep up the good work.

    opened by almetica 12
  • `Entities.try_add_component` consuming the storage

    `Entities.try_add_component` consuming the storage

    Entities.try_add_component and Entities.add_component are consuming the storage that is passed in because they both call storages.try_add_component is a trait that is defined as:

    pub trait AddComponent<T> {
        fn try_add_component(
            self,
            component: T,
            entity: EntityId,
            entities: &Entities,
        ) -> Result<(), error::AddComponent>;
        #[cfg(feature = "panic")]
        #[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
        fn add_component(self, component: T, entity: EntityId, entities: &Entities);
    }
    

    In other words, it takes self, which moves the storage in, so we cannot use the storage again inside a run, which is bad when inserting a number of the same component on many entities among other issue.

    In short, doing this:

    			entities.add_component(storage, c, entity1);
    			entities.add_component(storage, c, entity2);
    

    Causes this error:

    102 |         storage: &mut ViewMut<MapCoord>,
        |         ------- move occurs because `storage` has type `&mut shipyard::view::ViewMut<'_, core::map::coord::MapCoord>`, which does not implement the `Copy` trait
    ...
    105 |             entities.add_component(storage, c, entity1);
        |                                    ------- value moved here
    106 |             entities.add_component(storage, c, entity2);
        |                                    ^^^^^^^ value used here after move
    

    How are you supposed to access a storage multiple times after adding components with it?

    opened by OvermindDL1 10
  • Entity creation verbosity

    Entity creation verbosity

    Entity creation is currently quite verbose:

    world.run(
        |mut entities: EntitiesViewMut,
            mut a: ViewMut<A>,
            mut b: ViewMut<B>,
            mut c: ViewMut<C>,
            mut d: ViewMut<D>,
            mut e: ViewMut<E>,
            mut f: ViewMut<F>,
            mut g: ViewMut<G>,
            mut h: ViewMut<H>| {
            entities.add_entity(
                (
                    &mut a, &mut b, &mut c, &mut d, &mut e, &mut f, &mut g, &mut h,
                ),
                (
                    A::default(),
                    B::default(),
                    C::default(),
                    D::default(),
                    E::default(),
                    F::default(),
                    G::default(),
                    H::default(),
                ),
            );
        },
    );
    

    This makes sense in the context of a system where arbitrary type borrows can (and should) be discouraged.

    Ideally there would also be an api that is closer to this for world initialization:

    world.add_entity(
        (
            A::default(),
            B::default(),
            C::default(),
            D::default(),
            E::default(),
            F::default(),
            G::default(),
            H::default(),
        )
    );
    

    Or maybe something like

    world.run(|mut all_storages: AllStorages|
        all_storages.add_entity(
            (
                A::default(),
                B::default(),
                C::default(),
                D::default(),
                E::default(),
                F::default(),
                G::default(),
                H::default(),
            ),
        );
    );
    
    ergonomics 
    opened by cart 10
  • Slow scheduling ?

    Slow scheduling ?

    Hi, following code snippet on my PC (after refresh) take ~1.4s to execute both with parallel and without parallel feature. But this doesn't make sense, because if I split this on 2 workloads: first with immutable borrows and second one with mutable borrows, I got ~800ms

    scheduler testing
    #![feature(bench_black_box)]
    
    use shipyard::{IntoIter, View, ViewMut, Workload, World};
    use std::{hint::black_box, time::Instant};
    
    struct Health(u32);
    struct Position {
      x: f32,
      y: f32,
    }
    
    struct Hero(u32);
    
    fn immutable_iter(positions: View<Position>, healths: View<Health>) {
      let mut result: i64 = 0;
      for i in 0..1_000_000 {
          result += black_box(result + i);
      }
      black_box(result);
    }
    
    fn mut_iter1(mut positions: ViewMut<Position>, mut healths: ViewMut<Health>) {
      let mut result: i64 = 0;
      for i in 0..1_000_000 {
          result += black_box(result + i);
      }
      black_box(result);
    }
    
    fn mut_iter2(mut healths: ViewMut<Health>, mut hero: ViewMut<Hero>) {
      let mut result: i64 = 0;
      for i in 0..1_000_000 {
          result += black_box(result + i);
      }
      black_box(result);
    }
    
    fn main() {
      let mut world = World::new();
    
      let mut work_load = Workload::builder("Int cycle");
      for i in 0..1000 {
          if i % 2 == 0 || i == 0 {
              work_load.with_system(&immutable_iter);
          } else {
              if i % 3 == 0 {
                  work_load.with_system(&mut_iter1);
              } else {
                  work_load.with_system(&mut_iter2);
              }
          }
      }
    
    
      work_load.add_to_world(&world).unwrap();
    
      let start_time = Instant::now();
    
      world.run_workload("Int cycle").unwrap();
    
      println!(
          "performance {:?}",
          Instant::now().duration_since(start_time)
      );
    }
    

    I though that scheduler should give performance boots even in cases like this

    Do you interesting on creating own executor, instead of relay on rayon?

    I tested tested/created executor which run parallel everything what could be executed, then disable other thread and execute remains in single thread mode without atomic operations (it's also possible to re-order systems, based on execution time)

    I can provide code, but don't know how to integrate wtih shipyard

    opened by mrazorvin 9
  • How to use both View and ViewMut's Get trait as a generic?

    How to use both View and ViewMut's Get trait as a generic?

    #[deirve(Component)]
    struct Person();
    
    fn my_func<T>(p: &T, id: EntityId, ...)
    where T: Get
    {
        if p.get(id).is_ok() { ... }
        ...
    }
    
    fn system1(
        people: View<Person> // This will be ViewMut in other system.
    )
    {
        ...
        my_func(&people, id, ...);
    }
    

    I'd like to create some function like this.

    p can be View and ViewMut. Can I get some help?

    opened by PudgeKim 8
  • Is there any way to use Vec<ViewMut> as a parameter for a system?

    Is there any way to use Vec as a parameter for a system?

    I have some multiple components which are just for marker. So they don't have any field.

    For example,

    #[derive(Component)] 
    Apple();
    
    #[derive(Component)] 
    Banana();
    
    #[derive(Component)] 
    Grape();
    
    ...
    

    The shipyard's system limits the number of parameter. So I want to make a parameter as a vector. Like this.

    fn my_system(v: Vec<Box<Component>> // maybe this kind of thing?
    {
        v.iter().for_each(|component| {
             // some condition
             component.add_component_unchecked(..);
             // do something
            }
    
    }
    

    Is there any way to do this kind of thing?

    opened by PudgeKim 8
  • AddComponent Send and Sync

    AddComponent Send and Sync

    Hi, I'm using 0.5.0 and trying to use this crate without any Send and Sync. I turned off default features and added the 'std' and 'thread_local' features but unfortunately came unstuck when I needed to add an entity with components that were not send and sync. The send and sync requirements in this case come from the crate's add_component.rs file.

    I'd just like to know if Send and Sync is still on in relation to add_component for a reason or is this just something that still needs to be conditionally adjusted depending on feature flags?

    And great work on the crate so far

    opened by mmulli 7
  • No parallelization when accessing NonSend/NonSync storage in workload

    No parallelization when accessing NonSend/NonSync storage in workload

    Currently a system with a NonSend and/or NonSync input always runs alone, but it should be possible to run other non-conflicting systems in parallel while running that system on the main thread.

    I'm happy to post a PR, seems like it should be relatively straightforward, just checking first since it involves some core code that I'm not too familiar with. Does this conflict with anything you're working on/have planned, or is there some reason it's not feasible?

    enhancement 
    opened by poconn 7
  • Q: How to iter over every component against every other only once, excluding self?

    Q: How to iter over every component against every other only once, excluding self?

    Hi! When doing collision testing, it is common to do a collision check for every collider against every other collider, ignoring checking self against self and ignoring the order of the colliders. I would normally write code like the following to accomplish that:

    for id_1 in 0..num_ids - 1 {
        for id_2 in id_1 + 1..num_ids {
            // check collider with id_1 against collider with id_2
        }
    }
    

    The above loop is nice since it avoids any unnecessary iterations and combinations of id_1 and id_2. id_1 vs id_2 is the same as id_2 vs id_1 and this loop nicely ignores those.

    With shipyard, I'm struggling to do this consisely.

    Idea 1

    Keep track of which id combos we have checked and continue if we detect it's already done

    pub fn system_test_collisions_1(colliders: View<Collider>, mesh_storage: UniqueView<MeshStorage>) {
        let mut done = std::collections::HashSet::new();
        let mut results = vec![];
    
        for (id_a, collider_a) in colliders.iter().with_id() {
            for (id_b, collider_b) in colliders.iter().with_id() {
                let (key_ab, key_ba) = ((id_a, id_b), (id_b, id_a));
    
                if done.contains(&key_ab) || done.contains(&key_ba) {
                    continue;
                }
    
                done.insert(key_ab);
                done.insert(key_ba);
    
                if let Some(result) = test_collision(&collider_a, id_a, &collider_b, id_b, &mesh_storage) {
                    results.push(result);
                }
            }
        }
    }
    

    Idea 2

    Create a list of all ids with a collider, build up pairs of ids and components according to the loop above, then finally iterate over the pairs.

    fn system_test_collisions_2(colliders: View<Collider>, mesh_storage: UniqueView<MeshStorage>) {
        let ids = colliders
            .iter()
            .with_id()
            .map(|(id, _)| id)
            .collect::<Vec<_>>();
    
        let mut pairs = vec![];
        for i in 0..ids.len() - 1 {
            let id_a = ids[i];
            let Ok(collider_a) = colliders.get(id_a) else { continue };
    
            for j in i + 1..ids.len() {
                let id_b = ids[j];
                let Ok(collider_b) = colliders.get(id_b) else { continue };
    
                pairs.push((id_a, collider_a, id_b, collider_b));
            }
        }
    
        let results = pairs
            .iter()
            .filter_map(|(id_a, collider_a, id_b, collider_b)| {
                test_collision(&collider_a, id_a, &collider_b, id_b, &mesh_storage)
            })
            .collect::<Vec<_>>();
    }
    

    Idea 1 seems unnecessarily slow with hashing and allocations, Idea 2 seems a bit convoluted but is perhaps better since it can easily be parallellized with par_iter if I want to.

    I feel like one could do better. I would like to do the above more idiomatically or in a more performant way with less allocations if possible. How would you do this kind of iteration in shipyard? Also, next level: what if I want to do mutable borrows of the colliders? xP

    opened by EriKWDev 1
  • feature request: TraitView

    feature request: TraitView

    This was discussed on Zulip, but isn't being actively worked on at the moment, so opening a tracking issue with @leudz blessing. I have no idea how difficult it would be to add this, might be super hard!

    Motivation:

    Let's say we're building a renderer where we don't know all the possible variants of the meshes/materials, but we do know they satisfy a certain interface. It would be wonderful to do something like this:

    pub fn render_sys(renderer: ViewMut<MyRenderer>, meshes: TraitView<&dyn Mesh>, materials: TraitView<&dyn Material>) {
        for (mesh, material) in (&meshes, &materials).iter() {
            // assume Mesh is a trait with a draw() method
            // that takes a context like OpenGl / WebGPU /etc.
            // and a &dyn Material
            mesh.draw(&mut renderer.context, material);
        }
    }
    

    This is doable without any heap allocation in an application by having the Mesh/Material be enums, though this requires all the variants be defined in a rigid manner.

    If the above renderer were used as a library, then downstream code could merely add Mesh and Material components that satisfy the trait. Much more flexible!

    Prior art:

    https://github.com/BoxyUwU/bevy_unofficial_trait_queries

    opened by dakom 0
  • Dynamic API For Use With Scripting

    Dynamic API For Use With Scripting

    Hey there, I'm considering building a game engine on top of shipyard, but scripting support is essential. What I think I need is an API to shipyard that doesn't require the use of Rust's fancy type checking. I'm not 100% sure I know what I need to accomplish what I need to accomplish, but I want to start the discussion to start figuring it out. :slightly_smiling_face:

    Firstly, don't get me wrong, I love Rust's fancy type system and, though I haven't use shipyard extensively yet, I think I like the direction that you have been going with in terms of ergonomics. The problem, though is that I need to be able to insert new components and systems that are not known at compile time. I'm looking to accomplish something similar to Amethyst's Scripting RFC ( you don't necessarily need to read it, I'll explain ).

    So the goal is to have an API, potentially accessible over a C FFI, though not necessary for my use-case yet ( and maybe that FFI could be a separate crate? ), that we can use to store components, which will be opaque byte arrays. The component data will likely be stored in the C memory layout, but that is not important to the ECS itself it just needs to store the bytes.

    I need to be able to register components at run-time, though and so I need a way to distinguish between all of the different component types, even though they are all byte-arrays. In other words, we can't use the Rust type system to identify components. We might need to identify components by strings or integers or otherwise have some sort of ComponentID.

    I also need to be able to query the components in systems that are created at runtime. I think workloads would need to be defined at runtime, too.


    I don't know anything about the internals of shipyard yet, and I may not have explained that very well, but here's the gist:

    • I need to create components at runtime:
      • Each component will be a byte array
      • Each component needs to have a component ID I can use to query the component in systems
    • I need to create systems and workloads at runtime:
      • I will know at runtime which component IDs I need to have read or write access to for each system like normal
    • An FFI is optional
      • The API to do the actual runtime registration of components and systems can be a base for an FFI if necessary

    I might be able to help with this, but no promises. I'll probably be checking out the internals of shipyard a bit to see how it works and how easy it might be to implement.

    opened by zicklag 16
  • Tag component

    Tag component

    It's possible to add empty components but they'll still use an EntityId per component and up to an usize per entity in the World (could go down with #10 if the tagged entities are in the right spot). This could be far less, one bit per entity in the World with the possibility to go even lower using pagination.

    Since the storage wouldn't store EntityId it won't be possible to call with_id on these storages but with #52 we'll still be able to iterate them. There would be a new Tag type used like Unique to access the storage.

    enhancement 
    opened by leudz 0
Releases(0.5.0)
  • 0.5.0(Mar 29, 2021)

    This release took quite some time but I'm very happy of all the changes. The api is now macro free. It took a few tries to get to this api for workloads but it works now. There is just a rustc weird behavior that makes it impossible to use values for workload systems. It's an acceptable price to get rid of the macro but it'd be great if it could be fixed.

    Most notable changes:

    • Remove system! macro
      This macro was used to hide what information workloads need. For example using system!(check) expended to (|world: &World| world.try_run(check), check)). The scheduler would use the function on the right to define what the function on the left borrows. If the two functions didn't match you could run into runtime errors.

      The new api (.with_system(&check)) does not have this problem.

    • Remove packs
      Packs are removed temporarily, this implementation had too many limitations. The next implementation should not have the same problems but requires a fairly big amount of work so this version ships without packs and the next version should add them without modifying any existing api.

      remove was the most impacted, to remove two or more components you had to write Remove::<(usize, u32)>::remove((&mut usizes, &mut u32s), entity);. With 0.5 it's just (&mut usizes, &mut u32s).remove(entity).

    • Bulk add entity
      This new method is faster than adding entities one by one.

    • Remove Shiperator
      0.4 used a custom Iterator trait to modify some methods' behavior like filter. With 0.5, iterating an exclusive storage yields a Mut<T> by default which removes the need for a custom trait.

    • Accurate modification tracking by default
      In 0.4 when you tracked modification on a storage it would tag all components accessed exclusively regardless if they were modified or not. This is not true anymore, this code would tag exactly what you would expect:

      fn increment_even(mut u32s: ViewMut<u32>) {
          for mut i in (&mut u32s).iter() {
              if *i % 2 == 0 {
                  *i += 1;
              }
          }
      }
      
    • No more try_*
      In 0.4 all fallible functions had two versions, one that panicked and one prefixed with try_ that returned a Result. Now all functions that can fail because of storage access return a Result while almost all others panic.

    • Workload building is more flexible In 0.4 you could only create workloads directly with World and it was borrowed for the entire building time.

      world
          .add_workload("Add & Check")
          .with_system(system!(add))
          .with_system(system!(check))
          .build();
      

      In 0.5 you only need to borrow World to actually add the workload to it.

      Workload::builder("Add & Check")
          .with_system(&add)
          .with_system(&check)
          .add_to_world(&world)
          .unwrap();
      

      You can also "prepare" systems in advance, merge WorkloadBuilders, extend a WorkloadBuilder with a system iterator or even nest multiple workloads.

    • Workload debug
      When you create a workload you can print information about how it will be scheduled and why two systems can't run at the same time.

    • Custom view
      A lot of internal traits, types and functions are exposed in 0.5. One of them is Borrow, it allows user defined views. Custom views are very important to build clean apis on top of Shipyard. For example this custom view (https://github.com/dakom/shipyard-scenegraph/blob/master/crate/src/views.rs#L10-L28) borrows 11 storages. Users won't have to borrow 11 views though, just a single one. Creating a custom view is currently fairly verbose, I want to add a proc macro that would automatically derive the necessary traits.

    • Custom storage
      Shipyard comes with two default storages: SparseSet and Unique. 0.5 allows you to define your own storages. You can then build a view for them and access them in systems for example. The advantage compared to simply storing your type in Unique is to be able to define what your storage should do when an entity is deleted.

    Source code(tar.gz)
    Source code(zip)
  • 0.4.1(Jun 5, 2020)

    Fixes a bug reported by @hasenbanck where adding multiple workloads with the same name would cause a panic when running them.

    • New AddWorkload error
    • Simplify SetDefaultWorkload error
    Source code(tar.gz)
    Source code(zip)
  • 0.4.0(Apr 25, 2020)

    What's new

    • Rework of systems and types used to borrow storage
    • Workloads had to be reworked to handle this change
    • Workloads can now return errors
    • Iterator and IntoIterator are now supported

    For 0.3 users there's a migration guide to help with all the changes.

    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Feb 29, 2020)

    What's new

    • User guide

      Learning to use a crate with the documentation can be hard. There's now a guide to explain what can be done with Shipyard and how.

    • No need to register components anymore

      Components had to be registered before accessing them by using World::new or World::register.
      Storages are now automatically created when they are first accessed.

    • !Send and !Sync components

      All components had to be Send + Sync, this is a strict limitation to be sure storages can use the parallel feature.
      With 0.3 !Send and !Sync types can be stored and accessed while still following Rust's rules. These rules limit threading for these types but doesn't always prevent it.

    • Unique components

      When we only need a single instance of a component, keeping an id around to access it can be annoying. A Unique component won't be attached to any entity but will have the storage all for itself.

    • Components sorting

    • No std support

    • And more 😉

    Source code(tar.gz)
    Source code(zip)
Owner
Dylan Ancel
Dylan Ancel
Creative Coding Framework based on Entity Component System (ECS) written in Rust

creativity creativity is Creative Coding Framework based on Entity Component System (ECS) written in Rust. Key Features TBA Quick Start TBA How To Con

Chris Ohk 9 Nov 6, 2021
A generated entity component system 🦎

gecs ?? A generated entity component system. The gecs crate provides a compile-time generated, zero-overhead ECS for simulations on a budget. Unlike o

null 61 Jun 16, 2023
Minimalistic implementation of entity kinds for Bevy ECS.

Bevy ?? Kindly This crate is a minimalistic implementation of Kinded Entities for Bevy game engine. In summary, it allows the user to define, construc

null 10 Jan 26, 2023
Rust bindings for entity-gym.

EntityGym for Rust EntityGym is a Python library that defines a novel entity-based abstraction for reinforcement learning environments which enables h

null 18 Apr 15, 2023
Work-in-progress Nintendo Switch emulator, written in Rust and slightly less focused on gaming

pegasus Work-in-progress Nintendo Switch emulator, written in pure Rust and slightly less focused on gaming Information This project aims to be a diff

XorTroll 21 Nov 22, 2022
A safe, fast and cross-platform 2D component-based game framework written in rust

shura shura is a safe, fast and cross-platform 2D component-based game framework written in rust. shura helps you to manage big games with a component

Andri 28 Jan 17, 2023
A 2D rust game engine focused on portability.

The Cross Platform Engine Emerald is designed to be as lightweight as possible, while remaining a fully-featured and cross-platform game engine. The a

bombfuse 480 Dec 28, 2022
A rollback library that buffers component state. Useful for netcode.

bevy_timewarp Buffer and rollback to states up to a few frames ago, for rollback networking. Doesn't do any networking, just concerned with buffering

Richard Jones 7 Sep 4, 2023
A nine slice/patch plugin for bevy ui nodes as single component using a fragment shader.

Bevy nine slice/patch Material Plugin Quick and easy auto-scaling nine slice/patch material for bevy ui nodes implemented as Fragment Shader. Features

Lorenz 13 Dec 14, 2023
a prototype crate for creating modular and performant 3D CPU particle systems, inspired by Unity's Shuriken Particle System.

bevy_prototype_particles This is a prototype crate for creating modular and performant 3D CPU particle systems, inspired by Unity's Shuriken Particle

James Liu 28 Sep 12, 2022
🤹‍ 2D sprite rendering extension for the specs ECS system

specs-blit 2D sprite rendering extension for the Specs ECS system. All sprites are loaded onto a big array on the heap. Example // Setup the specs wor

Thomas Versteeg 8 Aug 14, 2022
Hanabi — a particle system plugin for the Bevy game engine.

Hanabi — a particle system plugin for the Bevy game engine

Jerome Humbert 256 Dec 30, 2022
An atomic save/load system for Bevy Game Engine.

☢️ Bevy Atomic Save An atomic save/load system for Bevy. Features Save and load a World into a RON file on disk Control which entities should particip

null 11 Jan 28, 2023
Action-based animation system for Bevy.

bevy_action_animation Action-based animation system for Bevy. Introduction This plugin provides users of the Bevy game engine with an action/trigger-b

undersquire 5 Apr 15, 2023
Simple action system for Bevy.

bevy_action Simple action system for Bevy. Introduction This plugin exists mainly to facilitate a common action system for other plugins to hook in to

undersquire 4 Apr 15, 2023
A light-weight Anchor-Offset based 2D sprite rendering system for the bevy engine.

Bevy AoUI A light-weight anchor-offset based 2D sprite layout system for the bevy engine. Bevy AoUI provides a light-weight rectangular anchor-offset

Mincong Lu 4 Nov 22, 2023
🎆 CPU-driven, batch-rendered particle system for the Bevy game engine.

Bevy Firework ?? Bevy firework is a particle system plugin where particles are simulated on the CPU and use GPU batching for rendering. This allows ea

Manuel Brea Carreras 24 Mar 12, 2024
An extendable system made up of autonomous execution services known as nodes organized in a tree of processes. Inspired by Godot!

NodeTree NodeTree is a framework to create large scalable programs and games through a tree of processes. Each process is fully autonomous and is capa

LunaticWyrm 3 Apr 10, 2024
Victorem - easy UDP game server and client framework for creating simple 2D and 3D online game prototype in Rust.

Victorem Easy UDP game server and client framework for creating simple 2D and 3D online game prototype in Rust. Example Cargo.toml [dependencies] vict

Victor Winbringer 27 Jan 7, 2023