This crate defines the ComponentGroup trait. This trait is used to make managing a group of specs::Component instances easier. This is useful for when you have several components that are often created, read, and updated together. You can use this trait to easily move an entire group of components between instances of specs::World .

This crate also provides a custom derive (documented below) that you can use to automatically implement the trait. This removes any of the boilerplate you may have needed to write in order to implement the trait yourself and makes modifying your group of components much easier.

use component_group :: ComponentGroup ; #[ derive ( Debug , Clone , Component )] #[ storage ( VecStorage )] pub struct Position { x : i32 , y : i32 } #[ derive ( Debug , Clone , Component )] #[ storage ( VecStorage )] pub struct Velocity { x : i32 , y : i32 } #[ derive ( Debug , Clone , Component )] #[ storage ( VecStorage )] pub struct Health ( u32 ); #[ derive ( Debug , Clone , Component )] #[ storage ( HashMapStorage )] pub struct Animation { frame : usize } #[ derive ( ComponentGroup )] struct PlayerComponents { position : Position , velocity : Velocity , health : Health , animation : Option < Animation > , }

See the documentation for ComponentGroup for the exact operations you can now perform on the PlayerComponents struct. The rest of the documentation below goes into the motivation behind this crate and details about how to use it.

The ComponentGroup trait makes operating on many components at once much easier and less error-prone. Trying to update all of your code every time you add a new component to an entity is very difficult to manage. By grouping all of the components together in a single struct, you can better manage all of them and make fewer mistakes when you edit your code. The following is an example of what your code might look like without this trait.

use specs ::{ World , WorldExt , Builder , Entity , Component , VecStorage , ReadStorage , WriteStorage , Join }; use specs :: error :: Error as SpecsError ; use specs_derive :: Component ; #[ derive ( Debug , Component )] #[ storage ( VecStorage )] pub struct Position { x : i32 , y : i32 } #[ derive ( Debug , Component )] #[ storage ( VecStorage )] pub struct Velocity { x : i32 , y : i32 } #[ derive ( Debug , Component )] #[ storage ( VecStorage )] pub struct Health ( u32 ); fn main () - > Result < (), SpecsError > { let mut level1 = World :: new (); level1 . create_entity () . with ( Position { x : 12 , y : 59 }) . with ( Velocity { x : - 1 , y : 2 }) . with ( Health ( 5 )) . build (); let mut level2 = World :: new (); let player_entity = find_player_entity ( & level1 ); let ( positions , velocities , healths ) = level1 . system_data :: < ( ReadStorage < Position > , ReadStorage < Velocity > , ReadStorage < Health > , ) > (); let position = positions . get ( player_entity ). map ( | pos | Position { x : pos . x , y : pos . y }) . expect ( "expected a Position component to be present" ); let velocity = velocities . get ( player_entity ). map ( | vel | Velocity { x : vel . x , y : vel . y }) . expect ( "expected a Velocity component to be present" ); let health = healths . get ( player_entity ). map ( | health | Health ( health . 0 )) . expect ( "expected a Health component to be present" ); level2 . create_entity () . with ( position ) . with ( velocity ) . with ( health ) . build (); let player_entity = find_player_entity ( & level2 ); let ( positions , velocities , healths ) = level2 . system_data :: < ( ReadStorage < Position > , ReadStorage < Velocity > , ReadStorage < Health > , ) > (); let position = positions . get ( player_entity ). map ( | pos | Position { x : pos . x , y : pos . y }) . expect ( "expected a Position component to be present" ); let velocity = velocities . get ( player_entity ). map ( | vel | Velocity { x : vel . x , y : vel . y }) . expect ( "expected a Velocity component to be present" ); let health = healths . get ( player_entity ). map ( | health | Health ( health . 0 )) . expect ( "expected a Health component to be present" ); let player_entity = find_player_entity ( & level1 ); let ( mut positions , mut velocities , mut healths ) = level1 . system_data :: < ( WriteStorage < Position > , WriteStorage < Velocity > , WriteStorage < Health > , ) > (); positions . insert ( player_entity , position ) ? ; velocities . insert ( player_entity , velocity ) ? ; healths . insert ( player_entity , health ) ? ; Ok (()) }

There is a lot of duplication in this code! Many of the duplicated pieces of code have slight differences like which world the components are being fetched from or whether we wanted a ReadStorage or a WriteStorage .The purpose of this crate is to take all of that duplication and make reusable methods that operate on all of the components at once.

Instead of having to keep track of all of this code throughout your codebase, implementing the ComponentGroup trait puts it all in one place. This makes updating your group much less error-prone because adding/modifying/removing a component to/from the group only requires modifying one area of code.

The code that needs to be written to operate on all of these components together is unavoidable given the API that specs provides, but at least you can make it easier by using this crate. We've eliminated the need to write most of the repetitive code you saw above by allowing you to automatically derive the ComponentGroup trait. All you need to do is define the components in the group and everything else is generated for you.

The next section of the documentation shows you how to manually implement the ComponentGroup trait for a given group of components. This is still a lot of boilerplate, but it is all grouped in one place. The section after that shows how to remove all the boilerplate by automatically deriving the trait.

This example is meant to show what manually implementing this trait can be like. The basic idea is to move all of the duplicated code from above into reusable methods on a struct that groups all of the components that need to be modified together. Implementing it manually like this is still quite cumbersome, so a way to automatically derive the trait is also provided. See below for more details about that.

use component_group :: ComponentGroup ; use specs ::{ World , WorldExt , Builder , Entity , Entities , Component , VecStorage , ReadStorage , WriteStorage , Join }; use specs :: error :: Error as SpecsError ; use specs_derive :: Component ; #[ derive ( Debug , Component )] #[ storage ( VecStorage )] pub struct Position { x : i32 , y : i32 } #[ derive ( Debug , Component )] #[ storage ( VecStorage )] pub struct Velocity { x : i32 , y : i32 } #[ derive ( Debug , Component )] #[ storage ( VecStorage )] pub struct Health ( u32 ); pub struct PlayerComponents { position : Position , velocity : Velocity , health : Health , } impl ComponentGroup for PlayerComponents { type UpdateError = SpecsError ; fn first_from_world ( world : & World ) - > Option < ( Entity , Self ) > { let ( entities , positions , velocities , healths ) = world . system_data :: < ( Entities , ReadStorage < Position > , ReadStorage < Velocity > , ReadStorage < Health > , ) > (); ( & entities , & positions , & velocities , & healths ). join (). next () . map ( | ( entity , pos , vel , health ) | ( entity , Self { position : Position { x : pos . x , y : pos . y }, velocity : Velocity { x : vel . x , y : vel . y }, health : Health ( health . 0 ), })) } fn from_world ( world : & World , entity : Entity ) - > Self { let ( positions , velocities , healths ) = world . system_data :: < ( ReadStorage < Position > , ReadStorage < Velocity > , ReadStorage < Health > , ) > (); Self { position : positions . get ( entity ). map ( | pos | Position { x : pos . x , y : pos . y }) . expect ( "expected a Position component to be present" ), velocity : velocities . get ( entity ). map ( | vel | Velocity { x : vel . x , y : vel . y }) . expect ( "expected a Velocity component to be present" ), health : healths . get ( entity ). map ( | health | Health ( health . 0 )) . expect ( "expected a Health component to be present" ), } } fn create ( self , world : & mut World ) - > Entity { let Self { position , velocity , health } = self ; world . create_entity () . with ( position ) . with ( velocity ) . with ( health ) . build () } fn update ( self , world : & mut World , entity : Entity ) - > Result < (), Self :: UpdateError > { let ( mut positions , mut velocities , mut healths ) = world . system_data :: < ( WriteStorage < Position > , WriteStorage < Velocity > , WriteStorage < Health > , ) > (); positions . insert ( entity , self . position ) ? ; velocities . insert ( entity , self . velocity ) ? ; healths . insert ( entity , self . health ) ? ; Ok (()) } fn remove ( world : & mut World , entity : Entity ) - > Self { let ( mut positions , mut velocities , mut healths ) = world . system_data :: < ( WriteStorage < Position > , WriteStorage < Velocity > , WriteStorage < Health > , ) > (); Self { position : positions . remove ( entity ). map ( | pos | Position { x : pos . x , y : pos . y }) . expect ( "expected a Position component to be present" ), velocity : velocities . remove ( entity ). map ( | vel | Velocity { x : vel . x , y : vel . y }) . expect ( "expected a Velocity component to be present" ), health : healths . remove ( entity ). map ( | health | Health ( health . 0 )) . expect ( "expected a Health component to be present" ), } } } fn main () - > Result < (), SpecsError > { let mut level1 = World :: new (); let player = PlayerComponents { position : Position { x : 12 , y : 59 }, velocity : Velocity { x : - 1 , y : 2 }, health : Health ( 5 ), }; player . create ( & mut level1 ); let mut level2 = World :: new (); let player_entity = find_player_entity ( & level1 ); let player = PlayerComponents :: from_world ( & level1 , player_entity ); player . create ( & mut level2 ); let ( _ , player ) = PlayerComponents :: first_from_world ( & level2 ). unwrap (); let player_entity = find_player_entity ( & level1 ); player . update ( & mut level1 , player_entity ) ? ; Ok (()) }

You can also automatically implement the ComponentGroup trait using #[derive(ComponentGroup)] . This removes all the boilerplate you saw in the example above and automatically provides each of the methods in ComponentGroup . All fields in the struct must implement Clone so that they can be copied within the methods that get implemented.

use component_group :: ComponentGroup ; use specs ::{ World , WorldExt , Component , VecStorage }; use specs :: error :: Error as SpecsError ; use specs_derive :: Component ; #[ derive ( Debug , Clone , Component )] #[ storage ( VecStorage )] pub struct Position { x : i32 , y : i32 } #[ derive ( Debug , Clone , Component )] #[ storage ( VecStorage )] pub struct Velocity { x : i32 , y : i32 } #[ derive ( Debug , Clone , Component )] #[ storage ( VecStorage )] pub struct Health ( u32 ); #[ derive ( ComponentGroup )] struct PlayerComponents { position : Position , velocity : Velocity , health : Health , } fn main () - > Result < (), SpecsError > { let mut level1 = World :: new (); let player = PlayerComponents { position : Position { x : 12 , y : 59 }, velocity : Velocity { x : - 1 , y : 2 }, health : Health ( 5 ), }; player . create ( & mut level1 ); let mut level2 = World :: new (); let player_entity = find_player_entity ( & level1 ); let player = PlayerComponents :: from_world ( & level1 , player_entity ); player . create ( & mut level2 ); let ( _ , player ) = PlayerComponents :: first_from_world ( & level2 ). unwrap (); let player_entity = find_player_entity ( & level1 ); player . update ( & mut level1 , player_entity ) ? ; Ok (()) }

You can also use Option to ignore part of the group if it isn't specified during creation or if it isn't available in the World during extraction. If the field is None , a call to update will remove that component for that entity from the component's storage.

#[ derive ( Debug , Clone , Component )] #[ storage ( HashMapStorage )] pub struct Animation { frame : usize } #[ derive ( ComponentGroup )] struct PlayerComponents { position : Position , velocity : Velocity , health : Health , animation : Option < Animation > , } fn main () - > Result < (), SpecsError > { let mut level1 = World :: new (); let player = PlayerComponents { position : Position { x : 12 , y : 59 }, velocity : Velocity { x : - 1 , y : 2 }, health : Health ( 5 ), animation : None , }; player . create ( & mut level1 ); let mut level2 = World :: new (); let player_entity = find_player_entity ( & level1 ); let player = PlayerComponents :: from_world ( & level1 , player_entity ); player . create ( & mut level2 ); let ( _ , player ) = PlayerComponents :: first_from_world ( & level2 ). unwrap (); let player_entity = find_player_entity ( & level1 ); player . update ( & mut level1 , player_entity ) ? ; Ok (()) }

Note: The way we match for the Option type is very naive right now. Using Option<YourComponent> as the type of your field will work, but using std::option::Option<YourComponent> will not.

In the future, when Generic Associated Types (GATs) are implemented, this trait may be updated as follows:

ⓘ This example is not tested

pub trait ComponentGroup : Sized { type UpdateError ; type GroupIter < 'a > ; fn all_from_world < 'a > ( world : & 'a World ) - > Self :: GroupIter < 'a > ; }

It just isn't possible to express this as part of the trait right now. Adding this would be a breaking change, so that update would not occur without a new major version being released.

As a workaround, you can add the method yourself using the impl Trait feature:

#[ derive ( ComponentGroup )] struct PlayerComponents { position : Position , velocity : Velocity , health : Health , } impl PlayerComponents { pub fn all_from_world < 'a > ( world : & 'a World ) - > impl Iterator < Item = Self > + 'a { } } fn main () { let mut level1 = World :: new (); for group in PlayerComponents :: all_from_world ( & level1 ) { } }

It is possible to use the ComponentGroup trait and custom derive with generic structs. Just make sure to add Send + Sync + Component + Clone trait bounds to the generic type parameters or you will get a compile error. (The Send + Sync part is required by the specs crate.)