use std::time::Duration;
use std::time::Instant;
use crate::tui_internal::history_cell::HistoryCell;
use super::chunking::AdaptiveChunkingPolicy;
use super::chunking::ChunkingDecision;
use super::chunking::ChunkingMode;
use super::chunking::DrainPlan;
use super::chunking::QueueSnapshot;
use super::controller::PlanStreamController;
use super::controller::StreamController;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum CommitTickScope {
AnyMode,
CatchUpOnly,
}
pub(crate) struct CommitTickOutput {
pub(crate) cells: Vec<Box<dyn HistoryCell>>,
pub(crate) has_controller: bool,
pub(crate) all_idle: bool,
}
impl Default for CommitTickOutput {
fn default() -> Self {
Self {
cells: Vec::new(),
has_controller: false,
all_idle: true,
}
}
}
pub(crate) fn run_commit_tick(
policy: &mut AdaptiveChunkingPolicy,
stream_controller: Option<&mut StreamController>,
plan_stream_controller: Option<&mut PlanStreamController>,
scope: CommitTickScope,
now: Instant,
) -> CommitTickOutput {
let snapshot = stream_queue_snapshot(
stream_controller.as_deref(),
plan_stream_controller.as_deref(),
now,
);
let decision = resolve_chunking_plan(policy, snapshot, now);
if scope == CommitTickScope::CatchUpOnly && decision.mode != ChunkingMode::CatchUp {
return CommitTickOutput::default();
}
apply_commit_tick_plan(
decision.drain_plan,
stream_controller,
plan_stream_controller,
)
}
fn stream_queue_snapshot(
stream_controller: Option<&StreamController>,
plan_stream_controller: Option<&PlanStreamController>,
now: Instant,
) -> QueueSnapshot {
let mut queued_lines = 0usize;
let mut oldest_age: Option<Duration> = None;
if let Some(controller) = stream_controller {
queued_lines += controller.queued_lines();
oldest_age = max_duration(oldest_age, controller.oldest_queued_age(now));
}
if let Some(controller) = plan_stream_controller {
queued_lines += controller.queued_lines();
oldest_age = max_duration(oldest_age, controller.oldest_queued_age(now));
}
QueueSnapshot {
queued_lines,
oldest_age,
}
}
fn resolve_chunking_plan(
policy: &mut AdaptiveChunkingPolicy,
snapshot: QueueSnapshot,
now: Instant,
) -> ChunkingDecision {
let prior_mode = policy.mode();
let decision = policy.decide(snapshot, now);
if decision.mode != prior_mode {
tracing::trace!(
prior_mode = ?prior_mode,
new_mode = ?decision.mode,
queued_lines = snapshot.queued_lines,
oldest_queued_age_ms = snapshot.oldest_age.map(|age| age.as_millis() as u64),
entered_catch_up = decision.entered_catch_up,
"stream chunking mode transition"
);
}
decision
}
fn apply_commit_tick_plan(
drain_plan: DrainPlan,
stream_controller: Option<&mut StreamController>,
plan_stream_controller: Option<&mut PlanStreamController>,
) -> CommitTickOutput {
let mut output = CommitTickOutput::default();
if let Some(controller) = stream_controller {
output.has_controller = true;
let (cell, is_idle) = drain_stream_controller(controller, drain_plan);
if let Some(cell) = cell {
output.cells.push(cell);
}
output.all_idle &= is_idle;
}
if let Some(controller) = plan_stream_controller {
output.has_controller = true;
let (cell, is_idle) = drain_plan_stream_controller(controller, drain_plan);
if let Some(cell) = cell {
output.cells.push(cell);
}
output.all_idle &= is_idle;
}
output
}
fn drain_stream_controller(
controller: &mut StreamController,
drain_plan: DrainPlan,
) -> (Option<Box<dyn HistoryCell>>, bool) {
match drain_plan {
DrainPlan::Single => controller.on_commit_tick(),
DrainPlan::Batch(max_lines) => controller.on_commit_tick_batch(max_lines),
}
}
fn drain_plan_stream_controller(
controller: &mut PlanStreamController,
drain_plan: DrainPlan,
) -> (Option<Box<dyn HistoryCell>>, bool) {
match drain_plan {
DrainPlan::Single => controller.on_commit_tick(),
DrainPlan::Batch(max_lines) => controller.on_commit_tick_batch(max_lines),
}
}
fn max_duration(lhs: Option<Duration>, rhs: Option<Duration>) -> Option<Duration> {
match (lhs, rhs) {
(Some(left), Some(right)) => Some(left.max(right)),
(Some(left), None) => Some(left),
(None, Some(right)) => Some(right),
(None, None) => None,
}
}