use std::collections::BTreeMap;
use std::fmt;
use bevy::ecs::world::World;
use bevy::math::Vec3;
use bevy::prelude::Entity;
use bevy::transform::components::Transform;
use crate::core::{Person, PersonKey, SocietySettings};
use crate::defs::ActivityId;
use crate::fast_forward::{FastForward, FastForwardError, fast_forward};
use crate::save::{RestoreError, SocietySave, capture, restore};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TieChange {
pub target: u64,
pub before: Option<f32>,
pub after: Option<f32>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PersonPreview {
pub key: u64,
pub from: [f32; 2],
pub to: [f32; 2],
pub doing_before: Option<ActivityId>,
pub doing_after: Option<ActivityId>,
pub ties: Vec<TieChange>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Preview {
pub run: FastForward,
pub persons: Vec<PersonPreview>,
pub dropped_activities: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PreviewError {
Run(FastForwardError),
Unkeyed(usize),
Reset(RestoreError),
}
impl fmt::Display for PreviewError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Run(e) => write!(f, "{e}"),
Self::Unkeyed(n) => write!(
f,
"{n} person(s) have no PersonKey, so the society could not be put back after a \
preview; insert a PersonKey on every person first"
),
Self::Reset(e) => write!(f, "the preview ran but the reset failed: {e}"),
}
}
}
impl std::error::Error for PreviewError {}
fn positions(world: &mut World) -> BTreeMap<u64, (Entity, Vec3)> {
let mut state = world.query::<(Entity, &PersonKey, &Transform)>();
state
.iter(world)
.map(|(e, k, t)| (k.0, (e, t.translation)))
.collect()
}
pub fn preview_forward(world: &mut World, days: f32) -> Result<Preview, PreviewError> {
let people = world
.query_filtered::<(), bevy::prelude::With<Person>>()
.iter(world)
.count();
let keyed = positions(world).len();
if keyed < people {
return Err(PreviewError::Unkeyed(people - keyed));
}
let plane = world
.get_resource::<SocietySettings>()
.map(|s| s.plane)
.unwrap_or_default();
let before = capture(world);
let start = positions(world);
let run = fast_forward(world, days).map_err(PreviewError::Run)?;
let after = capture(world);
let end = positions(world);
let report = restore(world, before.clone()).map_err(PreviewError::Reset)?;
for (entity, at) in start.values() {
if let Some(mut transform) = world.get_mut::<Transform>(*entity) {
transform.translation = *at;
}
}
let persons = describe(&before, &after, &start, &end, |v| {
let p = plane.project(v);
[p.x, p.y]
});
Ok(Preview {
run,
persons,
dropped_activities: report.dropped_activities,
})
}
fn describe(
before: &SocietySave,
after: &SocietySave,
start: &BTreeMap<u64, (Entity, Vec3)>,
end: &BTreeMap<u64, (Entity, Vec3)>,
project: impl Fn(Vec3) -> [f32; 2],
) -> Vec<PersonPreview> {
let later: BTreeMap<u64, &crate::save::PersonSave> =
after.persons.iter().map(|p| (p.key, p)).collect();
let mut out = Vec::new();
for was in &before.persons {
let Some(now) = later.get(&was.key) else {
continue;
};
let Some((_, from)) = start.get(&was.key) else {
continue;
};
let to = end.get(&was.key).map_or(*from, |(_, v)| *v);
let mut ties = Vec::new();
let old: BTreeMap<u64, f32> = was.ties.iter().map(|t| (t.target, t.affinity)).collect();
let new: BTreeMap<u64, f32> = now.ties.iter().map(|t| (t.target, t.affinity)).collect();
let mut targets: Vec<u64> = old.keys().chain(new.keys()).copied().collect();
targets.sort_unstable();
targets.dedup();
for target in targets {
let (b, a) = (old.get(&target).copied(), new.get(&target).copied());
if b != a {
ties.push(TieChange {
target,
before: b,
after: a,
});
}
}
out.push(PersonPreview {
key: was.key,
from: project(*from),
to: project(to),
doing_before: was.doing.map(|d| d.activity),
doing_after: now.doing.map(|d| d.activity),
ties,
});
}
out.sort_by_key(|p| p.key);
out
}