use std::{cmp::Ordering, sync::Arc};
use bevy::prelude::*;
#[cfg(feature = "trace")]
use bevy::utils::tracing::trace;
use crate::{
evaluators::Evaluator,
measures::{Measure, WeightedMeasure},
thinker::{Actor, Scorer, ScorerSpan},
};
#[derive(Clone, Component, Debug, Default, Reflect)]
pub struct Score(pub(crate) f32);
impl Score {
pub fn get(&self) -> f32 {
self.0
}
pub fn set(&mut self, value: f32) {
if !(0.0..=1.0).contains(&value) {
panic!("Score value must be between 0.0 and 1.0");
}
self.0 = value;
}
}
#[reflect_trait]
pub trait ScorerBuilder: std::fmt::Debug + Sync + Send {
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity);
fn label(&self) -> Option<&str> {
None
}
}
pub fn spawn_scorer<T: ScorerBuilder + ?Sized>(
builder: &T,
cmd: &mut Commands,
actor: Entity,
) -> Entity {
let scorer_ent = cmd.spawn_empty().id();
let span = ScorerSpan::new(scorer_ent, ScorerBuilder::label(builder));
let _guard = span.span().enter();
debug!("New Scorer spawned.");
cmd.entity(scorer_ent)
.insert(Name::new("Scorer"))
.insert(Score::default())
.insert(Actor(actor));
builder.build(cmd, scorer_ent, actor);
std::mem::drop(_guard);
cmd.entity(scorer_ent).insert(span);
scorer_ent
}
#[derive(Clone, Component, Debug, Reflect)]
pub struct FixedScore(pub f32);
impl FixedScore {
pub fn build(score: f32) -> FixedScorerBuilder {
FixedScorerBuilder { score, label: None }
}
}
pub fn fixed_score_system(mut query: Query<(&FixedScore, &mut Score, &ScorerSpan)>) {
for (FixedScore(fixed), mut score, _span) in query.iter_mut() {
#[cfg(feature = "trace")]
{
let _guard = _span.span().enter();
trace!("FixedScore: {}", fixed);
}
score.set(*fixed);
}
}
#[derive(Debug, Reflect)]
pub struct FixedScorerBuilder {
score: f32,
label: Option<String>,
}
impl FixedScorerBuilder {
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
}
impl ScorerBuilder for FixedScorerBuilder {
fn build(&self, cmd: &mut Commands, scorer: Entity, _actor: Entity) {
cmd.entity(scorer).insert(FixedScore(self.score));
}
fn label(&self) -> Option<&str> {
self.label.as_deref().or(Some("FixedScore"))
}
}
#[derive(Component, Debug, Reflect)]
pub struct AllOrNothing {
threshold: f32,
scorers: Vec<Scorer>,
}
impl AllOrNothing {
pub fn build(threshold: f32) -> AllOrNothingBuilder {
AllOrNothingBuilder {
threshold,
scorers: Vec::new(),
scorer_labels: Vec::new(),
label: None,
}
}
}
pub fn all_or_nothing_system(
query: Query<(Entity, &AllOrNothing, &ScorerSpan)>,
mut scores: Query<&mut Score>,
) {
for (
aon_ent,
AllOrNothing {
threshold,
scorers: children,
},
_span,
) in query.iter()
{
let mut sum = 0.0;
for Scorer(child) in children.iter() {
let score = scores.get_mut(*child).expect("where is it?");
if score.0 < *threshold {
sum = 0.0;
break;
} else {
sum += score.0;
}
}
let mut score = scores.get_mut(aon_ent).expect("where did it go?");
score.set(crate::evaluators::clamp(sum, 0.0, 1.0));
#[cfg(feature = "trace")]
{
let _guard = _span.span().enter();
trace!("AllOrNothing score: {}", score.get());
}
}
}
#[derive(Debug, Clone, Reflect)]
pub struct AllOrNothingBuilder {
threshold: f32,
#[reflect(ignore)]
scorers: Vec<Arc<dyn ScorerBuilder>>,
scorer_labels: Vec<String>,
label: Option<String>,
}
impl AllOrNothingBuilder {
pub fn push(mut self, scorer: impl ScorerBuilder + 'static) -> Self {
if let Some(label) = scorer.label() {
self.scorer_labels.push(label.into());
} else {
self.scorer_labels.push("Unnamed Scorer".into());
}
self.scorers.push(Arc::new(scorer));
self
}
pub fn label(mut self, label: impl AsRef<str>) -> Self {
self.label = Some(label.as_ref().into());
self
}
}
impl ScorerBuilder for AllOrNothingBuilder {
fn label(&self) -> Option<&str> {
self.label.as_deref().or(Some("AllOrNothing"))
}
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let scorers: Vec<_> = self
.scorers
.iter()
.map(|scorer| spawn_scorer(&**scorer, cmd, actor))
.collect();
cmd.entity(scorer)
.insert(Score::default())
.push_children(&scorers[..])
.insert(Name::new("Scorer"))
.insert(AllOrNothing {
threshold: self.threshold,
scorers: scorers.into_iter().map(Scorer).collect(),
});
}
}
#[derive(Component, Debug, Reflect)]
pub struct SumOfScorers {
threshold: f32,
scorers: Vec<Scorer>,
scorer_labels: Vec<String>,
}
impl SumOfScorers {
pub fn build(threshold: f32) -> SumOfScorersBuilder {
SumOfScorersBuilder {
threshold,
scorers: Vec::new(),
scorer_labels: Vec::new(),
label: None,
}
}
}
pub fn sum_of_scorers_system(
query: Query<(Entity, &SumOfScorers, &ScorerSpan)>,
mut scores: Query<&mut Score>,
) {
for (
sos_ent,
SumOfScorers {
threshold,
scorers: children,
..
},
_span,
) in query.iter()
{
let mut sum = 0.0;
for Scorer(child) in children.iter() {
let score = scores.get_mut(*child).expect("where is it?");
sum += score.0;
}
if sum < *threshold {
sum = 0.0;
}
let mut score = scores.get_mut(sos_ent).expect("where did it go?");
score.set(crate::evaluators::clamp(sum, 0.0, 1.0));
#[cfg(feature = "trace")]
{
let _guard = _span.span().enter();
trace!(
"SumOfScorers score: {}, from {} scores",
score.get(),
children.len()
);
}
}
}
#[derive(Debug, Clone, Reflect)]
pub struct SumOfScorersBuilder {
threshold: f32,
#[reflect(ignore)]
scorers: Vec<Arc<dyn ScorerBuilder>>,
scorer_labels: Vec<String>,
label: Option<String>,
}
impl SumOfScorersBuilder {
pub fn push(mut self, scorer: impl ScorerBuilder + 'static) -> Self {
if let Some(label) = scorer.label() {
self.scorer_labels.push(label.into());
} else {
self.scorer_labels.push("Unnamed Scorer".into());
}
self.scorers.push(Arc::new(scorer));
self
}
pub fn label(mut self, label: impl AsRef<str>) -> Self {
self.label = Some(label.as_ref().into());
self
}
}
impl ScorerBuilder for SumOfScorersBuilder {
fn label(&self) -> Option<&str> {
self.label.as_deref().or(Some("SumOfScorers"))
}
#[allow(clippy::needless_collect)]
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let scorers: Vec<_> = self
.scorers
.iter()
.map(|scorer| spawn_scorer(&**scorer, cmd, actor))
.collect();
cmd.entity(scorer)
.push_children(&scorers[..])
.insert(SumOfScorers {
threshold: self.threshold,
scorers: scorers.into_iter().map(Scorer).collect(),
scorer_labels: self.scorer_labels.clone(),
});
}
}
#[derive(Component, Debug, Reflect)]
pub struct ProductOfScorers {
threshold: f32,
use_compensation: bool,
scorers: Vec<Scorer>,
scorer_labels: Vec<String>,
}
impl ProductOfScorers {
pub fn build(threshold: f32) -> ProductOfScorersBuilder {
ProductOfScorersBuilder {
threshold,
use_compensation: false,
scorers: Vec::new(),
scorer_labels: Vec::new(),
label: None,
}
}
}
pub fn product_of_scorers_system(
query: Query<(Entity, &ProductOfScorers, &ScorerSpan)>,
mut scores: Query<&mut Score>,
) {
for (
sos_ent,
ProductOfScorers {
threshold,
use_compensation,
scorers: children,
..
},
_span,
) in query.iter()
{
let mut product = 1.0;
let mut num_scorers = 0;
for Scorer(child) in children.iter() {
let score = scores.get_mut(*child).expect("where is it?");
product *= score.0;
num_scorers += 1;
}
if *use_compensation && product < 1.0 {
let mod_factor = 1.0 - 1.0 / (num_scorers as f32);
let makeup = (1.0 - product) * mod_factor;
product += makeup * product;
}
if product < *threshold {
product = 0.0;
}
let mut score = scores.get_mut(sos_ent).expect("where did it go?");
score.set(product.clamp(0.0, 1.0));
#[cfg(feature = "trace")]
{
let _guard = _span.span().enter();
trace!(
"ProductOfScorers score: {}, from {} scores",
score.get(),
children.len()
);
}
}
}
#[derive(Debug, Clone)]
pub struct ProductOfScorersBuilder {
threshold: f32,
use_compensation: bool,
scorers: Vec<Arc<dyn ScorerBuilder>>,
scorer_labels: Vec<String>,
label: Option<String>,
}
impl ProductOfScorersBuilder {
pub fn use_compensation(mut self, use_compensation: bool) -> Self {
self.use_compensation = use_compensation;
self
}
pub fn push(mut self, scorer: impl ScorerBuilder + 'static) -> Self {
if let Some(label) = scorer.label() {
self.scorer_labels.push(label.into());
} else {
self.scorer_labels.push("Unnamed Scorer".into());
}
self.scorers.push(Arc::new(scorer));
self
}
pub fn label(mut self, label: impl AsRef<str>) -> Self {
self.label = Some(label.as_ref().into());
self
}
}
impl ScorerBuilder for ProductOfScorersBuilder {
fn label(&self) -> Option<&str> {
self.label.as_deref().or(Some("ProductOfScorers"))
}
#[allow(clippy::needless_collect)]
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let scorers: Vec<_> = self
.scorers
.iter()
.map(|scorer| spawn_scorer(&**scorer, cmd, actor))
.collect();
cmd.entity(scorer)
.push_children(&scorers[..])
.insert(ProductOfScorers {
threshold: self.threshold,
use_compensation: self.use_compensation,
scorers: scorers.into_iter().map(Scorer).collect(),
scorer_labels: self.scorer_labels.clone(),
});
}
}
#[derive(Component, Debug, Reflect)]
pub struct WinningScorer {
threshold: f32,
scorers: Vec<Scorer>,
scorer_labels: Vec<String>,
}
impl WinningScorer {
pub fn build(threshold: f32) -> WinningScorerBuilder {
WinningScorerBuilder {
threshold,
scorers: Vec::new(),
scorer_labels: Vec::new(),
label: None,
}
}
}
pub fn winning_scorer_system(
mut query: Query<(Entity, &mut WinningScorer, &ScorerSpan)>,
mut scores: Query<&mut Score>,
) {
for (sos_ent, mut winning_scorer, _span) in query.iter_mut() {
let (threshold, children) = (winning_scorer.threshold, &mut winning_scorer.scorers);
let mut all_scores = children
.iter()
.map(|Scorer(e)| scores.get(*e).expect("where is it?"))
.collect::<Vec<&Score>>();
all_scores.sort_by(|a, b| a.get().partial_cmp(&b.get()).unwrap_or(Ordering::Equal));
let winning_score_or_zero = match all_scores.last() {
Some(s) => {
if s.get() < threshold {
0.0
} else {
s.get()
}
}
None => 0.0,
};
let mut score = scores.get_mut(sos_ent).expect("where did it go?");
score.set(crate::evaluators::clamp(winning_score_or_zero, 0.0, 1.0));
#[cfg(feature = "trace")]
{
let _guard = _span.span().enter();
trace!(
"WinningScorer score: {}, from {} scores",
score.get(),
children.len()
);
}
}
}
#[derive(Debug, Clone, Reflect)]
pub struct WinningScorerBuilder {
threshold: f32,
#[reflect(ignore)]
scorers: Vec<Arc<dyn ScorerBuilder>>,
scorer_labels: Vec<String>,
label: Option<String>,
}
impl WinningScorerBuilder {
pub fn push(mut self, scorer: impl ScorerBuilder + 'static) -> Self {
if let Some(label) = scorer.label() {
self.scorer_labels.push(label.into());
} else {
self.scorer_labels.push("Unnamed Scorer".into())
}
self.scorers.push(Arc::new(scorer));
self
}
pub fn label(mut self, label: impl AsRef<str>) -> Self {
self.label = Some(label.as_ref().into());
self
}
}
impl ScorerBuilder for WinningScorerBuilder {
fn label(&self) -> Option<&str> {
self.label.as_deref().or(Some("WinningScorer"))
}
#[allow(clippy::needless_collect)]
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let scorers: Vec<_> = self
.scorers
.iter()
.map(|scorer| spawn_scorer(&**scorer, cmd, actor))
.collect();
cmd.entity(scorer)
.push_children(&scorers[..])
.insert(WinningScorer {
threshold: self.threshold,
scorers: scorers.into_iter().map(Scorer).collect(),
scorer_labels: self.scorer_labels.clone(),
});
}
}
#[derive(Component, Debug, Reflect)]
pub struct EvaluatingScorer {
scorer: Scorer,
evaluator_string: String,
#[reflect(ignore)]
evaluator: Arc<dyn Evaluator>,
}
impl EvaluatingScorer {
pub fn build(
scorer: impl ScorerBuilder + 'static,
evaluator: impl Evaluator + 'static,
) -> EvaluatingScorerBuilder {
EvaluatingScorerBuilder {
scorer_label: scorer.label().map(|s| s.into()),
evaluator: Arc::new(evaluator),
scorer: Arc::new(scorer),
label: None,
}
}
}
pub fn evaluating_scorer_system(
query: Query<(Entity, &EvaluatingScorer, &ScorerSpan)>,
mut scores: Query<&mut Score>,
) {
for (sos_ent, eval_scorer, _span) in query.iter() {
let inner_score = scores
.get(eval_scorer.scorer.0)
.expect("where did it go?")
.get();
let mut score = scores.get_mut(sos_ent).expect("where did it go?");
score.set(crate::evaluators::clamp(
eval_scorer.evaluator.evaluate(inner_score),
0.0,
1.0,
));
#[cfg(feature = "trace")]
{
let _guard = _span.span().enter();
trace!(
"EvaluatingScorer score: {}, from score: {}",
score.get(),
inner_score
);
}
}
}
#[derive(Debug, Reflect)]
pub struct EvaluatingScorerBuilder {
#[reflect(ignore)]
scorer: Arc<dyn ScorerBuilder>,
scorer_label: Option<String>,
#[reflect(ignore)]
evaluator: Arc<dyn Evaluator>,
label: Option<String>,
}
impl ScorerBuilder for EvaluatingScorerBuilder {
fn label(&self) -> Option<&str> {
self.label.as_deref().or(Some("EvaluatingScorer"))
}
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let inner_scorer = spawn_scorer(&*self.scorer, cmd, actor);
let scorers = vec![inner_scorer];
cmd.entity(scorer)
.push_children(&scorers[..])
.insert(EvaluatingScorer {
evaluator: self.evaluator.clone(),
scorer: Scorer(inner_scorer),
evaluator_string: format!("{:#?}", self.evaluator),
});
}
}
#[derive(Component, Debug, Reflect)]
pub struct MeasuredScorer {
threshold: f32,
#[reflect(ignore)]
measure: Arc<dyn Measure>,
measure_string: String,
scorers: Vec<(Scorer, f32)>,
}
impl MeasuredScorer {
pub fn build(threshold: f32) -> MeasuredScorerBuilder {
MeasuredScorerBuilder {
threshold,
measure: Arc::new(WeightedMeasure),
measure_string: format!("{WeightedMeasure:#?}"),
scorers: Vec::new(),
scorer_labels: Vec::new(),
label: None,
}
}
}
pub fn measured_scorers_system(
query: Query<(Entity, &MeasuredScorer, &ScorerSpan)>,
mut scores: Query<&mut Score>,
) {
for (
sos_ent,
MeasuredScorer {
threshold,
measure,
scorers: children,
..
},
_span,
) in query.iter()
{
let measured_score = measure.calculate(
children
.iter()
.map(|(scorer, weight)| (scores.get(scorer.0).expect("where is it?"), *weight))
.collect::<Vec<_>>(),
);
let mut score = scores.get_mut(sos_ent).expect("where did it go?");
if measured_score < *threshold {
score.set(0.0);
} else {
score.set(measured_score.clamp(0.0, 1.0));
}
#[cfg(feature = "trace")]
{
let _guard = _span.span().enter();
trace!(
"MeasuredScorer score: {}, from {} scores",
score.get(),
children.len()
);
}
}
}
#[derive(Debug, Reflect)]
pub struct MeasuredScorerBuilder {
threshold: f32,
#[reflect(ignore)]
measure: Arc<dyn Measure>,
measure_string: String,
#[reflect(ignore)]
scorers: Vec<(Arc<dyn ScorerBuilder>, f32)>,
scorer_labels: Vec<String>,
label: Option<String>,
}
impl MeasuredScorerBuilder {
pub fn measure(mut self, measure: impl Measure + 'static) -> Self {
self.measure_string = format!("{measure:#?}");
self.measure = Arc::new(measure);
self
}
pub fn push(mut self, scorer: impl ScorerBuilder + 'static, weight: f32) -> Self {
if let Some(label) = scorer.label() {
self.scorer_labels.push(label.into());
} else {
self.scorer_labels.push("Unnamed Scorer".into());
}
self.scorers.push((Arc::new(scorer), weight));
self
}
pub fn label(mut self, label: impl AsRef<str>) -> Self {
self.label = Some(label.as_ref().into());
self
}
}
impl ScorerBuilder for MeasuredScorerBuilder {
fn label(&self) -> Option<&str> {
self.label.as_deref().or(Some("MeasuredScorer"))
}
#[allow(clippy::needless_collect)]
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let scorers: Vec<_> = self
.scorers
.iter()
.map(|(scorer, _)| spawn_scorer(&**scorer, cmd, actor))
.collect();
cmd.entity(scorer)
.push_children(&scorers[..])
.insert(MeasuredScorer {
threshold: self.threshold,
measure: self.measure.clone(),
scorers: scorers
.into_iter()
.map(Scorer)
.zip(self.scorers.iter().map(|(_, weight)| *weight))
.collect(),
measure_string: self.measure_string.clone(),
});
}
}