bevy_replicon/core/replicated_clients/
client_visibility.rsuse bevy::{
ecs::entity::{EntityHashMap, EntityHashSet},
prelude::*,
utils::hashbrown::hash_map::Entry,
};
use super::VisibilityPolicy;
pub struct ClientVisibility {
filter: VisibilityFilter,
cached_visibility: Visibility,
}
impl ClientVisibility {
pub(super) fn new(policy: VisibilityPolicy) -> Self {
match policy {
VisibilityPolicy::All => Self::with_filter(VisibilityFilter::All),
VisibilityPolicy::Blacklist => Self::with_filter(VisibilityFilter::Blacklist {
list: Default::default(),
added: Default::default(),
removed: Default::default(),
}),
VisibilityPolicy::Whitelist => Self::with_filter(VisibilityFilter::Whitelist {
list: Default::default(),
added: Default::default(),
removed: Default::default(),
}),
}
}
fn with_filter(filter: VisibilityFilter) -> Self {
Self {
filter,
cached_visibility: Default::default(),
}
}
pub(super) fn clear(&mut self) {
match &mut self.filter {
VisibilityFilter::All => (),
VisibilityFilter::Blacklist {
list,
added,
removed,
} => {
list.clear();
added.clear();
removed.clear();
}
VisibilityFilter::Whitelist {
list,
added,
removed,
} => {
list.clear();
added.clear();
removed.clear();
}
}
}
pub(crate) fn update(&mut self) {
match &mut self.filter {
VisibilityFilter::All => (),
VisibilityFilter::Blacklist {
list,
added,
removed,
} => {
for entity in removed.drain() {
list.remove(&entity);
}
added.clear();
}
VisibilityFilter::Whitelist {
list,
added,
removed,
} => {
for entity in added.drain() {
list.insert(entity, WhitelistInfo::Visible);
}
removed.clear();
}
}
}
pub(super) fn remove_despawned(&mut self, entity: Entity) {
match &mut self.filter {
VisibilityFilter::All { .. } => (),
VisibilityFilter::Blacklist {
list,
added,
removed,
} => {
if list.remove(&entity).is_some() {
added.remove(&entity);
removed.remove(&entity);
}
}
VisibilityFilter::Whitelist {
list,
added,
removed,
} => {
if list.remove(&entity).is_some() {
added.remove(&entity);
removed.remove(&entity);
}
}
}
}
pub(super) fn drain_lost_visibility(&mut self) -> impl Iterator<Item = Entity> + '_ {
match &mut self.filter {
VisibilityFilter::All { .. } => VisibilityLostIter::AllVisible,
VisibilityFilter::Blacklist { added, .. } => VisibilityLostIter::Lost(added.drain()),
VisibilityFilter::Whitelist { removed, .. } => {
VisibilityLostIter::Lost(removed.drain())
}
}
}
pub fn set_visibility(&mut self, entity: Entity, visibile: bool) {
match &mut self.filter {
VisibilityFilter::All { .. } => {
if visibile {
debug!(
"ignoring visibility enable due to {:?}",
VisibilityPolicy::All
);
} else {
warn!(
"ignoring visibility disable due to {:?}",
VisibilityPolicy::All
);
}
}
VisibilityFilter::Blacklist {
list,
added,
removed,
} => {
if visibile {
let Entry::Occupied(mut entry) = list.entry(entity) else {
return;
};
if added.remove(&entity) {
entry.remove();
return;
}
entry.insert(BlacklistInfo::QueuedForRemoval);
removed.insert(entity);
} else {
if list.insert(entity, BlacklistInfo::Hidden).is_some() {
removed.remove(&entity);
return;
};
added.insert(entity);
}
}
VisibilityFilter::Whitelist {
list,
added,
removed,
} => {
if visibile {
if *list.entry(entity).or_insert(WhitelistInfo::JustAdded)
== WhitelistInfo::JustAdded
{
added.insert(entity);
}
removed.remove(&entity);
} else {
if list.remove(&entity).is_none() {
return;
}
if added.remove(&entity) {
return;
}
removed.insert(entity);
}
}
}
}
pub fn is_visible(&self, entity: Entity) -> bool {
match self.get_visibility_state(entity) {
Visibility::Hidden => false,
Visibility::Gained | Visibility::Visible => true,
}
}
pub(crate) fn cache_visibility(&mut self, entity: Entity) {
self.cached_visibility = self.get_visibility_state(entity);
}
pub(crate) fn cached_visibility(&self) -> Visibility {
self.cached_visibility
}
fn get_visibility_state(&self, entity: Entity) -> Visibility {
match &self.filter {
VisibilityFilter::All => Visibility::Visible,
VisibilityFilter::Blacklist { list, .. } => match list.get(&entity) {
Some(BlacklistInfo::QueuedForRemoval) => Visibility::Gained,
Some(BlacklistInfo::Hidden) => Visibility::Hidden,
None => Visibility::Visible,
},
VisibilityFilter::Whitelist { list, .. } => match list.get(&entity) {
Some(WhitelistInfo::JustAdded) => Visibility::Gained,
Some(WhitelistInfo::Visible) => Visibility::Visible,
None => Visibility::Hidden,
},
}
}
}
enum VisibilityFilter {
All,
Blacklist {
list: EntityHashMap<BlacklistInfo>,
added: EntityHashSet,
removed: EntityHashSet,
},
Whitelist {
list: EntityHashMap<WhitelistInfo>,
added: EntityHashSet,
removed: EntityHashSet,
},
}
#[derive(PartialEq, Clone, Copy)]
enum WhitelistInfo {
Visible,
JustAdded,
}
#[derive(PartialEq, Clone, Copy)]
enum BlacklistInfo {
Hidden,
QueuedForRemoval,
}
#[derive(PartialEq, Default, Clone, Copy)]
pub(crate) enum Visibility {
#[default]
Hidden,
Gained,
Visible,
}
enum VisibilityLostIter<T> {
AllVisible,
Lost(T),
}
impl<T: Iterator> Iterator for VisibilityLostIter<T> {
type Item = T::Item;
fn next(&mut self) -> Option<Self::Item> {
match self {
VisibilityLostIter::AllVisible => None,
VisibilityLostIter::Lost(entities) => entities.next(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all() {
let mut visibility = ClientVisibility::new(VisibilityPolicy::All);
assert!(visibility.is_visible(Entity::PLACEHOLDER));
visibility.set_visibility(Entity::PLACEHOLDER, true);
assert!(visibility.is_visible(Entity::PLACEHOLDER));
visibility.set_visibility(Entity::PLACEHOLDER, false);
assert!(
visibility.is_visible(Entity::PLACEHOLDER),
"shouldn't have any effect for this policy"
);
}
#[test]
fn blacklist_insertion() {
let mut visibility = ClientVisibility::new(VisibilityPolicy::Blacklist);
visibility.set_visibility(Entity::PLACEHOLDER, false);
assert!(!visibility.is_visible(Entity::PLACEHOLDER));
let VisibilityFilter::Blacklist {
list,
added,
removed,
} = &visibility.filter
else {
panic!("filter should be a blacklist");
};
assert!(list.contains_key(&Entity::PLACEHOLDER));
assert!(added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
visibility.update();
let VisibilityFilter::Blacklist {
list,
added,
removed,
} = &visibility.filter
else {
panic!("filter should be a blacklist");
};
assert!(list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
}
#[test]
fn blacklist_empty_removal() {
let mut visibility = ClientVisibility::new(VisibilityPolicy::Blacklist);
assert!(visibility.is_visible(Entity::PLACEHOLDER));
visibility.set_visibility(Entity::PLACEHOLDER, true);
assert!(visibility.is_visible(Entity::PLACEHOLDER));
let VisibilityFilter::Blacklist {
list,
added,
removed,
} = visibility.filter
else {
panic!("filter should be a blacklist");
};
assert!(!list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
}
#[test]
fn blacklist_removal() {
let mut visibility = ClientVisibility::new(VisibilityPolicy::Blacklist);
visibility.set_visibility(Entity::PLACEHOLDER, false);
visibility.update();
visibility.set_visibility(Entity::PLACEHOLDER, true);
assert!(visibility.is_visible(Entity::PLACEHOLDER));
let VisibilityFilter::Blacklist {
list,
added,
removed,
} = &visibility.filter
else {
panic!("filter should be a blacklist");
};
assert!(list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(removed.contains(&Entity::PLACEHOLDER));
visibility.update();
let VisibilityFilter::Blacklist {
list,
added,
removed,
} = &visibility.filter
else {
panic!("filter should be a blacklist");
};
assert!(!list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
}
#[test]
fn blacklist_insertion_removal() {
let mut visibility = ClientVisibility::new(VisibilityPolicy::Blacklist);
visibility.set_visibility(Entity::PLACEHOLDER, false);
visibility.set_visibility(Entity::PLACEHOLDER, true);
assert!(visibility.is_visible(Entity::PLACEHOLDER));
let VisibilityFilter::Blacklist {
list,
added,
removed,
} = visibility.filter
else {
panic!("filter should be a blacklist");
};
assert!(!list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
}
#[test]
fn blacklist_duplicate_insertion() {
let mut visibility = ClientVisibility::new(VisibilityPolicy::Blacklist);
visibility.set_visibility(Entity::PLACEHOLDER, false);
visibility.update();
visibility.set_visibility(Entity::PLACEHOLDER, false);
assert!(!visibility.is_visible(Entity::PLACEHOLDER));
let VisibilityFilter::Blacklist {
list,
added,
removed,
} = visibility.filter
else {
panic!("filter should be a blacklist");
};
assert!(list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
}
#[test]
fn whitelist_insertion() {
let mut visibility = ClientVisibility::new(VisibilityPolicy::Whitelist);
visibility.set_visibility(Entity::PLACEHOLDER, true);
assert!(visibility.is_visible(Entity::PLACEHOLDER));
let VisibilityFilter::Whitelist {
list,
added,
removed,
} = &visibility.filter
else {
panic!("filter should be a whitelist");
};
assert!(list.contains_key(&Entity::PLACEHOLDER));
assert!(added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
visibility.update();
let VisibilityFilter::Whitelist {
list,
added,
removed,
} = &visibility.filter
else {
panic!("filter should be a blacklist");
};
assert!(list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
}
#[test]
fn whitelist_empty_removal() {
let mut visibility = ClientVisibility::new(VisibilityPolicy::Whitelist);
assert!(!visibility.is_visible(Entity::PLACEHOLDER));
visibility.set_visibility(Entity::PLACEHOLDER, false);
assert!(!visibility.is_visible(Entity::PLACEHOLDER));
let VisibilityFilter::Whitelist {
list,
added,
removed,
} = visibility.filter
else {
panic!("filter should be a whitelist");
};
assert!(!list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
}
#[test]
fn whitelist_removal() {
let mut visibility = ClientVisibility::new(VisibilityPolicy::Whitelist);
visibility.set_visibility(Entity::PLACEHOLDER, true);
visibility.update();
visibility.set_visibility(Entity::PLACEHOLDER, false);
assert!(!visibility.is_visible(Entity::PLACEHOLDER));
let VisibilityFilter::Whitelist {
list,
added,
removed,
} = &visibility.filter
else {
panic!("filter should be a whitelist");
};
assert!(!list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(removed.contains(&Entity::PLACEHOLDER));
visibility.update();
let VisibilityFilter::Whitelist {
list,
added,
removed,
} = &visibility.filter
else {
panic!("filter should be a blacklist");
};
assert!(!list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
}
#[test]
fn whitelist_insertion_removal() {
let mut visibility = ClientVisibility::new(VisibilityPolicy::Whitelist);
visibility.set_visibility(Entity::PLACEHOLDER, true);
visibility.set_visibility(Entity::PLACEHOLDER, false);
assert!(!visibility.is_visible(Entity::PLACEHOLDER));
let VisibilityFilter::Whitelist {
list,
added,
removed,
} = visibility.filter
else {
panic!("filter should be a blacklist");
};
assert!(!list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
}
#[test]
fn whitelist_duplicate_insertion() {
let mut visibility = ClientVisibility::new(VisibilityPolicy::Whitelist);
visibility.set_visibility(Entity::PLACEHOLDER, true);
visibility.update();
visibility.set_visibility(Entity::PLACEHOLDER, true);
assert!(visibility.is_visible(Entity::PLACEHOLDER));
let VisibilityFilter::Whitelist {
list,
added,
removed,
} = visibility.filter
else {
panic!("filter should be a blacklist");
};
assert!(list.contains_key(&Entity::PLACEHOLDER));
assert!(!added.contains(&Entity::PLACEHOLDER));
assert!(!removed.contains(&Entity::PLACEHOLDER));
}
}