mod compile;
#[cfg(test)]
mod tests;
use concinnity_core::components::ShaderSource;
use concinnity_core::render::backend::{
LiveEdit, PipelineBuilder, PipelineSwap, PreparedPipelines,
};
use concinnity_engine::live_edit::sdf_field_sources::{SdfFieldEntry, SdfFieldMap};
use std::collections::BTreeMap;
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::PendingSdfVolumes;
use compile::{FieldKey, FieldResult, RebuildResult, Rebuilt, read_field};
type Compiler = Arc<dyn Fn(&str, &FieldKey, &ShaderSource) -> FieldResult + Send + Sync>;
pub(super) fn file_index(catalog: &SdfFieldMap) -> FileIndex<String> {
FileIndex::new(catalog.files().map(|(path, name)| (path, name.to_string())))
}
pub(crate) struct SdfReload {
pub(super) catalog: SdfFieldMap,
compiles: CompileQueue<FieldKey, RebuildResult>,
compiler: Compiler,
}
impl SdfReload {
pub(crate) fn new(catalog: SdfFieldMap) -> Self {
Self::with_compiler(catalog, Arc::new(compile::compile))
}
fn with_compiler(catalog: SdfFieldMap, compiler: Compiler) -> Self {
Self {
catalog,
compiles: CompileQueue::new(),
compiler,
}
}
pub(super) fn subjects(&self) -> impl Iterator<Item = ReloadSubject> + '_ {
self.catalog
.entries
.iter()
.map(|e| ReloadSubject::sdf_volume(&e.name))
}
pub(crate) fn request(
&mut self,
pending: &PendingSdfVolumes,
builder: Option<Arc<dyn PipelineBuilder>>,
) -> Vec<ReloadReport> {
let mut failed = Vec::new();
for (key, volumes) in groups(&self.catalog, pending) {
let names: Vec<&str> = volumes.iter().map(|v| v.name.as_str()).collect();
let started = read_field(&key.path).and_then(|field| {
let compiler = Arc::clone(&self.compiler);
let builder = builder.clone();
let name = names[0].to_string();
let job_key = key.clone();
let targets: Vec<(usize, String)> =
volumes.iter().map(|v| (v.volume, v.name.clone())).collect();
self.compiles.submit(key.clone(), move || {
compiler(&name, &job_key, &field)
.and_then(|c| compile::prepare(c, builder.as_deref(), &job_key, &targets))
})
});
match started {
Ok(()) => tracing::info!(
"SdfVolume hot-reload: recompiling '{}' for {}",
key.path,
quoted(&names)
),
Err(e) => failed.extend(volumes.iter().map(|v| ReloadReport {
subject: ReloadSubject::sdf_volume(&v.name),
outcome: ReloadOutcome::Failed(ReloadFailure::Unstarted(e.clone())),
})),
}
}
failed
}
pub(crate) fn poll(&mut self, backend: &mut dyn LiveEdit) -> Vec<ReloadReport> {
let mut reports = Vec::new();
for (key, result) in self.compiles.drain() {
let volumes = self
.catalog
.entries
.iter()
.filter(|e| FieldKey::of(e) == key);
match result {
Ok(mut rebuilt) => {
for entry in volumes {
let prepared = rebuilt.prepared.remove(&entry.volume);
reports.push(ReloadReport {
subject: ReloadSubject::sdf_volume(&entry.name),
outcome: apply(entry, &rebuilt, prepared, backend),
});
}
}
Err(e) => reports.extend(volumes.map(|entry| ReloadReport {
subject: ReloadSubject::sdf_volume(&entry.name),
outcome: ReloadOutcome::Failed(e.clone()),
})),
}
}
reports
}
}
fn groups<'a>(
catalog: &'a SdfFieldMap,
pending: &PendingSdfVolumes,
) -> BTreeMap<FieldKey, Vec<&'a SdfFieldEntry>> {
let mut groups: BTreeMap<FieldKey, Vec<&SdfFieldEntry>> = BTreeMap::new();
for entry in catalog.entries.iter().filter(|e| pending.wants(&e.name)) {
groups.entry(FieldKey::of(entry)).or_default().push(entry);
}
groups
}
fn apply(
entry: &SdfFieldEntry,
rebuilt: &Rebuilt,
prepared: Option<PreparedPipelines>,
backend: &mut dyn LiveEdit,
) -> ReloadOutcome {
let warnings = &rebuilt.compiled.warnings;
let started = Instant::now();
match backend.replace_sdf_volume_pipelines(entry.volume, &rebuilt.compiled.programs, prepared) {
Ok(PipelineSwap::Swapped) => ReloadOutcome::Swapped {
frame_time: started.elapsed(),
warnings: warnings.clone(),
},
Ok(PipelineSwap::NotResident) => ReloadOutcome::AppliesOnLoad {
warnings: warnings.clone(),
},
Err(e) => ReloadOutcome::Failed(ReloadFailure::Rejected(e.to_string())),
}
}
fn quoted(names: &[&str]) -> String {
names
.iter()
.map(|n| format!("'{n}'"))
.collect::<Vec<_>>()
.join(", ")
}