#![cfg_attr(
all(not(debug_assertions), not(feature = "windows-console")),
windows_subsystem = "windows"
)]
#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]
use std::sync::Arc;
use flume::SendError;
use hyperchad::{actions::logic::Value, app::AppBuilder, renderer::Color};
use moosicbox_app_native::{
PROFILE, RENDERER, STATE, STATE_LOCK, actions::handle_action, init_app_state,
};
use moosicbox_app_native_ui::Action;
use switchy_env::{var_parse_opt, var_parse_or};
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
if cfg!(feature = "profiling-tracing") {
} else {
#[allow(unused_mut)]
let mut layers = vec![];
#[cfg(feature = "console-subscriber")]
if matches!(
switchy_env::var("TOKIO_CONSOLE").as_deref(),
Ok("1" | "true")
) {
use moosicbox_log_runtime::DynLayer;
layers.push(Box::new(console_subscriber::spawn()) as DynLayer);
}
#[cfg(target_os = "android")]
let filename = None;
#[cfg(not(target_os = "android"))]
let filename = Some("moosicbox_app_native.log");
let paths =
moosicbox_log_runtime::resolve_paths(&moosicbox_log_runtime::LogRuntimePathsConfig {
app_name: "moosicbox",
state_dir_env: "MOOSICBOX_STATE_DIR",
log_dir_env: "MOOSICBOX_LOG_DIR",
});
let mut log_config = moosicbox_log_runtime::init::InitConfig::new(&paths);
log_config.source_mode = moosicbox_log_runtime::init::SourceMode::Both;
if let Some(filename) = filename {
log_config.sinks.file = Some(moosicbox_log_runtime::init::FileSinkConfig {
mode: moosicbox_log_runtime::init::FileMode::Exact(filename),
});
}
log_config.extra_layers = layers;
let _log_handle =
moosicbox_log_runtime::init::init(log_config).expect("Failed to initialize logging");
}
#[cfg(all(feature = "html", feature = "_canvas"))]
moosicbox_app_native::visualization::disable_interval();
let threads = var_parse_or("MAX_THREADS", 64usize);
log::debug!("Running with {threads} max blocking threads");
let runtime = switchy::unsync::runtime::Builder::new()
.max_blocking_threads(u16::try_from(threads).unwrap())
.build()
.unwrap();
let runtime = Arc::new(runtime);
let router = moosicbox_app_native::init();
let (action_tx, action_rx) = flume::unbounded();
let width = var_parse_opt::<f32>("WINDOW_WIDTH")
.unwrap_or(None)
.unwrap_or(1000.0);
let height = var_parse_opt::<f32>("WINDOW_HEIGHT")
.unwrap_or(None)
.unwrap_or(600.0);
let mut app = AppBuilder::new()
.with_title("MoosicBox".to_string())
.with_description("A music app for cows".to_string())
.with_router(router)
.with_runtime_handle(runtime.handle())
.with_background(Color::from_hex("#181a1b"))
.with_action_handler(move |x, value| {
Ok::<_, Box<SendError<(Action, Option<Value>)>>>(match Action::try_from(x) {
Ok(action) => {
action_tx.send((action, value.cloned())).map_err(Box::new)?;
true
}
Err(e) => {
log::error!("Failed to handle action: {e:?}");
false
}
})
})
.with_size(width, height);
#[cfg(any(feature = "egui", feature = "fltk"))]
app.initial_route("/");
#[cfg(feature = "_canvas")]
moosicbox_app_native::visualization::set_dimensions(
width,
f32::from(moosicbox_app_native_ui::VIZ_HEIGHT),
);
#[cfg(feature = "assets")]
for asset in moosicbox_app_native::assets::ASSETS.iter().cloned() {
log::trace!("app_native: adding static asset route: {asset:?}");
app = app.with_static_asset_route_result(asset).unwrap();
}
let state = runtime
.block_on(async move { init_app_state(moosicbox_app_state::AppState::new()).await })
.unwrap();
STATE_LOCK.set(state).unwrap();
runtime.spawn(async move {
while let Ok((action, value)) = action_rx.recv_async().await {
if let Err(e) = handle_action(action, value).await {
log::error!("Failed to handle action: {e:?}");
}
}
});
#[cfg(feature = "bundled")]
let (join_app_server, app_server_handle) = {
use moosicbox_app_native_bundled::service::Commander as _;
log::debug!("Starting app server");
let context = moosicbox_app_native_bundled::Context::new(&runtime.handle());
let server = moosicbox_app_native_bundled::service::Service::new(context);
let app_server_handle = server.handle();
let (tx, rx) = switchy::unsync::sync::oneshot::channel();
let join_app_server = server.start_on(&runtime.handle());
app_server_handle
.send_command(moosicbox_app_native_bundled::Command::WaitForStartup { sender: tx })
.expect("Failed to send WaitForStartup command");
log::debug!("Waiting for app server to start");
runtime.block_on(rx).expect("Failed to start app server");
log::debug!("App server started");
(join_app_server, app_server_handle)
};
if let (Some(x), Some(y)) = (
var_parse_opt::<i32>("WINDOW_X").unwrap_or(None),
var_parse_opt::<i32>("WINDOW_Y").unwrap_or(None),
) {
app = app.with_position(x, y);
}
log::debug!("app_native: setting up routes");
log::debug!("app_native: creating app");
let app = app.build_default()?;
moosicbox_assert::assert_or_panic!(
RENDERER.set(Box::new(app.renderer.clone())).is_ok(),
"Already set RENDERER"
);
runtime.spawn(async move {
let api_url = STATE
.get_current_connection()
.await
.unwrap()
.map(|x| x.api_url);
let connection_name = STATE.get_connection_name().await.unwrap();
let connection_id = STATE.get_or_init_connection_id().await.unwrap();
STATE
.set_state(moosicbox_app_state::UpdateAppState {
connection_id: Some(Some(connection_id)),
connection_name: Some(connection_name),
api_url: Some(api_url),
profile: Some(Some(PROFILE.to_string())),
..Default::default()
})
.await?;
Ok::<_, moosicbox_app_state::AppStateError>(())
});
log::debug!("app_native: running");
app.run()?;
#[cfg(feature = "bundled")]
{
use moosicbox_app_native_bundled::service::Commander as _;
log::debug!("Shutting down app server..");
if let Err(e) = app_server_handle.shutdown() {
moosicbox_assert::die_or_error!("AppServer failed to shutdown: {e:?}");
}
log::debug!("Joining app server...");
match runtime.block_on(join_app_server) {
Err(e) => {
moosicbox_assert::die_or_error!("Failed to join app server: {e:?}");
}
Ok(Err(e)) => {
moosicbox_assert::die_or_error!("Failed to join app server: {e:?}");
}
_ => {}
}
}
Ok(())
}