Skip to content

Commit

Permalink
Overlay+Given
Browse files Browse the repository at this point in the history
  • Loading branch information
spectria-limina committed Dec 12, 2024
1 parent 37c15ae commit bc9a2a3
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 18 deletions.
140 changes: 140 additions & 0 deletions src/ecs/given.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use std::ops::{Deref, DerefMut};

use bevy::ecs::{
query::{QueryData, QueryItem, ROQueryItem},
system::{ParamBuilder, SystemParam},
};
pub use bevy::prelude::*;

use super::*;

pub struct Given<'w, 's, D: QueryData, Marker = ()> {
given: Entity,
query: Query<'w, 's, D>,
_ph: PhantomData<Marker>,
}

impl<'w, 's, D: QueryData, Label> Given<'w, 's, D, Label> {
pub fn new(given: Entity, query: Query<'w, 's, D>) -> Self {
Self {
given,
query,
_ph: PhantomData,
}
}

pub fn get(&self) -> ROQueryItem<D> { self.query.get(self.given).unwrap() }
pub fn get_mut(&mut self) -> QueryItem<D> { self.query.get_mut(self.given).unwrap() }
}

impl<'w, 's, D: QueryData, Label> Deref for Given<'w, 's, D, Label> {
type Target = D;

fn deref(&self) -> &Self::Target { todo!() }
}
impl<'w, 's, D: QueryData, Label> DerefMut for Given<'w, 's, D, Label> {
fn deref_mut(&mut self) -> &mut Self::Target { todo!() }
}

unsafe impl<'w, 's, D: QueryData + 'static, Label> SystemParam for Given<'w, 's, D, Label> {
type State = (Entity, <Query<'w, 's, D> as SystemParam>::State);
type Item<'world, 'state> = Given<'world, 'state, D, Label>;

fn init_state(
_world: &mut World,
_system_meta: &mut bevy::ecs::system::SystemMeta,
) -> Self::State {
panic!("Given must be initialized by a SystemParamBuilder to provide an Entity");
}

unsafe fn get_param<'world, 'state>(
&mut (given, ref mut query_state): &'state mut Self::State,
_system_meta: &bevy::ecs::system::SystemMeta,
_world: UnsafeWorldCell<'world>,
_change_tick: Tick,
) -> Self::Item<'world, 'state> {
// SAFETY: The state was initialized using the GivenParamBuilder, which forwards to Query.
unsafe {
Self::Item::new(
given,
<Query<'w, 's, D> as SystemParam>::get_param(
query_state,
_system_meta,
_world,
_change_tick,
),
)
}
}
}

#[derive(Copy, Clone, Debug)]
pub struct GivenBuilder {
given: Entity,
}

impl GivenBuilder {
pub fn new(given: Entity) -> Self { Self { given } }
}

unsafe impl<'w, 's, D: QueryData + 'static, Label> SystemParamBuilder<Given<'w, 's, D, Label>>
for GivenBuilder
{
fn build(
self,
world: &mut World,
meta: &mut bevy::ecs::system::SystemMeta,
) -> <Given<'w, 's, D, Label> as SystemParam>::State {
(
self.given,
ParamBuilder::of::<Query<D>>().build(world, meta),
)
}
}

#[cfg(test)]
mod test {
use super::*;

#[cfg(test)]
fn no_val_panic() {
let mut world = World::new();

let mut sys = (ParamBuilder,)
.build_state(&mut world)
.build_any_system(|given: Given<Entity>| given.get());

let _result: Entity = sys.run((), &mut world);
}

#[cfg(test)]
fn entity() {
let mut world = World::new();

let entity = world.spawn(()).id();
let mut sys = (GivenBuilder::new(entity),)
.build_state(&mut world)
.build_any_system(|given: Given<Entity>| given.get());

let result: Entity = sys.run((), &mut world);

assert_eq!(result, entity);
}

#[cfg(test)]
fn component() {
let mut world = World::new();

#[derive(Component)]
struct C(u32);

let entity = world.spawn_batch((1..10).map(C)).nth(7).unwrap();
let mut sys = (GivenBuilder::new(entity),)
.build_state(&mut world)
.build_any_system(|given: Given<&C>| given.get().0);

let result: u32 = sys.run((), &mut world);

assert_eq!(result, 32);
}
}
23 changes: 11 additions & 12 deletions src/ecs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
use std::{any::TypeId, borrow::Cow, marker::PhantomData};

use bevy::{
ecs::{
archetype::ArchetypeComponentId,
component::{ComponentId, Tick},
query::Access,
schedule::InternedSystemSet,
system::IntoObserverSystem,
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld},
},
prelude::*,
use bevy::ecs::{
archetype::ArchetypeComponentId,
component::{ComponentId, Tick},
query::Access,
schedule::InternedSystemSet,
system::IntoObserverSystem,
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld},
};

pub mod conflicts;
pub mod given;
pub mod nested;
pub mod param;

pub use conflicts::*;
pub use nested::*;
#[allow(unused_imports)]
pub use {conflicts::*, given::*, nested::*, param::*};

/// Marker component for child entities added by a specific component.
#[derive(Component, Copy, Clone, Default, Debug)]
Expand Down
2 changes: 1 addition & 1 deletion src/ecs/nested.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use bevy::{
};
use derive_where::derive_where;

use super::{diagnose_conflicts, AccessDiags, Cached};
use super::*;

pub type ArgInner<'a, Arg> = <Arg as SystemInput>::Inner<'a>;
pub type ArgParam<'a, Arg> = <Arg as SystemInput>::Param<'a>;
Expand Down
161 changes: 161 additions & 0 deletions src/ecs/param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use std::{any::TypeId, marker::PhantomData};

use bevy::{
ecs::system::{LocalBuilder, SystemMeta, SystemParam},
prelude::{SystemParamBuilder, *},
};
use derive_where::derive_where;

pub trait ICantBelieveItsNotClone {
type Butter;
fn i_cant_believe_its_not_clone(&self) -> Self::Butter;
}

impl<C: Clone> ICantBelieveItsNotClone for &C {
type Butter = C;
fn i_cant_believe_its_not_clone(&self) -> Self::Butter { C::clone(self) }
}

impl<T: Clone> ICantBelieveItsNotClone for LocalBuilder<T> {
type Butter = Self;
fn i_cant_believe_its_not_clone(&self) -> Self::Butter { LocalBuilder(self.0.clone()) }
}

#[derive_where(Copy, Clone; IfP, ElseQ)]
pub struct OverlayBuilder<P, Q, IfP, ElseQ> {
i: IfP,
e: ElseQ,
_ph: PhantomData<(P, Q)>,
}

impl<P, Q, IfP, ElseQ> OverlayBuilder<P, Q, IfP, ElseQ>
where
P: SystemParam + 'static,
Q: SystemParam + 'static,
IfP: SystemParamBuilder<P>,
ElseQ: SystemParamBuilder<Q>,
{
pub fn new(i: IfP, e: ElseQ) -> Self {
Self {
i,
e,
_ph: PhantomData,
}
}
}

type ParamState<P> = <P as SystemParam>::State;

unsafe impl<P, Q, IfP, ElseQ> SystemParamBuilder<Q> for OverlayBuilder<P, Q, IfP, ElseQ>
where
P: SystemParam + 'static,
Q: SystemParam + 'static,
IfP: SystemParamBuilder<P>,
ElseQ: SystemParamBuilder<Q>,
{
fn build(self, world: &mut World, meta: &mut SystemMeta) -> ParamState<Q> {
let p_ty = TypeId::of::<P>();
let q_ty = TypeId::of::<Q>();
if p_ty == q_ty {
let mut out: ParamState<P> = self.i.build(world, meta);
// SAFETY: We proved above that P == Q, so this operations are valid by substitution.
// There are also no implicit hidden lifetime parameters in ParamState.
unsafe { (&mut out as *mut ParamState<P> as *mut ParamState<Q>).read() }
} else {
self.e.build(world, meta)
}
}
}

pub fn overlay_matching<OParam, OBuilder, Param1, Param2, Builder1, Builder2>(
overlay: OBuilder,
(builder1, builder2): (Builder1, Builder2),
) -> (
OverlayBuilder<OParam, Param1, OBuilder, Builder1>,
OverlayBuilder<OParam, Param2, OBuilder, Builder2>,
)
where
OParam: SystemParam + 'static,
OBuilder: SystemParamBuilder<OParam> + ICantBelieveItsNotClone<Butter = OBuilder>,
Param1: SystemParam + 'static,
Param2: SystemParam + 'static,
Builder1: SystemParamBuilder<Param1>,
Builder2: SystemParamBuilder<Param2>,
{
(
OverlayBuilder::new(overlay.i_cant_believe_its_not_clone(), builder1),
OverlayBuilder::new(overlay, builder2),
)
}

#[cfg(test)]
mod test {
use bevy::ecs::system::{LocalBuilder, ParamBuilder};

use super::*;

#[test]
fn overlay_eq() {
let mut world = World::new();

let overlay = LocalBuilder(true);
let builder = OverlayBuilder::<Local<bool>, Local<bool>, _, _>::new(
overlay,
ParamBuilder::of::<Local<bool>>(),
);

let mut sys = (builder,)
.build_state(&mut world)
.build_any_system(|b: Local<bool>| *b);

let result: bool = sys.run((), &mut world);

assert!(result);
}

#[test]
fn overlay_ne() {
let mut world = World::new();

let overlay = LocalBuilder(1);
let builder =
OverlayBuilder::<Local<u32>, _, _, _>::new(overlay, ParamBuilder::of::<Local<bool>>());

let mut sys = (builder,)
.build_state(&mut world)
.build_any_system(|b: Local<bool>| *b);

let result = sys.run((), &mut world);

assert!(!result);
}

#[test]
fn overlay_2() {
let mut world = World::new();

let overlay = LocalBuilder(true);
macro_rules! builder {
() => {
(
ParamBuilder::of::<Local<u32>>(),
ParamBuilder::of::<Local<bool>>(),
)
};
}
let overlay_builder = overlay_matching(overlay, builder!());

let sys_fn = |u: Local<u32>, b: Local<bool>| (*u, *b);
let mut sys = builder!().build_state(&mut world).build_any_system(sys_fn);
let mut overlay_sys = overlay_builder
.build_state(&mut world)
.build_any_system(sys_fn);

let result = sys.run((), &mut world);
let overlay_result = overlay_sys.run((), &mut world);

// Check that the no-overlay version had a different result first, to test the test.
assert_eq!(result, (0, false));
assert_eq!(overlay_result, (0, true));
}
}
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#![forbid(unsafe_op_in_unsafe_fn)]
#![allow(dead_code)]
#![allow(unexpected_cfgs)]
// I'll clean em up later
#![allow(unused_imports)]

use std::path::{Path, PathBuf};

Expand Down
14 changes: 9 additions & 5 deletions src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ pub fn add_test_camera(mut commands: Commands, win_q: Query<&Window, With<Primar
win_rect.size(),
win_rect.center(),
);
commands.spawn((Camera2d, OrthographicProjection::default_2d(), Transform {
translation: win_rect.center().extend(0.0),
scale: Vec3::new(1.0, -1.0, 1.0),
rotation: Quat::IDENTITY,
}));
commands.spawn((
Camera2d,
OrthographicProjection::default_2d(),
Transform {
translation: win_rect.center().extend(0.0),
scale: Vec3::new(1.0, -1.0, 1.0),
rotation: Quat::IDENTITY,
},
));
}

#[derive(Bundle, Default)]
Expand Down

0 comments on commit bc9a2a3

Please sign in to comment.