Create a Bounded structure for bounded values.
Some checks failed
Monorepo build / build-flake (push) Has been cancelled

This commit was merged in pull request #404.
This commit is contained in:
2026-03-01 14:53:27 -05:00
parent 147f7fb502
commit b4fe78288e
6 changed files with 230 additions and 1 deletions

4
Cargo.lock generated
View File

@@ -4964,6 +4964,10 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "utils"
version = "0.1.0"
[[package]]
name = "uuid"
version = "0.8.2"

View File

@@ -37,7 +37,7 @@ members = [
"visions/server",
# "visions/types",
"visions/ui",
"visions/core", "glimmer/yew",
"visions/core", "glimmer/yew", "utils",
# "bike-lights/bike",
]

6
utils/Cargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[package]
name = "utils"
version = "0.1.0"
edition = "2024"
[dependencies]

6
utils/Taskfile.yml Normal file
View File

@@ -0,0 +1,6 @@
version: "3"
tasks:
test:
cmds:
- cargo watch -x 'nextest run'

211
utils/src/bounded.rs Normal file
View File

@@ -0,0 +1,211 @@
use std::ops::{Add, Deref, Neg, Sub};
#[derive(Clone, Copy, Debug, Eq, Ord)]
pub struct Bounded<V> {
current: V,
min: V,
max: V,
}
fn clamp<V: PartialOrd>(value: V, min: V, max: V) -> V {
if value < min {
min
} else if value > max {
max
} else {
value
}
}
impl<V: Clone + Copy + PartialOrd> Bounded<V> {
pub fn new(current: V, min: V, max: V) -> Self {
Self {
current: clamp(current, min, max),
min,
max,
}
}
}
impl<V: PartialEq> PartialEq for Bounded<V> {
fn eq(&self, rhs: &Self) -> bool {
self.current == rhs.current
}
}
impl<V: PartialEq> PartialEq<V> for Bounded<V> {
fn eq(&self, rhs: &V) -> bool {
self.current == *rhs
}
}
impl<V: PartialOrd> PartialOrd for Bounded<V> {
fn partial_cmp(&self, rhs: &Self) -> Option<std::cmp::Ordering> {
self.current.partial_cmp(&rhs.current)
}
}
impl<V: PartialOrd> PartialOrd<V> for Bounded<V> {
fn partial_cmp(&self, rhs: &V) -> Option<std::cmp::Ordering> {
self.current.partial_cmp(rhs)
}
}
impl<V> Deref for Bounded<V> {
type Target = V;
fn deref(&self) -> &Self::Target {
&self.current
}
}
impl<V: Add<Output = V> + PartialOrd + Copy> Add<V> for Bounded<V> {
type Output = Bounded<V>;
fn add(self, rhs: V) -> Self::Output {
let new_value: V = self.current + rhs;
Bounded::new(new_value, self.min, self.max)
}
}
impl<V: Add<Output = V> + PartialOrd + Copy> Add for Bounded<V> {
type Output = Bounded<V>;
fn add(self, rhs: Bounded<V>) -> Self::Output {
let new_value: V = self.current + rhs.current;
Bounded::new(new_value, self.min, self.max)
}
}
impl<V: Sub<Output = V> + PartialOrd + Copy> Sub<V> for Bounded<V> {
type Output = Bounded<V>;
fn sub(self, rhs: V) -> Self::Output {
let new_value: V = self.current - rhs;
Bounded::new(new_value, self.min, self.max)
}
}
impl<V: Sub<Output = V> + PartialOrd + Copy> Sub for Bounded<V> {
type Output = Bounded<V>;
fn sub(self, rhs: Bounded<V>) -> Self::Output {
let new_value: V = self.current - rhs.current;
Bounded::new(new_value, self.min, self.max)
}
}
impl<V: Neg<Output = V> + PartialOrd + Copy> Neg for Bounded<V> {
type Output = Bounded<V>;
fn neg(self) -> Self::Output {
Self {
current: -self.current,
..self
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn safe_construction() {
assert_eq!(Bounded::new(5, 0, 10), 5);
assert_eq!(Bounded::new(5, 6, 10), 6);
assert_eq!(Bounded::new(5, 0, 4), 4);
}
#[test]
fn can_compare_against_primitive() {
let value = Bounded::new(5, 0, 10);
assert_eq!(value, 5);
assert_ne!(value, 4);
assert!(Bounded::new(5, 0, 10) < 6);
assert!(Bounded::new(5, 0, 10) > 4);
}
#[test]
fn can_compare_two_values() {
assert_eq!(Bounded::new(5, 0, 10), Bounded::new(5, 0, 10));
assert_eq!(Bounded::new(5, 0, 10), Bounded::new(5, 1, 9));
assert_ne!(Bounded::new(5, 0, 10), Bounded::new(4, 0, 10));
assert!(Bounded::new(5, 0, 10) < Bounded::new(6, 0, 10));
assert!(Bounded::new(5, 0, 10) < Bounded::new(6, 1, 9));
assert!(Bounded::new(5, 0, 10) > Bounded::new(4, 0, 10));
assert!(Bounded::new(5, 0, 10) > Bounded::new(4, 1, 9));
}
#[test]
fn can_extract_value() {
let value = Bounded::new(5, 0, 10);
let primitive: i32 = *value;
let func: Box<dyn FnOnce(i32)> = Box::new(|_: i32| {});
assert_eq!(primitive, 5);
func(*value);
}
#[test]
fn can_use_value_as_index() {
let lst = vec!['a', 'b', 'c', 'd'];
let idx: Bounded<usize> = Bounded::new(2, 0, 2);
assert_eq!(lst[*idx], 'c');
assert_eq!(lst[*Bounded::new(3, 0, 9)], 'd');
}
#[test]
fn can_add_number_to_value() {
assert_eq!(Bounded::new(5, 0, 10) + 3, Bounded::new(8, 0, 10));
assert_eq!(Bounded::new(5, 0, 10) + 6, Bounded::new(10, 0, 10));
}
#[test]
fn can_add_two_values() {
assert_eq!(
Bounded::new(5, 0, 10) + Bounded::new(3, 0, 10),
Bounded::new(8, 0, 10)
);
assert_eq!(
Bounded::new(5, 0, 10) + Bounded::new(6, 0, 10),
Bounded::new(10, 0, 10)
);
assert_eq!(
Bounded::new(5, 0, 10) + Bounded::new(6, 0, 9),
Bounded::new(10, 0, 10)
);
}
#[test]
fn can_subtract_number_from_value() {
assert_eq!(Bounded::new(5, 0, 10) - 3, Bounded::new(2, 0, 10));
assert_eq!(Bounded::new(5, 0, 10) - 6, Bounded::new(0, 0, 10));
}
#[test]
fn can_subtract_two_values() {
assert_eq!(
Bounded::new(5, 0, 10) - Bounded::new(3, 0, 10),
Bounded::new(2, 0, 10)
);
assert_eq!(
Bounded::new(5, 0, 10) - Bounded::new(6, 0, 10),
Bounded::new(0, 0, 10)
);
assert_eq!(
Bounded::new(5, 0, 10) - Bounded::new(6, 1, 9),
Bounded::new(0, 0, 10)
);
}
#[test]
fn can_negate() {
assert_eq!(Bounded::new(5, -10, 10).neg(), Bounded::new(-5, -10, 10));
}
}

2
utils/src/lib.rs Normal file
View File

@@ -0,0 +1,2 @@
mod bounded;
pub use bounded::*;