use std::sync::{Arc, Mutex};
use argonauts_core::NoteLevel;
use argonauts_core::app::{AppEvent, ViewData};
use argonauts_core::behavior_map::MapRequest;
use argonauts_core::deep_map::{DeepDone, DeepProgress, DeepStart};
use argonauts_core::settings::behaviors::{BehaviorMap, BehaviorUpdate};
use tokio_util::sync::CancellationToken;
use super::gather::project_of;
use super::service::{AfterRun, DeepMaps};
use crate::controller::{Ctx, Effect, Internal, Outbox};
use crate::host::clis::RepoModels;
const ALREADY_RUNNING: &str =
"The deeper map is already being made. x on the Behaviors tab stops it.";
const REDO_WAITS: &str = "The deeper map takes no new actions until it's made again with the \
summarizer and prompt it now asks for. d on the Behaviors tab asks.";
#[derive(Debug, Clone, Default)]
pub(crate) struct DeepWork {
running: Arc<Mutex<Option<CancellationToken>>>,
shown: Arc<Mutex<Option<MapRequest>>>,
told_of_redo: Arc<std::sync::atomic::AtomicBool>,
}
impl DeepWork {
fn begin(&self) -> Option<CancellationToken> {
let mut running = self.lock();
if running.is_some() {
return None;
}
let stop = CancellationToken::new();
*running = Some(stop.clone());
Some(stop)
}
fn end(&self) {
*self.lock() = None;
}
fn tell_once_of_redo(&self) -> bool {
!self
.told_of_redo
.swap(true, std::sync::atomic::Ordering::Relaxed)
}
fn stop(&self) {
if let Some(stop) = self.lock().as_ref() {
stop.cancel();
}
}
pub(crate) fn showing(&self, request: MapRequest) {
*self
.shown
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(request);
}
fn shown(&self) -> Option<MapRequest> {
*self
.shown
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
fn lock(&self) -> std::sync::MutexGuard<'_, Option<CancellationToken>> {
self.running
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
fn maps(ctx: &Ctx) -> DeepMaps {
let env = &ctx.env;
DeepMaps::new(
env.paths.clone(),
ctx.settings().clone(),
ctx.services.registry.clone(),
Some(RepoModels::new(&env.repo_root, &env.state_dir)),
project_of(&env.state_dir),
)
.with_workers(ctx.services.workers.clone())
}
pub(crate) fn offer(ctx: &Ctx) -> anyhow::Result<Vec<Effect>> {
let maps = maps(ctx);
let out = ctx.out.clone();
tokio::spawn(async move {
let offer = maps.offer().await;
out.send(AppEvent::View {
data: Box::new(ViewData::DeepOffer {
offer: Box::new(offer),
}),
});
});
Ok(Vec::new())
}
pub(crate) fn start(ctx: &Ctx, start: DeepStart) -> anyhow::Result<Vec<Effect>> {
let Some(stop) = ctx.deep.begin() else {
ctx.notice(NoteLevel::Info, ALREADY_RUNNING);
return Ok(Vec::new());
};
let maps = maps(ctx);
let (out, work, internal) = (ctx.out.clone(), ctx.deep.clone(), ctx.internal.clone());
tokio::spawn(async move {
let mut progress = progress_to(out.clone());
let mut first = true;
let mut placed = {
let internal = internal.clone();
move || {
let _ = internal.send(Internal::DeepMapMade { deeper: first });
first = false;
}
};
let done = maps
.update(&start, &stop, &mut progress, &mut placed)
.await
.unwrap_or_else(failed);
work.end();
out.send(AppEvent::DeepDone { done });
let _ = internal.send(Internal::DeepMapMade { deeper: true });
});
Ok(Vec::new())
}
pub(crate) fn stop(ctx: &Ctx) -> anyhow::Result<Vec<Effect>> {
ctx.deep.stop();
Ok(Vec::new())
}
pub(crate) fn after_run(ctx: &Ctx) {
if ctx.settings().behaviors.update != BehaviorUpdate::AfterEachRun {
return;
}
let maps = maps(ctx);
let (out, work, internal) = (ctx.out.clone(), ctx.deep.clone(), ctx.internal.clone());
tokio::spawn(async move {
let start = match maps.after_run().await {
Ok(AfterRun::Update(start)) => start,
Ok(AfterRun::Nothing) => return,
Ok(AfterRun::RedoWaits) => {
if work.tell_once_of_redo() {
out.send(AppEvent::Notice {
level: NoteLevel::Info,
text: REDO_WAITS.to_owned(),
});
}
return;
}
Err(err) => {
out.send(AppEvent::DeepDone { done: failed(err) });
return;
}
};
let Some(stop) = work.begin() else {
return;
};
let mut progress = progress_to(out.clone());
let mut placed = {
let internal = internal.clone();
move || {
let _ = internal.send(Internal::DeepMapMade { deeper: false });
}
};
let done = maps
.update(&start, &stop, &mut progress, &mut placed)
.await
.unwrap_or_else(failed);
work.end();
out.send(AppEvent::DeepDone { done });
let _ = internal.send(Internal::DeepMapMade { deeper: false });
});
}
pub(crate) async fn made(ctx: &mut Ctx, deeper: bool) -> anyhow::Result<Vec<Effect>> {
let Some(mut request) = ctx.deep.shown() else {
return Ok(Vec::new());
};
if deeper {
request.map = Some(BehaviorMap::Deeper);
ctx.deep.showing(request);
}
crate::views::behaviors::view(ctx, request).await
}
fn progress_to(out: Outbox) -> impl FnMut(DeepProgress) + Send {
move |progress| out.send(AppEvent::DeepProgress { progress })
}
fn failed(err: anyhow::Error) -> DeepDone {
DeepDone {
failed: Some(format!("{err:#}")),
..DeepDone::default()
}
}
#[cfg(test)]
#[path = "tab_tests.rs"]
mod tests;