diff --git a/Cargo.toml b/Cargo.toml index d200155..9508a4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ rand = "0.8.5" regex = "1.10.3" base64 = "0.21.7" chrono = "0.4.35" +anyhow = {version = "1.0", features = ["backtrace"]} # To make life easier: lazy_static = "1.4.0" diff --git a/README.md b/README.md index 9cf3208..edbb843 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Or with logging: - Investigate the occasionally high CPU usage on the Pi (like 300%!) - Could multiple update rates per window be useful? - CD to Pi? + - Format all debug types with `:?` when possible - Fun ideas: - Run the dashboard on a PVM, or an original iMac, eventually? diff --git a/src/dashboard_defs/clock.rs b/src/dashboard_defs/clock.rs index 5903fd2..3dc01e4 100644 --- a/src/dashboard_defs/clock.rs +++ b/src/dashboard_defs/clock.rs @@ -11,9 +11,9 @@ use crate::{ utility_types::{ vec2f::Vec2f, + generic_result::*, update_rate::UpdateRate, - dynamic_optional::DynamicOptional, - generic_result::{GenericResult, MaybeError} + dynamic_optional::DynamicOptional }, dashboard_defs::shared_window_state::SharedWindowState diff --git a/src/dashboard_defs/dashboard.rs b/src/dashboard_defs/dashboard.rs index 0e326c0..850f2e3 100644 --- a/src/dashboard_defs/dashboard.rs +++ b/src/dashboard_defs/dashboard.rs @@ -10,8 +10,8 @@ use crate::{ utility_types::{ json_utils, vec2f::Vec2f, + generic_result::*, dynamic_optional::DynamicOptional, - generic_result::{GenericResult, MaybeError}, update_rate::{UpdateRate, UpdateRateCreator} }, diff --git a/src/dashboard_defs/spinitron.rs b/src/dashboard_defs/spinitron.rs index 764405f..181a15d 100644 --- a/src/dashboard_defs/spinitron.rs +++ b/src/dashboard_defs/spinitron.rs @@ -13,8 +13,8 @@ use crate::{ utility_types::{ vec2f::Vec2f, + generic_result::*, update_rate::UpdateRate, - generic_result::MaybeError, dynamic_optional::DynamicOptional }, diff --git a/src/dashboard_defs/surprise.rs b/src/dashboard_defs/surprise.rs index edd23e9..f650285 100644 --- a/src/dashboard_defs/surprise.rs +++ b/src/dashboard_defs/surprise.rs @@ -25,10 +25,10 @@ use crate::{ }, utility_types::{ + generic_result::*, dynamic_optional::DynamicOptional, vec2f::{Vec2f, assert_in_unit_interval}, - update_rate::{UpdateRateCreator, Seconds}, - generic_result::{GenericResult, MaybeError} + update_rate::{Seconds, UpdateRateCreator} }, texture::{TexturePool, TextureCreationInfo}, @@ -36,7 +36,9 @@ use crate::{ }; /* Note: some surprises may take somewhat long to be -triggered if their update rates are relatively infrequent. */ +triggered if their update rates are relatively infrequent. +TODO: make a separate updater for just getting the artificial +triggering going (this will be the socket-polling updater). */ type NumAppearanceSteps = u16; type SurpriseAppearanceChance = f64; // 0 to 1 @@ -109,8 +111,6 @@ pub fn make_surprise_window( ////////// The core updater function that runs once every N milliseconds for each surprise - // TODO: make a separate updater for just getting the artificial triggering going (this will be the socket-polling updater) - fn updater_fn(params: WindowUpdaterParams) -> MaybeError { let surprise_info = params.window.get_state_mut::(); let rand_generator = &mut params.shared_window_state.get_mut::().rand_generator; @@ -184,7 +184,7 @@ pub fn make_surprise_window( let surprise_path_set: HashSet = surprise_paths.iter().map(Rc::clone).collect(); if surprise_path_set.len() != surprise_creation_info.len() { - return Err("There are duplicate paths in the set of surprises".into()); + return error_msg!("There are duplicate paths in the set of surprises"); } ////////// Setting up the shared surprise info that can be triggered via signals @@ -197,12 +197,12 @@ pub fn make_surprise_window( Ok(listener) => listener, Err(err) => { - return Err(format!( + return error_msg!( "Could not create a surprise stream listener. \ Perhaps the socket at '{artificial_triggering_socket_path}' is already in use, or \ maybe it was still around from a crash? \ Official error: '{err}'." - ).into()); + ); } }; diff --git a/src/dashboard_defs/twilio.rs b/src/dashboard_defs/twilio.rs index 604f178..b609920 100644 --- a/src/dashboard_defs/twilio.rs +++ b/src/dashboard_defs/twilio.rs @@ -5,9 +5,10 @@ use crate::{ request, utility_types::{ + vec2f::Vec2f, + generic_result::*, + update_rate::UpdateRate, dynamic_optional::DynamicOptional, - update_rate::UpdateRate, vec2f::Vec2f, - generic_result::{GenericResult, MaybeError}, thread_task::{ContinuallyUpdated, Updatable} }, @@ -652,7 +653,7 @@ pub fn make_twilio_window( assert!(phone_numbers.len() == 1); - let number = phone_numbers[0]["phone_number"].as_str().ok_or("Expected the phone number to be a string!")?; + let number = phone_numbers[0]["phone_number"].as_str().context("Expected the phone number to be a string!")?; let formatted_number = TwilioStateData::format_phone_number(number, "Messages to ", ":", ""); ////////// diff --git a/src/dashboard_defs/updatable_text_pattern.rs b/src/dashboard_defs/updatable_text_pattern.rs index 0f6b500..3766e41 100644 --- a/src/dashboard_defs/updatable_text_pattern.rs +++ b/src/dashboard_defs/updatable_text_pattern.rs @@ -18,8 +18,8 @@ use crate::{ utility_types::{ vec2f::Vec2f, + generic_result::*, update_rate::UpdateRate, - generic_result::MaybeError, dynamic_optional::DynamicOptional }, diff --git a/src/dashboard_defs/weather.rs b/src/dashboard_defs/weather.rs index fda693d..3005ea2 100644 --- a/src/dashboard_defs/weather.rs +++ b/src/dashboard_defs/weather.rs @@ -13,7 +13,7 @@ use crate::{ utility_types::{ vec2f::Vec2f, - generic_result::MaybeError, + generic_result::*, dynamic_optional::DynamicOptional, update_rate::{UpdateRateCreator, Seconds} }, diff --git a/src/main.rs b/src/main.rs index 790b4e1..bf4a1d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,9 +72,11 @@ fn main() -> utility_types::generic_result::MaybeError { ////////// - let sdl_context = sdl2::init()?; - let sdl_video_subsystem = sdl_context.video()?; - let mut sdl_event_pump = sdl_context.event_pump()?; + use crate::utility_types::generic_result::ToGenericError; + + let sdl_context = sdl2::init().to_generic()?; + let sdl_video_subsystem = sdl_context.video().to_generic()?; + let mut sdl_event_pump = sdl_context.event_pump().to_generic()?; use sdl2::video::WindowBuilder; @@ -94,7 +96,7 @@ fn main() -> utility_types::generic_result::MaybeError { ), ScreenOption::Fullscreen => { - let mode = sdl_video_subsystem.display_mode(0, 0)?; + let mode = sdl_video_subsystem.display_mode(0, 0).to_generic()?; build_window( mode.w as u32, mode.h as u32, @@ -113,7 +115,7 @@ fn main() -> utility_types::generic_result::MaybeError { } use sdl2::image::LoadSurface; - sdl_window.set_icon(sdl2::surface::Surface::from_file(app_config.icon_path)?); + sdl_window.set_icon(sdl2::surface::Surface::from_file(app_config.icon_path).to_generic()?); ////////// @@ -141,13 +143,13 @@ fn main() -> utility_types::generic_result::MaybeError { ////////// - let mut sdl_timer = sdl_context.timer()?; + let mut sdl_timer = sdl_context.timer().to_generic()?; let sdl_performance_frequency = sdl_timer.performance_frequency(); let sdl_ttf_context = sdl2::ttf::init()?; let texture_creator = sdl_canvas.texture_creator(); - let fps = sdl_video_subsystem.current_display_mode(0)?.refresh_rate as u32; + let fps = sdl_video_subsystem.current_display_mode(0).to_generic()?.refresh_rate as u32; let sdl_renderer_info = sdl_canvas.info(); let max_texture_size = (sdl_renderer_info.max_texture_width, sdl_renderer_info.max_texture_height); @@ -179,7 +181,8 @@ fn main() -> utility_types::generic_result::MaybeError { let mut pausing_window = false; // let mut initial_num_textures_in_pool = None; - log::info!("Finished setting up window. Canvas size: {:?}. Renderer info: {:?}.", rendering_params.sdl_canvas.output_size()?, sdl_renderer_info); + log::info!("Finished setting up window. Canvas size: {:?}. Renderer info: {:?}.", + rendering_params.sdl_canvas.output_size().to_generic()?, sdl_renderer_info); 'running: loop { for sdl_event in sdl_event_pump.poll_iter() { diff --git a/src/request.rs b/src/request.rs index e7fc7f9..e8008ff 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,5 +1,5 @@ use std::borrow::Cow; -use crate::utility_types::generic_result::GenericResult; +use crate::utility_types::generic_result::*; pub fn build_url(base_url: &str, path_params: &[Cow], query_params: &[(&str, Cow)]) -> String { @@ -37,10 +37,10 @@ pub fn get_with_maybe_header(url: &str, maybe_header: Option<(&str, &str)>) -> G Ok(response) } else { - Err(format!( + error_msg!( "Response status code for URL '{url}' was not '{EXPECTED_STATUS_CODE}', \ but '{}', with this reason: '{}'", response.status_code, response.reason_phrase - ).into()) + ) } } @@ -51,5 +51,5 @@ pub fn get(url: &str) -> GenericResult { // This function is monadic! pub fn as_type serde::Deserialize<'de>>(response: GenericResult) -> GenericResult { let unpacked_response = response?; - Ok(serde_json::from_str(unpacked_response.as_str()?)?) + serde_json::from_str(unpacked_response.as_str()?).to_generic() } diff --git a/src/spinitron/api.rs b/src/spinitron/api.rs index dcfae92..d1d95bd 100644 --- a/src/spinitron/api.rs +++ b/src/spinitron/api.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use crate::{ request, - utility_types::generic_result::GenericResult, + utility_types::generic_result::*, spinitron::{ wrapper_types::MaybeSpinitronModelId, @@ -24,11 +24,11 @@ fn get_json_from_spinitron_request( ////////// Getting the API endpoint let full_typename = std::any::type_name::(); - let last_colon_ind = full_typename.rfind(':').ok_or("Expected a colon in the model typename")?; + let last_colon_ind = full_typename.rfind(':').context("Expected a colon in the model typename")?; let typename = &full_typename[last_colon_ind + 1..]; let mut typename_chars = typename.chars(); - let first_char = typename_chars.next().ok_or("The typename has no chars in it, which is impossible")?; + let first_char = typename_chars.next().context("The typename has no chars in it, which is impossible")?; let api_endpoint = format!("{}{}s", first_char.to_lowercase(), &typename[1..]); ////////// Checking endpoint validity @@ -36,7 +36,7 @@ fn get_json_from_spinitron_request( const VALID_ENDPOINTS: [&str; NUM_SPINITRON_MODEL_TYPES] = ["spins", "playlists", "personas", "shows"]; if !VALID_ENDPOINTS.contains(&api_endpoint.as_str()) { - return Err(format!("Invalid Spinitron API endpoint '{api_endpoint}'").into()); + return error_msg!("Invalid Spinitron API endpoint '{api_endpoint}'"); } ////////// Limiting the requested fields by what exists within the given model type @@ -44,7 +44,7 @@ fn get_json_from_spinitron_request( let default_model_as_serde_value = serde_json::to_value(T::default())?; let default_model_as_serde_obj = default_model_as_serde_value.as_object() - .ok_or("Expected JSON to be an object for the default Spinitron model")?; + .context("Expected JSON to be an object for the default Spinitron model")?; // TODO: stop the `collect` allocation below let fields: Vec<&str> = default_model_as_serde_obj.iter().map(|(key, _)| key.as_str()).collect(); @@ -77,8 +77,8 @@ fn get_json_from_spinitron_request( } fn get_vec_from_spinitron_json(json: &serde_json::Value) -> GenericResult> { - let parsed_json_as_object = json.as_object().ok_or("Expected JSON to be an object")?; - Ok(serde_json::from_value(parsed_json_as_object["items"].clone())?) + let parsed_json_as_object = json.as_object().context("Expected JSON to be an object")?; + serde_json::from_value(parsed_json_as_object["items"].clone()).to_generic() } // This is a singular request @@ -87,7 +87,7 @@ fn do_request(api_key: &str, possible_model_id: Mayb if possible_model_id.is_some() { // If requesting a via model id, just a raw item will be returned - Ok(serde_json::from_value(response_json)?) + serde_json::from_value(response_json).to_generic() } else { diff --git a/src/spinitron/model.rs b/src/spinitron/model.rs index 05f2434..c7ef749 100644 --- a/src/spinitron/model.rs +++ b/src/spinitron/model.rs @@ -7,7 +7,7 @@ use serde::{Serialize, Deserialize}; use crate::{ texture::TextureCreationInfo, - utility_types::generic_result::GenericResult, + utility_types::generic_result::*, spinitron::{ wrapper_types::*, diff --git a/src/spinitron/state.rs b/src/spinitron/state.rs index e706ff7..c316c93 100644 --- a/src/spinitron/state.rs +++ b/src/spinitron/state.rs @@ -7,7 +7,7 @@ use crate::{ texture::TextureCreationInfo, utility_types::{ - generic_result::{GenericResult, MaybeError}, + generic_result::*, thread_task::{Updatable, ContinuallyUpdated} }, @@ -135,7 +135,7 @@ impl SpinitronStateData { and precaching anything from disk in byte form as well. */ match info.as_ref() { TextureCreationInfo::Path(path) => - Ok(std::fs::read(path as &str)?), + std::fs::read(path as &str).to_generic(), TextureCreationInfo::Url(url) => Ok(request::get(url)?.as_bytes().to_vec()), diff --git a/src/texture.rs b/src/texture.rs index cb8425a..9a83a80 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -13,13 +13,8 @@ use sdl2::{ use crate::{ request, - window_tree::{CanvasSDL, ColorSDL}, - - utility_types::{ - vec2f::assert_in_unit_interval, - generic_result::{GenericResult, MaybeError} - } + utility_types::{generic_result::*, vec2f::assert_in_unit_interval} }; ////////// @@ -302,13 +297,12 @@ impl<'a> TexturePool<'a> { let possible_text_metadata = self.text_metadata.get(handle); if possible_text_metadata.is_none() { - canvas.copy(texture, None, screen_dest)?; - return Ok(()); + return canvas.copy(texture, None, screen_dest).to_generic(); } ////////// - let text_metadata = possible_text_metadata.ok_or("Expected text metadata")?; + let text_metadata = possible_text_metadata.context("Expected text metadata")?; let texture_size = text_metadata.size; // TODO: compute the time since the unix epoch outside this fn, somehow (or, use the SDL timer) @@ -337,8 +331,7 @@ impl<'a> TexturePool<'a> { ); if !should_wrap { - canvas.copy(texture, texture_src, screen_dest)?; - return Ok(()); + return canvas.copy(texture, texture_src, screen_dest).to_generic(); } ////////// @@ -347,10 +340,10 @@ impl<'a> TexturePool<'a> { texture_src, screen_dest, texture_size, &text_metadata.text ); - canvas.copy(texture, texture_src, right_screen_dest)?; + canvas.copy(texture, texture_src, right_screen_dest).to_generic()?; if let Some((left_texture_src, left_screen_dest)) = possible_left_rects { - canvas.copy(texture, left_texture_src, left_screen_dest)?; + canvas.copy(texture, left_texture_src, left_screen_dest).to_generic()?; } Ok(()) @@ -610,15 +603,15 @@ impl<'a> TexturePool<'a> { let mut joined_surface = Surface::new( total_surface_width.max(text_display_info.pixel_area.0), pixel_height, subsurfaces[0].pixel_format_enum() - )?; + ).to_generic()?; let mut dest_rect = Rect::new(0, 0, 1, 1); for mut subsurface in subsurfaces { - subsurface.set_blend_mode(render::BlendMode::None)?; + subsurface.set_blend_mode(render::BlendMode::None).to_generic()?; (dest_rect.w, dest_rect.h) = (subsurface.width() as i32, subsurface.height() as i32); - subsurface.blit(None, &mut joined_surface, dest_rect)?; + subsurface.blit(None, &mut joined_surface, dest_rect).to_generic()?; dest_rect.x += dest_rect.w; } @@ -660,9 +653,9 @@ impl<'a> TexturePool<'a> { let mut blank_surface = font_pair.0.render(Self::BLANK_TEXT_DEFAULT).blended(text_display_info.color)?; Ok(if blank_surface.width() < max_width || blank_surface.height() != needed_height { - let mut corrected = Surface::new(max_width, needed_height, blank_surface.pixel_format_enum())?; - blank_surface.set_blend_mode(render::BlendMode::None)?; - blank_surface.blit(None, &mut corrected, None)?; + let mut corrected = Surface::new(max_width, needed_height, blank_surface.pixel_format_enum()).to_generic()?; + blank_surface.set_blend_mode(render::BlendMode::None).to_generic()?; + blank_surface.blit(None, &mut corrected, None).to_generic()?; corrected } else { @@ -677,7 +670,7 @@ impl<'a> TexturePool<'a> { ////////// fn make_raw_texture(&mut self, creation_info: &TextureCreationInfo) -> GenericResult> { - Ok(match creation_info { + match creation_info { // Use this whenever possible (whenever you can preload data into byte form)! TextureCreationInfo::RawBytes(bytes) => self.texture_creator.load_texture_bytes(bytes), @@ -698,6 +691,6 @@ impl<'a> TexturePool<'a> { Ok(self.texture_creator.create_texture_from_surface(surface)?) } - }?) + }.to_generic() } } diff --git a/src/utility_types/generic_result.rs b/src/utility_types/generic_result.rs index e700dee..d565ac3 100644 --- a/src/utility_types/generic_result.rs +++ b/src/utility_types/generic_result.rs @@ -1,2 +1,24 @@ +pub use anyhow::Context; // For `.context` + pub type MaybeError = GenericResult<()>; -pub type GenericResult = Result>; +pub type GenericResult = anyhow::Result; + +macro_rules! error_msg { + ($fmt:expr $(, $($arg:tt)*)?) => { + Err(anyhow::anyhow!($fmt $(, $($arg)*)?)) + }; +} + +pub(crate) use error_msg; + +pub trait ToGenericError { + fn to_generic(self) -> GenericResult; +} + +impl ToGenericError for Result +where E: std::fmt::Debug + std::fmt::Display + Send + Sync + 'static { + + fn to_generic(self) -> anyhow::Result { + self.map_err(anyhow::Error::msg) + } +} diff --git a/src/utility_types/json_utils.rs b/src/utility_types/json_utils.rs index be76b66..68cea47 100644 --- a/src/utility_types/json_utils.rs +++ b/src/utility_types/json_utils.rs @@ -1,15 +1,15 @@ // TODO: put more in here -use crate::utility_types::generic_result::GenericResult; +use crate::utility_types::generic_result::*; pub fn load_from_file serde::Deserialize<'de>>(path: &str) -> GenericResult { let file_contents = match std::fs::read_to_string(path) { Ok(contents) => Ok(contents), - Err(err) => Err(format!( + Err(err) => error_msg!( "The API key file at path '{path}' could not be found. Official error: '{err}'." - )) + ) }?; - Ok(serde_json::from_str(&file_contents)?) + serde_json::from_str(&file_contents).to_generic() } diff --git a/src/utility_types/thread_task.rs b/src/utility_types/thread_task.rs index 3551a82..e9eaf17 100644 --- a/src/utility_types/thread_task.rs +++ b/src/utility_types/thread_task.rs @@ -1,7 +1,7 @@ use std::thread; use std::sync::mpsc; -use crate::utility_types::generic_result::{GenericResult, MaybeError}; +use crate::utility_types::generic_result::*; ////////// @@ -16,7 +16,7 @@ in short bursts when you call `update` (or work with coroutines somehow)? */ pub trait Updatable: Clone + Send { - type Param: Clone + Send; // TODO: remove this param type idea here + type Param: Clone + Send + Sync; fn update(&mut self, param: &Self::Param) -> MaybeError; } @@ -77,7 +77,7 @@ impl ContinuallyUpdated { // This unblocks the param receiver and starts a new update iteration with a new param fn run_new_update_itetation(&self, param: &T::Param) -> MaybeError { - Ok(self.param_sender.send(param.clone())?) + self.param_sender.send(param.clone()).to_generic() } // This returns false if a thread failed to complete its operation. diff --git a/src/window_tree.rs b/src/window_tree.rs index 9862157..77d3b65 100644 --- a/src/window_tree.rs +++ b/src/window_tree.rs @@ -3,9 +3,9 @@ use sdl2::{self, rect::Rect}; use crate::{ utility_types::{ vec2f::Vec2f, + generic_result::*, dynamic_optional::DynamicOptional, - update_rate::{UpdateRate, FrameCounter}, - generic_result::{GenericResult, MaybeError} + update_rate::{UpdateRate, FrameCounter} }, texture::{TexturePool, TextureHandle, TextureCreationInfo} @@ -234,7 +234,7 @@ impl Window { ////////// These are the window rendering functions (both public and private) pub fn render(&mut self, rendering_params: &mut PerFrameConstantRenderingParams) -> MaybeError { - let output_size = rendering_params.sdl_canvas.output_size()?; + let output_size = rendering_params.sdl_canvas.output_size().to_generic()?; let sdl_window_bounds = FRect {x: 0.0, y: 0.0, width: output_size.0 as f32, height: output_size.1 as f32}; self.inner_render(rendering_params, sdl_window_bounds) } @@ -307,7 +307,7 @@ impl Window { if let Some(border_color) = &self.maybe_border_color { possibly_draw_with_transparency(border_color, &mut rendering_params.sdl_canvas, - |canvas| Ok(canvas.draw_rect(uncorrected_screen_dest.into())?))?; + |canvas| canvas.draw_rect(uncorrected_screen_dest.into()).to_generic())?; } return Ok(()); @@ -331,7 +331,7 @@ impl Window { WindowContents::Color(color) => possibly_draw_with_transparency( color, sdl_canvas, |canvas| - Ok(canvas.fill_rect::(uncorrected_screen_dest.into())?) + canvas.fill_rect::(uncorrected_screen_dest.into()).to_generic() )?, WindowContents::Lines(line_series) => { @@ -343,10 +343,9 @@ impl Window { PointSDL::new(xy.0 as i32, xy.1 as i32) }).collect(); - possibly_draw_with_transparency(&series.0, sdl_canvas, |canvas| { - canvas.draw_lines(&*converted_series)?; - Ok(()) - })?; + possibly_draw_with_transparency(&series.0, sdl_canvas, |canvas| + canvas.draw_lines(&*converted_series).to_generic() + )?; } },