Specs - Parallel ECS

Overview

Specs

Specs Parallel ECS

Build Status Crates.io Gitter MIT/Apache Docs.rs Code coverage LoC

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
    • Systems 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.40

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 hibitset
rayon rayon
shred shred
shrev 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.

Issues
  • Tracked

    Tracked

    Somehow I managed to break github with that last PR... and now it won't let me re-open it so here's a new one.

    Old PR: https://github.com/slide-rs/specs/pull/305


    This change is Reviewable

    ready 
    opened by Aceeri 34
  • another storage to help synchronizing with external storage

    another storage to help synchronizing with external storage

    I use specs with nphysics (actually a fork on my own but 0.8 nphysics will impl things..) I have to synchronize RigidBodyHandle in specs world and in nphysics world.

    My needs are:

    • for insert everything is sync: the method to create RigidBodyHandle component takes nphysics world and create them altogether
    • for remove I accept that some object are still alive in nphysics world but have no component in specs. and then I want to regularly sync things by removing those objects that have been deleted from specs world. But to do so I have issues:

    I tried to implement using flagged storage in current master but I can't get the deleted component I just have the index of the entity deleted and that is not enough:

    extern crate specs;
    
    use specs::prelude::*;
    
    #[derive(Debug)]
    struct Vel(usize);
    
    impl Component for Vel {
        type Storage = FlaggedStorage<Self, VecStorage<Self>>;
    }
    
    fn main() {
        let mut vels = vec![];
        let mut world = World::new();
        world.register::<Vel>();
        let mut removed_id = world.write::<Vel>().track_removed();
    
        vels.push(0.0);
        let e0 = world.create_entity().with(Vel(0)).build();
    
        {
            // remove an entity
            println!("entity removed:  {:?}", e0);
            world.delete_entity(e0).unwrap();
    
            // add an entity
            vels.push(0.3);
            let e1 = world.create_entity().with(Vel(1)).build();
            println!("entity created:  {:?}", e1);
    
            // try to find removed component
            let mut removed = BitSet::new();
            world.write::<Vel>().populate_removed(&mut removed_id, &mut removed);
    
            let entities = world.entities();
            for (e, _, v) in (&*entities, &removed, &world.read::<Vel>()).join() {
                // this does show we found new component instead
                println!("component of removed ? {:?}: {:?}", e, v);
            }
        }
    }
    

    The solution I'll implement is to create a new storage that keeps deleted component in some cache that I'll clear regularly (with removing their nphysic body). This implementation is straightforward but might be needed by others so that is why I open this issue and also to know if you have better solution.

    discussion 
    opened by thiolliere 28
  • Saveload overhaul

    Saveload overhaul

    TODO

    • [x] Commit messages need to be improved
    • [x] There is some weird lifetime error that I don't really understand

    Motivation for this PR

    • Rename some items to make the code easier to understand
    • Remove a couple of unnecessary bounds
    • Allow passing references of storages (previously you could not reuse the WriteStorage)

    Fixes #138 Fixes #282


    This change is Reviewable

    enhancement ready 
    opened by torkleyy 28
  • Saveload custom derive

    Saveload custom derive

    Split from #434 A custom derive for IntoSerialize and FromDeserialize named #[derive(Saveload)]. It may seem odd to derive them together, but after spending significant time trying to do it the normal way (one derive per trait) it became clear that due to the requirement of "proxy" types that fill the IntoSerialize::Data and FromDeserialize::Data fields, it made sense to do it this way or you start getting into attribute hell.


    This change is Reviewable

    in progress 
    opened by WaDelma 28
  • Custom derive for Component trait

    Custom derive for Component trait

    This PR introduces an optional specs_derive crate containing a custom derive macro for defining new components in a less verbose way. The ergonomics loosely resemble the derivative crate.

    Before

    #[derive(Debug)]
    struct Pos(f32, f32, f32);
    
    impl Component for Pos {
        type Storage = VecStorage<Pos>;
    }
    

    After

    #[derive(Component, Debug)]
    struct Pos(f32, f32, f32);
    

    The macro will store components in VecStorages by default. To specify a different storage type, you may use the #[component] attribute.

    #[derive(Component, Debug)]
    #[component(HashMapStorage)]
    struct Pos(f32, f32, f32);
    

    I also included a revised basic.rs example called basic_derive.rs to demonstrate its usage, but this can probably be omitted from the PR if people ultimately prefer implementing Component the explicit way.

    EDIT: Use DenseVecStorage by default.

    ready 
    opened by ebkalderon 26
  • wasm32 support

    wasm32 support

    Hello,

    Tried to build it with --target wasm32-unknown-unknown, haven't succeeeded. At first, I found out the dispatcher might be problematic (it's supporting parallel execution), so I went and annotated shred with conditional compilation directives (it already had it for emscripted, so I just duplicated it with analogue for target_arch = "wasm32" here. It builds fine then.

    Going back to specs, I've overriden the shred to use my branch:

    [dependencies.shred]
    git = "https://github.com/BartAdv/shred"
    rev = "4d6b1a9c4d0bc1cd8d7bba275e7fbb4d3be69f1b"
    

    only to see

    error[E0433]: failed to resolve. Use of undeclared type or module `imp`
      --> /home/bart/.cargo/registry/src/github.com-1ecc6299db9ec823/rand-0.3.20/src/os.rs:35:18
       |
    35 | pub struct OsRng(imp::OsRng);
       |                  ^^^ Use of undeclared type or module `imp`
    
    error[E0433]: failed to resolve. Use of undeclared type or module `imp`
      --> /home/bart/.cargo/registry/src/github.com-1ecc6299db9ec823/rand-0.3.20/src/os.rs:40:9
       |
    40 |         imp::OsRng::new().map(OsRng)
       |         ^^^ Use of undeclared type or module `imp`
    
    

    OK, I get that - rand crate relies on some sys stuff. So I wanted to fix whatever dep is wanting rand. Found out, the rayon-core uses it. As it turns out, rayon is used by hibitset (gated by feature flag parallel), so I wanted to check if I could gate the specs rayon dependncy similarily...:

    diff --git a/Cargo.toml b/Cargo.toml
    index ef2a182..a979465 100644
    --- a/Cargo.toml
    +++ b/Cargo.toml
    @@ -23,10 +23,9 @@ derivative = "1"
     fnv = "1.0"
     hibitset = { version = "0.4.1", features = ["parallel"] }
     mopa = "0.2"
    -shred = "0.5.0"
     shred-derive = "0.3"
     tuple_utils = "0.2"
    -rayon = "0.8.2"
    +rayon = { version = "0.8.2", optional = true}
     
     futures = { version = "0.1", optional = true }
     serde = { version = "1.0", optional = true, features = ["serde_derive"] }
    @@ -34,9 +33,14 @@ serde = { version = "1.0", optional = true, features = ["serde_derive"] }
     # enable rudy via --features rudy
     rudy = { version = "0.1", optional = true }
     
    +[dependencies.shred]
    +git = "https://github.com/BartAdv/shred"
    +rev = "4d6b1a9c4d0bc1cd8d7bba275e7fbb4d3be69f1b"
    +
     [features]
     common = ["futures"]
     nightly = []
    +parallel = ["hibitset/parallel", "rayon"]
     
     [package.metadata.docs.rs]
     features = ["common", "serde"]
    

    ...to no avail, the build is still giving me

    error[E0433]: failed to resolve. Use of undeclared type or module `imp`
      --> /home/bart/.cargo/registry/src/github.com-1ecc6299db9ec823/rand-0.3.20/src/os.rs:35:18
       |
    35 | pub struct OsRng(imp::OsRng);
       |                  ^^^ Use of undeclared type or module `imp`
    

    Now - maybe someone more compoenent than me could try and check it? Is it planned to support wasm32-unknown-unknown target?

    feature-request hard 
    opened by BartAdv 25
  • Relicense under dual MIT/Apache-2.0

    Relicense under dual MIT/Apache-2.0

    TL;DR the Rust ecosystem is largely Apache-2.0. Being available under that license is good for interoperation. The MIT license as an add-on can be nice for GPLv2 projects to use your code.

    Why?

    The MIT license requires reproducing countless copies of the same copyright header with different names in the copyright field, for every MIT library in use. The Apache license does not have this drawback. However, this is not the primary motivation for me creating these issues. The Apache license also has protections from patent trolls and an explicit contribution licensing clause. However, the Apache license is incompatible with GPLv2. This is why Rust is dual-licensed as MIT/Apache (the "primary" license being Apache, MIT only for GPLv2 compat), and doing so would be wise for this project. This also makes this crate suitable for inclusion and unrestricted sharing in the Rust standard distribution and other projects using dual MIT/Apache, such as my personal ulterior motive, the Robigalia project.

    Some ask, "Does this really apply to binary redistributions? Does MIT really require reproducing the whole thing?" I'm not a lawyer, and I can't give legal advice, but some Google Android apps include open source attributions using this interpretation. Others also agree with it. But, again, the copyright notice redistribution is not the primary motivation for the dual-licensing. It's stronger protections to licensees and better interoperation with the wider Rust ecosystem.

    Contributor checkoff

    To agree to relicensing, comment with :

    I license past and future contributions under the dual MIT/Apache-2.0 license, allowing licensees to choose either at their option.

    Checklist

    • [x] @kvark
    • [x] me
    • [x] @Aceeri
    • [x] @serprex
    • [x] @vitvakatu
    • [x] @malleusinferni
    • [x] @thiolliere
    • [x] @murarth
    • [x] @jeffparsons
    • [x] @msiglreith
    • [x] @lschmierer
    • [x] @WaDelma
    • [x] @Kimundi
    • [x] @mrZalli
    • [x] @Jragonmiris
    • [x] @minecrawler
    • [x] @futile
    • [ ] @White-Oak
    ready 
    opened by torkleyy 21
  • Integrating rayon and scoped threading

    Integrating rayon and scoped threading

    This is addresses #54

    opened by WaDelma 20
  • Idea: OpenCL-backed storage

    Idea: OpenCL-backed storage

    I have a scientific simulation / numerical computation project I've written using Specs. But last night I did a spike where I prototyped an version that offloaded the calculations to the GPU via OpenCL, and I saw a 100x performance improvement over the Specs (i.e, CPU-based) version. That's kind of hard to ignore.

    It occurred to me that if Specs had a component storage type backed by OpenCL buffers, then you could write Systems that apply OpenCL kernels to the data, and thereby make a Specs application that offloads some calculations to the GPU. You'd get to keep Specs' entity management, as well as have other systems that do CPU-based work in the same application, while shifting the heaviest lifting to the GPU.

    Has any thinking been done on this front, or any related work? What major challenges do you see in pulling this off?

    feature-request hard 
    opened by michaelmelanson 19
  • Lifetime issues with component storages

    Lifetime issues with component storages

    I originally posted about this in the users forum and then on Reddit.

    Terminology Note: When I say "owned X" in the text below, I mean that you have ownership of a value X of type T, not a reference to that value (e.g. &T or &mut T)

    You can look at this issue from two perspectives:

    1. It is hard to get a SystemData containing references to storages. Example: You can get (ReadStorage<A>, ReadStorage<B>) by calling the system_data method on World but not (&ReadStorage<A>, &ReadStorage<B>)
    2. It is not possible to use the Join trait with the owned versions of component storages. Example: You can do (&a, &b).join() but not (a, b).join()

    Both of these issues exist for good reasons, but they make code like the following very hard to write:

    fn iter_components<'a>(world: &'a World) -> impl Iterator<Item=(&Position, &Velocity)> + 'a {
        // Two *owned* storages allocated on the stack of this function
        let (positions, velocities) = world.system_data::<(ReadStorage<Position>, ReadStorage<Velocity>)>();
    
        // Returning a reference to stack allocated data (does not and should not work!)
        (&positions, &velocities).join()
    }
    

    This doesn't work and you get the following error: (copied from users forum post, see that for the full code example)

    error[E0515]: cannot return value referencing local variable `velocities`       
      --> src/main.rs:22:5                                                          
       |                                                                            
    22 |     (&positions, &velocities).join()                                       
       |     ^^^^^^^^^^^^^-----------^^^^^^^^                                       
       |     |            |                                                         
       |     |            `velocities` is borrowed here                             
       |     returns a value referencing data owned by the current function         
                                                                                    
    error[E0515]: cannot return value referencing local variable `positions`        
      --> src/main.rs:22:5                                                          
       |                                                                            
    22 |     (&positions, &velocities).join()                                       
       |     ^----------^^^^^^^^^^^^^^^^^^^^^                                       
       |     ||                                                                     
       |     |`positions` is borrowed here                                          
       |     returns a value referencing data owned by the current function
    

    If either of the two things I listed above weren't the case, this code could potentially compile:

    1. If you could get a system data instance containing references to storages: (the storages would have to be owned within the world or something)
    fn iter_components<'a>(world: &'a World) -> impl Iterator<Item=(&Position, &Velocity)> + 'a {
        // Two references to storages allocated somewhere by World
        // Notice the `&` before the `ReadStorage` types here
        let (positions, velocities) = world.system_data::<(&ReadStorage<Position>, &ReadStorage<Velocity>)>();
    
        // positions and velocities are both references, so we can use them here no problem
        (positions, velocities).join()
    }
    
    1. If it were possible to use the Join trait on owned versions of the storages:
    fn iter_components<'a>(world: &'a World) -> impl Iterator<Item=(&Position, &Velocity)> + 'a {
        // Two *owned* storages allocated on the stack of this function
        let (positions, velocities) = world.system_data::<(ReadStorage<Position>, ReadStorage<Velocity>)>();
    
        // Storages are *moved* into this tuple that we call `join` on. This works!
        (positions, velocities).join()
    }
    

    A note about this: ReadStorage makes it pretty obvious that we are immutably borrowing here, but with WriteStorage we would probably need some methods like read_owned() and write_owned() to emulate what you could do with & and &mut before. See the following code for more details:

    fn iter_components<'a>(world: &'a World) -> impl Iterator<Item=(&mut Position, &Velocity)> + 'a {
        // Two *owned* storages allocated on the stack of this function
        let (positions, velocities) = world.system_data::<(WriteStorage<Position>, WriteStorage<Velocity>)>();
    
        // You can join over either of these storages in two modes:
        // * `&positions` for read-only access to the components
        // * `&mut positions` for read-write access to the components
        // `write_owned()` could wrap the storage in a type that implements
        // the `Join` trait and allows access to mutable references
        // Instead of `read_owned()` we could default to the behaviour that
        // the owned version of a storage only allows reading
    
        // Storages are *moved* into this tuple that we call `join` on. This works!
        (positions.write_owned(), velocities.read_owned()).join()
    }
    

    I think this solution is probably the easiest to implement because it doesn't involve the World having to hold on to arbitrary storages for some extended amount of time.

    The API I would suggest is:

    • ReadStorage<T> and WriteStorage<T> implement Join and produce &T
    • WriteStorage<T> provides a method (e.g. write_owned()) that returns an owned value that implements Join and produces &mut T (perhaps something similar to MaybeJoin?)

    This may cause issues with World because it's probably not a good thing if a storage lives longer than expected. I am not familiar enough with the internals to comment on that.

    enhancement discussion 
    opened by sunjay 19
  • 03_dispatcher: fix clippy::unnecessary_mut_passed

    03_dispatcher: fix clippy::unnecessary_mut_passed

    Fixes:

    warning: the method `dispatch` doesn't need a mutable reference
      --> src/main.rs:70:25
       |
    70 |     dispatcher.dispatch(&mut world);
       |                         ^^^^^^^^^^
    

    Checklist

    • [ ] I've added tests for all code changes and additions (where applicable)
    • [ ] I've added a demonstration of the new feature to one or more examples
    • [ ] I've updated the book to reflect my changes
    • [ ] Usage of new public items is shown in the API docs

    API changes

    opened by softmoth 0
  • New flagged storage for parallel joins

    New flagged storage for parallel joins

    Checklist

    • [ ] I've added tests for all code changes and additions (where applicable)
    • [ ] I've added a demonstration of the new feature to one or more examples
    • [ ] I've updated the book to reflect my changes
    • [ ] Usage of new public items is shown in the API docs

    API changes

    Breaking: RestrictedStorage removed (unsound without streaming JoinIter) Breaking: DerefFlaggedStorage removed (unsound without streaming JoinIter) Breaking: JoinIter::get/JoinIter::get_unchecked removed (unsound see https://github.com/amethyst/specs/issues/647) (can probably be brought back with a streaming JoinIter impl using GATs)

    UnsplitFlaggedStorage added for deferred mutable access (like DerefFlaggedStorage) that can also be used in parallel joins (unlike any other flagged storage) (locked behind nightly feature).

    Breaking: Safety comments on Join/UnprotectedStorage traits improved (downstream crates that implement novel storages should review these).

    opened by Imberflur 0
  • DispatcherBuilder::contains

    DispatcherBuilder::contains

    Description

    DispatcherBuilder should be able to tell us if a system has already been added:

    pub fn contains(&self, name: &str) -> bool {
        self.map.contains_key(name)
    }
    

    Motivation

    This would allow callsites to conditionally add systems, avoiding panics.

    Drawbacks

    None - it is not a breaking change and has no performance implications.


    I can work on this feature.

    feature-request 
    opened by schell 0
  • Specs_derive: Missing license file in crate source

    Specs_derive: Missing license file in crate source

    Description

    The license file is not include in the crate of specs_derive.

    Expected behavior

    Having a license file that would ease packaging in fedora.

    bug 
    opened by remilauzier 0
  • Fix some clippy warnings

    Fix some clippy warnings

    Checklist

    • [ ] I've added tests for all code changes and additions (where applicable)
    • [ ] I've added a demonstration of the new feature to one or more examples
    • [ ] I've updated the book to reflect my changes
    • [ ] Usage of new public items is shown in the API docs

    API changes

    Not sure, maybe the part that remove mutability.

    opened by remilauzier 0
  • In MarkerAllocator Trait retrieve_entity_internal could take id by reference

    In MarkerAllocator Trait retrieve_entity_internal could take id by reference

    Description

    Currently the method retrieve_entity_internal of the MarkerAllocator Trait moves the id of the identifier instead of taking it by reference. This is not a concern for small identifier such as i32/i64 but forces unnecessary allocations when using larger structures (such as a string for example). The implementation of the method will usually be some kind of value get from a map which would only need a reference itself. Therefore the move seems suboptimal here. I may be missing something here but I really do not see the advantage of moving the id instead of taking a reference.

    Motivation

    When deserializing an important amount of entities (>100k) marked with a string identifier (for example a uuid) the string has to be cloned for each entity which degrades the performance.

    Drawbacks

    • Is it a breaking change? Yes unfortunately 😢 . A workaround is easily done by implementing another method outside the trait but it's not optimal.

    • Can it impact performance, learnability, etc? No

    Unresolved questions


    I'll happily provide a PR if this change was approved.

    feature-request 
    opened by Dollab 0
  • [Question] How to change values of Component's from outside of World ?

    [Question] How to change values of Component's from outside of World ?

    Description

    Hello. Im making a GUI prototype based on the ECS architecture using specs. i would like to modfiy the values of components from outside the world through widgets.

    Motivation

    Lets says i have Label widget, and i would like to modify its TextComponent's value when i click on a Button widget.

    struct TextComponent {
        text: String
    }
    
    struct Label {
        id: Entity,
        width: usize,
        height: usize,
        x: usize,
        y: usize,
        text: TextComponent
    }
    
    impl Label {
        fn text(&self) -> &TextComponent {
            &self.text
        }
        
        fn set_text(&mut self, new_text: String) {
            // label has the new value, but world has the old value...
            // how to notify the world that this entity's component changed ?
            self.text = TextComponent {
                text: new_text
                }
           
        }
        
        impl build(world: &mut World) -> Label {
            world.register::<TextComponent>();
            let text = TextComponent::default();
            let id = world.create_entity()
                .width(text);
                
            Label {
                id: id,
                width: 100,
                height: 75,
                x:0,
                y:0,
                text: text
            }
        }
    }
    

    Drawbacks

    i would like to avoid using Rc> if its possible, it does not scale with lots of entities and has some performance costs How do games solves this problem ? Im thinking some kinf of messaging system but what libraries are should i use ? I tried the observer pattern, but its very challenging with rust ownership's system to hold a mutable references of World in each widget.

    Unresolved questions


    feature-request 
    opened by kivimango 1
  • Add DerefFlaggedGen storage that includes the entity generation in events

    Add DerefFlaggedGen storage that includes the entity generation in events

    Inspired by #724

    Checklist

    • [ ] I've added tests for all code changes and additions (where applicable)
    • [ ] I've added a demonstration of the new feature to one or more examples
    • [ ] I've updated the book to reflect my changes
    • [ ] Usage of new public items is shown in the API docs

    API changes

    opened by Imberflur 0
  • Track entity generation in ComponentEvent (fixes #720)

    Track entity generation in ComponentEvent (fixes #720)

    Checklist

    • [ ] I've added tests for all code changes and additions (where applicable)
    • [ ] I've updated the book to reflect my changes

    API changes

    • ComponentEvent now contains an Entity instead of an Index
    • Join::get works with Entitys now
    • JoinIter::get_unchecked has been removed, since there is no way to obtain generation from a pure function
    • FlaggedAccessMut, OccupiedEntry, VacantEntry, and PairedStorage all store an Entity instead of an Index
    • UnprotectedStorages now work with Entitys instead of Indexes

    Obviously, this feature requires many far-reaching changes across many different components of Specs in order to provide the required information all the way down to the level where ComponentEvents are generated and consumed. This is definitely a breaking change in so many different areas and it's unlikely it would be accepted even without the core problems that prevent it from being fully implemented as-is.

    Help needed

    There are three remaining compiler errors that I need to fix:

       Compiling specs v0.16.1 (/Users/LoganDark/specs)
    error[E0308]: mismatched types
      --> src/changeset.rs:83:35
       |
    83 |                 self.inner.remove(id);
       |                                   ^^ expected struct `entity::Entity`, found `u32`
    
    error[E0308]: mismatched types
       --> src/join/mod.rs:394:58
        |
    394 |             .map(|idx| unsafe { J::get(&mut self.values, idx) })
        |                                                          ^^^ expected struct `entity::Entity`, found `u32`
    
    error[E0308]: mismatched types
       --> src/join/par_join.rs:136:40
        |
    136 |             J::get(&mut *values.get(), idx)
        |                                        ^^^ expected struct `entity::Entity`, found `u32`
    
    error: aborting due to 3 previous errors
    

    Perhaps those are a sign that this PR is impossible. Perhaps it's a sign that I need someone more skilled than myself to fix the issue.

    1. The first compiler error has to do with removing all components from a ChangeSet. It iterates through its bitmask and removes every component found inside. The issue is that while it has a bitmask of IDs it does not know the generations for each ID. This will require some deeper modifications to fix.
    2. The second compiler error has to do with the core join logic so it's a bit more complicated. Namely, it's inside the next method of JoinIter. This iterator not only needs to know the IDs it's iterating over but also the generations. This is complicated to implement because the iterator itself is created from only the bitmask and storage from an implementor of Join, which would mean that all implementors of Join would have to store generations in addition to the bitmask, which would probably lead to high memory usage and/or inefficiency. Because of the complexity of this issue it's possible that it will prevent this PR from ever being implemented since it would hurt performance and efficiency.
    3. The third compiler error is another one like the second, complex enough for us to lose all hope. It has to do with parallel joins and producers and other complex things, so we need to drill through the chain a fair bit to find the root of the issue. We see that a map function on BitIter<J::Mask> is providing an index but no generation, and that's really all we need to know. It's iterating over a bitmask again with no generation information. We see that the BitProducer itself that contains the BitIter is stored inside a JoinProducer, which is created using the BitProducer as a component, which means that the BitProducer is what needs to provide generations. Well, the BitProducer is first created in JoinParIter::drive_unindexed which once again produces the BitProducer using only an implementor of Join, which brings us back to the exact same issue as the second compiler error. This makes me sad and it makes the compiler sad too and it also probably makes @Imberflur sad.

    These issues are so complex and involved that it's entirely possible that the architecture of Specs completely prohibits this feature from being implemented effectively unless every Join stored generation information. And no, we can't just not change Joins to work with Entitys because the Join is how you access FlaggedStorages and that's where the FlaggedStorage gets information for constructing ComponentEvents.

    opened by LoganDark 0
  • Allow components to perform cleanup (with access to the World) when they are removed

    Allow components to perform cleanup (with access to the World) when they are removed

    Description

    Allow components one last access to the world when they are deleted. This could be done by either:

    1. Allowing some sort of call inside of Drop that gets the encompassing World (unlikely, probably really unsafe, stinks of globals)
    2. Creating some sort of trait that acts like Drop but allows the component to access the world like a System does. Perhaps it could be named Cleanup or something?

    Motivation

    I have a component that represents ownership over some physics objects. When the entity holding that component is deleted, I want the physics objects to also be deleted. The problem is that inside Drop, I have no access to the physics world because it's a resource. :(

    Drawbacks

    • Is it a breaking change? No, features are only added
    • Can it impact performance, learnability, etc? The performance of deleting large amounts of entities with components that require cleanup may be impacted. However, due to monomorphization, if you go with the cleanup trait then there will be no code generated for components without it, just like Drop. That means only code that explicitly opts into this feature will be impacted

    Unresolved questions

    None right now

    feature-request 
    opened by LoganDark 5
Releases(0.16.1)
  • 0.16.1(Feb 17, 2020)

    • JoinIter now implements Clone when inner types are Clone -- usually for immutable join()s. (#620)
    • Bump hibitset to 0.6.3. (#620)
    • StorageEntry::replace replaces a component, returning the previous value if any. (#622)
    Source code(tar.gz)
    Source code(zip)
  • 0.16.0(Feb 13, 2020)

    • Update syn, quote and proc-macro2 to 1.0. (#648)
    • Implement ParJoin for MaybeJoin if the inner type is ParJoin. (#655)
    • Remove "nightly" feature -- improved panic messages are available on stable. (#671)
    • Bump shred to 0.10.2. (#671, #674, #683)
    • Components and resources no longer need to be Send + Sync if parallel feature is disabled (#673, #674)
    • Bump uuid to 0.8.1. (#683)
    • Bump rayon to 1.3.0. (#683)
    Source code(tar.gz)
    Source code(zip)
  • v0.15.1(Sep 15, 2019)

  • v0.15.0(Jun 29, 2019)

    • Moved World to shred, added WorldExt trait for Specs functionality (#550)
    • Add UuidMarker for UUID <-> Entity mappings (#584)
    • Implement Join on BitSetLike trait object (#599)
    • Expose inner field of AntiStorage (#603)
    • Remove fnv in favour of hashbrown (#606)
    • Reexport hibitset, rayon, shred and shrev (#606)
    • Reexport shred_derive::SystemData when shred-derive feature is enabled (#606)
    • Reexport specs_derive::{Component, ConvertSaveload} when specs-derive feature is enabled (#606)
    Source code(tar.gz)
    Source code(zip)
  • v0.14.1(Nov 26, 2018)

  • v0.12.2(Sep 9, 2018)

  • v0.10(Oct 4, 2017)

    Changes

    • Separate CheckStorage into two variants and fix soundness issues (#203)
    • Fix Merge system and add test for it (#243, #248)
    • Add more examples, docs, tests, benchmarks (#249, #251, #254, #256, #258)
    • Use Results to make Specs more robust (#260)
    • Check code coverage with cargo-travis (#265)
    • Make common::Errors atomic and more convenient (#255, #262)
    • Add World::delete_all to clear the world (#257)
    • Fix insertion into occupied NullStorage entry ([#269])
    • Add Storage::drain method (#273)

    Upgrading

    Upgrading should be easy, because the only breaking changes were the CheckStorage and a few version bumps.

    Source code(tar.gz)
    Source code(zip)
Owner
Amethyst Engine
Data-oriented game engine written in Rust
Amethyst Engine
A tilemap rendering crate for bevy which is more ECS friendly.

bevy_ecs_tilemap A tilemap rendering plugin for bevy which is more ECS friendly by having an entity per tile. Features A tile per entity Fast renderin

John 118 Nov 25, 2021
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
High performance Rust ECS library

Legion aims to be a feature rich high performance Entity component system (ECS) library for Rust game projects with minimal boilerplate. Getting Start

Amethyst Engine 1.2k Nov 25, 2021
A minimalist and safe ECS library for rust!

The full ECS (Entity-Component-System) library. Support an Open Source Developer! ♥️ Composed of two smaller libraries: world_dispatcher: the System p

Joël Lupien 114 Sep 24, 2021
A tilemap rendering crate for bevy which is more ECS friendly.

bevy_ecs_tilemap A tilemap rendering plugin for bevy which is more ECS friendly by having an entity per tile. Features A tile per entity Fast renderin

John 118 Nov 25, 2021
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
ECS-friendly ldtk plugin for bevy, leveraging bevy_ecs_tilemap

bevy_ecs_ldtk An ECS-friendly ldtk plugin for bevy. Uses bevy_ecs_tilemap as a base. Not released yet, still in development. bevy_ecs_tilemap once sup

Trevor Lovell 1 Nov 18, 2021
This project now lives on in a rewrite at https://gitlab.redox-os.org/redox-os/parallel

MIT/Rust Parallel: A Command-line CPU Load Balancer Written in Rust This is an attempt at recreating the functionality of GNU Parallel, a work-stealer

Michael Murphy 1.2k Nov 17, 2021
A parallel universal-ctags wrapper for git repository

ptags A parallel universal-ctags wrapper for git repository Description ptags is a universal-ctags wrapper to have the following features. Search git

null 97 Nov 22, 2021
Fast, parallel, extensible and adaptable genetic algorithms framework written in Rust

oxigen Oxigen is a parallel genetic algorithm framework implemented in Rust. The name comes from the merge of OXIdación (Rust translated to Spanish) a

Martín Pozo 122 Sep 24, 2021
Radiate is a parallel genetic programming engine capable of evolving solutions to many problems as well as training learning algorithms.

Radiate Coming from Evolutionary Radiation. Evolutionary radiation is a rapid increase in the number of species with a common ancestor, characterized

kalivas 86 Nov 22, 2021
Single-reader, multi-writer & single-reader, multi-verifier; broadcasts reads to multiple writeable destinations in parallel

Bus Writer This Rust crate provides a generic single-reader, multi-writer, with support for callbacks for monitoring progress. It also provides a gene

Pop!_OS 25 Jan 5, 2021
Rust crate for configurable parallel web crawling, designed to crawl for content

url-crawler A configurable parallel web crawler, designed to crawl a website for content. Changelog Docs.rs Example extern crate url_crawler; use std:

Pop!_OS 56 Aug 22, 2021
Splits test files into multiple groups to run tests in parallel nodes

split-test split-test splits tests into multiple groups based on timing data to run tests in parallel. Installation Download binary from GitHub releas

Fumiaki MATSUSHIMA 20 Oct 19, 2021
Parallel finance a decentralized lending protocol built on top of the Polkadot ecosystem. Our unique approach will allow users to earn "double interests" from staking and lending their tokens simultaneously.

Parallel Finance A new Cumulus-based Substrate node, ready for hacking ?? Getting Started Follow these steps to get started with the Cumulus Template

parallel-finance 47 Nov 20, 2021
Download a file using multiple threads in parallel for faster download speeds.

multidl Download a file using multiple threads in parallel for faster download speeds. Uses 0 external dependencies. Usage Usage: multidl [--help] ADD

Divyanshu Agrawal 2 Sep 12, 2021
Rust Parallel Iterator With Output Sequential Consistency

par_iter_sync: Parallel Iterator With Sequential Output Crate like rayon do not offer synchronization mechanism. This crate provides easy mixture of p

Congyu 1 Oct 30, 2021
Collects accurate files while running in parallel through directories. (Simple, Fast, Powerful)

collectfiles Collects accurate files while running in parallel through directories. (Simple, Fast, Powerful) | Docs | Latest Note | [dependencies] col

Doha Lee 1 Oct 19, 2021
1 library and 2 binary crates to run SSH/SCP commands on a "mass" of hosts in parallel

massh 1 library and 2 binary crates to run SSH/SCP commands on a "mass" of hosts in parallel. The binary crates are CLI and GUI "frontends" for the li

null 1 Oct 20, 2021
The Solana Program Library (SPL) is a collection of on-chain programs targeting the Sealevel parallel runtime.

Solana Program Library The Solana Program Library (SPL) is a collection of on-chain programs targeting the Sealevel parallel runtime. These programs a

null 2 Nov 23, 2021
Parallel iterator processing library for Rust

Parallel iterator processing library for Rust I keep needing one, so I wrote it. See [IteratorExt] for supported operations. In essence, if you have:

Dawid Ciężarkiewicz 1 Nov 24, 2021