Compare commits

..

No commits in common. "7a3ea6103078beed529d03d7b9706c1aeabf976f" and "06a527a55281eb350437801b46b717ad1268406c" have entirely different histories.

16 changed files with 236 additions and 960 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/.vscode
/.embuild /.embuild
/target /target
/Cargo.lock /Cargo.lock

26
.vscode/launch.json vendored
View File

@ -1,26 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "probe-rs-debug",
"request": "launch",
"name": "probe-rs Test",
"cwd": "${workspaceFolder}",
"connectUnderReset": true,
"chip": "ESP32S3",
"flashingConfig": {
"flashingEnabled": true,
"haltAfterReset": true
},
"coreConfigs": [
{
"coreIndex": 0,
"programBinary": "./target/xtensa-esp32s3-espidf/debug/${workspaceFolderBasename}"
}
]
}
]
}

29
.vscode/tasks.json vendored
View File

@ -1,29 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"command": "build",
"problemMatcher": [
"$rustc"
],
"group": {
"kind": "build",
"isDefault": true
},
"label": "rust: cargo build"
},
{
"type": "cargo",
"command": "espflash flash --no-stub --monitor",
"problemMatcher": [
"$rustc"
],
"group": {
"kind": "build",
"isDefault": false
},
"label": "rust: cargo espflash"
}
]
}

View File

@ -11,13 +11,11 @@ name = "renderbug"
harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors
[profile.release] [profile.release]
opt-level = 3 opt-level = "s"
lto = true
[profile.dev] [profile.dev]
debug = true # Symbols are nice and they don't increase the size on Flash debug = true # Symbols are nice and they don't increase the size on Flash
opt-level = "z" opt-level = "z"
lto = true
[features] [features]
default = ["std", "esp-idf-svc/native", "rmt", "smart-leds"] default = ["std", "esp-idf-svc/native", "rmt", "smart-leds"]
@ -31,6 +29,8 @@ pio = ["esp-idf-svc/pio"]
std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"] std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"]
alloc = ["esp-idf-svc/alloc"] alloc = ["esp-idf-svc/alloc"]
threads = []
[dependencies] [dependencies]
log = { version = "0.4", default-features = false } log = { version = "0.4", default-features = false }
esp-idf-svc = { version = "0.49", default-features = false } esp-idf-svc = { version = "0.49", default-features = false }
@ -50,7 +50,6 @@ embedded-graphics = { version = "0.8.1", optional = true, features = ["fixed_poi
ansi_term = "0.12.1" ansi_term = "0.12.1"
num = "0.4.3" num = "0.4.3"
chrono = "0.4.38" chrono = "0.4.38"
fugit = "0.3.7"
[build-dependencies] [build-dependencies]
embuild = "0.32.0" embuild = "0.32.0"

View File

@ -2,7 +2,6 @@ use palette::Hsv;
use rgb::RGB8; use rgb::RGB8;
use crate::events::{Event, EventBus};
use crate::time::Periodically; use crate::time::Periodically;
use crate::geometry::*; use crate::geometry::*;
use crate::render::{Shader, Surface, Surfaces}; use crate::render::{Shader, Surface, Surfaces};
@ -55,6 +54,15 @@ impl Shader for ThinkingShader {
} }
} }
#[derive(Debug)]
struct GayBarberShader {}
impl Shader for GayBarberShader {
fn draw(&self, coords: &VirtualCoordinates, frame: usize) -> RGB8 {
Hsv::new_srgb((frame as u8).wrapping_add(coords.x).wrapping_add(coords.y), 255, sin8((frame % 128) as u8)).into_rgb8()
}
}
impl<T: Surface> IdleTask<T> { impl<T: Surface> IdleTask<T> {
pub fn new<S: Surfaces<Surface = T>>(surfaces: &mut S) -> Self { pub fn new<S: Surfaces<Surface = T>>(surfaces: &mut S) -> Self {
IdleTask { IdleTask {
@ -76,7 +84,7 @@ impl<T: Surface> Task for IdleTask<T> {
self.shimmer.set_opacity(64); self.shimmer.set_opacity(64);
} }
fn tick(&mut self, event: &Event, bus: &mut EventBus) {} fn tick(&mut self) {}
fn stop(&mut self) { fn stop(&mut self) {
self.solid.clear_shader(); self.solid.clear_shader();
@ -115,7 +123,7 @@ impl TestShader {
Self::SweepY => Self::SinX, Self::SweepY => Self::SinX,
Self::SinX => Self::SinY, Self::SinX => Self::SinY,
Self::SinY => Self::Metaballs, Self::SinY => Self::Metaballs,
Self::Metaballs => Self::Red Self::Metaballs => Self::RGB
} }
} }
} }
@ -173,7 +181,7 @@ impl<T: Surface> Task for TestPattern<T> {
self.surface.set_shader(Box::new(self.pattern.clone())); self.surface.set_shader(Box::new(self.pattern.clone()));
} }
fn tick(&mut self, event: &Event, bus: &mut EventBus) { fn tick(&mut self) {
self.updater.run(|| { self.updater.run(|| {
self.pattern = self.pattern.next(); self.pattern = self.pattern.next();
log::info!("Test pattern: {:?}", self.pattern); log::info!("Test pattern: {:?}", self.pattern);

View File

@ -1,255 +1,89 @@
use crate::events::{Event, EventBus};
use crate::geometry::*; use crate::geometry::*;
use crate::lib8::interpolate::Fract8Ops; use crate::lib8::interpolate::Fract8Ops;
use crate::power::AsMilliwatts; use crate::power::AsMilliwatts;
use crate::render::{PixelView, Sample, Shader, Surface, Surfaces, HardwarePixel}; use crate::render::{PixelView, Sample, Shader, Surface, Surfaces, HardwarePixel};
use crate::task::Task;
use std::fmt::Debug; use std::fmt::Debug;
use std::rc::Rc;
use std::cell::RefCell;
use std::io; use std::io;
use std::ops::IndexMut; use std::ops::IndexMut;
use std::sync::atomic::AtomicBool; #[cfg(feature="threads")]
use std::sync::RwLock;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
#[derive(Debug)] #[derive(Debug)]
struct ShaderBinding { pub struct ShaderBinding {
shader: Option<Box<dyn Shader>>, shader: Option<Box<dyn Shader>>,
rect: Rectangle<Virtual>, rect: Rectangle<Virtual>,
opacity: u8 opacity: u8
} }
#[derive(Debug)] #[derive(Clone)]
struct SurfaceUpdate { pub struct BoundSurface<T> {
shader: Option<Option<Box<dyn Shader>>>, pub binding: T
rect: Option<Rectangle<Virtual>>,
opacity: Option<u8>,
slot: usize
} }
impl SurfaceUpdate { impl Debug for BoundSurface<Rc<RefCell<ShaderBinding>>> {
fn merge(&mut self, mut other: Self) {
if other.shader.is_some() {
self.shader = other.shader.take()
}
if other.rect.is_some() {
self.rect = other.rect.take()
}
if other.opacity.is_some() {
self.opacity = other.opacity.take()
}
}
}
impl Debug for Box<dyn Shader> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Shader").finish() f.debug_struct("BoundSurface")
.field("shader", &self.binding.borrow().shader)
.field("opacity", &self.binding.borrow().opacity)
.finish()
} }
} }
impl Default for SurfaceUpdate { pub type SimpleSurface = BoundSurface<Rc<RefCell<ShaderBinding>>>;
impl Default for BoundSurface<Rc<RefCell<ShaderBinding>>>{
fn default() -> Self { fn default() -> Self {
SurfaceUpdate { Self {
shader: None, binding: Rc::new(RefCell::new(ShaderBinding {
rect: None, shader: None,
opacity: None, rect: Rectangle::everything(),
slot: usize::MAX opacity: 255
})),
} }
} }
} }
#[derive(Debug)] impl Surface for BoundSurface<Rc<RefCell<ShaderBinding>>> {
pub struct BufferedSurface { fn rect(&self) -> Rectangle<Virtual> {
updater: Arc<UpdateQueue>, self.binding.borrow().rect
slot: usize
}
impl Surface for BufferedSurface {
fn clear_shader(&mut self) {
self.updater.push(SurfaceUpdate {
shader: None,
slot: self.slot,
..Default::default()
});
} }
fn set_opacity(&mut self, opacity: u8) { fn with_shader<F: FnMut(&dyn Shader)>(&self, mut f: F) {
self.updater.push(SurfaceUpdate { if let Some(ref shader) = self.binding.borrow().shader {
opacity: Some(opacity), f(shader.as_ref());
slot: self.slot, }
..Default::default()
});
}
fn set_rect(&mut self, rect: &Rectangle<Virtual>) {
self.updater.push(SurfaceUpdate {
rect: Some(rect.clone()),
slot: self.slot,
..Default::default()
});
} }
fn set_shader(&mut self, shader: Box<dyn Shader>) { fn set_shader(&mut self, shader: Box<dyn Shader>) {
self.updater.push(SurfaceUpdate { self.binding.borrow_mut().shader = Some(shader);
shader: Some(Some(shader)), }
slot: self.slot,
..Default::default() fn clear_shader(&mut self) {
}); self.binding.borrow_mut().shader = None;
}
fn set_rect(&mut self, rect: &Rectangle<Virtual>) {
self.binding.borrow_mut().rect = *rect;
}
fn opacity(&self) -> u8 {
self.binding.borrow().opacity
}
fn set_opacity(&mut self, opacity: u8) {
self.binding.borrow_mut().opacity = opacity
} }
} }
#[derive(Debug)] #[cfg(feature="threads")]
struct UpdateQueue { pub type SharedSurface = BoundSurface<Arc<Mutex<ShaderBinding>>>;
pending: Mutex<Vec<SurfaceUpdate>>,
damaged: AtomicBool
}
impl UpdateQueue { #[cfg(feature="threads")]
fn new() -> Self { impl Default for BoundSurface<Arc<Mutex<ShaderBinding>>> {
UpdateQueue {
pending: Mutex::new(Vec::new()),
damaged: AtomicBool::new(false)
}
}
fn push(&self, update: SurfaceUpdate) {
let mut locked = self.pending.lock().unwrap();
let mut existing_slot = None;
for existing in locked.iter_mut() {
if existing.slot == update.slot {
existing_slot = Some(existing);
break
}
}
match existing_slot {
Some(tgt) => {
log::debug!("Updating existing shader update");
tgt.merge(update);
}
_ => {
log::debug!("Pushing new shader update");
locked.push(update);
self.damaged.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
}
}
#[derive(Debug)]
pub struct ShaderChain {
bindings: Vec<ShaderBinding>,
updates: Arc<UpdateQueue>
}
impl ShaderChain {
pub fn new() -> Self {
ShaderChain {
bindings: Vec::new(),
updates: Arc::new(UpdateQueue::new())
}
}
pub fn is_dirty(&self) -> bool {
self.updates.damaged.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn commit(&mut self) {
let mut queue: Vec<SurfaceUpdate> = {
let mut updates = self.updates.pending.lock().unwrap();
std::mem::take(updates.as_mut())
};
for update in queue.iter_mut() {
let target_slot = &mut self.bindings[update.slot];
if let Some(shader) = update.shader.take() {
target_slot.shader = shader;
}
if let Some(opacity) = update.opacity.take() {
target_slot.opacity = opacity;
}
if let Some(rect) = update.rect.take() {
target_slot.rect = rect;
}
}
self.updates.damaged.store(false, std::sync::atomic::Ordering::Relaxed);
}
}
impl Surfaces for ShaderChain {
type Error = ();
type Surface = BufferedSurface;
fn new_surface(&mut self, area: &Rectangle<Virtual>) -> Result<Self::Surface, Self::Error> {
let next_slot = self.bindings.len();
self.bindings.push(ShaderBinding {
opacity: 255,
shader: None,
rect: area.clone()
});
Ok(BufferedSurface {
updater: Arc::clone(&self.updates),
slot: next_slot
})
}
fn render_to<S: Sample>(&self, output: &mut S, frame: usize) {
for surface in self.bindings.iter() {
let opacity = surface.opacity;
if opacity > 0 {
let rect = surface.rect;
let mut sample = output.sample(&rect);
if let Some(ref shader) = surface.shader {
while let Some((virt_coords, pixel)) = sample.next() {
*pixel = pixel.blend8(shader.draw(&virt_coords, frame).into(), opacity);
}
}
}
}
}
}
#[derive(Clone)]
pub struct BufferedSurfacePool {
pool: Arc<RwLock<ShaderChain>>
}
impl BufferedSurfacePool {
pub fn new() -> Self {
BufferedSurfacePool {
pool: Arc::new(RwLock::new(ShaderChain::new()))
}
}
}
impl Surfaces for BufferedSurfacePool {
type Error = ();
type Surface = <ShaderChain as Surfaces>::Surface;
fn new_surface(&mut self, area: &crate::geometry::Rectangle<crate::geometry::Virtual>) -> Result<Self::Surface, Self::Error> {
self.pool.write().unwrap().new_surface(area)
}
fn render_to<S: crate::render::Sample>(&self, output: &mut S, frame: usize) {
self.pool.read().unwrap().render_to(output, frame);
}
}
impl Task for BufferedSurfacePool {
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
if self.pool.read().unwrap().is_dirty() {
self.pool.write().unwrap().commit();
}
}
}
#[derive(Clone)]
pub struct SharedSurface {
binding: Arc<Mutex<ShaderBinding>>
}
impl Default for SharedSurface {
fn default() -> Self { fn default() -> Self {
Self { Self {
binding: Arc::new(Mutex::new(ShaderBinding { binding: Arc::new(Mutex::new(ShaderBinding {
@ -261,7 +95,19 @@ impl Default for SharedSurface {
} }
} }
impl Surface for SharedSurface { #[cfg(feature="threads")]
impl Surface for BoundSurface<Arc<Mutex<ShaderBinding>>> {
fn rect(&self) -> Rectangle<Virtual> {
let r = self.binding.lock().unwrap();
r.rect.clone()
}
fn with_shader<F: FnMut(&dyn Shader)>(&self, mut f: F) {
if let Some(ref shader) = self.binding.lock().unwrap().shader {
f(shader.as_ref());
}
}
fn set_shader(&mut self, shader: Box<dyn Shader>) { fn set_shader(&mut self, shader: Box<dyn Shader>) {
self.binding.lock().unwrap().shader = Some(shader); self.binding.lock().unwrap().shader = Some(shader);
} }
@ -274,48 +120,67 @@ impl Surface for SharedSurface {
self.binding.lock().unwrap().rect = rect.clone(); self.binding.lock().unwrap().rect = rect.clone();
} }
fn opacity(&self) -> u8 {
self.binding.lock().unwrap().opacity
}
fn set_opacity(&mut self, opacity: u8) { fn set_opacity(&mut self, opacity: u8) {
self.binding.lock().unwrap().opacity = opacity self.binding.lock().unwrap().opacity = opacity
} }
} }
#[cfg(feature="threads")]
#[derive(Clone)] impl Debug for BoundSurface<Arc<Mutex<ShaderBinding>>> {
pub struct SurfacePool { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
surfaces: Vec<SharedSurface> f.debug_struct("BoundSurface")
.field("shader", &self.binding.lock().unwrap().shader)
.finish()
}
} }
impl SurfacePool { #[derive(Clone)]
pub struct SurfacePool<S: Surface + Default> {
surfaces: Vec<S>
}
impl<S: Surface + Debug> Debug for SurfacePool<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.surfaces.fmt(f)
}
}
impl<S: Surface + Default> SurfacePool<S> {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
surfaces: Vec::new() surfaces: Vec::new()
} }
} }
pub fn iter(&self) -> std::slice::Iter<S> {
self.surfaces.iter()
}
} }
impl Surfaces for SurfacePool { impl<S: Surface + Default> Surfaces for SurfacePool<S> {
type Surface = SharedSurface; type Surface = S;
type Error = io::Error; fn new_surface(&mut self, area: &Rectangle<Virtual>) -> Result<S, io::Error> {
fn new_surface(&mut self, area: &Rectangle<Virtual>) -> Result<Self::Surface, Self::Error> { let mut surface = S::default();
let mut surface = SharedSurface::default();
surface.set_rect(area); surface.set_rect(area);
self.surfaces.push(surface.clone()); self.surfaces.push(surface.clone());
return Ok(surface); return Ok(surface);
} }
fn render_to<Sampler: Sample>(&self, output: &mut Sampler, frame: usize) { fn render_to<Sampler: Sample>(&self, output: &mut Sampler, frame: usize) {
for surface in self.surfaces.iter() { for surface in self.iter() {
let binding = surface.binding.lock().unwrap(); let opacity = surface.opacity();
let opacity = binding.opacity;
if opacity > 0 { if opacity > 0 {
let rect = binding.rect; let rect = surface.rect();
let mut sample = output.sample(&rect); let mut sample = output.sample(&rect);
surface.with_shader(|shader| {
if let Some(ref shader) = binding.shader {
while let Some((virt_coords, pixel)) = sample.next() { while let Some((virt_coords, pixel)) = sample.next() {
*pixel = pixel.blend8(shader.draw(&virt_coords, frame).into(), opacity); *pixel = pixel.blend8(shader.draw(&virt_coords, frame).into(), opacity);
} }
} })
} }
} }
} }

View File

@ -1,163 +0,0 @@
use core::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputEvent {
PowerOn,
PowerOff,
NetworkActivity,
NetworkOnline,
NetworkOffline
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Variant {
Byte(u8),
UInt(u32),
Int(i32),
BigUInt(u64),
BigInt(i64),
Boolean(bool)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Event {
ReadyToRock,
Tick,
StartThing(&'static str),
StopThing(&'static str),
Input(InputEvent),
PropertyChange(&'static str, Variant)
}
pub struct SystemState {
key: &'static str,
value: Variant,
values: Vec::<Box<SystemState>>
}
impl SystemState {
pub fn new() -> Self {
SystemState {
key: "",
value: Variant::Byte(0),
values: Vec::new()
}
}
fn get_key(&self, key: &'static str) -> Option<&Self> {
if key == self.key {
Some(self)
} else {
for next in self.values.iter() {
match next.get_key(key) {
None => (),
Some(next_val) => return Some(next_val)
}
}
return None
}
}
fn get_key_mut(&mut self, key: &'static str) -> Option<&mut Self> {
if key == self.key {
Some(self)
} else {
for next in self.values.iter_mut() {
match next.get_key_mut(key) {
None => (),
Some(next_val) => return Some(next_val)
}
}
return None
}
}
pub fn get(&self, key: &'static str) -> Option<Variant> {
match self.get_key(key) {
None => None,
Some(v) => Some(v.value)
}
}
pub fn set<V>(&mut self, key: &'static str, value: V) where Variant: From<V> {
match self.get_key_mut(key) {
None => self.values.push(Box::new(SystemState {
value: value.into(),
key: key,
values: Vec::new()
})),
Some(found_key) => {
found_key.value = value.into()
}
}
}
}
impl Event {
pub fn new_tick() -> Self {
Event::Tick
}
pub fn new_property_change<T>(key: &'static str, data: T) -> Self where Variant: From<T> {
Event::PropertyChange(key, Variant::from(data))
}
pub fn new_ready_to_rock() -> Self {
Event::ReadyToRock
}
pub fn new_input_event(event: InputEvent) -> Self {
Event::Input(event)
}
}
impl Into<u8> for Variant {
fn into(self) -> u8 {
match self {
Variant::Byte(b) => b,
_ => 0
}
}
}
impl From<bool> for Variant {
fn from(value: bool) -> Self {
Variant::Boolean(value)
}
}
impl From<i64> for Variant {
fn from(value: i64) -> Self {
Variant::BigInt(value)
}
}
impl From<u8> for Variant {
fn from(value: u8) -> Self {
Variant::Byte(value)
}
}
pub struct EventBus {
pending: Vec<Event>
}
impl EventBus {
pub fn new() -> Self {
EventBus {
pending: Vec::new()
}
}
pub fn next(&mut self) -> Event {
if self.pending.len() == 0 {
Event::new_tick()
} else {
self.pending.pop().unwrap()
}
}
pub fn push(&mut self, event: Event) {
self.pending.push(event);
}
}

View File

@ -82,25 +82,6 @@ pub fn lerp7by8(a: i8, b: i8, frac: u8) -> i8 {
} }
} }
pub fn lerp8by8(a: u8, b: u8, frac: u8) -> u8 {
if b > a {
let delta = b - a;
let scaled = scale8(delta, frac);
return a + scaled;
} else {
let delta = a - b;
let scaled = scale8(delta, frac);
return a - scaled;
}
}
pub fn map8(x: u8, range_start: u8, range_end: u8) -> u8 {
let range_width = range_end - range_start;
let mut out = scale8(x, range_width);
out += range_start;
return out;
}
pub fn ease8InOutQuad(i: u8) -> u8 { pub fn ease8InOutQuad(i: u8) -> u8 {
let j = if i & 0x80 != 0 { let j = if i & 0x80 != 0 {
255 - i 255 - i

View File

@ -8,13 +8,9 @@ mod platform;
mod animations; mod animations;
mod mappings; mod mappings;
mod buffers; mod buffers;
mod events;
use events::Event;
use crate::events::EventBus;
use crate::platform::{DefaultBoard, Board}; use crate::platform::{DefaultBoard, Board};
use crate::task::{FixedSizeScheduler, Scheduler}; use crate::task::{Task, Scheduler};
use crate::render::{Surfaces, Renderer}; use crate::render::{Surfaces, Renderer};
use crate::geometry::Rectangle; use crate::geometry::Rectangle;
@ -23,39 +19,27 @@ fn main() {
log::info!("Board: {}", core::any::type_name_of_val(&board)); log::info!("Board: {}", core::any::type_name_of_val(&board));
log::info!("Creating tasks");
let mut system = board.system_tasks();
log::info!("System scheduler: {}", core::any::type_name_of_val(&system));
log::info!("Creating output");
let output = board.output(); let output = board.output();
log::info!("Output: {}", core::any::type_name_of_val(&output)); log::info!("Output: {}", core::any::type_name_of_val(&output));
log::info!("Preparing surfaces");
let mut surfaces = board.surfaces(); let mut surfaces = board.surfaces();
log::info!("Surface implementation: {}", core::any::type_name_of_val(&output)); log::info!("Surface implementation: {}", core::any::type_name_of_val(&output));
log::info!("Creating animations"); log::info!("Creating animations");
let mut animations = FixedSizeScheduler::new([ let mut tasks: Vec<Box<dyn Task + 'static>> = vec![
Box::new(animations::IdleTask::new(&mut surfaces)), Box::new(animations::IdleTask::new(&mut surfaces)),
//Box::new(animations::TestPattern::new(surfaces.new_surface(&Rectangle::everything()).unwrap())), Box::new(animations::TestPattern::new(surfaces.new_surface(&Rectangle::everything()).unwrap())),
]); ];
let mut renderer = FixedSizeScheduler::new([Box::new(Renderer::new(output, surfaces))]); tasks.append(&mut board.system_tasks());
tasks.push(Box::new(Renderer::new(output, surfaces)));
log::info!("Starting event bus"); let mut runner = Scheduler::new(tasks);
let mut bus = EventBus::new();
log::info!("Runner ready: {:?}", runner);
log::info!("Ready to rock and roll"); log::info!("Ready to rock and roll");
bus.push(Event::new_ready_to_rock());
loop { loop {
let next_event = bus.next(); runner.tick();
match next_event {
events::Event::Tick => (),
_ => log::info!("Event: {:?}", next_event)
}
animations.tick(&next_event, &mut bus);
system.tick(&next_event, &mut bus);
renderer.tick(&next_event, &mut bus);
} }
} }

View File

@ -1,8 +1,6 @@
use crate::buffers::Pixbuf;
use crate::geometry::*; use crate::geometry::*;
use crate::lib8::interpolate::scale8; use crate::lib8::interpolate::scale8;
use crate::render::PixelView;
use core::cmp::{max, min}; use core::cmp::{max, min};
use core::fmt::{Formatter, Debug}; use core::fmt::{Formatter, Debug};
@ -101,12 +99,6 @@ impl<const STRIDE_NUM: usize> StrideMapping<STRIDE_NUM> {
]) ])
} }
pub fn new_fairylights() -> Self {
Self::from_json(&[
(0, 0, 50, false)
])
}
pub fn new_jar() -> Self { pub fn new_jar() -> Self {
Self::from_json(&[ Self::from_json(&[
(0, 0, 17, false), (0, 0, 17, false),
@ -309,29 +301,3 @@ impl<'a> CoordinateView<'a> for StrideView<'a> {
None None
} }
} }
pub struct StrideSampler<'a, P: Pixbuf> {
pixbuf: &'a mut P,
selection: StrideView<'a>
}
impl<'a, P: Pixbuf> StrideSampler<'a, P> {
pub fn new(pixbuf: &'a mut P, selection: StrideView<'a>) -> Self {
StrideSampler {
pixbuf,
selection
}
}
}
impl<'a, P: Pixbuf> PixelView for StrideSampler<'a, P> {
type Pixel = P::Pixel;
fn next(&mut self) -> Option<(Coordinates<Virtual>, &mut Self::Pixel)> {
if let Some((virt, coords)) = self.selection.next() {
let idx = self.selection.map.strides[coords.x as usize].pixel_idx_for_offset(coords.y);
Some((virt, &mut self.pixbuf[idx]))
} else {
None
}
}
}

View File

@ -1,108 +1,45 @@
use core::borrow::BorrowMut;
use std::sync::Arc; use std::sync::Arc;
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::Mutex; use std::sync::Mutex;
use std::thread::JoinHandle;
use std::thread::ScopedJoinHandle;
use chrono::DateTime; use chrono::DateTime;
use chrono::Timelike;
use chrono::Utc; use chrono::Utc;
use esp_idf_svc::eventloop::{EspSubscription, EspSystemEventLoop, System}; use esp_idf_svc::eventloop::{EspSubscription, EspSystemEventLoop, System};
use esp_idf_svc::hal::modem::Modem; use esp_idf_svc::hal::modem::Modem;
use esp_idf_svc::hal::prelude::Peripherals; use esp_idf_svc::hal::prelude::Peripherals;
use esp_idf_svc::hal::task::thread::ThreadSpawnConfiguration;
use esp_idf_svc::mqtt::client::EspMqttClient;
use esp_idf_svc::mqtt::client::EspMqttConnection;
use esp_idf_svc::mqtt::client::MqttClientConfiguration;
use esp_idf_svc::netif::IpEvent; use esp_idf_svc::netif::IpEvent;
use esp_idf_svc::nvs::{EspDefaultNvsPartition, EspNvsPartition, NvsDefault}; use esp_idf_svc::nvs::{EspDefaultNvsPartition, EspNvsPartition, NvsDefault};
use esp_idf_svc::sntp::EspSntp; use esp_idf_svc::sntp::EspSntp;
use esp_idf_svc::sys::esp_efuse_mac_get_default; use esp_idf_svc::sys::esp_efuse_mac_get_default;
use esp_idf_svc::wifi::{AuthMethod, ClientConfiguration, Configuration, EspWifi, WifiEvent}; use esp_idf_svc::wifi::{AuthMethod, ClientConfiguration, Configuration, EspWifi, WifiEvent};
use rgb::Rgb; use rgb::Rgb;
use super::smart_leds_lib::rmt::FastWs2812Esp32Rmt;
use super::smart_leds_lib::StrideOutput;
use super::Board; use super::Board;
use crate::buffers::BufferedSurfacePool;
use crate::buffers::Pixbuf;
use crate::events::Event;
use crate::events::EventBus;
use crate::lib8::interpolate::lerp8by8;
use crate::mappings::StrideMapping;
use crate::task::FixedSizeScheduler;
use crate::task::Task; use crate::task::Task;
use crate::buffers::{Pixbuf, SurfacePool};
use crate::mappings::StrideMapping;
use crate::platform::smart_leds_lib::StrideOutput;
use crate::platform::smart_leds_lib::rmt::FastWs2812Esp32Rmt;
use crate::time::Periodically; use crate::time::Periodically;
pub mod i2s { #[cfg(feature="threads")]
use esp_idf_svc::hal::i2s::*; use crate::buffers::SharedSurface as SurfaceType;
use rgb::ComponentBytes;
use rgb::Rgb;
use crate::mappings::*; #[cfg(not(feature="threads"))]
use crate::buffers::Pixbuf; use crate::buffers::SimpleSurface as SurfaceType;
use crate::render::Output;
use crate::render::Sample;
pub struct I2SOutput<'d> { pub struct Esp32Board<'a> {
driver: I2sDriver<'d, I2sTx>, output: Option<<Self as Board>::Output>,
pixbuf: [Rgb<u8>; 310], surfaces: Option<SurfacePool<SurfaceType>>,
pixmap: StrideMapping, tasks: Option<Vec<Box<dyn Task>>>
}
impl<'d> I2SOutput<'d> {
fn new(driver: I2sDriver<'d, I2sTx>) -> Self {
I2SOutput {
driver,
pixbuf: Pixbuf::new(),
pixmap: StrideMapping::new_jar()
}
}
}
impl<'d> Output for I2SOutput<'d> {
fn on_event(&mut self, event: &crate::events::Event) {
}
fn blank(&mut self) {
self.pixbuf.blank();
}
fn commit(&mut self) {
let bytes = self.pixbuf.as_bytes();
let mut written = self.driver.preload_data(bytes).unwrap();
self.driver.tx_enable().unwrap();
while written < bytes.len() {
let next = &bytes[written..];
written += self.driver.write(next, 0).unwrap();
}
self.driver.tx_disable().unwrap();
}
}
impl<'d> Sample for I2SOutput<'d> {
type Pixel = Rgb<u8>;
fn sample(&mut self, rect: &crate::geometry::Rectangle<crate::geometry::Virtual>) -> impl crate::render::PixelView<Pixel = Self::Pixel> {
StrideSampler::new(&mut self.pixbuf, self.pixmap.select(rect))
}
}
} }
pub struct Esp32Board { impl<'a> Board for Esp32Board<'a> {
sys_loop: EspSystemEventLoop, type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'a>>;
modem: Option<Modem>, type Surfaces = SurfacePool<SurfaceType>;
surfaces: BufferedSurfacePool,
output: Option<<Self as Board>::Output>
}
impl Board for Esp32Board {
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'static>>;
type Surfaces = BufferedSurfacePool;
type Scheduler = FixedSizeScheduler<4>;
fn take() -> Self { fn take() -> Self {
// It is necessary to call this function once. Otherwise some patches to the runtime // It is necessary to call this function once. Otherwise some patches to the runtime
@ -114,86 +51,48 @@ impl Board for Esp32Board {
let peripherals = Peripherals::take().unwrap(); let peripherals = Peripherals::take().unwrap();
let sys_loop = EspSystemEventLoop::take().unwrap(); let sys_loop = EspSystemEventLoop::take().unwrap();
let nvs = EspDefaultNvsPartition::take().unwrap();
let channel = peripherals.rmt.channel0;
let pins = peripherals.pins;
let mut chip_id: [u8; 8] = [0; 8]; let mut chip_id: [u8; 8] = [0; 8];
unsafe { unsafe {
esp_efuse_mac_get_default(&mut chip_id as *mut u8); esp_efuse_mac_get_default(&mut chip_id as *mut u8);
} }
log::info!("Setting up output for chip ID {:x?}", chip_id); log::info!("Setting up for chip ID {:?}", chip_id);
let (pixmap, target) = match chip_id {
[72, 202, 67, 89, 145, 204, 0, 0] => {
(StrideMapping::new_panel(), FastWs2812Esp32Rmt::new(channel, pins.gpio5).unwrap())
},
[140, 170, 181, 131, 95, 116, 0, 0] => {
(StrideMapping::new_jar(), FastWs2812Esp32Rmt::new(channel, pins.gpio14).unwrap())
},
_ => {
(StrideMapping::new(), FastWs2812Esp32Rmt::new(channel, pins.gpio5).unwrap())
}
};
let tasks: Vec<Box<dyn Task>> = vec![
Box::new(WifiTask::new(peripherals.modem, sys_loop.clone(), &nvs)),
];
const POWER_VOLTS : u32 = 5; const POWER_VOLTS : u32 = 5;
const POWER_MA : u32 = 500; const POWER_MA : u32 = 500;
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA; const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
let pins = peripherals.pins;
ThreadSpawnConfiguration { let output = StrideOutput::new(
pin_to_core: Some(esp_idf_svc::hal::cpu::Core::Core1), Pixbuf::new(),
..Default::default() pixmap,
}.set().unwrap(); target,
// Wifi driver creates too many interrupts on core0, so we need to use RMT on core1. MAX_POWER_MW
// But the implementation spawns a thread based on the core the driver was created in, );
// so we create the driver in another thread briefly.
// Fun stuff.
let output = match chip_id { // panel test board
[72, 202, 67, 89, 145, 204, 0, 0] => {
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_panel(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
},
[0x8C, 0xAA, 0xB5, 0x83, 0x5f, 0x74, 0x0, 0x0] => { //ponderjar
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_jar(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio14).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
},
[0x4a, 0xca, 0x43, 0x59, 0x85, 0x58, 0x0, 0x0] => { // Albus the tree
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_jar(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
},
[0x48, 0xca, 0x43, 0x59, 0x9d, 0x48, 0x0, 0x0] => { // kitchen cabinets
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_fairylights(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
},
[0x48, 0xca, 0x43, 0x59, 0x9e, 0xdc, 0x0, 0x0] => { // front window
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_fairylights(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
}
_ => {
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
}
};
ThreadSpawnConfiguration {
..Default::default()
}.set().unwrap();
Esp32Board { Esp32Board {
modem: Some(peripherals.modem), surfaces: Some(SurfacePool::new()),
sys_loop: sys_loop.clone(), output: Some(output),
surfaces: BufferedSurfacePool::new(), tasks: Some(tasks),
output: Some(output)
} }
} }
@ -202,168 +101,11 @@ impl Board for Esp32Board {
} }
fn surfaces(&mut self) -> Self::Surfaces { fn surfaces(&mut self) -> Self::Surfaces {
self.surfaces.clone() self.surfaces.take().unwrap()
} }
fn system_tasks(&mut self) -> Self::Scheduler { fn system_tasks(&mut self) -> Vec<Box<dyn Task>> {
let nvs = EspDefaultNvsPartition::take().unwrap(); self.tasks.take().unwrap()
FixedSizeScheduler::new([
Box::new(WifiTask::new(self.modem.take().unwrap(), self.sys_loop.clone(), &nvs)),
Box::new(CircadianRhythm::new()),
Box::new(MqttTask::new()),
Box::new(self.surfaces.clone())
])
}
}
#[derive(Debug, Clone, Copy)]
struct ScheduleEntry {
hour: u8,
brightness: u8
}
struct CircadianRhythm {
time_check: Periodically,
schedule: [ScheduleEntry;10]
}
impl CircadianRhythm {
fn new() -> Self {
CircadianRhythm {
time_check: Periodically::new_every_n_seconds(5),
schedule: [
ScheduleEntry { hour: 0, brightness: 0 },
ScheduleEntry { hour: 5, brightness: 0 },
ScheduleEntry { hour: 6, brightness: 10 },
ScheduleEntry { hour: 7, brightness: 20 },
ScheduleEntry { hour: 8, brightness: 80 },
ScheduleEntry { hour: 11, brightness: 120 },
ScheduleEntry { hour: 18, brightness: 200 },
ScheduleEntry { hour: 19, brightness: 255 },
ScheduleEntry { hour: 22, brightness: 120 },
ScheduleEntry { hour: 23, brightness: 5 }
]
}
}
fn brightness_for_time(&self, hour: u8, minute: u8) -> u8 {
let mut start = self.schedule.last().unwrap();
let mut end = self.schedule.first().unwrap();
for cur in self.schedule.iter() {
if (cur.hour <= hour ) {
start = cur;
} else {
end = cur;
break;
}
}
log::info!("hour={:?} minute={:?} start={:?} end={:?}", hour, minute, start, end);
let mut adjusted_end = end.clone();
if start.hour > end.hour {
adjusted_end.hour += 24;
}
let start_time = start.hour * 60;
let end_time = end.hour * 60;
let now_time = hour * 60 + minute;
let duration = end_time - start_time;
let cur_duration = now_time - start_time;
let frac = map_range(cur_duration.into(), 0, duration.into(), 0, 255) as u8;
lerp8by8(start.brightness, end.brightness, frac)
}
}
fn map_range(x: u16, in_min: u16, in_max: u16, out_min: u16, out_max: u16) -> u16 {
let run = in_max - in_min;
if run == 0 {
return 0;
}
let rise = out_max - out_min;
let delta = x - in_min;
return (delta * rise) / run + out_min;
}
impl Task for CircadianRhythm {
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
if self.time_check.tick() || event.eq(&Event::ReadyToRock) {
let now: DateTime<Utc> = std::time::SystemTime::now().into();
let next_brightness = self.brightness_for_time(now.hour() as u8, now.minute() as u8);
bus.push(Event::new_property_change("output.brightness", next_brightness));
}
}
}
struct MqttTask {
client: Option<EspMqttClient<'static>>,
conn_thread: Option<JoinHandle<()>>,
}
impl MqttTask {
fn new() -> Self {
MqttTask {
conn_thread: None,
client: None
}
}
fn start_mqtt(&mut self) {
log::info!("Starting MQTT");
let (client, mut conn) = EspMqttClient::new(
"mqtt://10.0.0.2:1883",
&MqttClientConfiguration {
client_id: Some("renderbug-rs"),
..Default::default()
}
).unwrap();
log::info!("Connected!");
self.conn_thread = Some(std::thread::Builder::new()
.stack_size(6000)
.spawn(move || {
conn.next().unwrap();
}).unwrap());
self.client = Some(client);
}
}
impl Task for MqttTask {
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
match event {
Event::Input(crate::events::InputEvent::NetworkOnline) => {
log::info!("Registering with MQTT");
self.start_mqtt();
if let Some(ref mut client) = self.client {
client.enqueue(
"homeassistant-test/renderbug/rust",
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
false,
"hello, world".as_bytes()
).unwrap();
log::info!("MQTT should be online!");
}
},
Event::PropertyChange(name, value) => {
if let Some(ref mut client) = self.client {
let payload = format!("name={} value={:?}", name, value);
client.enqueue(
"homeassistant-test/renderbug/rust/property-change",
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
false,
payload.as_bytes()
).unwrap();
log::info!("property change bump: {}", payload);
}
}
_ => ()
}
} }
} }
@ -394,7 +136,6 @@ struct WifiTask {
impl WifiTask { impl WifiTask {
fn new(modem: Modem, sys_loop: EspSystemEventLoop, nvs: &EspNvsPartition<NvsDefault>) -> Self { fn new(modem: Modem, sys_loop: EspSystemEventLoop, nvs: &EspNvsPartition<NvsDefault>) -> Self {
log::info!("Installing wifi driver");
let mut wifi = EspWifi::new( let mut wifi = EspWifi::new(
modem, modem,
sys_loop.clone(), sys_loop.clone(),
@ -439,6 +180,8 @@ impl Task for WifiTask {
fn start(&mut self) { fn start(&mut self) {
log::info!("Starting wifi!"); log::info!("Starting wifi!");
log::info!("waiting for loop");
let wifi_state = self.state.clone(); let wifi_state = self.state.clone();
self.wifi_sub = Some(self.sys_loop.subscribe::<WifiEvent, _>( move |evt| { self.wifi_sub = Some(self.sys_loop.subscribe::<WifiEvent, _>( move |evt| {
log::warn!("wifi event {:?}", evt); log::warn!("wifi event {:?}", evt);
@ -473,7 +216,7 @@ impl Task for WifiTask {
self.connect(); self.connect();
} }
fn tick(&mut self, event: &Event, bus: &mut EventBus) { fn tick(&mut self ) {
if self.connection_check.tick() { if self.connection_check.tick() {
let cur_state = *self.state.lock().unwrap(); let cur_state = *self.state.lock().unwrap();
@ -488,12 +231,10 @@ impl Task for WifiTask {
log::info!("online: {:?}", cur_state); log::info!("online: {:?}", cur_state);
self.last_state = cur_state; self.last_state = cur_state;
match cur_state {
WifiState::Connected => bus.push(Event::new_input_event(crate::events::InputEvent::NetworkOnline)),
_ => bus.push(Event::new_input_event(crate::events::InputEvent::NetworkOffline))
}
} }
let now: DateTime<Utc> = std::time::SystemTime::now().into();
log::info!("Current time: {} status={:?}", now.format("%d/%m/%Y %T"), self.ntp.get_sync_status());
} }
} }

View File

@ -1,44 +0,0 @@
use rgb::Rgb;
use super::smart_leds_lib::rmt::FastWs2812Esp32Rmt;
use super::smart_leds_lib::StrideOutput;
use crate::task::{FixedSizeScheduler, Task};
use crate::buffers::StaticSurfacePool;
use super::Board;
pub struct Esp32Board<'a> {
output: Option<<Self as Board>::Output>,
surfaces: Option<StaticSurfacePool>,
tasks: Option<[&'a mut dyn Task; 1]>
}
impl<'a> Board for Esp32Board<'a> {
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'a>>;
type Surfaces = StaticSurfacePool;
type Scheduler = FixedSizeScheduler<0>;
fn take() -> Self {
let peripherals = esp_hal::init(esp_hal::Config::default());
//esp_alloc::heap_allocator!(72 * 1024);
const POWER_VOLTS : u32 = 5;
const POWER_MA : u32 = 500;
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
let pins = peripherals.pins;
Esp32Board { output: None, surfaces: None, tasks: None }
}
fn output(&mut self) -> Self::Output {
self.output.take().unwrap()
}
fn surfaces(&mut self) -> Self::Surfaces {
self.surfaces.take().unwrap()
}
fn system_tasks(&mut self) -> Self::Scheduler {
FixedSizeScheduler::new([])
}
}

View File

@ -9,15 +9,14 @@ pub mod esp32;
pub type DefaultBoard = esp32::Esp32Board; pub type DefaultBoard = esp32::Esp32Board;
use crate::render::{Output, Surfaces}; use crate::render::{Output, Surfaces};
use crate::task::Scheduler; use crate::task::Task;
pub trait Board { pub trait Board {
type Output: Output; type Output: Output;
type Surfaces: Surfaces; type Surfaces: Surfaces;
type Scheduler: Scheduler;
fn take() -> Self; fn take() -> Self;
fn output(&mut self) -> Self::Output; fn output(&mut self) -> Self::Output;
fn surfaces(&mut self) -> Self::Surfaces; fn surfaces(&mut self) -> Self::Surfaces;
fn system_tasks(&mut self) -> Self::Scheduler; fn system_tasks(&mut self) -> Vec<Box<dyn Task>>;
} }

View File

@ -1,7 +1,6 @@
use smart_leds_trait::SmartLedsWrite; use smart_leds_trait::SmartLedsWrite;
use crate::buffers::Pixbuf; use crate::buffers::Pixbuf;
use crate::events::Variant;
use crate::render::{HardwarePixel, Output, PixelView, Sample}; use crate::render::{HardwarePixel, Output, PixelView, Sample};
use crate::power::brightness_for_mw; use crate::power::brightness_for_mw;
use crate::geometry::*; use crate::geometry::*;
@ -9,6 +8,23 @@ use crate::mappings::*;
use core::fmt::Debug; use core::fmt::Debug;
struct StrideSampler<'a, P: Pixbuf> {
pixbuf: &'a mut P,
selection: StrideView<'a>
}
impl<'a, P: Pixbuf> PixelView for StrideSampler<'a, P> {
type Pixel = P::Pixel;
fn next(&mut self) -> Option<(Coordinates<Virtual>, &mut Self::Pixel)> {
if let Some((virt, coords)) = self.selection.next() {
let idx = self.selection.map.strides[coords.x as usize].pixel_idx_for_offset(coords.y);
Some((virt, &mut self.pixbuf[idx]))
} else {
None
}
}
}
impl<P: Pixbuf, T: FastWrite> Debug for StrideOutput<P, T> { impl<P: Pixbuf, T: FastWrite> Debug for StrideOutput<P, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("StrideOutput").finish() f.debug_struct("StrideOutput").finish()
@ -19,8 +35,7 @@ pub struct StrideOutput<P: Pixbuf, T: FastWrite> {
pixbuf: P, pixbuf: P,
stride_map: StrideMapping, stride_map: StrideMapping,
target: T, target: T,
max_mw: u32, max_mw: u32
brightness: u8
} }
impl<P: Pixbuf, T: FastWrite> StrideOutput<P, T> { impl<P: Pixbuf, T: FastWrite> StrideOutput<P, T> {
@ -30,8 +45,7 @@ impl<P: Pixbuf, T: FastWrite> StrideOutput<P, T> {
pixbuf, pixbuf,
stride_map, stride_map,
target, target,
max_mw, max_mw
brightness: 255
} }
} }
} }
@ -39,7 +53,10 @@ impl<P: Pixbuf, T: FastWrite> StrideOutput<P, T> {
impl<P: Pixbuf, T: FastWrite> Sample for StrideOutput<P, T> { impl<P: Pixbuf, T: FastWrite> Sample for StrideOutput<P, T> {
type Pixel = P::Pixel; type Pixel = P::Pixel;
fn sample(&mut self, rect: &Rectangle<Virtual>) -> impl PixelView<Pixel = Self::Pixel> { fn sample(&mut self, rect: &Rectangle<Virtual>) -> impl PixelView<Pixel = Self::Pixel> {
StrideSampler::new(&mut self.pixbuf,self.stride_map.select(rect)) StrideSampler {
pixbuf: &mut self.pixbuf,
selection: self.stride_map.select(rect)
}
} }
} }
@ -49,18 +66,11 @@ impl<P: Pixbuf<Pixel=T::Color>, T: FastWrite> Output for StrideOutput<P, T> {
} }
fn commit(&mut self) { fn commit(&mut self) {
let b = brightness_for_mw(self.pixbuf.as_milliwatts(), self.brightness, self.max_mw); let b = brightness_for_mw(self.pixbuf.as_milliwatts(), 255, self.max_mw);
if self.target.fast_write(self.pixbuf.iter_with_brightness(b)).is_err() { if self.target.fast_write(self.pixbuf.iter_with_brightness(b)).is_err() {
panic!("Could not write frame!"); panic!("Could not write frame!");
}; };
} }
fn on_event(&mut self, event: &crate::events::Event) {
match event {
crate::events::Event::PropertyChange("output.brightness", new_brightness) => self.brightness = new_brightness.clone().into(),
_ => ()
}
}
} }
pub trait FastWrite: Send { pub trait FastWrite: Send {

View File

@ -1,6 +1,6 @@
use std::io;
use rgb::Rgb; use rgb::Rgb;
use crate::events::{Event, EventBus};
use crate::geometry::*; use crate::geometry::*;
use crate::lib8::interpolate::Fract8Ops; use crate::lib8::interpolate::Fract8Ops;
use crate::power::AsMilliwatts; use crate::power::AsMilliwatts;
@ -29,21 +29,22 @@ pub trait Shader: Send + Sync {
pub trait Surfaces: Send + Sync { pub trait Surfaces: Send + Sync {
type Surface: Surface; type Surface: Surface;
type Error: Debug; fn new_surface(&mut self, area: &Rectangle<Virtual>) -> Result<Self::Surface, io::Error>;
fn new_surface(&mut self, area: &Rectangle<Virtual>) -> Result<Self::Surface, Self::Error>;
fn render_to<S: Sample>(&self, output: &mut S, frame: usize); fn render_to<S: Sample>(&self, output: &mut S, frame: usize);
} }
pub trait Surface: Send + Sync { pub trait Surface: Send + Sync {
fn with_shader<F: FnMut(&dyn Shader)>(&self, f: F);
fn set_shader(&mut self, shader: Box<dyn Shader>); fn set_shader(&mut self, shader: Box<dyn Shader>);
fn clear_shader(&mut self); fn clear_shader(&mut self);
fn rect(&self) -> Rectangle<Virtual>;
fn set_rect(&mut self, rect: &Rectangle<Virtual>); fn set_rect(&mut self, rect: &Rectangle<Virtual>);
fn opacity(&self) -> u8;
fn set_opacity(&mut self, opacity: u8); fn set_opacity(&mut self, opacity: u8);
} }
pub trait Output: Sample + Send { pub trait Output: Sample + Send {
fn on_event(&mut self, event: &Event);
fn blank(&mut self); fn blank(&mut self);
fn commit(&mut self); fn commit(&mut self);
} }
@ -72,22 +73,17 @@ impl<T: Output, S: Surfaces> Renderer<T, S> {
impl<T: Output, S: Surfaces> Task for Renderer<T, S> { impl<T: Output, S: Surfaces> Task for Renderer<T, S> {
fn name(&self) -> &'static str { "Renderer" } fn name(&self) -> &'static str { "Renderer" }
fn tick(&mut self, event: &Event, _bus: &mut EventBus) { fn tick(&mut self) {
match event { self.output.blank();
crate::events::Event::Tick => {
self.output.blank();
self.surfaces.render_to(&mut self.output, self.frame); self.surfaces.render_to(&mut self.output, self.frame);
self.output.commit(); self.output.commit();
self.fps.insert(1); self.fps.insert(1);
self.frame += 1; self.frame += 1;
self.fps_display.run(|| { self.fps_display.run(|| {
log::info!("FPS: {}", self.fps.measurement()); log::info!("FPS: {}", self.fps.measurement());
}); });
},
_ => self.output.on_event(event)
}
} }
} }

View File

@ -1,9 +1,7 @@
use core::fmt; use core::fmt;
use crate::events::{Event, EventBus}; pub trait Task: core::fmt::Debug {
fn tick(&mut self) {}
pub trait Task: Send {
fn tick(&mut self, event: &Event, bus: &mut EventBus) {}
fn start(&mut self) {} fn start(&mut self) {}
fn stop(&mut self) {} fn stop(&mut self) {}
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
@ -57,7 +55,7 @@ impl ScheduledTask {
} }
} }
fn tick(&mut self, event: &Event, bus: &mut EventBus) { fn tick(&mut self) {
self.state = match self.state { self.state = match self.state {
ScheduledState::Start => { ScheduledState::Start => {
log::info!("Starting task {}", self.task.name()); log::info!("Starting task {}", self.task.name());
@ -65,7 +63,7 @@ impl ScheduledTask {
ScheduledState::Running ScheduledState::Running
}, },
ScheduledState::Running => { ScheduledState::Running => {
self.task.tick(event, bus); self.task.tick();
ScheduledState::Running ScheduledState::Running
}, },
ScheduledState::Stop => { ScheduledState::Stop => {
@ -79,35 +77,25 @@ impl ScheduledTask {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct FixedSizeScheduler<const TASK_COUNT: usize> { pub struct Scheduler {
tasks: [Option<ScheduledTask>; TASK_COUNT], tasks: Vec<ScheduledTask>,
} }
impl<const TASK_COUNT: usize> FixedSizeScheduler<TASK_COUNT> { impl Scheduler {
pub fn new(tasks: [Box<dyn Task>; TASK_COUNT]) -> Self { pub fn new(tasks: Vec<Box<dyn Task>>) -> Self {
let mut scheduled = [const { None }; TASK_COUNT]; let mut scheduled = Vec::new();
let mut idx = 0;
for task in tasks { for task in tasks {
scheduled[idx] = Some(ScheduledTask::new(task)); log::info!("Scheduling task {} {:?}", task.name(), task);
idx += 1; scheduled.push(ScheduledTask::new(task));
} }
FixedSizeScheduler { Scheduler {
tasks: scheduled tasks: scheduled
} }
} }
}
impl<const TASK_COUNT: usize> Scheduler for FixedSizeScheduler<TASK_COUNT> { pub fn tick(&mut self) {
fn tick(&mut self, event: &Event, bus: &mut EventBus) { for task in &mut self.tasks {
for slot in &mut self.tasks { task.tick();
match slot {
Some(task) => task.tick(event, bus),
_ => ()
}
} }
} }
} }
pub trait Scheduler {
fn tick(&mut self, event: &Event, bus: &mut EventBus);
}