5 releases
0.1.4 | Mar 25, 2024 |
---|---|
0.1.3 | Mar 13, 2024 |
0.1.2 | Feb 20, 2024 |
0.1.1 | Nov 6, 2023 |
0.1.0 | Oct 31, 2023 |
#541 in Game dev
197 downloads per month
Used in 2 crates
33KB
563 lines
🍎 Moonshine Kind
A simple type safety solution for Bevy ECS.
Overview
An Entity
is a generic way to reference entities within Bevy ECS:
#[derive(Component)]
struct FruitBasket {
fruits: Vec<Entity>
}
A problem with using entities in this way is that there is no information about the "kind" of the entity. This can result in code that is error prone, hard to debug, and hard to read.
This crate attempts to solve the problem by introducing a new type Instance<T>
which functions like an entity, but also contains information about the "kind" of the entity:
#[derive(Component)]
struct FruitBasket {
fruits: Vec<Instance<Fruit>>
}
Features
- Improved type safety and readability for entities
- Custom entity kinds
- Kind-specific commands and queries
- Zero or minimal boilerplate for defining entity kinds
Usage
Kind
and Instance
By definition, an entity is of kind T
if it matches the query Query<(), <T as Kind>::Filter>
.
By default, all Bevy components automatically implement the Kind
trait:
impl<T: Component> Kind for T {
type Filter = With<T>;
}
This means you can use any component as an argument to Instance<T>
. For example:
#[derive(Component)]
struct Apple;
#[derive(Component)]
struct Orange;
fn count_apples(apples: Query<Instance<Apple>>) {
println!("Apples: {}", apples.iter().count());
}
Alternatively, you can define your own kinds by implementing the Kind
trait:
struct Fruit;
impl Kind for Fruit {
type Filter = Or<With<Apple>, With<Orange>>;
}
fn count_fruits(fruits: Query<Instance<Fruit>>) {
println!("Fruits: {}", fruits.iter().count());
}
InstanceRef
and InstanceMut
If a kind is also a component (such as Apple
or Orange
in examples above), you may use InstanceRef<T>
and InstanceMut<T>
to access the instance and component data together:
impl Apple {
fn is_fresh(&self) -> bool {
...
}
}
fn fresh_apples(apples: Query<InstanceRef<Apple>>) -> Vec<Instance<Apple>> {
let mut fresh_apples = Vec::new();
for apple in apples.iter() {
if apple.is_fresh() {
fresh_apples.push(apple.instance());
}
}
fresh_apples
}
Instance(Ref)Commands
You may also extend InstanceCommands<T>
and InstanceRefCommands<T>
types to define kind-specific commands.
These behave similar to Instance<T>
and InstanceRef<T>
, and are accessible via GetInstanceCommands
and GetInstanceRefCommands
traits:
#[derive(Component)]
struct Human;
trait Eat {
fn eat(&mut self, fruit: Instance<Fruit>);
}
impl Eat for InstanceCommands<'_, '_, '_, Human> {
fn eat(&mut self, fruit: Instance<Fruit>) {
...
}
}
fn eat(human: Query<Instance<Human>>, fruits: Query<Instance<Fruit>>, mut commands: Commands) {
let human = human.single();
if let Some(fruit) = fruits.iter().next() {
commands.instance(human).eat(fruit);
// Alternatively:
// commands.instance_ref(human).eat(fruit);
}
}
⚠️ There is currently no support for InstanceMutCommands<T>
.
Instance<Any>
When writing generic code, it may be desirable to have an instance that can be of any kind:
use moonshine_kind::Any;
struct Container<T: Kind = Any> {
items: Vec<Instance<T>>
}
Note that Instance<Any>
is functionally equivalent to Entity
.
Examples
See examples for more complete examples.
Limitations
Instance Invalidation
This crate does not monitor instances for invalidation. This means that if an entity is modified in such a way that it no longer matches a given kind T
(such as removing component T
), all instances which reference it would become invalid.
If necessary, you must manually check instances for validity prior to usage:
fn prune_fruits(
In(fruits): In<Vec<Instance<Fruit>>>,
query: Query<Instance<Fruit>>) -> Vec<Instance<Fruit>> {
fruits.retain(|fruit| {
// Is the Fruit still a Fruit?
query.get(fruit.entity()).is_ok()
})
}
Dependencies
~10MB
~187K SLoC