use crate::loose_bool::LooseBool;
use loose_enum::loose_enum;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(
feature = "bevy_reflect",
derive(bevy_reflect::Reflect),
reflect(Debug, Clone, PartialEq)
)]
pub struct Filter {
#[serde(rename = "f")]
pub filter_type: FilterType,
#[serde(rename = "p")]
pub parameter1: i32,
#[serde(rename = "t")]
pub parameter2: i32,
#[serde(rename = "r")]
pub reverse: LooseBool,
#[serde(rename = "c")]
pub chunks: Option<i32>,
#[serde(rename = "n")]
pub random_behaviour: Option<RandomBehaviour>,
#[serde(rename = "s")]
pub random_seed: Option<i32>,
#[serde(rename = "d")]
pub limit_behaviour: Option<LimitBehaviour>,
#[serde(rename = "l")]
pub limit_percent: Option<f32>,
}
impl Default for Filter {
fn default() -> Self {
Self {
filter_type: FilterType::default(),
parameter1: 1,
parameter2: 0,
reverse: LooseBool::False,
chunks: Some(0),
random_behaviour: Some(RandomBehaviour::None),
random_seed: Some(0),
limit_behaviour: Some(LimitBehaviour::None),
limit_percent: Some(1.0),
}
}
}
impl Filter {
#[must_use]
#[inline]
#[deprecated(note = "Experimental. Does not consider random in calculations.")]
pub fn is_in_filter(&self, mut light_id: i32, mut group_size: i32) -> bool {
assert!(light_id < group_size);
if let Some(limit) = self.limit_percent
&& limit > 0.0
&& light_id >= (group_size as f32 * limit) as i32
{
return false;
}
if self.reverse.is_true() {
light_id = group_size - light_id - 1;
}
if let Some(chunks) = self.chunks
&& chunks > 0
&& chunks < group_size
{
light_id = (light_id as f32 / (group_size as f32 / chunks as f32)) as i32;
group_size = chunks;
}
match self.filter_type {
FilterType::Division => {
let start = self.parameter2 * group_size / self.parameter1.max(1);
let end = (self.parameter2 + 1) * group_size / self.parameter1.max(1);
light_id >= start && light_id < end.max(start + 1)
}
FilterType::StepAndOffset => {
let offset_light_id = light_id - self.parameter1;
offset_light_id % self.parameter2.max(1) == 0 && offset_light_id >= 0
}
FilterType::Undefined(_) => true,
}
}
#[allow(deprecated)]
#[must_use]
#[inline]
#[deprecated(note = "Experimental. Does not consider random in calculations.")]
pub(crate) fn count_filtered_without_limit(&self, mut group_size: i32) -> i32 {
if let Some(chunks) = self.chunks
&& chunks > 0
&& chunks < group_size
{
group_size = chunks;
}
match self.filter_type {
FilterType::Division => {
let start = self.parameter2 * group_size / self.parameter1.max(1);
let end = (self.parameter2 + 1) * group_size / self.parameter1.max(1);
end.max(start + 1) - start
}
FilterType::StepAndOffset => {
group_size / self.parameter2.max(1) - self.parameter1 / self.parameter2.max(1)
}
FilterType::Undefined(_) => group_size,
}
}
#[allow(deprecated)]
#[must_use]
#[inline]
#[deprecated(note = "Experimental. Does not consider random in calculations.")]
#[allow(deprecated)]
pub fn count_filtered(&self, group_size: i32) -> i32 {
let filtered = self.count_filtered_without_limit(group_size);
if let Some(limit) = self.limit_percent
&& limit > 0.0
{
(filtered as f32 * limit) as i32
} else {
filtered
}
}
#[allow(deprecated)]
#[must_use]
#[inline]
#[deprecated(note = "Experimental. Does not consider random in calculations.")]
pub fn get_relative_index(&self, mut light_id: i32, mut group_size: i32) -> i32 {
assert!(light_id < group_size);
if self.reverse.is_true() {
light_id = group_size - light_id;
}
if let Some(chunks) = self.chunks
&& chunks > 0
&& chunks < group_size
{
light_id = (light_id as f32 / (group_size as f32 / chunks as f32)) as i32;
group_size = chunks;
}
match self.filter_type {
FilterType::Division => {
let start = self.parameter2 * group_size / self.parameter1.max(1);
light_id - start
}
FilterType::StepAndOffset => {
let offset_light_id = light_id - self.parameter1;
offset_light_id / self.parameter2.max(1)
}
FilterType::Undefined(_) => group_size,
}
}
}
loose_enum! {
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(
feature = "bevy_reflect",
derive(bevy_reflect::Reflect),
reflect(Debug, Clone, PartialEq)
)]
pub enum FilterType: i32 {
#[default]
Division = 1,
StepAndOffset = 2,
}
}
loose_enum!(
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(
feature = "bevy_reflect",
derive(bevy_reflect::Reflect),
reflect(Debug, Clone, PartialEq)
)]
pub enum RandomBehaviour: i32 {
#[default]
None = 0,
KeepOrder = 1,
RandomElements = 2,
}
);
loose_enum!(
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(
feature = "bevy_reflect",
derive(bevy_reflect::Reflect),
reflect(Debug, Clone, PartialEq)
)]
pub enum LimitBehaviour: i32 {
#[default]
None = 0,
Beat = 1,
Value = 2,
Both = 3,
}
);
impl LimitBehaviour {
pub fn beat_enabled(&self) -> bool {
matches!(self, LimitBehaviour::Beat | LimitBehaviour::Both)
}
pub fn value_enabled(&self) -> bool {
matches!(self, LimitBehaviour::Value | LimitBehaviour::Both)
}
}
#[allow(deprecated)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn division_first_half() {
let filter = Filter {
filter_type: FilterType::Division,
parameter1: 2,
parameter2: 0,
..Default::default()
};
assert!((0..6).all(|i| filter.is_in_filter(i, 12)));
assert!((6..12).all(|i| !filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 6);
assert!((0..6).all(|i| filter.get_relative_index(i, 12) == i));
}
#[test]
fn division_second_half() {
let filter = Filter {
filter_type: FilterType::Division,
parameter1: 2,
parameter2: 1,
..Default::default()
};
assert!((0..6).all(|i| !filter.is_in_filter(i, 12)));
assert!((6..12).all(|i| filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 6);
assert!((6..12).all(|i| filter.get_relative_index(i, 12) == i - 6));
}
#[test]
fn division_first_half_rev() {
let filter = Filter {
filter_type: FilterType::Division,
parameter1: 2,
parameter2: 0,
reverse: LooseBool::True,
..Default::default()
};
assert!((0..6).all(|i| !filter.is_in_filter(i, 12)));
assert!((6..12).all(|i| filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 6);
assert!((6..12).all(|i| filter.get_relative_index(i, 12) == 12 - i));
}
#[test]
fn division_second_half_rev() {
let filter = Filter {
filter_type: FilterType::Division,
parameter1: 2,
parameter2: 1,
reverse: LooseBool::True,
..Default::default()
};
assert!((0..6).all(|i| filter.is_in_filter(i, 12)));
assert!((6..12).all(|i| !filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 6);
assert!((0..6).all(|i| filter.get_relative_index(i, 12) == 6 - i));
}
#[test]
fn division_select_all() {
let filter = Filter {
filter_type: FilterType::Division,
parameter1: 1,
parameter2: 0,
..Default::default()
};
assert!((0..12).all(|i| filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 12);
assert!((0..12).all(|i| filter.get_relative_index(i, 12) == i));
}
#[test]
fn division_larger_than_group_size() {
for i in 0..12 {
let filter = Filter {
filter_type: FilterType::Division,
parameter1: 12,
parameter2: i,
..Default::default()
};
let expected_id = match i {
0 => 0,
1 => 0,
2 => 1,
3 => 2,
4 => 2,
5 => 3,
6 => 4,
7 => 4,
8 => 5,
9 => 6,
10 => 6,
11 => 7,
_ => unreachable!(),
};
assert!(filter.is_in_filter(expected_id, 8));
assert!(
(0..8)
.filter(|x| *x != expected_id)
.all(|i| !filter.is_in_filter(i, 8))
);
assert_eq!(filter.count_filtered(8), 1);
assert_eq!(filter.get_relative_index(expected_id, 8), 0);
}
}
#[test]
fn step_select_all() {
let filter = Filter {
filter_type: FilterType::StepAndOffset,
parameter1: 0,
parameter2: 1,
..Default::default()
};
assert!((0..12).all(|i| filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 12);
assert!((0..12).all(|i| filter.get_relative_index(i, 12) == i));
}
#[test]
fn step_start_index() {
for outer in 0..12 {
let filter = Filter {
filter_type: FilterType::StepAndOffset,
parameter1: outer,
parameter2: 1,
..Default::default()
};
assert!((0..outer).all(|i| !filter.is_in_filter(i, 12)));
assert!((outer..12).all(|i| filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 12 - outer);
assert!((outer..12).all(|i| filter.get_relative_index(i, 12) == i - outer));
}
}
#[test]
fn step_every_other() {
let filter = Filter {
filter_type: FilterType::StepAndOffset,
parameter1: 0,
parameter2: 2,
..Default::default()
};
for i in 0..12 {
assert_eq!(filter.is_in_filter(i, 12), i % 2 == 0);
if i % 2 == 0 {
assert_eq!(filter.get_relative_index(i, 12), i / 2);
}
}
assert_eq!(filter.count_filtered(12), 6);
}
#[test]
fn step_every_other_offset() {
let filter = Filter {
filter_type: FilterType::StepAndOffset,
parameter1: 1,
parameter2: 2,
..Default::default()
};
for i in 0..12 {
assert_eq!(filter.is_in_filter(i, 12), i % 2 != 0);
if i % 2 != 0 {
assert_eq!(filter.get_relative_index(i, 12), i / 2);
}
}
assert_eq!(filter.count_filtered(12), 6);
}
#[test]
fn chunks_of_two() {
let filter = Filter {
chunks: Some(6),
..Default::default()
};
assert!((0..12).all(|i| filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 6);
assert!((0..6).all(|i| {
filter.get_relative_index(i * 2, 12) == i
&& filter.get_relative_index(i * 2 + 1, 12) == i
}));
}
#[test]
fn chunks_of_six() {
let filter = Filter {
chunks: Some(2),
..Default::default()
};
assert!((0..12).all(|i| filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 2);
assert!((0..6).all(|i| filter.get_relative_index(i, 12) == 0));
assert!((0..6).all(|i| filter.get_relative_index(i + 6, 12) == 1));
}
#[test]
fn chunks_out_of_bounds() {
let filter = Filter {
chunks: Some(24),
..Default::default()
};
assert!((0..12).all(|i| filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 12);
assert!((0..12).all(|i| filter.get_relative_index(i, 12) == i));
}
#[test]
fn chunks_non_factor() {
let filter = Filter {
chunks: Some(3),
..Default::default()
};
assert!((0..8).all(|i| filter.is_in_filter(i, 8)));
assert_eq!(filter.count_filtered(8), 3);
assert!((0..3).all(|i| filter.get_relative_index(i, 8) == 0));
assert!((3..6).all(|i| filter.get_relative_index(i, 8) == 1));
assert!((6..8).all(|i| filter.get_relative_index(i, 8) == 2));
}
#[test]
fn limit() {
let filter = Filter {
limit_percent: Some(0.5),
..Default::default()
};
assert!((0..6).all(|i| filter.is_in_filter(i, 12)));
assert!((6..12).all(|i| !filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 6);
assert_eq!(filter.count_filtered_without_limit(12), 12);
assert!((0..6).all(|i| filter.get_relative_index(i, 12) == i));
}
#[test]
fn limit_non_factor_none() {
let filter = Filter {
limit_percent: Some(0.01),
..Default::default()
};
assert!((0..8).all(|i| !filter.is_in_filter(i, 8)));
assert_eq!(filter.count_filtered(8), 0);
assert_eq!(filter.count_filtered_without_limit(8), 8);
}
#[test]
fn limit_non_factor_all_but_one() {
let filter = Filter {
limit_percent: Some(0.9),
..Default::default()
};
assert!((0..7).all(|i| filter.is_in_filter(i, 8)));
assert!(!filter.is_in_filter(7, 8));
assert_eq!(filter.count_filtered(8), 7);
assert_eq!(filter.count_filtered_without_limit(8), 8);
assert!((0..7).all(|i| filter.get_relative_index(i, 8) == i));
}
#[test]
fn limit_zero() {
let filter = Filter {
limit_percent: Some(0.0),
..Default::default()
};
assert!((0..12).all(|i| filter.is_in_filter(i, 12)));
assert_eq!(filter.count_filtered(12), 12);
assert_eq!(filter.count_filtered_without_limit(12), 12);
assert!((0..12).all(|i| filter.get_relative_index(i, 12) == i));
}
}