use crate::site::load::load_site;
use bevy::{ecs::hierarchy::ChildOf, prelude::*};
use rmf_site_format::SiteID;
use std::{
collections::{HashMap, HashSet},
marker::PhantomData,
ops::Deref,
};
#[derive(Debug, Clone, Component)]
pub struct RecencyRanking<T: Component> {
entities: Vec<Entity>,
_ignore: PhantomData<T>,
}
impl<T: Component> RecencyRanking<T> {
pub fn new() -> Self {
Self {
entities: default(),
_ignore: default(),
}
}
pub fn entities(&self) -> &Vec<Entity> {
&self.entities
}
pub fn from_u32(ranking: &Vec<u32>, id_to_entity: &HashMap<u32, Entity>) -> Result<Self, u32> {
let entities: Result<Vec<Entity>, u32> = ranking
.iter()
.map(|id| id_to_entity.get(id).copied().ok_or(*id))
.collect();
let entities = entities?;
Ok(Self {
entities,
_ignore: default(),
})
}
pub fn to_u32(&self, site_ids: &Query<&SiteID>) -> Vec<u32> {
self.entities
.iter()
.filter_map(|e| site_ids.get(*e).ok().copied())
.map(|s| s.0)
.collect()
}
}
impl<T: Component> Default for RecencyRanking<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Component> Deref for RecencyRanking<T> {
type Target = Vec<Entity>;
fn deref(&self) -> &Self::Target {
&self.entities
}
}
#[derive(Debug, Clone, Copy, Component)]
pub struct RecencyRank<T: Component> {
rank: usize,
out_of: usize,
_ignore: PhantomData<T>,
}
impl<T: Component> PartialEq for RecencyRank<T> {
fn eq(&self, other: &Self) -> bool {
self.rank.eq(&other.rank)
}
}
impl<T: Component> Eq for RecencyRank<T> {}
impl<T: Component> PartialOrd for RecencyRank<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.rank.partial_cmp(&other.rank)
}
}
impl<T: Component> Ord for RecencyRank<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.rank.cmp(&other.rank)
}
}
impl<T: Component> RecencyRank<T> {
fn new(rank: usize, out_of: usize) -> Self {
Self {
rank,
out_of,
_ignore: default(),
}
}
pub fn rank(&self) -> usize {
self.rank
}
pub fn out_of(&self) -> usize {
self.out_of
}
pub fn proportion(&self) -> f32 {
self.rank() as f32 / self.out_of() as f32
}
}
#[derive(Debug, Clone, Copy, Event)]
pub struct ChangeRank<T: Component> {
of: Entity,
by: RankAdjustment,
_ignore: PhantomData<T>,
}
impl<T: Component> ChangeRank<T> {
pub fn new(of: Entity, by: RankAdjustment) -> Self {
Self {
of,
by,
_ignore: default(),
}
}
pub fn of(&self) -> Entity {
self.of
}
pub fn by(&self) -> RankAdjustment {
self.by
}
}
#[derive(Debug, Clone, Copy)]
pub enum RankAdjustment {
Delta(i64),
ToTop,
ToBottom,
}
impl RankAdjustment {
pub fn label(&self) -> &'static str {
match self {
RankAdjustment::Delta(v) => {
if *v > 0 {
"Move up"
} else if *v < 0 {
"Move down"
} else {
"Move nowhere"
}
}
RankAdjustment::ToTop => "Move to top",
RankAdjustment::ToBottom => "Move to bottom",
}
}
}
#[derive(Default, Clone, Copy, Component)]
pub struct SuppressRecencyRank;
#[derive(Default)]
pub struct RecencyRankingPlugin<T>(PhantomData<T>);
impl<T: Component> Plugin for RecencyRankingPlugin<T> {
fn build(&self, app: &mut App) {
app.add_event::<ChangeRank<T>>().add_systems(
Update,
(
update_recency_rankings::<T>.after(load_site),
update_recency_ranks::<T>.after(update_recency_rankings::<T>),
),
);
}
}
fn update_recency_rankings<T: Component>(
mut rankings: Query<(Entity, &mut RecencyRanking<T>)>,
new_entities: Query<Entity, (Added<T>, Without<SuppressRecencyRank>)>,
moved_entities: Query<Entity, (Changed<ChildOf>, With<T>, Without<SuppressRecencyRank>)>,
newly_suppressed_entities: Query<Entity, (With<T>, Added<SuppressRecencyRank>)>,
mut unsuppressed_entities: RemovedComponents<SuppressRecencyRank>,
mut no_longer_relevant: RemovedComponents<T>,
child_of: Query<&ChildOf>,
mut rank_changes: EventReader<ChangeRank<T>>,
) {
for e in new_entities.iter().chain(unsuppressed_entities.read()) {
let mut next = Some(e);
while let Some(in_scope) = next {
if let Ok((_, mut ranking)) = rankings.get_mut(in_scope) {
if ranking.entities.iter().find(|check| **check == e).is_none() {
ranking.entities.push(e);
}
}
next = child_of.get(in_scope).ok().map(|co| co.parent());
}
}
for e in &moved_entities {
if new_entities.contains(e) {
continue;
}
let mut remain_in_scope = HashSet::new();
let mut next = Some(e);
while let Some(in_scope) = next {
if let Ok((_, mut ranking)) = rankings.get_mut(in_scope) {
remain_in_scope.insert(in_scope);
if ranking.entities.iter().find(|check| **check == e).is_none() {
ranking.entities.push(e);
}
}
next = child_of.get(in_scope).ok().map(|co| co.parent());
}
for (e_ranking, mut ranking) in &mut rankings {
if !remain_in_scope.contains(&e_ranking) {
ranking.entities.retain(|check| *check != e);
}
}
}
for e in newly_suppressed_entities
.iter()
.chain(no_longer_relevant.read())
{
for (_, mut ranking) in &mut rankings {
ranking.entities.retain(|check| *check != e);
}
}
for ChangeRank { of, by, .. } in rank_changes.read() {
let mut next = Some(*of);
while let Some(in_scope) = next {
if let Ok((_, mut ranking)) = rankings.get_mut(in_scope) {
match by {
RankAdjustment::Delta(delta) => {
if let Some(original_rank) = ranking.entities.iter().position(|e| *e == *of)
{
ranking.entities.retain(|e| *e != *of);
let new_rank = (original_rank as i64 + *delta).max(0) as usize;
if new_rank < ranking.entities.len() {
ranking.entities.insert(new_rank, *of);
} else {
ranking.entities.push(*of);
}
}
}
RankAdjustment::ToTop => {
ranking.entities.retain(|e| *e != *of);
ranking.entities.push(*of);
}
RankAdjustment::ToBottom => {
ranking.entities.retain(|e| *e != *of);
ranking.entities.insert(0, *of);
}
}
}
next = child_of.get(in_scope).ok().map(|co| co.parent());
}
}
}
fn update_recency_ranks<T: Component>(
mut commands: Commands,
rankings: Query<&RecencyRanking<T>, Changed<RecencyRanking<T>>>,
mut ranks: Query<&mut RecencyRank<T>>,
) {
for ranking in &rankings {
let out_of = ranking.len();
for (rank, e) in ranking.iter().enumerate() {
if let Ok(mut r) = ranks.get_mut(*e) {
r.rank = rank;
} else {
commands
.entity(*e)
.insert(RecencyRank::<T>::new(rank, out_of));
}
}
}
}