mod compile;
#[cfg(test)]
mod tests;
use concinnity_core::ecs::asset_id::AssetId;
use concinnity_core::render::backend::{LiveEdit, PipelineBuilder, PipelineSwap};
use concinnity_engine::live_edit::parked::ShaderOverrides;
use concinnity_engine::live_edit::shader_sources::{ShaderSourceEntry, ShaderSourceMap};
use std::sync::Arc;
use std::time::Instant;
use super::compile_queue::CompileQueue;
use super::files::FileIndex;
use super::report::{ReloadFailure, ReloadOutcome, ReloadReport, ReloadSubject};
use super::signals::PendingShaders;
use compile::{CompileResult, RebuildResult, Rebuilt, ShaderTexts};
type Compiler = Arc<dyn Fn(&str, &ShaderTexts) -> CompileResult + Send + Sync>;
pub(super) fn file_index(catalog: &ShaderSourceMap) -> FileIndex<AssetId> {
FileIndex::new(catalog.files())
}
pub(crate) struct ShaderReload {
pub(super) catalog: ShaderSourceMap,
overrides: ShaderOverrides,
compiles: CompileQueue<AssetId, RebuildResult>,
compiler: Compiler,
}
impl ShaderReload {
pub(crate) fn new(catalog: ShaderSourceMap, overrides: ShaderOverrides) -> Self {
Self::with_compiler(catalog, overrides, Arc::new(compile::compile))
}
fn with_compiler(
catalog: ShaderSourceMap,
overrides: ShaderOverrides,
compiler: Compiler,
) -> Self {
Self {
catalog,
overrides,
compiles: CompileQueue::new(),
compiler,
}
}
pub(super) fn subjects(&self) -> impl Iterator<Item = ReloadSubject> + '_ {
self.catalog
.entries
.iter()
.map(|e| ReloadSubject::shader(&e.name))
}
pub(crate) fn request(
&mut self,
pending: &PendingShaders,
builder: Option<Arc<dyn PipelineBuilder>>,
) -> Vec<ReloadReport> {
let mut failed = Vec::new();
for entry in self.catalog.entries.iter().filter(|e| pending.wants(&e.id)) {
let started = ShaderTexts::read(entry).and_then(|texts| {
let compiler = Arc::clone(&self.compiler);
let builder = builder.clone();
let name = entry.name.clone();
let bucket = entry.bucket;
self.compiles.submit(entry.id, move || {
compiler(&name, &texts)
.and_then(|c| compile::prepare(c, builder.as_deref(), bucket))
})
});
match started {
Ok(()) => tracing::info!("Shader hot-reload: recompiling '{}'", entry.name),
Err(e) => failed.push(ReloadReport {
subject: ReloadSubject::shader(&entry.name),
outcome: ReloadOutcome::Failed(ReloadFailure::Unstarted(e)),
}),
}
}
failed
}
pub(crate) fn poll(&mut self, backend: &mut dyn LiveEdit) -> Vec<ReloadReport> {
self.compiles
.drain()
.into_iter()
.filter_map(|(id, result)| {
let entry = self.catalog.get(id)?;
let outcome = match result {
Ok(rebuilt) => apply(entry, rebuilt, backend, &self.overrides),
Err(e) => ReloadOutcome::Failed(e),
};
Some(ReloadReport {
subject: ReloadSubject::shader(&entry.name),
outcome,
})
})
.collect()
}
}
fn apply(
entry: &ShaderSourceEntry,
rebuilt: Rebuilt,
backend: &mut dyn LiveEdit,
overrides: &ShaderOverrides,
) -> ReloadOutcome {
let Rebuilt { compiled, prepared } = rebuilt;
let programs = Arc::new(compiled.programs);
let warnings = compiled.warnings;
let started = Instant::now();
let outcome = match backend.update_world_shader(entry.bucket, &programs, prepared) {
Ok(PipelineSwap::Swapped) => ReloadOutcome::Swapped {
frame_time: started.elapsed(),
warnings,
},
Ok(PipelineSwap::NotResident) => ReloadOutcome::AppliesOnLoad { warnings },
Err(e) => {
return ReloadOutcome::Failed(ReloadFailure::Rejected(e.to_string()));
}
};
if entry.bucket != 0 {
overrides.set(entry.bucket, programs);
}
outcome
}