Crate moonshine_kind
source ยทExpand description
ยง๐ Moonshine Kind
Simple type safety solution for Bevy.
ยงOverview
An Entity is a generic way to reference entities within Bevy:
use bevy::prelude::*;
#[derive(Component)]
struct FruitBasket {
fruits: Vec<Entity>
}A problem with using entities in this way is the lack of information about the โkindโ of the entity. This results in code that is error prone, hard to debug, and read.
This crate attempts to solve this problem by introducing a new Instance<T> type which behaves like an Entity but also contains information about the โkindโ of the entity:
use bevy::prelude::*;
use moonshine_kind::prelude::*;
#[derive(Component)]
struct Fruit;
#[derive(Component)]
struct FruitBasket {
fruits: Vec<Instance<Fruit>>
}ยงFeatures
- Improved type safety and readability for Bevy code
- Ability to define custom entity kinds
- Ability to define commands for specific entity kinds
- Zero or minimal boilerplate
ยงUsage
ยงKind and Instance
By definition, an Entity is of Kind T if it matches Query<(), <T as Kind>::Filter>.
Any Component automatically implements the Kind trait:
impl<T: Component> Kind for T {
type Filter = With<T>;
}This means you may use any Component as an argument to Instance<T>:
use bevy::prelude::*;
use moonshine_kind::prelude::*;
#[derive(Component)]
struct Apple;
fn count_apples(apples: Query<Instance<Apple>>) {
println!("Apples: {}", apples.iter().count());
}Alternatively, you may also define your own kind by implementing the Kind trait:
use bevy::prelude::*;
use moonshine_kind::prelude::*;
#[derive(Component)]
struct Apple;
#[derive(Component)]
struct Orange;
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, you may use InstanceRef<T> and InstanceMut<T> to access Instance<T> and the associated component data with a single query term:
use bevy::prelude::*;
use moonshine_kind::prelude::*;
#[derive(Component)]
struct Apple {
freshness: f32
}
impl Apple {
fn is_fresh(&self) -> bool {
self.freshness >= 1.0
}
}
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
}ยงInstanceCommands
You may also extend InstanceCommands<T> to define Commands specific to a Kind.
InstanceCommands<T> behaves like EntityCommands, and is accessible via commands.instance(...) (see GetInstanceCommands<T> for details):
use bevy::prelude::*;
use moonshine_kind::prelude::*;
struct Fruit;
impl Kind for Fruit {
type Filter = (/* ... */);
}
#[derive(Component)]
struct Human;
trait Eat {
fn eat(&mut self, fruit: Instance<Fruit>);
}
// Humans can eat:
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);
}
}ยงInstance<Any>
When writing generic code, it may be desirable to have an instance that can be of Any kind:
use moonshine_kind::{prelude::*, Any};
struct Container<T: Kind = Any> {
items: Vec<Instance<T>>
}Instance<Any> is functionally equivalent to Entity.
ยงCasting
An Instance<T> is safely convertible to an Instance<U> if CastInto<U> is implemented for T.
This is done by using the .cast_into() method:
You may use the kind macro to implement this trait for given kind pair:
use bevy::prelude::*;
use moonshine_kind::prelude::*;
#[derive(Component)]
struct Apple;
struct Fruit;
impl Kind for Fruit {
type Filter = With<Apple>;
}
// An Apple is a Fruit because we said so:
kind!(Apple is Fruit);
fn init_apple(apple: Instance<Apple>, commands: &mut Commands) {
init_fruit(apple.cast_into(), commands);
// ...
}
fn init_fruit(fruit: Instance<Fruit>, commands: &mut Commands) {
// ...
}ยงExamples
See examples/fruits.rs for a complete example.
ยง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 some Kind T (such as removing Component T), any Instance<T> which references it would be invalid.
It is recommended to avoid using kind semantics for components that may be removed at runtime without despawning their associated entity.
However, if necessary, you may check instances for validity prior to usage:
use bevy::prelude::*;
use moonshine_kind::prelude::*;
struct Fruit;
impl Kind for Fruit {
type Filter = (/* ... */);
}
fn prune_fruits(
mut fruits: Vec<Instance<Fruit>>,
query: &Query<(), <Fruit as Kind>::Filter>
) -> Vec<Instance<Fruit>> {
fruits.retain(|fruit| {
// Is the Fruit still a Fruit?
query.get(fruit.entity()).is_ok()
});
fruits
}ยงSupport
Please post an issue for any bugs, questions, or suggestions.
You may also contact me on the official Bevy Discord server as @Zeenobit.
Modulesยง
Macrosยง
- safe_castDeprecated
Structsยง
- Represents the kind of any
Entity. EntityCommandswith kind semantics.- A
QueryDataitem which represents a mutable reference to anInstance<T>and its associatedComponent. - Automatically generated
WorldQueryitem type forInstanceMut, returned when iterating over query results. - Automatically generated
WorldQuerytype for a read-only variant ofInstanceMut. - Automatically generated
WorldQueryitem type forInstanceMutReadOnly, returned when iterating over query results.
Traitsยง
- A trait which allows safe casting from one
Kindto another. - Extension trait to access
InstanceCommands<T>fromCommands. - A type which represents the kind of an
Entity.
Type Aliasesยง
- A short alias for using a
Kindas aQueryFilter.