๐Ÿ“ฆ Zeenobit / moonshine_kind

A simple type safety solution for Bevy ECS.

โ˜… 46 stars โ‘‚ 3 forks ๐Ÿ‘ 46 watching โš–๏ธ MIT License
๐Ÿ“ฅ Clone https://github.com/Zeenobit/moonshine_kind.git
HTTPS git clone https://github.com/Zeenobit/moonshine_kind.git
SSH git clone git@github.com:Zeenobit/moonshine_kind.git
CLI gh repo clone Zeenobit/moonshine_kind
Zeenobit Zeenobit Update README.md 5e3b12d 3 months ago ๐Ÿ“ History
๐Ÿ“‚ main View all commits โ†’
๐Ÿ“ .github
๐Ÿ“ examples
๐Ÿ“ src
๐Ÿ“„ .gitattributes
๐Ÿ“„ .gitignore
๐Ÿ“„ Cargo.toml
๐Ÿ“„ LICENSE
๐Ÿ“„ README.md
๐Ÿ“„ README.md

๐ŸŽ Moonshine Kind

crates.io downloads docs.rs license stars

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
  • No runtime overhead
  • Zero boilerplate
This crate may be used separately, but is also included as part of ๐Ÿธ Moonshine Core.

Usage

[Kind] and [Instance<T>]

By definition, an [Entity] is of kind T if it matches [Query<(), <T as Kind>::Filter>].

Any [Component] automatically implements the [Kind] trait:

``rust,ignore impl<T: Component> Kind for T { type Filter = With<T>; } %%CODEBLOCK2%%rust use bevy::prelude::*; use moonshine_kind::prelude::*; #[derive(Component)] struct Apple; fn count_apples(apples: Query<Instance<Apple>>) { println!("Apples: {}", apples.iter().count()); } %%CODEBLOCK3%%rust 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()); } %%CODEBLOCK4%%rust 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 } %%CODEBLOCK5%%rust 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().unwrap(); if let Some(fruit) = fruits.iter().next() { commands.instance(human).eat(fruit); } } %%CODEBLOCK6%%rust use moonshine_kind::{prelude::*, Any}; struct Container<T: Kind = Any> { items: Vec<Instance<T>> } %%CODEBLOCK7%%rust 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: impl CastInto<Fruit> for Apple {} fn init_apple(apple: Instance<Apple>, commands: &mut Commands) { init_fruit(apple.cast_into(), commands); // ... } fn init_fruit(fruit: Instance<Fruit>, commands: &mut Commands) { // ... } %%CODEBLOCK8%%rust use bevy::prelude::*; use moonshine_kind::prelude::*; #[derive(Component, Default)] struct Apple; #[derive(Component)] #[require(Apple)] // Require all GrannySmith instances to also have Apple struct GrannySmith; impl CastInto<Apple> for GrannySmith {} // GrannySmith is an Apple; Guaranteed! %%CODEBLOCK9%%rust 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](https://github.com/Zeenobit/moonshine_kind/issues/new) for any bugs, questions, or suggestions. You may also contact me on the official [Bevy Discord](https://discord.gg/bevy) server as **@Zeenobit**. [Entity]:https://docs.rs/bevy/latest/bevy/ecs/entity/struct.Entity.html [Component]:https://docs.rs/bevy/latest/bevy/ecs/component/trait.Component.html [Query]:https://docs.rs/bevy/latest/bevy/ecs/system/struct.Query.html [Commands]:https://docs.rs/bevy/latest/bevy/ecs/prelude/struct.Commands.html [EntityCommands]:https://docs.rs/bevy/latest/bevy/ecs/system/struct.EntityCommands.html [Kind]:https://docs.rs/moonshine-kind/0.1.4/moonshine_kind/trait.Kind.html [Instance]:https://docs.rs/moonshine-kind/latest/moonshine_kind/struct.Instance.html [InstanceRef]:https://docs.rs/moonshine-kind/latest/moonshine_kind/struct.InstanceRef.html [InstanceMut]:https://docs.rs/moonshine-kind/latest/moonshine_kind/struct.InstanceMut.html [InstanceCommands]:https://docs.rs/moonshine-kind/latest/moonshine_kind/struct.InstanceCommands.html [GetInstanceCommands]:https://docs.rs/moonshine-kind/latest/moonshine_kind/trait.GetInstanceCommands.html [Any]:https://docs.rs/moonshine-kind/latest/moonshine_kind/struct.Any.html [CastInto`]:https://docs.rs/moonshine-kind/latest/moonshine_kind/trait.CastInto.html