Skip to content

Commit

Permalink
Merge pull request #81 from DavJCosby/proc-macros
Browse files Browse the repository at this point in the history
Proc macros
  • Loading branch information
DavJCosby authored Sep 27, 2024
2 parents a1925f0 + ce9dfcd commit b59922c
Show file tree
Hide file tree
Showing 21 changed files with 343 additions and 240 deletions.
14 changes: 10 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ rust-version = "1.70"

[features]
default = ["drivers", "scheduler"]
drivers = ["compact_str"]
drivers = ["compact_str", "driver_macros"]
scheduler = ["spin_sleep"]
named_colors = []

Expand All @@ -18,13 +18,19 @@ palette = { version = "0.7", default-features = false, features = [
] }
smallvec = "1.13"
compact_str = { version = "0.8", optional = true }
driver_macros = { path = "./src/driver/driver_macros", optional = true }
spin_sleep = { version = "1.2", optional = true }

[dev-dependencies]
criterion = {version = "0.5.1", default-features = false, features = ["cargo_bench_support"]}
criterion = { version = "0.5.1", default-features = false, features = [
"cargo_bench_support",
] }
ratatui = "0.28"
crossterm = {version = "0.28"}
rand = {version = "0.8.5", default-features = false, features = ["std", "std_rng"]}
crossterm = { version = "0.28" }
rand = { version = "0.8.5", default-features = false, features = [
"std",
"std_rng",
] }

[profile.bench]
lto = true
Expand Down
70 changes: 60 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ sled.set_vertices(Rgb::new(1.0, 1.0, 1.0));
**Set all LEDs 2 units away from the `center_point` to red:**
```rust
sled.set_at_dist(2.0, Rgb::new(1.0, 0.0, 0.0));
// or from any other point using sled.set_at_dist_from(distance, pos, color)
// or relative to any other point using:
// sled.set_at_dist_from(distance, pos, color)
```

![Set at Distance](resources/at_distance.png)
Expand Down Expand Up @@ -147,6 +148,7 @@ Drivers are useful for encapsulating everything you need to drive a lighting eff

```rust
let mut driver = Driver::new();
use driver_macros::*;

driver.set_startup_commands(|_sled, buffers, _filters| {
let colors = buffers.create_buffer::<Rgb>("colors");
Expand All @@ -167,21 +169,21 @@ driver.set_draw_commands(|sled, buffers, _filters, time_info| {

for i in 0..num_colors {
let alpha = i as f32 / num_colors as f32;
let angle = elapsed + (2 * PI * alpha);
sled.set_at_angle(angle, colors[i])?;
let angle = elapsed + (2.0 * PI * alpha);
sled.set_at_angle(angle, colors[i]);
}
Ok(())
});
```
To start using the Driver, give it ownership over a Sled using `.mount()` and use `.step()` to manually refresh it.
```rust
let sled = Sled::new("path/to/config.toml")?;
let sled = Sled::new("path/to/config.yap")?;
driver.mount(sled); // sled gets moved into driver here.

loop {
driver.step();
let colors = driver.colors();
// and so on...
// display those colors however you want
}
```
![Basic Time-Driven Effect Using Buffers](resources/driver1.gif)
Expand All @@ -200,13 +202,61 @@ let sled = driver.dismount();

> If you don't need Drivers for your project, you can shed a dependency or two by disabling the `drivers` compiler feature.
For more examples of ways to use drivers, see [drivers/examples](https://github.com/DavJCosby/sled/tree/master/examples/drivers) in the project's github repository.

### Driver Macros
Some macros have been provided to make authoring drivers a more ergonomic experience. You can apply the following attributes to functions that you want to use for driver commands:
* `#[startup_commands]`
* `#[compute_commands]`
* `#[draw_commands]`

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 driver_macros::*;
use sled::{BufferContainer, SledResult, TimeInfo};

#[startup_commands]
fn startup(buffers: &mut BufferContainer) -> SledResult {
let colors = buffers.create_buffer::<Rgb>("colors");
colors.extend([
Rgb::new(1.0, 0.0, 0.0),
Rgb::new(0.0, 1.0, 0.0),
Rgb::new(0.0, 0.0, 1.0),
]);
Ok(())
}

#[draw_commands]
fn draw(sled: &mut Sled, buffers: &BufferContainer, time_info: &TimeInfo) -> SledResult {
let elapsed = time_info.elapsed.as_secs_f32();
let colors = buffers.get_buffer::<Rgb>("colors")?;
let num_colors = colors.len();
// clear our canvas each frame
sled.set_all(Rgb::new(0.0, 0.0, 0.0));

for i in 0..num_colors {
let alpha = i as f32 / num_colors as f32;
let angle = elapsed + (2 * PI * alpha);
sled.set_at_angle(angle, colors[i])?;
}
Ok(())
}

//--snip--//

let mut driver = Driver::new();
driver.set_startup_commands(startup);
driver.set_draw_commands(draw));
```

### 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, and then modify them either through compute commands or from outside the driver depending on your needs.

```rust
fn startup(sled: &mut Sled, buffers: &mut BufferContainer, _filters: &mut Filters) -> Result<(), SledError> {
#[startup_commands]
fn startup(sled: &mut Sled, buffers: &mut BufferContainer) -> SledResult {
let wall_toggles: &mut Vec<bool> = buffers.create_buffer("wall_toggles");
let wall_colors: &mut Vec<Rgb> = buffers.create_buffer("wall_colors");
let some_important_data = buffers.create_buffer::<MY_CUSTOM_TYPE>("important_data");
Expand Down Expand Up @@ -256,7 +306,7 @@ let color: &mut Rgb = buffers.get_buffer_item_mut("wall_colors", 2)?;
```

### Filters
For exceptionally performance-sensitive applications, Filters can be used to predefine important LED regions. They act as sets, containing only the indicies of the LEDs captured in the set. When we want to perform an operation on that set, we pass the Filter back to the Sled like this:
For exceptionally performance-sensitive scenarios, Filters can be used to predefine important LED regions. They act as sets, containing only the indicies of the LEDs captured in the set. When we want to perform an operation on that set, we pass the Filter back to the Sled like this:

```rust
let all_due_north: Filter = sled.at_dir(Vec2::new(0.0, 1.0));
Expand All @@ -266,10 +316,10 @@ sled.for_each_in_filter(&all_due_north, |led| {
```
> Note that other methods exist like `.set_filter(filter, color)`, `.modulate_filter(filter, color_rule)`, and `.map_filter(filter, map)`
The `Filters` struct provided by Driver is basically a hashmap of `&str` keys to Sled Filter structs. Using this, we can pre-compute important sets and then store them to the driver for later usage.
The `Filters` struct provided by Driver is basically a hashmap of `&str` keys to Sled `Filter` structs. Using this, we can pre-compute important sets and then store them to the driver for later usage.


A slightly better example would be to image that we have an incredibly expensive mapping function that will only have a visible impact on the LEDs within some radius $R$ from a given point $P$. Rather than checking the distance of each LED from that point every frame, we can instead do something like this:
A slightly better example would be to imagine that we have an incredibly expensive mapping function that will only have a visible impact on the LEDs within some radius $R$ from a given point $P$. Rather than checking the distance of each LED from that point every frame, we can instead do something like this:

```rust
let startup_commands = |sled, buffers, filters| {
Expand Down Expand Up @@ -321,7 +371,7 @@ scheduler.loop_until_err(|| {
Ok(())
});

// best for where you don't wanna pass everything through a closure
// best for times when you don't want to pass everything through a closure
loop {
// -snip- //
scheduler.sleep_until_next_frame();
Expand Down
2 changes: 1 addition & 1 deletion benches/comet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use sled::Sled;
use std::time::Duration;

fn trail(c: &mut Criterion) {
let sled = Sled::new("./benches/resources/config.toml").unwrap();
let sled = Sled::new("./benches/resources/config.yap").unwrap();
let mut driver = comet::build_driver();
driver.mount(sled);

Expand Down
26 changes: 0 additions & 26 deletions benches/resources/config.toml

This file was deleted.

5 changes: 5 additions & 0 deletions benches/resources/config.yap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
center: (0, 0.5)
density: 30
--segments--
(-2, 0) --> (0.5, -1) --> (3.5, 0) -->
(2, 2) --> (-2, 2) --> (-2, 0)
2 changes: 1 addition & 1 deletion benches/ripples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use sled::Sled;
use std::time::Duration;

fn ripples(c: &mut Criterion) {
let sled = Sled::new("./benches/resources/config.toml").unwrap();
let sled = Sled::new("./benches/resources/config.yap").unwrap();
let mut driver = ripples::build_driver();
driver.mount(sled);

Expand Down
4 changes: 1 addition & 3 deletions examples/calibration.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
mod resources;

use resources::tui::SledTerminalDisplay;

use glam::Vec2;
use resources::tui::SledTerminalDisplay;
use sled::{color::Rgb, Sled, SledError};

fn main() -> Result<(), SledError> {
Expand Down
144 changes: 39 additions & 105 deletions examples/drivers/comet.rs
Original file line number Diff line number Diff line change
@@ -1,122 +1,56 @@
use rand::Rng;
use sled::driver::{BufferContainer, Driver, Filters, TimeInfo};
use sled::{color::Rgb, Sled, SledError, Vec2};
use driver_macros::*;
use sled::driver::{Driver, TimeInfo};
use sled::SledResult;
use sled::{color::Rgb, Sled};

const NUM_STARS: usize = 5000;
const VELOCITY: f32 = 6.0;
const DIRECTION: Vec2 = Vec2::new(-0.7071, -0.7071);
use std::f32::consts::TAU;
const INV_TAU: f32 = 1.0 / TAU;

const GREEN_RADIUS: f32 = 2.33;
const GREEN_COUNT: usize = 64;
const GREEN: Rgb = Rgb::new(0.6, 0.93, 0.762);

const BLUE_RADIUS: f32 = 3.0;
const BLUE_COUNT: usize = 96;
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();

driver.set_startup_commands(startup);
driver.set_compute_commands(compute);
driver.set_draw_commands(draw);

return driver;
driver
}

fn startup(
sled: &mut Sled,
buffers: &mut BufferContainer,
_filters: &mut Filters,
) -> Result<(), SledError> {
let stars = buffers.create_buffer::<Vec2>("stars");
let center = sled.center_point();
let mut rng = rand::thread_rng();

let orth = DIRECTION.perp();
#[draw_commands]
fn draw(sled: &mut Sled, time_info: &TimeInfo) -> SledResult {
let elapsed = time_info.elapsed.as_secs_f32();

for _ in 0..NUM_STARS {
let sign = match rng.gen_bool(0.5) {
true => 1.0,
false => -1.0,
};
let inner_time_scale = elapsed / GREEN_RADIUS;
let outer_time_scale = elapsed / BLUE_RADIUS;

let spawn_pos = center
+ (DIRECTION * rng.gen_range(40.0..300.0))
+ (orth * rng.gen_range(1.45..35.0) * sign);

stars.push(spawn_pos);
// speckle in swirling green points
for i in 0..GREEN_COUNT {
let angle = inner_time_scale + (TAU / GREEN_COUNT as f32) * i as f32 % TAU;
sled.modulate_at_angle(angle, |led| led.color + GREEN);
}

let colors = buffers.create_buffer::<Rgb>("colors");
colors.extend([
Rgb::new(0.15, 0.5, 1.0),
Rgb::new(0.25, 0.3, 1.0),
Rgb::new(0.05, 0.4, 0.8),
Rgb::new(0.7, 0.0, 0.6),
Rgb::new(0.05, 0.75, 1.0),
Rgb::new(0.1, 0.8, 0.6),
Rgb::new(0.6, 0.05, 0.2),
Rgb::new(0.85, 0.15, 0.3),
Rgb::new(0.0, 0.0, 1.0),
Rgb::new(1.0, 0.71, 0.705),
]);

Ok(())
}

fn compute(
sled: &Sled,
buffers: &mut BufferContainer,
_filters: &mut Filters,
time_info: &TimeInfo,
) -> Result<(), SledError> {
let mut rng = rand::thread_rng();
let delta = time_info.delta.as_secs_f32();
let stars = buffers.get_buffer_mut::<Vec2>("stars")?;
let center = sled.center_point();

let orth = DIRECTION.perp();

for star in stars {
*star -= DIRECTION * VELOCITY * delta;
if star.x.signum() != DIRECTION.x.signum() && star.y.signum() != DIRECTION.y.signum() {
let dq = (*star - center).length_squared();
if dq > 1000.0 {
let sign = match rng.gen_bool(0.5) {
true => 1.0,
false => -1.0,
};

let spawn_pos = center
+ (DIRECTION * rng.gen_range(40.0..300.0))
+ (orth * rng.gen_range(1.5..35.0) * sign);

*star = spawn_pos;
}
}
// speckle in swirling blue points
for i in 0..BLUE_COUNT {
let angle = outer_time_scale + (TAU / BLUE_COUNT as f32) * i as f32 % TAU;
sled.modulate_at_angle(angle, |led| led.color + BLUE);
}

Ok(())
}

fn draw(
sled: &mut Sled,
buffers: &BufferContainer,
_filters: &Filters,
time_info: &TimeInfo,
) -> Result<(), SledError> {
let stars = buffers.get_buffer::<Vec2>("stars")?;
let center = sled.center_point();
let delta = time_info.delta.as_secs_f32();

let fade_amount = 1.0 - (delta * 25.0);

sled.for_each(|led| led.color *= fade_amount);

let mut i = 0;
for star in stars {
let d = Vec2::new(star.x - center.x, star.y - center.y);
let c = *buffers.get_buffer_item::<Rgb>("colors", i % 10)?;
sled.modulate_at_dir(d, |led| {
let d_sq = (d.length() - led.distance()).powi(2);
led.color + (c * 1.0 / d_sq)
});
i += 1;
}
// brighten or darken points depending on time and angle to simulate a sweeping
// trail thing.
let radar_time_scale = elapsed / TRAIL_RADIUS;
let angle = (radar_time_scale % TAU) + TAU;
sled.map(|led| {
let da = (led.angle() + angle) % TAU;
let fac = 1.0 - (da * INV_TAU).powf(1.25);
led.color * fac
});

Ok(())
}
Loading

0 comments on commit b59922c

Please sign in to comment.