Skip to content

Zeenobit/moonshine_kind

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🍎 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:

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
}

InstanceCommands

You may also extend the InstanceCommands<T> type to define kind-specific commands:

#[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);
    }
}

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()
    })
}

About

A simple type safety solution for Bevy ECS.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages