Skip to content

Commit

Permalink
Further cleanup, tracing improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
chipnertkj committed May 4, 2024
1 parent 491832f commit 97b33f0
Show file tree
Hide file tree
Showing 36 changed files with 605 additions and 431 deletions.
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ derive_more = { version = "0.99", features = ["nightly"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3" }
tracing-web = "0.1"
tracing-appender = "0.2"
## error handling
color-eyre = "0.6"
## multithreading
## concurrency
futures = { version = "0.3" }
tokio = { version = "1.37", features = [
"macros",
"fs",
Expand Down
3 changes: 2 additions & 1 deletion backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ chipbox-backend-lib = { workspace = true }
## tracing
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
## multithreading
tracing-appender = { workspace = true }
## concurrency
tokio = { workspace = true }
## tauri
tauri = { workspace = true }
Expand Down
120 changes: 120 additions & 0 deletions backend/lib/src/app_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use self::app_state::AppState;
use crate::{common, AppThread, ThreadMsg};
use common::app::{BackendMsg, BackendResponse, FrontendMsg, FrontendRequest};

mod app_state;

/// All data required for managing the application.
///
/// Includes its state and the handle to the Tauri application.
pub struct AppData {
pub state: AppState,
tauri_app: tauri::AppHandle,
}

impl AppData {
/// Create new app data.
pub fn new(tauri_app: tauri::AppHandle) -> Self {
Self {
state: Default::default(),
tauri_app,
}
}

/// Associated Tauri application handle.
pub fn tauri_app(&self) -> &tauri::AppHandle {
&self.tauri_app
}

/// Handles a message from the parent thread.
/// Returns `true` if the app should quit.
pub fn handle_msg(&mut self, msg: ThreadMsg) -> bool {
match msg {
// Handle frontend message.
ThreadMsg::Frontend(msg) => self.handle_frontend_msg(msg),
// Quit.
ThreadMsg::Exit => return true,
};
false
}

/// Handles messages from the frontend.
fn handle_frontend_msg(&mut self, msg: FrontendMsg) {
match msg {
FrontendMsg::Request(request) => {
self.handle_frontend_request(request)
}
}
}

/// Handles the request and sends a response to the frontend.
fn handle_frontend_request(&mut self, request: FrontendRequest) {
// Handle request and prepare response.
let response = match request {
FrontendRequest::BackendAppState => {
self.backend_app_state_response()
}
FrontendRequest::Settings => self.backend_settings_response(),
FrontendRequest::UseDefaultSettings => {
self.use_default_settings_response()
}
};

// Send response.
AppThread::send_message(
&self.tauri_app,
BackendMsg::Response(response),
);
}

/// Reply with current state.
fn backend_app_state_response(&mut self) -> BackendResponse {
// Get state and convert it to `BackendAppState`.
let app_state = (&self.state).into();

// Prepare response.
BackendResponse::BackendAppState(app_state)
}

/// Reply with current settings.
fn backend_settings_response(&mut self) -> BackendResponse {
// Get settings.
let settings_opt = match self.state {
AppState::Idle { ref settings } => Some(settings.clone()),
_ => None,
};

// Prepare response.
BackendResponse::Settings(settings_opt)
}

/// Set default settings and reply with a copy.
pub fn use_default_settings_response(&mut self) -> BackendResponse {
// Apply and return settings.
match self.state {
// Fail gracefully if in the middle of reading settings.
AppState::ReadingSettings => {
tracing::error!("Frontend attempted to set settings to default while backend was still reading config.");
}
// Change state to idle if awaiting config.
AppState::AwaitConfig { .. } => {
self.state = AppState::Idle {
settings: Default::default(),
};
}
// Modify if idle.
AppState::Idle { ref mut settings } => {
*settings = Default::default();
}
// Modify if editing a project.
AppState::Edit {
ref mut settings, ..
} => {
*settings = Default::default();
}
}

// Prepare response.
BackendResponse::UseDefaultSettings(Default::default())
}
}
49 changes: 49 additions & 0 deletions backend/lib/src/app_data/app_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
pub use edit_state::EditState;
pub mod edit_state;

use crate::common;
use common::app::{AwaitConfigReason, BackendAppState};
use common::Settings;

#[derive(Default)]
/// Backend application state.
pub enum AppState {
#[default]
/// Backend is in the process of reading user config.
ReadingSettings,
/// Settings read has been attempted, but no valid configuration was found.
AwaitConfig { reason: AwaitConfigReason },
/// Backend is awaiting commands from the frontend.
Idle { settings: Settings },
/// Backend is ready to edit a project.
Edit {
inner: Box<EditState>,
settings: Settings,
},
}

/// Initialize app state from an optional configuration.
impl From<Option<Settings>> for AppState {
fn from(settings_opt: Option<Settings>) -> Self {
match settings_opt {
Some(settings) => AppState::Idle { settings },
None => AppState::AwaitConfig {
reason: AwaitConfigReason::NoConfig,
},
}
}
}

/// Convert app state to a minimal, serializable version.
impl From<&AppState> for BackendAppState {
fn from(app: &AppState) -> Self {
match app {
AppState::ReadingSettings => BackendAppState::ReadingSettings,
AppState::AwaitConfig { ref reason } => {
BackendAppState::AwaitConfig { reason: *reason }
}
AppState::Idle { .. } => BackendAppState::Idle,
AppState::Edit { .. } => BackendAppState::Editor,
}
}
}
30 changes: 30 additions & 0 deletions backend/lib/src/app_data/app_state/edit_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
pub mod audio_engine;

use self::audio_engine::AudioEngine;
use crate::common;
use common::{Project, Settings};

pub struct EditState {
pub project: Project,
pub audio_engine: AudioEngine,
}

impl EditState {
/// # Errors
/// Failure if encountered problems while creating the audio engine.
pub fn create_project(
settings: &Settings,
name: String,
) -> Result<Self, audio_engine::Error> {
let audio_engine = AudioEngine::from_settings(&settings.audio_engine)?;
let editor = Self {
audio_engine,
project: Project::new(name),
};
Ok(editor)
}

pub fn play_stream(&mut self) -> Result<(), cpal::PlayStreamError> {
self.audio_engine.play()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ pub enum Error {
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Settings(e) => Some(e),
Self::HostUnavailable(e) => Some(e),
Self::Settings(err) => Some(err),
Self::HostUnavailable(err) => Some(err),
}
}
}

impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Settings(e) => e.fmt(f),
Self::HostUnavailable(e) => e.fmt(f),
Self::Settings(err) => err.fmt(f),
Self::HostUnavailable(err) => err.fmt(f),
}
}
}
Expand All @@ -64,31 +64,31 @@ impl AudioEngine {
pub fn from_settings(settings: &Settings) -> Result<Self, Error> {
// Read HostId and open Host.
let host_id = HostId::try_from(&settings.host)
.map_err(|e| Error::Settings(SettingsError::HostIdParse(e)))?;
.map_err(|err| Error::Settings(SettingsError::HostIdParse(err)))?;
let host = cpal::host_from_id(host_id.into())
.map_err(Error::HostUnavailable)?;

// Open output device.
let output_device = Self::output_device(&host, &settings.output_device)
.map_err(|e| {
.map_err(|err| {
Error::Settings(SettingsError::InvalidStreamConfig(
stream_config::Error::Device(e),
stream_config::Error::Device(err),
))
})?;

// Get output stream config.
let output_stream_config =
StreamConfig::from_settings(&settings.output_stream_config)
.map_err(|e| {
Error::Settings(SettingsError::StreamConfigParse(e))
.map_err(|err| {
Error::Settings(SettingsError::StreamConfigParse(err))
})?;
let supported_output_stream_config =
Self::supported_output_stream_config(
&output_device,
&output_stream_config,
)
.map_err(|e| {
Error::Settings(SettingsError::InvalidStreamConfig(e))
.map_err(|err| {
Error::Settings(SettingsError::InvalidStreamConfig(err))
})?;

// Calculate frame count in buffer.
Expand Down Expand Up @@ -138,7 +138,9 @@ impl AudioEngine {
&supported_output_stream_config,
consumer,
)
.map_err(|e| Error::Settings(SettingsError::InvalidStreamConfig(e)))?;
.map_err(|err| {
Error::Settings(SettingsError::InvalidStreamConfig(err))
})?;
let output_stream_handle = Self::add_thread_local_stream(output_stream);

// Construct.
Expand Down Expand Up @@ -250,9 +252,9 @@ impl AudioEngine {
// Get default output device config.
StreamConfig::Default => output_device
.default_output_config()
.map_err(|e| {
.map_err(|err| {
stream_config::Error::Device(device::Error::Other(
Box::new(e),
Box::new(err),
))
})?,
// Read custom output stream config.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ impl std::fmt::Display for Error {
"unable to find a default device or no default device is set"
),
Error::NoMatch => write!(f, "no matching device"),
Error::Disconnected(e) => {
write!(f, "device was disconnected: {e}")
Error::Disconnected(err) => {
write!(f, "device was disconnected: {err}")
}
Error::Other(e) => write!(f, "{e}"),
Error::Other(err) => write!(f, "{err}"),
}
}
}
Expand All @@ -27,8 +27,8 @@ impl std::error::Error for Error {
match self {
Error::NoDefault => None,
Error::NoMatch => None,
Error::Disconnected(e) => Some(e.as_ref()),
Error::Other(e) => Some(e.as_ref()),
Error::Disconnected(err) => Some(err.as_ref()),
Error::Other(err) => Some(err.as_ref()),
}
}
}
Loading

0 comments on commit 97b33f0

Please sign in to comment.