#![recursion_limit = "256"]
#![windows_subsystem = "windows"]
#![forbid(unsafe_code)]
#![deny(clippy::unwrap_used)]
#![warn(clippy::todo)]
#![warn(missing_debug_implementations)]
#![allow(clippy::too_many_arguments, clippy::type_complexity)]
mod engine_link;
mod platform;
mod remote;
mod remote_link;
use eframe::egui_wgpu::{WgpuConfiguration, WgpuSetup, WgpuSetupCreateNew};
use gled_ui::{
app::App, extension::AppExtension, link::EngineLink,
ui::window_common::default_viewport_builder,
};
use std::sync::Arc;
use wgpu::PresentMode;
#[derive(Debug, clap::Parser)]
#[command(version, about)]
struct Args {
#[arg(short = 's', long)]
show_open_font_license: bool,
#[arg(long, value_name = "SERVER")]
connect: Option<String>,
#[arg(long, value_name = "SHA256", requires = "connect")]
fingerprint: Option<String>,
}
fn init_keyring_store() {
#[cfg(target_os = "macos")]
{
let store = apple_native_keyring_store::keychain::Store::new()
.expect("Could not create Apple keyring store");
keyring_core::set_default_store(store);
}
#[cfg(target_os = "windows")]
{
let store = windows_native_keyring_store::Store::new()
.expect("Could not create Windows keyring store");
keyring_core::set_default_store(store);
}
#[cfg(target_os = "linux")]
{
let store = dbus_secret_service_keyring_store::Store::new()
.expect("Could not create DBus Secret Service keyring store");
keyring_core::set_default_store(store);
}
}
fn main() {
let args = <Args as clap::Parser>::parse();
if args.show_open_font_license {
println!("{}", gled_ui::style::OPEN_FONT_LICENSE);
return;
}
#[cfg(feature = "profiling")]
let _puffin_servers = start_profile_servers();
tracing_subscriber::fmt::init();
let remote_url = args
.connect
.as_deref()
.map(gled_server::client::websocket_url);
let engine_start = match remote_url {
Some(_) => {
gled_ui::ui::temperature::start_local_thread();
None
}
None => {
init_keyring_store();
Some(gled_core::start::services().expect("Could not start gled's services"))
}
};
#[cfg(not(debug_assertions))]
gled_ui::ui::update_check::Update::start_thread();
let mut wgpu_options = WgpuConfiguration::default();
wgpu_options.surface.present_mode = PresentMode::AutoVsync;
wgpu_options.wgpu_setup = match wgpu_options.wgpu_setup {
WgpuSetup::CreateNew(create_new) => WgpuSetup::CreateNew(WgpuSetupCreateNew {
native_adapter_selector: Some(Arc::new(|adapters, surface| {
gled_core::gpu_choice::choose(adapters, |adapter| {
surface.is_none_or(|surface| adapter.is_surface_supported(surface))
})
.ok_or_else(|| "No GPU can draw gled's window".to_owned())
})),
device_descriptor: Arc::new(|adapter| wgpu::DeviceDescriptor {
label: Some("gled window device"),
#[cfg(feature = "profiling")]
required_features: adapter.features()
& wgpu_profiler::GpuProfiler::ALL_WGPU_TIMER_FEATURES,
required_limits: adapter.limits(),
..Default::default()
}),
..create_new
}),
_ => unreachable!(),
};
let options = eframe::NativeOptions {
viewport: default_viewport_builder()
.with_inner_size([1300.0, 1024.0])
.with_drag_and_drop(true)
.with_min_inner_size([300.0, 200.0]),
wgpu_options,
dithering: false,
..Default::default()
};
eframe::run_native(
"gled",
options,
Box::new(|cc| {
gled_ui::style::apply(&cc.egui_ctx);
cc.wgpu_render_state
.as_ref()
.expect("wgpu render state is not available")
.device
.on_uncaptured_error(Arc::new(|error| {
tracing::error!("WGPU error: {:?}", error);
}));
if !gled_ui::render_state::set(
cc.wgpu_render_state
.clone()
.ok_or("wgpu render state is not available")?,
) {
return Err("the app was started twice".into());
}
let settings = cc
.storage
.and_then(|storage| eframe::get_value(storage, gled_ui::settings::STORAGE_KEY))
.unwrap_or_else(platform::previous_client_settings);
let subscriptions = gled_protocol::message::Subscriptions {
preview: true,
sound: true,
input_events: true,
midi: true,
};
let (link, extension): (Box<dyn EngineLink>, Option<Box<dyn AppExtension>>) =
match (engine_start, remote_url) {
(Some(engine_start), _) => {
let connector = engine_start
.spawn(gled_ui::wgpu_render_state().adapter)
.map_err(|err| err.to_string())?;
(
Box::new(engine_link::InProcessLink::new(connector.connect())),
Some(Box::new(remote::RemoteAccessExtension::new(connector))),
)
}
(None, Some(url)) => (
Box::new(remote_link::RemoteLink::new(
url,
args.fingerprint,
cc.egui_ctx.clone(),
)?),
None,
),
(None, None) => return Err("neither an engine nor a server".into()),
};
let connection = gled_ui::link::Connection::new(link, subscriptions);
Ok(Box::new(App::new(connection, settings, extension)))
}),
)
.expect("Could not run native");
}
#[cfg(feature = "profiling")]
struct PuffinViewerChildGuard(std::process::Child);
#[cfg(feature = "profiling")]
impl Drop for PuffinViewerChildGuard {
fn drop(&mut self) {
match self.0.kill() {
Err(e) => tracing::warn!("Could not kill puffin viewer process: {}", e),
Ok(_) => tracing::info!("Successfully killed puffin viewer process"),
}
}
}
#[cfg(feature = "profiling")]
fn start_profile_servers() -> (
puffin_http::Server,
puffin_http::Server,
PuffinViewerChildGuard,
PuffinViewerChildGuard,
) {
puffin::set_scopes_on(true);
let cpu_server = puffin_http::Server::new(&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT))
.expect("Could not start puffin server for cpu");
let gpu_server = puffin_http::Server::new_custom(
&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT + 1),
|sink| {
gled_core::runtime::PUFFIN_GPU_PROFILER
.lock()
.add_sink(sink)
},
|id| {
_ = gled_core::runtime::PUFFIN_GPU_PROFILER
.lock()
.remove_sink(id)
},
)
.expect("Could not start puffin server for gpu");
let cpu_profiler =
PuffinViewerChildGuard(std::process::Command::new("puffin_viewer").spawn().expect(
"Could not run puffin_viewer, maybe install it with: \"cargo install puffin_viewer\"",
));
let gpu_profiler = PuffinViewerChildGuard(std::process::Command::new("puffin_viewer").arg("--url").arg(format!("127.0.0.1:{}", puffin_http::DEFAULT_PORT + 1)).spawn().expect(
"Could not run puffin_viewer, maybe install it with: \"cargo install puffin_viewer\"",
));
(cpu_server, gpu_server, cpu_profiler, gpu_profiler)
}