use bevy::{
ecs::component::{ComponentHooks, StorageType},
prelude::*,
};
use crate::{ecs::CommandsExt, event::OnScore, scoring::Score};
pub struct Measured {
measure: Box<dyn Measure>,
}
impl Measured {
pub fn new(measure: impl Measure) -> Self {
Self {
measure: Box::new(measure),
}
}
pub fn calculate(&self, inputs: Vec<(&Score, &Weighted)>) -> Score {
self.measure.calculate(inputs)
}
pub fn measure(&self) -> &dyn Measure {
self.measure.as_ref()
}
pub fn set_measure(&mut self, measure: impl Measure) {
self.measure = Box::new(measure);
}
fn observer(
trigger: Trigger<OnScore>,
target: Query<(&Children, &Measured)>,
mut scores: Query<(&mut Score, Option<&Weighted>)>,
) {
let Ok((children, settings)) = target.get(trigger.entity()) else {
return;
};
let mut inputs = Vec::new();
for (child_score, weighted) in scores.iter_many(children) {
inputs.push((child_score, weighted.unwrap_or(&Weighted::MAX)));
}
let result = settings.calculate(inputs);
let Ok((mut actor_score, _)) = scores.get_mut(trigger.entity()) else {
return;
};
*actor_score = result;
}
}
impl Component for Measured {
const STORAGE_TYPE: StorageType = StorageType::Table;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_add(|mut world, _entity, _component| {
#[derive(Resource, Default)]
struct MeasuredObserverSpawned;
world
.commands()
.once::<MeasuredObserverSpawned>()
.observe(Self::observer);
});
}
}
#[derive(Component, Reflect)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Weighted {
weight: Score,
}
impl Default for Weighted {
fn default() -> Self {
Self::MAX
}
}
impl Weighted {
pub const MIN: Weighted = Weighted {
weight: unsafe { Score::new_unchecked(0.) },
};
pub const MAX: Weighted = Weighted {
weight: unsafe { Score::new_unchecked(1.) },
};
pub fn new(weight: impl Into<Score>) -> Self {
Self { weight: weight.into() }
}
pub fn get(&self) -> Score {
self.weight
}
pub fn set(&mut self, weight: impl Into<Score>) {
self.weight = weight.into();
}
}
pub trait Measure: Send + Sync + 'static {
fn calculate(&self, inputs: Vec<(&Score, &Weighted)>) -> Score;
}
#[derive(Clone, Copy)]
pub struct WeightedSum;
impl Measure for WeightedSum {
fn calculate(&self, inputs: Vec<(&Score, &Weighted)>) -> Score {
let sum = inputs
.iter()
.fold(0., |acc, (score, weight)| acc + score.get() * weight.get().get());
Score::new(sum)
}
}
#[derive(Clone, Copy)]
pub struct WeightedProduct;
impl Measure for WeightedProduct {
fn calculate(&self, inputs: Vec<(&Score, &Weighted)>) -> Score {
let product = inputs
.iter()
.fold(1., |acc, (score, weight)| acc * score.get() * weight.get().get());
Score::new(product)
}
}
#[derive(Clone, Copy)]
pub struct WeightedMax;
impl Measure for WeightedMax {
fn calculate(&self, inputs: Vec<(&Score, &Weighted)>) -> Score {
let max = inputs
.iter()
.fold(0., |best, (score, weight)| (score.get() * weight.get().get()).max(best));
Score::new(max)
}
}
#[derive(Clone, Copy)]
pub struct WeightedRMS;
impl Measure for WeightedRMS {
fn calculate(&self, inputs: Vec<(&Score, &Weighted)>) -> Score {
let weight_sum = inputs.iter().map(|(_, weight)| weight.get()).sum::<f32>();
if weight_sum == 0. {
Score::MIN
} else {
let rms = inputs
.iter()
.map(|(score, weight)| weight.get().get() / weight_sum * score.get().powf(2.))
.sum::<f32>()
.sqrt();
Score::new(rms)
}
}
}
impl<F> Measure for F
where
F: Fn(Vec<(&Score, &Weighted)>) -> Score + Send + Sync + 'static,
{
fn calculate(&self, inputs: Vec<(&Score, &Weighted)>) -> Score {
self(inputs)
}
}