From 8ab642f3dba0ab3d8b14f45bd54c0103f11fda56 Mon Sep 17 00:00:00 2001 From: Claudio Giovanni Mattera Date: Tue, 12 Nov 2024 19:15:06 +0100 Subject: [PATCH] Make crate compatible with no_std --- Cargo.toml | 14 ++- examples/comet.rs | 4 +- examples/drivers/comet.rs | 6 +- examples/drivers/mod.rs | 2 +- examples/drivers/ripples.rs | 8 +- examples/drivers/scan.rs | 6 +- examples/drivers/warpspeed.rs | 8 +- examples/ripples.rs | 4 +- examples/scan.rs | 4 +- examples/warpspeed.rs | 4 +- src/color.rs | 2 +- src/config.rs | 17 ++- src/driver/buffers.rs | 26 ++-- src/driver/filters.rs | 15 ++- src/driver/mod.rs | 50 +++++--- src/error.rs | 9 +- src/led.rs | 21 ++-- src/lib.rs | 74 ++++++----- src/scheduler/mod.rs | 189 ++++++++++++++++++++++++---- src/spatial_led/directional.rs | 8 +- src/spatial_led/filter.rs | 16 +-- src/spatial_led/indexical.rs | 7 +- src/spatial_led/maps_and_filters.rs | 7 +- src/spatial_led/meta.rs | 11 +- src/spatial_led/mod.rs | 4 +- src/spatial_led/positional.rs | 9 +- src/spatial_led/segmental.rs | 17 ++- src/time.rs | 64 ++++++++++ 28 files changed, 444 insertions(+), 162 deletions(-) create mode 100644 src/time.rs diff --git a/Cargo.toml b/Cargo.toml index a7a2557..cd495c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,20 +11,24 @@ documentation = "https://docs.rs/spatial_led" exclude = ["*.gif", "*.cast"] [features] -default = ["drivers", "scheduler"] +default = ["drivers", "scheduler", "std"] drivers = ["compact_str", "sled_driver_macros"] -scheduler = ["spin_sleep"] +scheduler = [] named_colors = [] +std = ["glam/std"] +async = [] +spin_sleep = ["std", "dep:spin_sleep"] [dependencies] -glam = { version = "0.29" } +glam = { version = "0.29", default-features = false, features = ["libm"] } palette = { version = "0.7", default-features = false, features = [ - "std", + "libm", "approx", ] } smallvec = "1.13" -compact_str = { version = "0.8", optional = true } +compact_str = { version = "0.8", default-features = false, optional = true } sled_driver_macros = { version = "0.1.2", optional = true } +num-traits = { version = "0.2", default-features = false } spin_sleep = { version = "1.2", optional = true } [dev-dependencies] diff --git a/examples/comet.rs b/examples/comet.rs index 5bd2d62..991baea 100644 --- a/examples/comet.rs +++ b/examples/comet.rs @@ -4,7 +4,7 @@ use drivers::comet; mod resources; use resources::tui::SledTerminalDisplay; -use spatial_led::{scheduler::Scheduler, Sled}; +use spatial_led::{scheduler::StdScheduler, Sled}; fn main() { let sled = Sled::new("./examples/resources/complex_room.yap").unwrap(); @@ -12,7 +12,7 @@ fn main() { let mut driver = comet::build_driver(); driver.mount(sled); - let mut scheduler = Scheduler::new(500.0); + let mut scheduler = StdScheduler::new(500.0); scheduler.loop_until_err(|| { driver.step(); display.set_leds(driver.colors_and_positions_coerced()); diff --git a/examples/drivers/comet.rs b/examples/drivers/comet.rs index 6338629..4fdabc1 100644 --- a/examples/drivers/comet.rs +++ b/examples/drivers/comet.rs @@ -1,5 +1,5 @@ +use spatial_led::driver::{StdDriver, TimeInfo}; use spatial_led::driver_macros::*; -use spatial_led::driver::{Driver, TimeInfo}; use spatial_led::SledResult; use spatial_led::{color::Rgb, Sled}; @@ -17,8 +17,8 @@ const BLUE: Rgb = Rgb::new(0.4, 0.51, 0.93); const TRAIL_RADIUS: f32 = 1.2; #[allow(dead_code)] -pub fn build_driver() -> Driver { - let mut driver = Driver::new(); +pub fn build_driver() -> StdDriver { + let mut driver = StdDriver::new(); driver.set_draw_commands(draw); driver } diff --git a/examples/drivers/mod.rs b/examples/drivers/mod.rs index 9b74cc0..53c9cfe 100644 --- a/examples/drivers/mod.rs +++ b/examples/drivers/mod.rs @@ -1,4 +1,4 @@ pub mod comet; pub mod ripples; +pub mod scan; pub mod warpspeed; -pub mod scan; \ No newline at end of file diff --git a/examples/drivers/ripples.rs b/examples/drivers/ripples.rs index 449c62c..4b197dc 100644 --- a/examples/drivers/ripples.rs +++ b/examples/drivers/ripples.rs @@ -1,6 +1,6 @@ -use spatial_led::driver_macros::*; use rand::Rng; -use spatial_led::driver::{BufferContainer, Driver, TimeInfo}; +use spatial_led::driver::{BufferContainer, StdDriver, TimeInfo}; +use spatial_led::driver_macros::*; use spatial_led::SledResult; use spatial_led::{color::Rgb, Sled, Vec2}; use std::ops::Range; @@ -11,8 +11,8 @@ const FEATHERING: f32 = 0.15; const INV_F: f32 = 1.0 / FEATHERING; #[allow(dead_code)] -pub fn build_driver() -> Driver { - let mut driver = Driver::new(); +pub fn build_driver() -> StdDriver { + let mut driver = StdDriver::new(); driver.set_startup_commands(startup); driver.set_compute_commands(compute); diff --git a/examples/drivers/scan.rs b/examples/drivers/scan.rs index 4a1ce7b..52eb8b3 100644 --- a/examples/drivers/scan.rs +++ b/examples/drivers/scan.rs @@ -4,7 +4,7 @@ use std::f32::consts::{PI, TAU}; use std::time::Duration; use glam::Vec2; -use spatial_led::driver::{Driver, TimeInfo}; +use spatial_led::driver::{StdDriver, TimeInfo}; use spatial_led::driver_macros::*; use spatial_led::BufferContainer; use spatial_led::{color::Rgb, Sled, SledResult}; @@ -12,8 +12,8 @@ use spatial_led::{color::Rgb, Sled, SledResult}; const SCAN_DURATION: f32 = 4.0; #[allow(dead_code)] -pub fn build_driver() -> Driver { - let mut driver = Driver::new(); +pub fn build_driver() -> StdDriver { + let mut driver = StdDriver::new(); driver.set_startup_commands(startup); driver.set_compute_commands(compute); driver.set_draw_commands(draw); diff --git a/examples/drivers/warpspeed.rs b/examples/drivers/warpspeed.rs index 03f9405..58c778c 100644 --- a/examples/drivers/warpspeed.rs +++ b/examples/drivers/warpspeed.rs @@ -1,6 +1,6 @@ -use spatial_led::driver_macros::*; use rand::Rng; -use spatial_led::driver::{BufferContainer, Driver, TimeInfo}; +use spatial_led::driver::{BufferContainer, StdDriver, TimeInfo}; +use spatial_led::driver_macros::*; use spatial_led::SledResult; use spatial_led::{color::Rgb, Sled, Vec2}; @@ -9,8 +9,8 @@ const VELOCITY: f32 = 6.0; const DIRECTION: Vec2 = Vec2::new(0.7071, -0.7071); #[allow(dead_code)] -pub fn build_driver() -> Driver { - let mut driver = Driver::new(); +pub fn build_driver() -> StdDriver { + let mut driver = StdDriver::new(); driver.set_startup_commands(startup); driver.set_compute_commands(compute); diff --git a/examples/ripples.rs b/examples/ripples.rs index 6be14f5..00e1fb2 100644 --- a/examples/ripples.rs +++ b/examples/ripples.rs @@ -4,7 +4,7 @@ use drivers::ripples; mod resources; use resources::tui::SledTerminalDisplay; -use spatial_led::{scheduler::Scheduler, Sled}; +use spatial_led::{scheduler::StdScheduler, Sled}; fn main() { let sled = Sled::new("./examples/resources/complex_room.yap").unwrap(); @@ -12,7 +12,7 @@ fn main() { let mut driver = ripples::build_driver(); driver.mount(sled); - let mut scheduler = Scheduler::new(500.0); + let mut scheduler = StdScheduler::new(500.0); scheduler.loop_until_err(|| { driver.step(); display.set_leds(driver.colors_and_positions_coerced()); diff --git a/examples/scan.rs b/examples/scan.rs index 988fe5e..2436a74 100644 --- a/examples/scan.rs +++ b/examples/scan.rs @@ -4,7 +4,7 @@ use drivers::scan; mod resources; use resources::tui::SledTerminalDisplay; -use spatial_led::{scheduler::Scheduler, Sled}; +use spatial_led::{scheduler::StdScheduler, Sled}; fn main() { let sled = Sled::new("./examples/resources/complex_room.yap").unwrap(); @@ -12,7 +12,7 @@ fn main() { let mut driver = scan::build_driver(); driver.mount(sled); - let mut scheduler = Scheduler::new(500.0); + let mut scheduler = StdScheduler::new(500.0); scheduler.loop_until_err(|| { driver.step(); display.set_leds(driver.colors_and_positions_coerced()); diff --git a/examples/warpspeed.rs b/examples/warpspeed.rs index ac0eb6d..546bd79 100644 --- a/examples/warpspeed.rs +++ b/examples/warpspeed.rs @@ -4,7 +4,7 @@ use drivers::warpspeed; mod resources; use resources::tui::SledTerminalDisplay; -use spatial_led::{color::Rgb, scheduler::Scheduler, Sled}; +use spatial_led::{color::Rgb, scheduler::StdScheduler, Sled}; fn main() { let sled = Sled::new("./examples/resources/complex_room.yap").unwrap(); @@ -19,7 +19,7 @@ fn main() { Rgb::new(0.0, 0.0, 1.0), ]); - let mut scheduler = Scheduler::new(500.0); + let mut scheduler = StdScheduler::new(500.0); scheduler.loop_until_err(|| { driver.step(); display.set_leds(driver.colors_and_positions_coerced()); diff --git a/src/color.rs b/src/color.rs index 9a1831a..143f2e8 100644 --- a/src/color.rs +++ b/src/color.rs @@ -8,7 +8,7 @@ pub mod consts { //! //! Adapted from palette's [named](https://docs.rs/palette/0.7.3/palette/named/index.html) module //! but expressed as 32-bit rgb instead of 8-bit for better compatability with sled. - //! + //! //! Colors are taken from the [SVG keyword //! colors](https://www.w3.org/TR/SVG11/types.html#ColorKeywords) (same as in //! CSS3) and they can be used as if they were pixel values: diff --git a/src/config.rs b/src/config.rs index 43de224..836907c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,15 @@ +use alloc::string::String; +use alloc::string::ToString as _; +use alloc::vec; +use alloc::vec::Vec; + use crate::error::SledError; +use core::str::Lines; use glam::Vec2; use smallvec::SmallVec; -use std::{fs, str::Lines}; + +#[cfg(not(feature = "std"))] +use num_traits::float::Float as _; pub(crate) struct Config { pub center_point: Vec2, @@ -95,7 +103,7 @@ fn extract_segments_from_string(s: &str) -> Vec { } impl Config { - pub fn from_string(string: String) -> Result { + pub fn from_string(string: &str) -> Result { let mut lines = string.lines(); let (center, density) = extract_center_and_density_from_lines(&mut lines); @@ -121,9 +129,10 @@ impl Config { }) } + #[cfg(feature = "std")] pub fn from_toml_file(path: &str) -> Result { - let as_string = fs::read_to_string(path).map_err(SledError::from_error)?; - Config::from_string(as_string) + let as_string = std::fs::read_to_string(path).map_err(SledError::from_error)?; + Config::from_string(&as_string) } } diff --git a/src/driver/buffers.rs b/src/driver/buffers.rs index 3a0c88b..3627501 100644 --- a/src/driver/buffers.rs +++ b/src/driver/buffers.rs @@ -1,22 +1,26 @@ -use std::{ +use core::{ any::{type_name, Any}, fmt::Debug, }; +use alloc::boxed::Box; +use alloc::collections::BTreeMap; +use alloc::format; +use alloc::vec::Vec; + use compact_str::{CompactString, ToCompactString}; -use std::collections::HashMap; use crate::SledError; #[derive(Debug)] pub struct BufferContainer { - buffers: HashMap>, + buffers: BTreeMap>, } impl BufferContainer { pub fn new() -> Self { BufferContainer { - buffers: HashMap::new(), + buffers: BTreeMap::new(), } } @@ -235,18 +239,18 @@ impl BufferContainer { Ok(()) } - pub fn iter(&self) -> std::collections::hash_map::Iter> { + pub fn iter(&self) -> alloc::collections::btree_map::Iter> { self.buffers.iter() } pub fn iter_mut( &mut self, - ) -> std::collections::hash_map::IterMut> { + ) -> alloc::collections::btree_map::IterMut> { self.buffers.iter_mut() } } -pub trait Buffer: std::fmt::Debug { +pub trait Buffer: core::fmt::Debug { fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; } @@ -264,16 +268,16 @@ impl Buffer for Vec { } } -impl std::iter::IntoIterator for BufferContainer { +impl core::iter::IntoIterator for BufferContainer { type Item = (CompactString, Box); - type IntoIter = std::collections::hash_map::IntoIter>; + type IntoIter = alloc::collections::btree_map::IntoIter>; fn into_iter(self) -> Self::IntoIter { self.buffers.into_iter() } } -impl std::iter::FromIterator<(CompactString, Box)> for BufferContainer { +impl core::iter::FromIterator<(CompactString, Box)> for BufferContainer { fn from_iter)>>(iter: T) -> Self { let mut bc = BufferContainer::new(); @@ -285,7 +289,7 @@ impl std::iter::FromIterator<(CompactString, Box)> for BufferContain } } -impl std::iter::Extend<(CompactString, Box)> for BufferContainer { +impl core::iter::Extend<(CompactString, Box)> for BufferContainer { fn extend)>>(&mut self, iter: T) { for (key, value) in iter { self.buffers.insert(key, value); diff --git a/src/driver/filters.rs b/src/driver/filters.rs index 534f942..8aac624 100644 --- a/src/driver/filters.rs +++ b/src/driver/filters.rs @@ -1,10 +1,13 @@ -use crate::{Filter, SledError}; +use alloc::collections::BTreeMap; +use alloc::format; + use compact_str::{CompactString, ToCompactString}; -use std::collections::HashMap; + +use crate::{Filter, SledError}; #[derive(Clone, Debug)] pub struct Filters { - map: HashMap, + map: BTreeMap, } impl Default for Filters { @@ -16,7 +19,7 @@ impl Default for Filters { impl Filters { pub fn new() -> Self { Filters { - map: HashMap::new(), + map: BTreeMap::new(), } } @@ -30,14 +33,14 @@ impl Filters { .ok_or_else(|| SledError::new(format!("No filter found with key '{}'", key))) } - pub fn iter(&self) -> std::collections::hash_map::Iter { + pub fn iter(&self) -> alloc::collections::btree_map::Iter { self.map.iter() } } impl IntoIterator for Filters { type Item = (CompactString, Filter); - type IntoIter = std::collections::hash_map::IntoIter; + type IntoIter = alloc::collections::btree_map::IntoIter; fn into_iter(self) -> Self::IntoIter { self.map.into_iter() diff --git a/src/driver/mod.rs b/src/driver/mod.rs index f3a6dec..f08edd0 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -1,9 +1,16 @@ +use core::time::Duration; + +use alloc::boxed::Box; + use crate::{ color::{Rgb, Srgb}, + time::Instant, Led, Sled, SledError, Vec2, }; -use std::time::{Duration, Instant}; +/// A driver representing instants with `std::time::Instant` +#[cfg(feature = "std")] +pub type StdDriver = Driver; mod filters; // mod sliders; @@ -26,26 +33,32 @@ type DrawCommands = Box +where + INSTANT: Instant, +{ sled: Option, startup_commands: StartupCommands, compute_commands: ComputeCommands, draw_commands: DrawCommands, - startup: Instant, - last_update: Instant, + startup: INSTANT, + last_update: INSTANT, buffers: BufferContainer, filters: Filters, } -impl Driver { +impl Driver +where + INSTANT: Instant, +{ pub fn new() -> Self { Driver { sled: None, startup_commands: Box::new(|_, _, _| Ok(())), compute_commands: Box::new(|_, _, _, _| Ok(())), draw_commands: Box::new(|_, _, _, _| Ok(())), - startup: Instant::now(), - last_update: Instant::now(), + startup: INSTANT::now(), + last_update: INSTANT::now(), buffers: BufferContainer::new(), filters: Filters::new(), } @@ -63,7 +76,7 @@ impl Driver { /// Define commands to be called as soon as a Sled is [mounted](Driver::mount) to the driver. This is a good place to initialize important buffer values. /// ```rust - /// # use spatial_led::{Vec2, BufferContainer, SledResult, driver::Driver}; + /// # use spatial_led::{Vec2, BufferContainer, SledResult, driver::StdDriver}; /// use spatial_led::driver_macros::*; /// /// #[startup_commands] @@ -78,7 +91,7 @@ impl Driver { /// } /// /// pub fn main() { - /// let mut driver = Driver::new(); + /// let mut driver = StdDriver::new(); /// driver.set_startup_commands(startup); /// } /// ``` @@ -93,7 +106,7 @@ impl Driver { /// Define commands to be called each time [Driver::step()] is called, right before we run [draw commands](Driver::set_draw_commands). /// ```rust - /// # use spatial_led::{Vec2, BufferContainer, TimeInfo, SledResult, driver::Driver}; + /// # use spatial_led::{Vec2, BufferContainer, TimeInfo, SledResult, driver::StdDriver}; /// use spatial_led::driver_macros::*; /// const WIND: Vec2 = Vec2::new(0.25, 1.5); /// @@ -108,7 +121,7 @@ impl Driver { /// } /// /// pub fn main() { - /// let mut driver = Driver::new(); + /// let mut driver = StdDriver::new(); /// driver.set_compute_commands(compute); /// } /// @@ -124,14 +137,14 @@ impl Driver { /// Define commands to be called each time [Driver::step()] is called, right after we run [compute commands](Driver::set_compute_commands). /// ```rust - /// # use spatial_led::{Sled, Vec2, color::Rgb, BufferContainer, TimeInfo, SledResult, driver::Driver}; + /// # use spatial_led::{Sled, Vec2, color::Rgb, BufferContainer, TimeInfo, SledResult, driver::StdDriver}; /// use spatial_led::driver_macros::*; /// /// #[draw_commands] /// fn draw(sled: &mut Sled, buffers: &BufferContainer) -> SledResult { /// // gradually fade all LEDs to black /// sled.map(|led| led.color * 0.95); - /// + /// /// // For each position in our buffer, draw white in the direction to it. /// let streak_positions = buffers.get_buffer::("positions")?; /// let center = sled.center_point(); @@ -143,7 +156,7 @@ impl Driver { /// } /// /// pub fn main() { - /// let mut driver = Driver::new(); + /// let mut driver = StdDriver::new(); /// driver.set_draw_commands(draw); /// } /// @@ -160,7 +173,7 @@ impl Driver { /// Takes ownership of the given Sled and runs the Driver's [startup commands](Driver::set_startup_commands). pub fn mount(&mut self, mut sled: Sled) { (self.startup_commands)(&mut sled, &mut self.buffers, &mut self.filters).unwrap(); - self.startup = Instant::now(); + self.startup = INSTANT::now(); self.last_update = self.startup; self.sled = Some(sled); } @@ -173,7 +186,7 @@ impl Driver { delta: self.last_update.elapsed(), }; - self.last_update = Instant::now(); + self.last_update = INSTANT::now(); (self.compute_commands)(sled, &mut self.buffers, &mut self.filters, &time_info) .unwrap(); (self.draw_commands)(sled, &self.buffers, &self.filters, &time_info).unwrap(); @@ -252,7 +265,10 @@ impl Driver { } } -impl Default for Driver { +impl Default for Driver +where + INSTANT: Instant, +{ fn default() -> Self { Self::new() } diff --git a/src/error.rs b/src/error.rs index d73f7b2..5f59f71 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,7 @@ -use std::{error::Error, fmt}; +use core::{error::Error, fmt}; + +use alloc::string::String; +use alloc::string::ToString as _; #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] /// Simple error type used by fallible Sled operations. @@ -22,7 +25,7 @@ impl SledError { } } -impl std::convert::From<&str> for SledError { +impl core::convert::From<&str> for SledError { fn from(value: &str) -> Self { SledError::new(value.to_string()) } @@ -34,4 +37,4 @@ impl fmt::Display for SledError { } } -impl Error for SledError {} \ No newline at end of file +impl Error for SledError {} diff --git a/src/led.rs b/src/led.rs index 4483c84..b8f46f1 100644 --- a/src/led.rs +++ b/src/led.rs @@ -1,6 +1,9 @@ use crate::color::Rgb; use glam::Vec2; +#[cfg(not(feature = "std"))] +use num_traits::float::Float as _; + #[derive(Copy, Clone)] /// An LED in our Sled configuration, representing both the color of the LED as well as it's spatial information. @@ -85,25 +88,25 @@ impl PartialEq for Led { impl Eq for Led {} impl PartialOrd for Led { - fn partial_cmp(&self, other: &Self) -> Option { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Led { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.index.cmp(&other.index()) } } -impl std::hash::Hash for Led { - fn hash(&self, state: &mut H) { +impl core::hash::Hash for Led { + fn hash(&self, state: &mut H) { self.index.hash(state); } } -impl std::fmt::Debug for Led { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for Led { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let dir = self.direction(); f.debug_struct("Led") .field("color", &self.color.into_components()) @@ -117,12 +120,12 @@ impl std::fmt::Debug for Led { } } -impl std::fmt::Display for Led { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for Led { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, "{}: ({}, {}, {})", self.index, self.color.red, self.color.green, self.color.blue ) } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index fce193a..e4ceccf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ +#![cfg_attr(not(feature = "std"), no_std)] + //! # Spatial LED (Sled) //!
- //!
+//! //! //! Sled is a rust library for creating spatial lighting effects for individually addressable LED strips. API ergonomics and performance are top priorities for this project. That said, Sled is still in its early stages of development which means there is plenty of room for improvement in both categories. //! @@ -36,7 +38,7 @@ //! (2, 2) --> (-2, 2) --> (-2, 0) //! ``` //! See [Sled::new()] for more information on this config format. -//! +//! //! ### Drawing //! Once you have your [Sled] struct, you can start drawing to it right away! Here’s a taste of some of the things Sled lets you do: //! @@ -138,12 +140,12 @@ //! For basic applications, the [Sled] struct gives you plenty of power. Odds are though, you'll want to create more advanced effects that might be time or user-input driven. A few optional (enabled by default, opt-out by disabling their compiler features) tools are provided to streamline that process. //! //! ## Drivers -//! [Drivers](driver::Driver) are useful for encapsulating everything you need to drive a lighting effect all in one place. Here's an example of what a simple, time-based one might look like: +//! [Drivers](driver::StdDriver) are useful for encapsulating everything you need to drive a lighting effect all in one place. Here's an example of what a simple, time-based one might look like: //! //! ```rust //! # use spatial_led::{Sled, color::Rgb}; -//! use spatial_led::driver::Driver; -//! let mut driver = Driver::new(); +//! use spatial_led::driver::StdDriver; +//! let mut driver = StdDriver::new(); //! //! driver.set_startup_commands(|_sled, buffers, _filters| { //! let colors = buffers.create_buffer::("colors"); @@ -171,12 +173,12 @@ //! Ok(()) //! }); //! ``` -//! To start using the Driver, give it ownership over a Sled using [.mount()](driver::Driver::mount) and use [.step()](driver::Driver::step) to manually refresh it. +//! To start using the Driver, give it ownership over a Sled using [.mount()](driver::StdDriver::mount) and use [.step()](driver::StdDriver::step) to manually refresh it. //! ```rust, no_run -//! # use spatial_led::{Sled, driver::Driver}; +//! # use spatial_led::{Sled, driver::StdDriver}; //! # fn main() -> Result<(), spatial_led::SledError> { //! let sled = Sled::new("path/to/config.yap")?; -//! # let mut driver = Driver::new(); +//! # let mut driver = StdDriver::new(); //! driver.mount(sled); // sled gets moved into driver here. //! //! loop { @@ -192,18 +194,20 @@ //! //! If you need to retrieve ownership of your sled later, you can do: //! ```rust -//! # use spatial_led::{Sled, driver::Driver}; +//! # use spatial_led::{Sled, driver::StdDriver}; //! # let mut sled = Sled::new("./examples/resources/config.yap").unwrap(); -//! # let mut driver = Driver::new(); +//! # let mut driver = StdDriver::new(); //! # driver.mount(sled); //! let sled = driver.dismount(); //! ``` //! -//! * [set_startup_commands()](driver::Driver::set_startup_commands) - Define a function or closure to run when `driver.mount()` is called. Grants mutable control over [Sled], [BufferContainer], and [Filters]. +//! * [set_startup_commands()](driver::StdDriver::set_startup_commands) - Define a function or closure to run when `driver.mount()` is called. Grants mutable control over [Sled], [BufferContainer], and [Filters]. //! -//! * [set_draw_commands()](driver::Driver::set_draw_commands) - Define a function or closure to run every time `driver.step()` is called. Grants mutable control over `Sled`, and immutable access to `BufferContainer`, `Filters`, and `TimeInfo`. +//! * [set_draw_commands()](driver::StdDriver::set_draw_commands) - Define a function or closure to run every time `driver.step()` is called. Grants mutable control over `Sled`, and immutable access to `BufferContainer`, `Filters`, and `TimeInfo`. //! -//! * [set_compute_commands()](driver::Driver::set_compute_commands) - Define a function or closure to run every time `driver.step()` is called, scheduled right before draw commands. Grants immutable access to `Sled`, mutable control over `BufferContainer` and `Filters` and immutable access to `TimeInfo`. +//! * [set_compute_commands()](driver::StdDriver::set_compute_commands) - Define a function or closure to run every time `driver.step()` is called, scheduled right before draw commands. Grants immutable access to `Sled`, mutable control over `BufferContainer` and `Filters` and immutable access to `TimeInfo`. +//! +//! Drivers need a representation of a time instant, which is provided as a generic `INSTANT` that must implement the trait `time::Instant`. For `std` targets, `std::time::Instant` can be used, and a type alias `StdDriver = Driver` is defined. For `no_std` targets, the client should define their own representation (e.g. using `embassy_time::Instant`). //! //! If you don't want to Drivers for your project, you can disable the `drivers` compiler feature to shed a couple dependencies. //! @@ -217,7 +221,7 @@ //! //! Using these, you can express your commands as a function that only explicitly states the parameters it needs. The previous example could be rewritten like this, for example: //! ```rust -//! # use spatial_led::{Sled, driver::Driver, color::Rgb}; +//! # use spatial_led::{Sled, driver::StdDriver, color::Rgb}; //! # use spatial_led::{BufferContainer, SledResult, TimeInfo}; //! use spatial_led::driver_macros::*; //! @@ -250,7 +254,7 @@ //! //! //--snip--/ //! -//! let mut driver = Driver::new(); +//! let mut driver = StdDriver::new(); //! driver.set_startup_commands(startup); //! driver.set_draw_commands(draw); //! ``` @@ -258,10 +262,10 @@ //! ### Buffers //! A driver exposes a data structure called [BufferContainer]. A BufferContainer essentially acts as a HashMap of `&str` keys to Vectors of any type you choose to instantiate. This is particularly useful for passing important data and settings in to the effect. //! -//! It's best practice to create buffers with [startup commands](driver::Driver::set_startup_commands), and then modify them either through [compute commands](driver::Driver::set_compute_commands) or from [outside the driver](driver::Driver::buffers_mut) depending on your needs. +//! It's best practice to create buffers with [startup commands](driver::StdDriver::set_startup_commands), and then modify them either through [compute commands](driver::StdDriver::set_compute_commands) or from [outside the driver](driver::StdDriver::buffers_mut) depending on your needs. //! //! ```rust -//! # use spatial_led::{Sled, driver::{BufferContainer, Filters, Driver}, SledResult, color::Rgb}; +//! # use spatial_led::{Sled, driver::{BufferContainer, Filters, StdDriver}, SledResult, color::Rgb}; //! # use spatial_led::driver_macros::*; //! # type MY_CUSTOM_TYPE = f32; //! #[startup_commands] @@ -272,15 +276,15 @@ //! Ok(()) //! } //! -//! # let mut driver = Driver::new(); +//! # let mut driver = StdDriver::new(); //! //! driver.set_startup_commands(startup); //! ``` //! //! To maniplate buffers from outside driver, just do: //! ```rust -//! # use spatial_led::{driver::{BufferContainer, Driver}}; -//! # let mut driver = Driver::new(); +//! # use spatial_led::{driver::{BufferContainer, StdDriver}}; +//! # let mut driver = StdDriver::new(); //! let buffers: &BufferContainer = driver.buffers(); //! // or //! let buffers: &mut BufferContainer = driver.buffers_mut(); @@ -289,8 +293,8 @@ //! Using a BufferContainer is relatively straightforward. //! ```rust //! # type MY_CUSTOM_TYPE = f32; -//! # use spatial_led::{color::Rgb, Sled, driver::Driver, driver::BufferContainer}; -//! # let mut driver = Driver::new(); +//! # use spatial_led::{color::Rgb, Sled, driver::StdDriver, driver::BufferContainer}; +//! # let mut driver = StdDriver::new(); //! driver.set_draw_commands(|sled: &mut Sled, buffers: &BufferContainer, _, _| { //! let wall_toggles = buffers.get_buffer::("wall_toggles")?; //! let wall_colors = buffers.get_buffer::("wall_colors")?; @@ -340,8 +344,8 @@ //! Rather than checking the distance of each LED from that point every frame, we can instead do something like this: //! //! ```rust -//! # use spatial_led::{Sled, Led, Filter, Vec2, color::Rgb, driver::Driver}; -//! # let mut driver = Driver::new(); +//! # use spatial_led::{Sled, Led, Filter, Vec2, color::Rgb, driver::StdDriver}; +//! # let mut driver = StdDriver::new(); //! driver.set_startup_commands(|sled, buffers, filters| { //! let area: Filter = sled.within_dist_from(5.0, Vec2::new(-0.25, 1.5)); //! @@ -368,12 +372,12 @@ //! I imagine this feature will get less love than buffers, but I can still see a handful of scenarios where this can be very useful for some users. In a future version this may become an opt-in compiler feature. //! //! ## Scheduler -//! The [Scheduler](scheduler::Scheduler) struct makes it super easy to schedule redraws at a fixed rate. +//! The [Scheduler](scheduler::StdScheduler) struct makes it super easy to schedule redraws at a fixed rate. //! //! ```rust, no_run -//! # use spatial_led::{scheduler::Scheduler, driver::Driver}; -//! # let mut driver = Driver::new(); -//! let mut scheduler = Scheduler::new(120.0); +//! # use spatial_led::{scheduler::StdScheduler, driver::StdDriver}; +//! # let mut driver = StdDriver::new(); +//! let mut scheduler = StdScheduler::new(120.0); //! //! scheduler.loop_forever(|| { //! driver.step(); @@ -384,9 +388,9 @@ //! Here are a few other methods that you might also consider: //! //! ```rust, no_run -//! # use spatial_led::{scheduler::Scheduler, driver::Driver}; -//! # let mut driver = Driver::new(); -//! # let mut scheduler = Scheduler::new(120.0); +//! # use spatial_led::{scheduler::StdScheduler, driver::StdDriver}; +//! # let mut driver = StdDriver::new(); +//! # let mut scheduler = StdScheduler::new(120.0); //! // loops until false is returned //! scheduler.loop_while_true(|| { //! // -snip- @@ -406,9 +410,15 @@ //! } //! ``` //! +//! Schedulers need a representation of a time instant, like drivers, and also a representation of a sleep function, which is provided as a generic `SLEEPER` that must implement the trait `time::Sleeper`. For `std` targets, `std::thread::sleep()` can be used, and a type alias `StdScheduler = Scheduler` is defined. For `no_std` targets, the client should define their own representation. +//! +//! For async environments, AsyncScheduler can be used instead. No predefined implementation is provided, the client should define their own, e.g. using `embassy_time::Timer::after().await`. +//! //! If you don't need the Scheduler struct and would like to keep spin_sleep's dependencies out of your project, you can disable the `scheduler` compiler feature. //! +extern crate alloc; + /// Exposes [palette](https://crates.io/crates/palette)'s color management tools and brings the Rgb struct forward for easier use in Sled projects. pub mod color; mod config; @@ -440,3 +450,5 @@ pub use glam::Vec2; pub use led::Led; pub use spatial_led::Filter; pub use spatial_led::Sled; + +pub mod time; diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 309dece..ac3fbe3 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -1,28 +1,65 @@ -use spin_sleep::SpinSleeper; -use std::time::{Duration, Instant}; +use core::time::Duration; + +use alloc::boxed::Box; + +use crate::time::AsyncSleeper; +use crate::time::Instant; +use crate::time::Sleeper; + +#[cfg(feature = "std")] +use crate::time::StdSleeper; + +#[cfg(feature = "spin_sleep")] +use crate::time::SpinSleeper; + +/// A scheduler representing instants with `std::time::Instant` and sleeping +/// with `std::thread::sleep` +#[cfg(feature = "std")] +pub type StdScheduler = Scheduler; + +/// A scheduler representing instants with `std::time::Instant` and sleeping +/// with `spin_sleep::sleep` +#[cfg(feature = "spin_sleep")] +pub type SpinScheduler = Scheduler; #[derive(Debug, Copy, Clone, Hash)] -pub struct Scheduler { +pub struct Scheduler { target_delta: Duration, - sleeper: SpinSleeper, - last_loop_end: Instant, + last_loop_end: INSTANT, + sleeper: SLEEPER, } -impl Default for Scheduler { +impl Default for Scheduler +where + INSTANT: Instant, + SLEEPER: Sleeper + Default, +{ /// assumes a default hz of 60 fn default() -> Self { Scheduler::new(60.0) } } -impl Scheduler { +impl Scheduler +where + INSTANT: Instant, + SLEEPER: Sleeper, +{ /// Constructs a new Scheduler struct that can schedule tasks at the given frequency `target_hz`. - pub fn new(target_hz: f32) -> Self { + pub fn new(target_hz: f32) -> Self + where + SLEEPER: Default, + { + Self::with_sleeper(target_hz, SLEEPER::default()) + } + + /// Constructs a new Scheduler struct that can schedule tasks at the given frequency `target_hz`, using a specific sleeper. + pub fn with_sleeper(target_hz: f32, sleeper: SLEEPER) -> Self { let target_delta = Duration::from_secs_f32(target_hz.recip()); Scheduler { target_delta, - sleeper: SpinSleeper::default(), - last_loop_end: Instant::now(), + last_loop_end: INSTANT::now(), + sleeper, } } @@ -46,9 +83,9 @@ impl Scheduler { /// Lets you run a task at a fixed interval, forever. /// ```rust, no_run - /// # use spatial_led::{scheduler::Scheduler}; + /// # use spatial_led::{scheduler::StdScheduler}; /// pub fn main() { - /// let mut scheduler = Scheduler::new(120.0); + /// let mut scheduler = StdScheduler::new(120.0); /// scheduler.loop_forever(|| { /// println!("This will print 120 times per second!"); /// }); @@ -63,9 +100,9 @@ impl Scheduler { /// Lets you run a task at a fixed interval. Will break when the function returns false. /// ```rust - /// # use spatial_led::{scheduler::Scheduler}; + /// # use spatial_led::{scheduler::StdScheduler}; /// pub fn main() { - /// let mut scheduler = Scheduler::new(240.0); + /// let mut scheduler = StdScheduler::new(240.0); /// let mut loop_count = 0; /// scheduler.loop_while_true(|| { /// // do something @@ -85,10 +122,10 @@ impl Scheduler { /// Lets you run a task at a fixed interval. Will break when the function returns a result of Err variant. /// ```rust - /// # use spatial_led::{scheduler::Scheduler}; + /// # use spatial_led::{scheduler::StdScheduler}; /// # use spatial_led::{Sled, SledResult, color::Rgb}; /// pub fn main() { - /// let mut scheduler = Scheduler::new(60.0); + /// let mut scheduler = StdScheduler::new(60.0); /// let mut sled = Sled::new("./examples/resources/config.yap").unwrap(); /// let mut segment_index = 0; /// scheduler.loop_until_err(|| { @@ -100,8 +137,8 @@ impl Scheduler { /// ``` pub fn loop_until_err( &mut self, - mut task: impl FnMut() -> Result>, - ) -> Box { + mut task: impl FnMut() -> Result>, + ) -> Box { loop { match task() { Ok(_) => self.sleep_until_next_frame(), @@ -111,19 +148,19 @@ impl Scheduler { } /// Can be called manually to sleep until the next scheduled frame. - /// + /// /// Valuable for when you'd like to avoid having to pass values into a closure, or would like more control over loop flow. /// ```rust - /// # use spatial_led::{scheduler::Scheduler}; + /// # use spatial_led::{scheduler::StdScheduler}; /// pub fn main() { - /// let mut scheduler = Scheduler::new(60.0); - /// + /// let mut scheduler = StdScheduler::new(60.0); + /// /// // print all numbers 0 to 59 in exactly one second. /// for i in 0..60 { /// println!("{}", i); /// scheduler.sleep_until_next_frame(); /// } - /// + /// /// } /// ``` pub fn sleep_until_next_frame(&mut self) { @@ -132,7 +169,111 @@ impl Scheduler { self.sleeper.sleep(self.target_delta - elapsed); self.last_loop_end += self.target_delta; } else { - self.last_loop_end = Instant::now(); + self.last_loop_end = INSTANT::now(); + } + } +} + +#[derive(Debug, Copy, Clone, Hash)] +pub struct AsyncScheduler { + target_delta: Duration, + last_loop_end: INSTANT, + sleeper: SLEEPER, +} + +impl Default for AsyncScheduler +where + INSTANT: Instant, + SLEEPER: AsyncSleeper + Default, +{ + /// assumes a default hz of 60 + fn default() -> Self { + Self::new(60.0) + } +} + +impl AsyncScheduler +where + INSTANT: Instant, + SLEEPER: AsyncSleeper, +{ + /// Constructs a new AsyncScheduler struct that can schedule tasks at the given frequency `target_hz`. + pub fn new(target_hz: f32) -> Self + where + SLEEPER: Default, + { + Self::with_sleeper(target_hz, SLEEPER::default()) + } + + /// Constructs a new AsyncScheduler struct that can schedule tasks at the given frequency `target_hz`. + pub fn with_sleeper(target_hz: f32, sleeper: SLEEPER) -> Self { + let target_delta = Duration::from_secs_f32(target_hz.recip()); + Self { + target_delta, + last_loop_end: INSTANT::now(), + sleeper, + } + } + + /// Allows you to change the frequency at which the scheduler tries to run tasks. + /// + /// Note: Deprecated in favor of [AsyncScheduler::set_hz()] + #[deprecated] + pub fn change_hz(&mut self, new_target_hz: f32) { + self.target_delta = Duration::from_secs_f32(new_target_hz.recip()) + } + + /// Allows you to change the frequency at which the scheduler tries to run tasks. + pub fn set_hz(&mut self, new_target_hz: f32) { + self.target_delta = Duration::from_secs_f32(new_target_hz.recip()) + } + + /// Returns the frequency the AsyncScheduler is currently set to. + pub fn hz(&self) -> f32 { + self.target_delta.as_secs_f32().recip() + } + + /// Lets you run a task at a fixed interval, forever. + pub async fn loop_forever(&mut self, mut task: impl FnMut()) -> ! { + loop { + task(); + self.sleep_until_next_frame().await; + } + } + + /// Lets you run a task at a fixed interval. Will break when the function returns false. + pub async fn loop_while_true(&mut self, mut task: impl FnMut() -> bool) { + loop { + if task() { + break; + } + self.sleep_until_next_frame().await; + } + } + + /// Lets you run a task at a fixed interval. Will break when the function returns a result of Err variant. + pub async fn loop_until_err( + &mut self, + mut task: impl FnMut() -> Result>, + ) -> Box { + loop { + match task() { + Ok(_) => self.sleep_until_next_frame().await, + Err(e) => return e, + } + } + } + + /// Can be called manually to sleep until the next scheduled frame. + /// + /// Valuable for when you'd like to avoid having to pass values into a closure, or would like more control over loop flow. + pub async fn sleep_until_next_frame(&mut self) { + let elapsed = self.last_loop_end.elapsed(); + if elapsed < self.target_delta { + self.sleeper.sleep(self.target_delta - elapsed).await; + self.last_loop_end += self.target_delta; + } else { + self.last_loop_end = INSTANT::now(); } } } diff --git a/src/spatial_led/directional.rs b/src/spatial_led/directional.rs index 6ded003..e699770 100644 --- a/src/spatial_led/directional.rs +++ b/src/spatial_led/directional.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use alloc::collections::BTreeSet; use crate::{color::Rgb, led::Led, Filter, Sled}; use glam::Vec2; @@ -42,7 +42,7 @@ impl Sled { intersecting_indices .iter() .map(|i| *i as u16) - .collect::>() + .collect::>() .into() } @@ -180,7 +180,7 @@ impl Sled { /// ///```rust ///# use spatial_led::{Sled, SledError, color::Rgb, Vec2}; - /// use std::f32::consts::PI; + /// use core::f32::consts::PI; ///# fn demo() -> Result<(), SledError> { ///# let mut sled = Sled::new("./examples/resources/config.yap")?; /// sled.modulate_at_angle(PI / 4.0, |led| led.color * 2.0); @@ -203,7 +203,7 @@ impl Sled { /// ///```rust ///# use spatial_led::{Sled, SledError, color::Rgb, Vec2}; - /// use std::f32::consts::PI; + /// use core::f32::consts::PI; ///# fn demo() -> Result<(), SledError> { ///# let mut sled = Sled::new("./examples/resources/config.yap")?; /// let angle = PI * 1.25; diff --git a/src/spatial_led/filter.rs b/src/spatial_led/filter.rs index 8852459..2f802ba 100644 --- a/src/spatial_led/filter.rs +++ b/src/spatial_led/filter.rs @@ -1,15 +1,15 @@ -use std::collections::{hash_set, HashSet}; +use alloc::collections::{btree_set, BTreeSet}; use crate::{color::Rgb, led::Led, spatial_led::Sled}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Filter { - led_indices: HashSet, + led_indices: BTreeSet, } impl From<&[Led]> for Filter { fn from(value: &[Led]) -> Self { - let mut hs = HashSet::new(); + let mut hs = BTreeSet::new(); for led in value { hs.insert(led.index()); } @@ -17,8 +17,8 @@ impl From<&[Led]> for Filter { } } -impl From> for Filter { - fn from(value: HashSet) -> Self { +impl From> for Filter { + fn from(value: BTreeSet) -> Self { Filter { led_indices: value } } } @@ -61,7 +61,7 @@ impl Filter { impl IntoIterator for Filter { type Item = u16; - type IntoIter = hash_set::IntoIter; + type IntoIter = btree_set::IntoIter; fn into_iter(self) -> Self::IntoIter { self.led_indices.into_iter() @@ -70,7 +70,7 @@ impl IntoIterator for Filter { impl IntoIterator for &Filter { type Item = u16; - type IntoIter = hash_set::IntoIter; + type IntoIter = btree_set::IntoIter; fn into_iter(self) -> Self::IntoIter { // this doesn't seem right; revisit @@ -80,7 +80,7 @@ impl IntoIterator for &Filter { impl FromIterator for Filter { fn from_iter>(iter: T) -> Self { - let mut set = HashSet::::new(); + let mut set = BTreeSet::::new(); for i in iter { set.insert(i); } diff --git a/src/spatial_led/indexical.rs b/src/spatial_led/indexical.rs index 8c598e4..3a79ee9 100644 --- a/src/spatial_led/indexical.rs +++ b/src/spatial_led/indexical.rs @@ -1,4 +1,7 @@ -use std::ops::Range; +use core::ops::Range; + +use alloc::format; +use alloc::string::ToString; use crate::{ color::Rgb, @@ -155,7 +158,7 @@ impl Sled { } /// For-each method granting mutable access to each [LED](Led) with an index in `index_range` - /// + /// /// Returns an [error](SledError) if the range extends beyond the size of the system. /// /// O(RANGE_SIZE) diff --git a/src/spatial_led/maps_and_filters.rs b/src/spatial_led/maps_and_filters.rs index 77d854c..76cb810 100644 --- a/src/spatial_led/maps_and_filters.rs +++ b/src/spatial_led/maps_and_filters.rs @@ -1,4 +1,7 @@ -use std::collections::HashSet; +use alloc::collections::BTreeSet; + +#[cfg(not(feature = "std"))] +use num_traits::float::Float as _; use crate::{ color::Rgb, @@ -70,7 +73,7 @@ impl Sled { /// Filters impl Sled { pub fn filter(&self, filter: impl Fn(&Led) -> bool) -> Filter { - let filtered: HashSet = self + let filtered: BTreeSet = self .leds .iter() .filter_map(|led| if filter(led) { Some(led.index()) } else { None }) diff --git a/src/spatial_led/meta.rs b/src/spatial_led/meta.rs index 4d0c04f..51ecb2c 100644 --- a/src/spatial_led/meta.rs +++ b/src/spatial_led/meta.rs @@ -1,4 +1,10 @@ -use std::ops::Range; +use core::ops::Range; + +use alloc::vec; +use alloc::vec::Vec; + +#[cfg(not(feature = "std"))] +use num_traits::float::Float as _; use crate::{ color, @@ -39,13 +45,14 @@ impl Sled { /// --> (3.5, 0) | (2, 2) /// --> (-2, 2) --> (-2, 0) /// ``` + #[cfg(feature = "std")] pub fn new(config_file_path: &str) -> Result { let config = Config::from_toml_file(config_file_path)?; Sled::new_from_config(config) } /// Works like [Sled::new()] but rather than reading the contents of a config file from disk, allows you to pass in the same information as a String. - pub fn new_from_string(string: String) -> Result { + pub fn new_from_string(string: &str) -> Result { let config = Config::from_string(string)?; Sled::new_from_config(config) } diff --git a/src/spatial_led/mod.rs b/src/spatial_led/mod.rs index a382f61..3fc03e4 100644 --- a/src/spatial_led/mod.rs +++ b/src/spatial_led/mod.rs @@ -1,4 +1,6 @@ -use std::ops::Range; +use core::ops::Range; + +use alloc::vec::Vec; use crate::{config::LineSegment, led::Led, Vec2}; diff --git a/src/spatial_led/positional.rs b/src/spatial_led/positional.rs index 6cc6ff3..834a549 100644 --- a/src/spatial_led/positional.rs +++ b/src/spatial_led/positional.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use alloc::collections::BTreeSet; use crate::{ color::Rgb, @@ -9,6 +9,9 @@ use crate::{ use glam::Vec2; use smallvec::{smallvec, SmallVec}; +#[cfg(not(feature = "std"))] +use num_traits::float::Float as _; + /// # position-based read and write methods impl Sled { /* closest getters/setters */ @@ -209,7 +212,7 @@ impl Sled { } pub fn at_dist_from(&self, dist: f32, pos: Vec2) -> Filter { - let mut all_at_distance = HashSet::new(); + let mut all_at_distance = BTreeSet::new(); for (segment_index, segment) in self.line_segments.iter().enumerate() { for alpha in segment.intersects_circle(pos, dist) { @@ -263,7 +266,7 @@ impl Sled { } pub fn within_dist_from(&self, dist: f32, pos: Vec2) -> Filter { - let mut all_within_distance = HashSet::new(); + let mut all_within_distance = BTreeSet::new(); let target_sq = dist.powi(2); diff --git a/src/spatial_led/segmental.rs b/src/spatial_led/segmental.rs index 66b888e..123cce8 100644 --- a/src/spatial_led/segmental.rs +++ b/src/spatial_led/segmental.rs @@ -1,10 +1,15 @@ +use core::ops::Range; + +use alloc::collections::BTreeSet; +use alloc::format; +use alloc::string::ToString; + use crate::{ color::Rgb, error::SledError, led::Led, spatial_led::{Filter, Sled}, }; -use std::{collections::HashSet, ops::Range}; /// # Segment-based read and write methods. impl Sled { @@ -66,7 +71,7 @@ impl Sled { } /// Returns the set of all [LEDs](Led) assigned to the line segments whose indices are within the given range. - /// + /// /// If the range exceeds the number of segments in the system, returns None. /// /// O(LEDS_IN_SEGMENTS) @@ -149,7 +154,7 @@ impl Sled { /// Also passes an "alpha" value into the closure, representing how far along the line segment you are. 0 = first LED in segement, 1 = last. /// /// Returns an [error](SledError) if the no segment of given index exists. - /// + /// /// O(LEDS_IN_SEGMENT) /// /// ```rust @@ -201,7 +206,7 @@ impl Sled { /// Vertices are distinct from line segement endpoints in that line segments with touching endpoints will share a vertex. /// /// Returns an [error](SledError) if no vertex of given index exists. - /// + /// /// O(1) /// /// ```rust @@ -232,7 +237,7 @@ impl Sled { /// Vertices are distinct from line segement endpoints in that line segments with touching endpoints will share a vertex. /// /// Returns an [error](SledError) if no vertex of given index exists. - /// + /// /// O(1) /// pub fn set_vertex(&mut self, vertex_index: usize, color: Rgb) -> Result<(), SledError> { @@ -250,7 +255,7 @@ impl Sled { /// Returns a [Filter] containing all vertices in the system. pub fn vertices(&self) -> Filter { - let hs: HashSet = self.vertex_indices.iter().map(|i| *i as u16).collect(); + let hs: BTreeSet = self.vertex_indices.iter().map(|i| *i as u16).collect(); hs.into() } diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..3b32c3f --- /dev/null +++ b/src/time.rs @@ -0,0 +1,64 @@ +use core::ops::AddAssign; +use core::ops::SubAssign; +use core::time::Duration; + +/// A trait to abstract a temporal instant +/// +/// Instants are monotonically increasing. +pub trait Instant: Clone + Copy + SubAssign + AddAssign { + /// Return the current instant + fn now() -> Self; + + /// Compute the duration since this instant + fn elapsed(&self) -> core::time::Duration; +} + +/// A trait to abstract a sleep function +pub trait Sleeper { + /// Sleep for the specified duration + fn sleep(&self, duration: core::time::Duration); +} + +/// A trait to abstract an asynchronous sleep function +pub trait AsyncSleeper { + /// Sleep for the specified duration + fn sleep( + &self, + duration: core::time::Duration, + ) -> impl core::future::Future + Send; +} + +#[cfg(feature = "std")] +impl Instant for std::time::Instant { + fn now() -> Self { + std::time::Instant::now() + } + + fn elapsed(&self) -> core::time::Duration { + self.elapsed() + } +} + +/// A sleeper that calls `std::thread::sleep()` +#[cfg(feature = "std")] +#[derive(Default)] +pub struct StdSleeper; + +#[cfg(feature = "std")] +impl Sleeper for StdSleeper { + fn sleep(&self, duration: core::time::Duration) { + std::thread::sleep(duration) + } +} + +/// A sleeper that calls `spin_sleep::sleep()` +#[cfg(feature = "spin_sleep")] +#[derive(Default)] +pub struct SpinSleeper; + +#[cfg(feature = "spin_sleep")] +impl Sleeper for SpinSleeper { + fn sleep(&self, duration: core::time::Duration) { + spin_sleep::sleep(duration) + } +}