mod board;
mod failure;
mod toast;
use concinnity_cook::compile::program::Diagnostic;
use std::time::Duration;
pub(crate) use board::{Latest, ReloadReports, ReportBoard};
pub(crate) use failure::ReloadFailure;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum SubjectKind {
Shader,
SdfVolume,
}
impl SubjectKind {
pub(crate) fn label(self) -> &'static str {
match self {
SubjectKind::Shader => "Shader",
SubjectKind::SdfVolume => "SdfVolume",
}
}
fn plural(self) -> &'static str {
match self {
SubjectKind::Shader => "Shaders",
SubjectKind::SdfVolume => "SdfVolumes",
}
}
fn rebuilt(self) -> &'static str {
match self {
SubjectKind::Shader => "pipeline",
SubjectKind::SdfVolume => "pipelines",
}
}
fn not_resident(self) -> &'static str {
match self {
SubjectKind::Shader => "its scene is not loaded, so the edit installs when it loads",
SubjectKind::SdfVolume => {
"it has no live pipeline, so the edit applies when the world is next built"
}
}
}
fn applies_when(self) -> &'static str {
match self {
SubjectKind::Shader => "applies when its scene loads",
SubjectKind::SdfVolume => "applies when the world is next built",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct ReloadSubject {
pub kind: SubjectKind,
pub name: String,
}
impl ReloadSubject {
pub(crate) fn shader(name: &str) -> Self {
Self {
kind: SubjectKind::Shader,
name: name.to_string(),
}
}
pub(crate) fn sdf_volume(name: &str) -> Self {
Self {
kind: SubjectKind::SdfVolume,
name: name.to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ReloadOutcome {
Swapped {
frame_time: Duration,
warnings: Vec<Diagnostic>,
},
AppliesOnLoad {
warnings: Vec<Diagnostic>,
},
Failed(ReloadFailure),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ReloadReport {
pub subject: ReloadSubject,
pub outcome: ReloadOutcome,
}
pub(crate) fn report(reports: &[ReloadReport], notify: Option<&crate::editor::notify::Notifier>) {
for report in reports {
log(report);
}
let Some(n) = notify else {
return;
};
for toast in toast::toasts(reports) {
if toast.error {
n.error_with(&toast.text, crate::editor::notify::Action::OpenConsole);
} else {
n.success(&toast.text);
}
}
}
fn log(ReloadReport { subject, outcome }: &ReloadReport) {
let (kind, name) = (subject.kind, &subject.name);
let what = kind.label();
match outcome {
ReloadOutcome::Swapped {
frame_time,
warnings,
} => tracing::info!(
"{what} hot-reload: '{name}' recompiled{}, {} swapped ({:.1} ms on the frame thread)",
warned(warnings),
kind.rebuilt(),
frame_time.as_secs_f64() * 1000.0
),
ReloadOutcome::AppliesOnLoad { warnings } => tracing::info!(
"{what} hot-reload: '{name}' recompiled{}; {}",
warned(warnings),
kind.not_resident()
),
ReloadOutcome::Failed(e) => tracing::error!(
"{what} hot-reload: '{name}' failed: {e} (live {} kept {} previous source)",
kind.rebuilt(),
if kind == SubjectKind::Shader {
"its"
} else {
"their"
}
),
}
}
fn warned(warnings: &[Diagnostic]) -> String {
match warnings.len() {
0 => String::new(),
1 => " with 1 warning".to_string(),
n => format!(" with {n} warnings"),
}
}