Specs - Parallel ECS

Related tags

rust ecs 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.

    discussion enhancement 
    opened by sunjay 19
  • 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
  • Export derive macro for `Component` in prelude.

    Export derive macro for `Component` in prelude.

    Checklist N/A

    opened by msmorgan 0
  • Consider inlcuding the entity generation in ComponentEvent

    Consider inlcuding the entity generation in ComponentEvent

    Description

    Including the entity generation instead of just the index within the component event used by FlaggedStorage.

    Motivation

    When an entity is deleted removal events are generated for its components in FlaggedStorages. If another entity is created at the same index there is no way to tell if the removed components are from the old entity or the new one. When using this for network syncing the means we end up sending useless component removed events (since we can't be sure they don't apply to the new entity).

    Drawbacks

    This would be a breaking change and would increase the size of ComponentEvent

    Unresolved questions


    I would be happy to implement this if approved.

    feature-request 
    opened by Imberflur 1
  • Can't use derive macro for Component if specs::Component is used under another name

    Can't use derive macro for Component if specs::Component is used under another name

    The derive macro for components seems to be assuming that the literal string "Component" in my source code is going to point to specs::Component.

    https://github.com/amethyst/specs/blob/d4435bdf496cf322c74886ca09dd8795984919b4/specs-derive/src/lib.rs#L72

    This isn't true if I have my own Component type and I have done something like use specs::Component as SComponent;. It's also not true if I'm also using another library using similar code that wants ownership of the non-namespaced name Component.

    The macro should be changed to use a fully-qualified specs::Component, so it can coexist with Components from other crates.

    Or am I doing something very wrong?

    opened by interfect 1
  • Question: Lifetime for components

    Question: Lifetime for components

    I use a "lifetime" component to handle entity's removing. This is pretty easy, as I can iterate all entities with "Lifetime" component and check if lifetime is still valid, otherwise delete entity.

    I want to have the same for components. Some components should only live for "x" seconds.

    Do you have any idea how to implement this?

    I first thought of a lazy_exec with time delay (just removing comp from entity). The solution should be similar to lazy Update, some "Read" resource which can be written in parallel.

    I do not want to iterate over all components. I do not want a lifetime field in every component.

    Thanks

    opened by Kaiser1989 10
  • How to visualize the storage while debugging?

    How to visualize the storage while debugging?

    First of all, this is a wonderful crate. I loved working on it and it forces me to follow the ECS principles. Thanks for your contribution,

    My Setup: I have N entities and I modify components using couple of systems independently using Dispatchers. Once it is completed, I have to pick some entities and derive some metrics (see below).

     // For example, after dispatcher I wanted to get the total heath and then end the program
     let health_storage = world.read_component::<Health>();
     let pl1_health = health_storage.get(pl1).unwrap(); // I want to avoid this
     let pl2_health = health_storage.get(pl2).unwrap(); // I want to avoid this
     let total_health = pl1_health + pl2_health;
     println!("{}", total_health);
     // Since I am working with multiple entities, I didnt do these lines as a system
     // When I set breakpoint, health_storage is shown as pointers
    

    Now the challenge is, if I debug this using Visual C++ or llvm in VSCode, the health_storage is not showing the values. It only shows pointers and I have to use get(Entity) to visualise. I would have been happy if I can execute the command directly while debugging (like watch commands) but Rust has issues with calling trait functions.

    Recently, I came to know about NatViz which help in improving visualisation while using Visual C++ debugger. In fact, I modified it to support NDArray and it works like a charm. I wanted to do the same with Storage but don't know how to list the entities-component pair. Can you help me?

    image

    One more question, how do you guys debug? Do you use println!?

    opened by selvavm 0
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
Scion is a tiny 2D game library built on top of wgpu, winit and legion.

Scion is a 2D game library made in rust. Please note that this project is in its first milestones and is subject to change according to convience need

Jérémy Thulliez 42 Jun 13, 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.1k Jun 10, 2021
A curated list of wgpu code and resources.

Awesome wgpu A curated list of wgpu code and resources. PRs welcome. About wgpu https://github.com/gfx-rs/wgpu-rs matrix chat https://matrix.to/#/#wgp

Roman Frołow 167 May 26, 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 43 Jun 13, 2021
Proof-of-concept of getting OpenXR rendering support for Bevy game engine using gfx-rs abstractions

Introduction Proof-of-concept of getting OpenXR rendering support for Bevy game engine using gfx-rs abstractions. (hand interaction with boxes missing

Mika 16 Jun 12, 2021
A modern 3D/2D game engine that uses wgpu.

Harmony A modern 3D/2D game engine that uses wgpu and is designed to work out of the box with minimal effort. It uses legion for handling game/renderi

John 146 Apr 16, 2021
A refreshingly simple data-driven game engine built in Rust

What is Bevy? Bevy is a refreshingly simple data-driven game engine built in Rust. It is free and open-source forever! WARNING Bevy is still in the ve

Bevy Engine 9.4k Jun 13, 2021