use crate::Submitter;
use bevy::{ecs::system::EntityCommands, prelude::*};
use std::borrow::Cow;
use std::marker::PhantomData;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConstructError {
#[error("invalid properties {message:?}")]
InvalidProps {
message: Cow<'static, str>,
},
#[error("missing resource {message:?}")]
MissingResource {
message: Cow<'static, str>,
},
}
pub enum ConstructProp<T: Construct> {
Value(T),
Prop(T::Props),
}
pub trait Construct: Sized {
type Props: Clone;
fn construct(
context: &mut ConstructContext,
props: Self::Props,
) -> Result<Self, ConstructError>;
fn patch<F: FnMut(&mut Self::Props)>(func: F) -> ConstructPatch<Self, F> {
ConstructPatch {
func,
_marker: PhantomData,
}
}
}
#[derive(Bundle)]
pub struct Add0<
A: Sync + Send + 'static + Bundle + Component,
B: Sync + Send + 'static + Bundle + Component,
>(pub A, pub B);
unsafe impl<
A: Submitter + Sync + Send + 'static + Bundle + Component,
B: Sync + Send + 'static + Bundle + Component,
> Submitter for Add0<A, B>
{
type Out = A::Out;
}
impl<A, B> Construct for Add0<A, B>
where
A: Construct + Sync + Send + 'static + Bundle + Component,
B: Construct<Props = ()> + Sync + Send + 'static + Bundle + Component,
{
type Props = A::Props;
fn construct(
context: &mut ConstructContext,
props: Self::Props,
) -> Result<Self, ConstructError> {
let a = A::construct(context, props)?;
let b = B::construct(context, ())?;
Ok(Add0(a, b))
}
}
impl<A, B> Construct for (A, B)
where
A: Construct,
B: Construct,
{
type Props = (A::Props, B::Props);
fn construct(
context: &mut ConstructContext,
props: Self::Props,
) -> Result<Self, ConstructError> {
let a = A::construct(context, props.0)?;
let b = B::construct(context, props.1)?;
Ok((a, b))
}
}
#[derive(Debug)]
pub struct ConstructContext<'a> {
pub id: Entity,
pub world: &'a mut World,
}
impl ConstructContext<'_> {
pub fn construct<T: Construct>(
&mut self,
props: impl Into<T::Props>,
) -> Result<T, ConstructError> {
T::construct(self, props.into())
}
pub fn construct_from_patch<P: Patch>(
&mut self,
patch: &mut P,
) -> Result<P::Construct, ConstructError>
where
<<P as Patch>::Construct as Construct>::Props: Default,
{
let mut props = <<P as Patch>::Construct as Construct>::Props::default();
patch.patch(&mut props);
self.construct(props)
}
}
pub trait ConstructExt {
fn construct<T: Construct + Bundle>(
&mut self,
props: impl Into<T::Props>,
) -> EntityCommands<'_>
where
<T as Construct>::Props: Send;
}
pub trait ConstructChildrenExt: ConstructExt {
fn construct_children<T: Construct + Bundle>(
&mut self,
props: impl IntoIterator<Item = impl Into<T::Props>>,
) -> EntityCommands<'_>
where
<T as Construct>::Props: Send;
}
struct ConstructCommand<T: Construct>(T::Props);
impl<T: Construct + Bundle> bevy::ecs::system::EntityCommand for ConstructCommand<T>
where
<T as Construct>::Props: Send,
{
fn apply(self, mut entity_world: EntityWorldMut) {
let id = entity_world.id();
entity_world.world_scope(move |world: &mut World| {
let mut context = ConstructContext { id, world };
let c = T::construct(&mut context, self.0).expect("component");
world.entity_mut(id).insert(c);
});
}
}
impl ConstructExt for Commands<'_, '_> {
fn construct<T: Construct + Bundle>(&mut self, props: impl Into<T::Props>) -> EntityCommands<'_>
where
<T as Construct>::Props: Send,
{
let mut s = self.spawn_empty();
s.queue(ConstructCommand::<T>(props.into()));
s
}
}
impl ConstructExt for ChildSpawnerCommands<'_> {
fn construct<T: Construct + Bundle>(&mut self, props: impl Into<T::Props>) -> EntityCommands<'_>
where
<T as Construct>::Props: Send,
{
let mut s = self.spawn_empty();
s.queue(ConstructCommand::<T>(props.into()));
s
}
}
impl ConstructExt for EntityCommands<'_> {
fn construct<T: Construct + Bundle>(&mut self, props: impl Into<T::Props>) -> EntityCommands<'_>
where
<T as Construct>::Props: Send,
{
self.queue(ConstructCommand::<T>(props.into()));
self.reborrow()
}
}
impl ConstructChildrenExt for EntityCommands<'_> {
fn construct_children<T: Construct + Bundle>(
&mut self,
props: impl IntoIterator<Item = impl Into<T::Props>>,
) -> EntityCommands<'_>
where
<T as Construct>::Props: Send,
{
self.with_children(|parent| {
for prop in props.into_iter() {
parent.construct::<T>(prop);
}
});
self.reborrow()
}
}
pub trait Patch: Send + Sync + 'static {
type Construct: Construct + Bundle;
fn patch(&mut self, props: &mut <Self::Construct as Construct>::Props);
}
pub struct ConstructPatch<C: Construct, F> {
func: F,
_marker: PhantomData<C>,
}
impl<C: Construct + Sync + Send + 'static + Bundle, F: FnMut(&mut C::Props) + Sync + Send + 'static>
Patch for ConstructPatch<C, F>
{
type Construct = C;
fn patch(&mut self, props: &mut <Self::Construct as Construct>::Props) {
(self.func)(props);
}
}
#[cfg(test)]
mod test {
use super::*;
#[derive(Default, Clone, Component)]
struct Player {
name: String,
}
impl Construct for Player {
type Props = Player;
fn construct(
_context: &mut ConstructContext,
props: Self::Props,
) -> Result<Self, ConstructError> {
Ok(props)
}
}
#[test]
fn test_patch_name() {
let mut player = Player {
name: "shane".into(),
};
assert_eq!(player.name, "shane");
let mut patch = Player::patch(|props| {
props.name = "fred".to_string();
});
patch.patch(&mut player);
assert_eq!(player.name, "fred");
}
}