use std::fmt::{Debug, Formatter, Result as FormatResult};
use std::marker::PhantomData;
use bevy_ecs::prelude::*;
use bevy_ecs::query::WorldQuery;
use bevy_ecs::system::EntityCommands;
use bevy_ecs::world::EntityRef;
pub trait EntityKind: 'static + Send + Sync {
type DefaultBundle: Bundle + Default;
type Bundle: Bundle;
unsafe fn from_entity_unchecked(entity: Entity) -> Self;
fn entity(&self) -> Entity;
}
#[derive(Bundle)]
pub struct KindBundle<T: EntityKind> {
kind: Kind<T>,
#[bundle]
default_bundle: T::DefaultBundle,
#[bundle]
bundle: T::Bundle,
}
impl<T: EntityKind> KindBundle<T> {
pub fn new(bundle: T::Bundle) -> Self {
Self {
kind: Kind::default(),
default_bundle: T::DefaultBundle::default(),
bundle,
}
}
}
impl<T: EntityKind> Clone for KindBundle<T>
where
T::Bundle: Clone,
{
fn clone(&self) -> Self {
Self {
kind: Kind::default(),
default_bundle: T::DefaultBundle::default(),
bundle: self.bundle.clone(),
}
}
}
impl<T: EntityKind> Default for KindBundle<T>
where
T::Bundle: Default,
{
fn default() -> Self {
Self::new(T::Bundle::default())
}
}
#[derive(WorldQuery)]
pub struct WithKind<T: EntityKind> {
with_kind: With<Kind<T>>,
}
#[derive(WorldQuery)]
pub struct EntityWithKind<T: EntityKind> {
entity: Entity,
with_kind: WithKind<T>,
}
impl<T: EntityKind> EntityWithKindItem<'_, T> {
pub fn entity(&self) -> Entity {
self.entity
}
pub fn get(&self) -> T {
unsafe { T::from_entity_unchecked(self.entity) }
}
}
impl<T: EntityKind> PartialEq<T> for EntityWithKindItem<'_, T> {
fn eq(&self, other: &T) -> bool {
self.entity() == other.entity()
}
}
impl<T: EntityKind + Debug> Debug for EntityWithKindItem<'_, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult {
write!(f, "{:?}", self.get())
}
}
pub struct EntityKindCommands<'w, 's, 'a, T: EntityKind>(
EntityCommands<'w, 's, 'a>,
PhantomData<T>,
);
impl<'w, 's, 'a, T: EntityKind> EntityKindCommands<'w, 's, 'a, T> {
pub unsafe fn from_entity_unchecked(entity: EntityCommands<'w, 's, 'a>) -> Self {
Self(entity, PhantomData)
}
pub fn entity(&self) -> Entity {
self.0.id()
}
pub fn get(&self) -> T {
unsafe { T::from_entity_unchecked(self.entity()) }
}
pub fn commands(&mut self) -> &mut Commands<'w, 's> {
self.0.commands()
}
pub fn as_entity(&mut self) -> &mut EntityCommands<'w, 's, 'a> {
&mut self.0
}
pub fn insert(&mut self, component: impl Component) -> &mut Self {
self.0.insert(component);
self
}
pub fn remove<S: Component>(&mut self) -> &mut Self {
self.0.remove::<S>();
self
}
}
pub trait InsertKind<'w, 's, 'a> {
fn insert_kind<T: EntityKind>(self, bundle: T::Bundle) -> EntityKindCommands<'w, 's, 'a, T>;
}
impl<'w, 's, 'a> InsertKind<'w, 's, 'a> for EntityCommands<'w, 's, 'a> {
fn insert_kind<T: EntityKind>(
mut self,
bundle: T::Bundle,
) -> EntityKindCommands<'w, 's, 'a, T> {
self.insert_bundle(KindBundle::<T>::new(bundle));
unsafe { EntityKindCommands::from_entity_unchecked(self) }
}
}
pub trait KindCommands<'w, 's, 'a> {
fn spawn_with_kind<T: EntityKind>(self, bundle: T::Bundle)
-> EntityKindCommands<'w, 's, 'a, T>;
fn with_kind<T: EntityKind>(self, kind: &T) -> EntityKindCommands<'w, 's, 'a, T>;
}
impl<'w, 's, 'a> KindCommands<'w, 's, 'a> for &'a mut Commands<'w, 's> {
fn spawn_with_kind<T: EntityKind>(
self,
bundle: T::Bundle,
) -> EntityKindCommands<'w, 's, 'a, T> {
self.spawn().insert_kind(bundle)
}
fn with_kind<T: EntityKind>(self, kind: &T) -> EntityKindCommands<'w, 's, 'a, T> {
unsafe { EntityKindCommands::from_entity_unchecked(self.entity(kind.entity())) }
}
}
pub trait TryWithKind {
fn try_with_kind<T: EntityKind>(self) -> Option<T>;
}
impl TryWithKind for &EntityRef<'_> {
fn try_with_kind<T: EntityKind>(self) -> Option<T> {
self.contains::<Kind<T>>()
.then(|| unsafe { T::from_entity_unchecked(self.id()) })
}
}
#[derive(Component)]
struct Kind<T: EntityKind>(PhantomData<T>);
impl<T: EntityKind> Default for Kind<T> {
fn default() -> Self {
Self(PhantomData)
}
}
#[cfg(test)]
mod tests {
use super::*;
use bevy_ecs::system::CommandQueue;
struct Containable(Entity);
impl EntityKind for Containable {
type DefaultBundle = ();
type Bundle = ();
unsafe fn from_entity_unchecked(entity: Entity) -> Self {
Self(entity)
}
fn entity(&self) -> Entity {
self.0
}
}
#[derive(Component, Default)]
struct Items(Vec<Containable>);
#[derive(Component)]
struct Capacity(usize);
struct Container(Entity);
impl EntityKind for Container {
type DefaultBundle = (Items,);
type Bundle = (Capacity,);
unsafe fn from_entity_unchecked(entity: Entity) -> Self {
Self(entity)
}
fn entity(&self) -> Entity {
self.0
}
}
trait InsertIntoContainer {
fn insert_into_container(self, container: &Container) -> Self;
}
impl InsertIntoContainer for &mut EntityKindCommands<'_, '_, '_, Containable> {
fn insert_into_container(self, &Container(entity): &Container) -> Self {
let item = self.get();
self.commands().add(move |world: &mut World| {
let &Capacity(capacity) = world
.get::<Capacity>(entity)
.expect("container must have capacity");
let Items(items) = world
.get_mut::<Items>(entity)
.expect("container must have items").into_inner();
if items.len() < capacity {
items.push(item);
}
});
self
}
}
pub trait Execute {
fn execute<F: FnOnce(&World, Commands) -> R, R>(self, f: F) -> R;
}
impl Execute for &mut World {
fn execute<F: FnOnce(&World, Commands) -> R, R>(self, f: F) -> R {
let mut queue = CommandQueue::default();
let commands = Commands::new(&mut queue, self);
let result = f(self, commands);
queue.apply(self);
result
}
}
#[test]
fn it_works() {
let mut world = World::new();
let container: Container = world
.execute(|_, mut commands| commands.spawn_with_kind::<Container>((Capacity(5),)).get());
assert!(world.entity(container.entity()).contains::<Capacity>());
assert!(world.entity(container.entity()).contains::<Items>());
world.execute(|_, mut commands| {
commands.spawn_with_kind::<Containable>(()).insert_into_container(&container);
});
assert_eq!(world.get::<Items>(container.entity()).unwrap().0.len(), 1);
}
}