brood
A fast and flexible entity component system library.
brood is built from the ground-up with the main goals of being ergonomic to use while also being as fast as, if not faster than, other popular entity component system (commonly abbreviated as ECS) libraries. brood is built with heterogeneous lists to allow for sets of arbitrary numbers of components, meaning there are no limitations on the size of your entities or the scope of your system views. All features you would expect from a standard ECS library are present, including interoperation with the serde and rayon libraries for serialization and parallel processing respectively.
Key Features
- Entities made up of an arbitrary number of components.
- Built-in support for
serde, providing pain-free serialization and deserialization ofWorldcontainers. - Inner- and outer-parallelism using
rayon. - Minimal boilerplate.
no_stdcompatible.
Usage
There are two main sides to using brood: storing entities and operating on entities.
Storing Entities
Before storing entities, there are a few definitions that should be established:
- Component: A single piece of data. In terms of this library, it is any type that implements the
Anytrait. - Entity: A set of components. These are defined using the
entity!()macro. - World: A container of entities.
Components are defined by simply defining their types. For example, the following structs are components:
In order to use these components within a World container, they will need to be contained in a Registry, provided to a World on creation. A Registry can be created using the Registry!() macro.
use Registry;
type Registry = Registry!;
A World can then be created using this Registry, and entities can be stored inside it.
use ;
let mut world = new;
// Store an entity inside the newly created World.
let position = Position ;
let velocity = Velocity ;
world.insert;
Note that entities stored in world above can be made up of any subset of the Registry's components, and can be provided in any order.
Operating on Entities
To operate on the entities stored in a World, a System must be used. Systems are defined to operate on any entities containing a specified set of components, reading and modifying those components. An example system could be defined and run as follows:
use ;
;
world.run_system;
This system will operate on every entity that contains both the Position and Velocity components (regardless of what other components they may contain), updating the Position component in-place using the value contained in the Velocity component.
There are lots of options for more complicated Systems, including optional components, custom filters, and post-processing logic. See the documentation for more information.
Serialization/Deserialization
brood provides first-class support for serialization and deserialization using serde. By enabling the serde crate feature, World containers and their contained entities can be serialized and deserialized using serde Serializers and Deserializers. Note that a World is (de)serializable as long as every component in the World's Registry is (de)serializable.
For example, a World can be serialized to bincode (and deserialized from the same) as follows:
use ;
type Registry = Registry!;
let mut world = new;
// Insert several entities made of different components.
world.insert
Note that there are two modes for serialization, depending on whether the serializer and deserializer is human readable. Human readable serialization will serialize entities row-wise, which is slower but easier to read by a human. Non-human readable serialization will serialize entities column-wise, which is much faster but much more difficult to read manually.
Parallel Processing
brood supports parallel processing through rayon. By enabling the rayon crate feature, operations on a World can be parallelized.
Operating on Entities in Parallel
To parallelize system operations on entities (commonly referred to as inner-parallelism), a ParSystem can be used instead of a standard System. This will allow the ParSystem's operations to be spread across multiple CPUs. For example, a ParSystem can be defined as follows:
use ;
use ParallelIterator;
type Registry = Registry!;
let mut world = new;
// Store an entity inside the newly created World.
let position = Position ;
let velocity = Velocity ;
world.insert;
;
world.run_par_system;
Defining ParSystems is very similar to defining Systems. See the documentation for more definition options.
Running Systems in Parallel
Multiple Systems and ParSystems can be run in parallel as well by defining a Schedule. A Schedule will automatically divide Systems into stages which can each be run all at the same time. These stages are designed to ensure they do not violate Rust's borrowing and mutability rules and are completely safe to use.
Define and run a Schedule that contains multiple Systems as follows:
use ;
;
type Registry = Registry!;
let mut world = new;
// Store an entity inside the newly created World.
let position = Position ;
let velocity = Velocity ;
world.insert;
;
;
let mut schedule = schedule!;
world.run_schedule;
Note that stages are determined by the Views of each System. Systems whose Views do not contain conflicting mutable borrows of components are grouped together into a single stage.
Minimum Supported Rust Version
This crate is guaranteed to compile on stable rustc 1.65.0 and up.
License
This project is licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.