mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
perf(tui/history): cache render requests
This commit is contained in:
+307
-109
@@ -1571,6 +1571,14 @@ pub(crate) enum TurnOrigin {
|
||||
Developer,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RenderRequestSeed {
|
||||
history_id: HistoryId,
|
||||
use_cache: bool,
|
||||
fallback_lines: Option<Rc<Vec<Line<'static>>>>,
|
||||
kind: RenderRequestKind,
|
||||
}
|
||||
|
||||
pub(crate) struct ChatWidget<'a> {
|
||||
app_event_tx: AppEventSender,
|
||||
code_op_tx: UnboundedSender<Op>,
|
||||
@@ -1582,6 +1590,9 @@ pub(crate) struct ChatWidget<'a> {
|
||||
history_cells: Vec<Box<dyn HistoryCell>>, // Store all history in memory
|
||||
history_cell_ids: Vec<Option<HistoryId>>,
|
||||
history_render: HistoryRenderState,
|
||||
render_request_cache: RefCell<Vec<RenderRequestSeed>>,
|
||||
render_request_cache_dirty: Cell<bool>,
|
||||
history_prefix_append_only: Cell<bool>,
|
||||
render_theme_epoch: u64,
|
||||
history_state: HistoryState,
|
||||
history_snapshot_dirty: bool,
|
||||
@@ -4752,6 +4763,111 @@ impl ChatWidget<'_> {
|
||||
/// Clear memoized cell heights (called when history/content changes)
|
||||
fn invalidate_height_cache(&mut self) {
|
||||
self.history_render.invalidate_height_cache();
|
||||
self.mark_render_requests_dirty();
|
||||
self.history_prefix_append_only.set(false);
|
||||
}
|
||||
|
||||
fn mark_render_requests_dirty(&self) {
|
||||
self.render_request_cache_dirty.set(true);
|
||||
}
|
||||
|
||||
fn rebuild_render_request_cache(&self) {
|
||||
let mut cache = self.render_request_cache.borrow_mut();
|
||||
cache.clear();
|
||||
cache.reserve(self.history_cells.len());
|
||||
|
||||
let reasoning_visible = self.is_reasoning_shown();
|
||||
for (idx, cell) in self.history_cells.iter().enumerate() {
|
||||
let (history_id, has_record) = if let Some(Some(id)) = self.history_cell_ids.get(idx) {
|
||||
let exists = self.history_state.index_of(*id).is_some();
|
||||
(*id, exists)
|
||||
} else {
|
||||
(HistoryId::ZERO, false)
|
||||
};
|
||||
|
||||
let cell_has_custom_render = cell.has_custom_render();
|
||||
let is_streaming = cell
|
||||
.as_any()
|
||||
.downcast_ref::<crate::history_cell::StreamingContentCell>()
|
||||
.is_some();
|
||||
|
||||
let mut use_cache = history_id != HistoryId::ZERO
|
||||
&& has_record
|
||||
&& !cell_has_custom_render
|
||||
&& !cell.is_animating()
|
||||
&& !is_streaming;
|
||||
|
||||
let mut kind = RenderRequestKind::Legacy;
|
||||
if history_id != HistoryId::ZERO {
|
||||
if let Some(record) = self.history_state.record(history_id) {
|
||||
match record {
|
||||
HistoryRecord::Exec(_) => {
|
||||
kind = RenderRequestKind::Exec { id: history_id };
|
||||
}
|
||||
HistoryRecord::MergedExec(_) => {
|
||||
kind = RenderRequestKind::MergedExec { id: history_id };
|
||||
}
|
||||
HistoryRecord::Explore(_) => {
|
||||
let hold_header = self.rendered_explore_should_hold(idx);
|
||||
kind = RenderRequestKind::Explore {
|
||||
id: history_id,
|
||||
hold_header,
|
||||
full_detail: reasoning_visible,
|
||||
};
|
||||
}
|
||||
HistoryRecord::Diff(_) => {
|
||||
kind = RenderRequestKind::Diff { id: history_id };
|
||||
}
|
||||
HistoryRecord::AssistantStream(stream_state) => {
|
||||
kind = RenderRequestKind::Streaming { id: history_id };
|
||||
if stream_state.in_progress {
|
||||
use_cache = false;
|
||||
}
|
||||
}
|
||||
HistoryRecord::AssistantMessage(_) => {
|
||||
kind = RenderRequestKind::Assistant { id: history_id };
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut fallback_lines: Option<Rc<Vec<Line<'static>>>> = None;
|
||||
if !cell_has_custom_render && !is_streaming {
|
||||
if history_id != HistoryId::ZERO {
|
||||
if let Some(record) = self.history_state.record(history_id) {
|
||||
if let Some(lines) = self.fallback_lines_for_record(cell.as_ref(), record) {
|
||||
let cached = self
|
||||
.history_render
|
||||
.cached_fallback_lines(history_id, || lines);
|
||||
fallback_lines = Some(cached);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let lines = cell.display_lines_trimmed();
|
||||
if !lines.is_empty() {
|
||||
fallback_lines = Some(Rc::new(lines));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache.push(RenderRequestSeed {
|
||||
history_id,
|
||||
use_cache,
|
||||
fallback_lines,
|
||||
kind,
|
||||
});
|
||||
}
|
||||
|
||||
self.render_request_cache_dirty.set(false);
|
||||
}
|
||||
|
||||
fn ensure_render_request_cache(&self) {
|
||||
if self.render_request_cache_dirty.get()
|
||||
|| self.render_request_cache.borrow().len() != self.history_cells.len()
|
||||
{
|
||||
self.rebuild_render_request_cache();
|
||||
}
|
||||
}
|
||||
|
||||
fn fallback_lines_for_record(
|
||||
@@ -5976,6 +6092,9 @@ impl ChatWidget<'_> {
|
||||
background_review_guard: None,
|
||||
processed_auto_review_agents: HashSet::new(),
|
||||
pending_turn_descriptor: None,
|
||||
render_request_cache: RefCell::new(Vec::new()),
|
||||
render_request_cache_dirty: Cell::new(true),
|
||||
history_prefix_append_only: Cell::new(true),
|
||||
pending_auto_turn_config: None,
|
||||
overall_task_status: "preparing".to_string(),
|
||||
active_plan_title: None,
|
||||
@@ -6333,6 +6452,9 @@ impl ChatWidget<'_> {
|
||||
background_review_guard: None,
|
||||
processed_auto_review_agents: HashSet::new(),
|
||||
pending_turn_descriptor: None,
|
||||
render_request_cache: RefCell::new(Vec::new()),
|
||||
render_request_cache_dirty: Cell::new(true),
|
||||
history_prefix_append_only: Cell::new(true),
|
||||
pending_auto_turn_config: None,
|
||||
overall_task_status: "preparing".to_string(),
|
||||
active_plan_title: None,
|
||||
@@ -7092,6 +7214,76 @@ impl ChatWidget<'_> {
|
||||
total_height.saturating_sub(content_area_height)
|
||||
}
|
||||
|
||||
fn try_append_prefix_fast(
|
||||
&self,
|
||||
render_requests: &[RenderRequest<'_>],
|
||||
render_settings: RenderSettings,
|
||||
prefix_width: u16,
|
||||
) -> bool {
|
||||
if !self.history_prefix_append_only.get() {
|
||||
return false;
|
||||
}
|
||||
if !self
|
||||
.history_render
|
||||
.can_append_prefix(prefix_width, render_requests.len())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let prev_count = self.history_render.last_prefix_count();
|
||||
if prev_count == 0 || render_requests.len() != prev_count.saturating_add(1) {
|
||||
return false;
|
||||
}
|
||||
let history_count = self.history_cells.len();
|
||||
if history_count < 2 {
|
||||
return false;
|
||||
}
|
||||
if history_count != self
|
||||
.history_render
|
||||
.last_history_count()
|
||||
.saturating_add(1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if render_requests.len() != history_count {
|
||||
return false;
|
||||
}
|
||||
let history_tail_start = history_count - 2;
|
||||
let tail = &render_requests[history_tail_start..history_count];
|
||||
if tail.len() != 2 {
|
||||
return false;
|
||||
}
|
||||
let cells = self
|
||||
.history_render
|
||||
.visible_cells(&self.history_state, tail, render_settings);
|
||||
if cells.len() != 2 {
|
||||
return false;
|
||||
}
|
||||
let prev = &cells[0];
|
||||
let next = &cells[1];
|
||||
if prev.height == 0 || next.height == 0 {
|
||||
return false;
|
||||
}
|
||||
let prev_is_reasoning = prev
|
||||
.cell
|
||||
.and_then(|cell| cell.as_any().downcast_ref::<crate::history_cell::CollapsibleReasoningCell>())
|
||||
.is_some();
|
||||
let next_is_reasoning = next
|
||||
.cell
|
||||
.and_then(|cell| cell.as_any().downcast_ref::<crate::history_cell::CollapsibleReasoningCell>())
|
||||
.is_some();
|
||||
if prev_is_reasoning || next_is_reasoning {
|
||||
return false;
|
||||
}
|
||||
let spacing = 1u16;
|
||||
let spacing_range = self
|
||||
.history_render
|
||||
.extend_prefix_for_append(prefix_width, spacing, next.height, history_count);
|
||||
if let Some(range) = spacing_range {
|
||||
self.history_render.append_spacing_range(range);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
if settings_handlers::handle_settings_key(self, key_event) {
|
||||
return;
|
||||
@@ -8918,6 +9110,9 @@ impl ChatWidget<'_> {
|
||||
}
|
||||
|
||||
let append = pos == self.history_cells.len();
|
||||
if !append {
|
||||
self.history_prefix_append_only.set(false);
|
||||
}
|
||||
if append {
|
||||
self.history_cells.push(cell);
|
||||
self.history_cell_ids.push(maybe_id);
|
||||
@@ -8987,7 +9182,16 @@ impl ChatWidget<'_> {
|
||||
} else {
|
||||
self.cell_order_dbg.insert(pos, Some(dbg));
|
||||
}
|
||||
self.invalidate_height_cache();
|
||||
if let Some(id) = maybe_id {
|
||||
if id != HistoryId::ZERO {
|
||||
self.history_render.invalidate_history_id(id);
|
||||
} else {
|
||||
self.history_render.invalidate_prefix_only();
|
||||
}
|
||||
} else {
|
||||
self.history_render.invalidate_prefix_only();
|
||||
}
|
||||
self.mark_render_requests_dirty();
|
||||
self.autoscroll_if_near_bottom();
|
||||
self.bottom_pane.set_has_chat_history(true);
|
||||
self.process_animation_cleanup();
|
||||
@@ -9118,6 +9322,9 @@ impl ChatWidget<'_> {
|
||||
Self::assign_history_id_inner(&mut cell, id);
|
||||
|
||||
let append = pos == self.history_cells.len();
|
||||
if !append {
|
||||
self.history_prefix_append_only.set(false);
|
||||
}
|
||||
if append {
|
||||
self.history_cells.push(cell);
|
||||
self.history_cell_ids.push(Some(id));
|
||||
@@ -9184,7 +9391,8 @@ impl ChatWidget<'_> {
|
||||
} else {
|
||||
self.cell_order_dbg.insert(pos, Some(dbg));
|
||||
}
|
||||
self.invalidate_height_cache();
|
||||
self.history_render.invalidate_history_id(id);
|
||||
self.mark_render_requests_dirty();
|
||||
self.autoscroll_if_near_bottom();
|
||||
self.bottom_pane.set_has_chat_history(true);
|
||||
self.process_animation_cleanup();
|
||||
@@ -9194,7 +9402,6 @@ impl ChatWidget<'_> {
|
||||
self.app_event_tx.send(AppEvent::RequestRedraw);
|
||||
self.refresh_explore_trailing_flags();
|
||||
self.refresh_reasoning_collapsed_visibility();
|
||||
self.history_render.invalidate_history_id(id);
|
||||
self.mark_history_dirty();
|
||||
pos
|
||||
}
|
||||
@@ -9854,6 +10061,7 @@ impl ChatWidget<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
let old_id = self.history_cell_ids.get(idx).and_then(|id| *id);
|
||||
let record = history_cell::record_from_cell(cell.as_ref());
|
||||
let mut maybe_id = None;
|
||||
|
||||
@@ -9893,7 +10101,18 @@ impl ChatWidget<'_> {
|
||||
if idx < self.history_cell_ids.len() {
|
||||
self.history_cell_ids[idx] = maybe_id;
|
||||
}
|
||||
self.invalidate_height_cache();
|
||||
if let Some(id) = old_id {
|
||||
self.history_render.invalidate_history_id(id);
|
||||
} else {
|
||||
self.history_render.invalidate_prefix_only();
|
||||
}
|
||||
if let Some(id) = maybe_id {
|
||||
if Some(id) != old_id {
|
||||
self.history_render.invalidate_history_id(id);
|
||||
}
|
||||
}
|
||||
self.mark_render_requests_dirty();
|
||||
self.history_prefix_append_only.set(false);
|
||||
self.request_redraw();
|
||||
self.refresh_explore_trailing_flags();
|
||||
// Keep debug info for this cell index as-is.
|
||||
@@ -9905,6 +10124,7 @@ impl ChatWidget<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
let removed_id = self.history_cell_ids.get(idx).and_then(|id| *id);
|
||||
if let Some(record_idx) = self.record_index_for_cell(idx) {
|
||||
let _ = self
|
||||
.history_state
|
||||
@@ -9921,7 +10141,13 @@ impl ChatWidget<'_> {
|
||||
if idx < self.cell_order_dbg.len() {
|
||||
self.cell_order_dbg.remove(idx);
|
||||
}
|
||||
self.invalidate_height_cache();
|
||||
if let Some(id) = removed_id {
|
||||
self.history_render.invalidate_history_id(id);
|
||||
} else {
|
||||
self.history_render.invalidate_prefix_only();
|
||||
}
|
||||
self.mark_render_requests_dirty();
|
||||
self.history_prefix_append_only.set(false);
|
||||
self.request_redraw();
|
||||
self.refresh_explore_trailing_flags();
|
||||
self.mark_history_dirty();
|
||||
@@ -11233,6 +11459,7 @@ impl ChatWidget<'_> {
|
||||
|
||||
fn mark_history_dirty(&mut self) {
|
||||
self.history_snapshot_dirty = true;
|
||||
self.render_request_cache_dirty.set(true);
|
||||
self.flush_history_snapshot_if_needed(false);
|
||||
}
|
||||
|
||||
@@ -11804,6 +12031,8 @@ impl ChatWidget<'_> {
|
||||
self.history_state.restore(snapshot);
|
||||
|
||||
self.history_render.invalidate_all();
|
||||
self.mark_render_requests_dirty();
|
||||
self.history_prefix_append_only.set(false);
|
||||
|
||||
self.history_cells.clear();
|
||||
self.history_cell_ids.clear();
|
||||
@@ -17052,6 +17281,8 @@ fi\n\
|
||||
|
||||
pub(crate) fn show_auto_drive_settings(&mut self) {
|
||||
self.history_render.invalidate_all();
|
||||
self.mark_render_requests_dirty();
|
||||
self.history_prefix_append_only.set(false);
|
||||
self.ensure_auto_drive_settings_overlay();
|
||||
}
|
||||
|
||||
@@ -17066,6 +17297,8 @@ fi\n\
|
||||
self.close_settings_overlay();
|
||||
}
|
||||
self.history_render.invalidate_all();
|
||||
self.mark_render_requests_dirty();
|
||||
self.history_prefix_append_only.set(false);
|
||||
let should_rebuild_view = if self.auto_state.is_active() {
|
||||
!self.auto_state.is_paused_manual()
|
||||
} else {
|
||||
@@ -23462,6 +23695,8 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
self.last_theme = new;
|
||||
self.render_theme_epoch = self.render_theme_epoch.saturating_add(1);
|
||||
self.history_render.invalidate_all();
|
||||
self.mark_render_requests_dirty();
|
||||
self.history_prefix_append_only.set(false);
|
||||
self.app_event_tx.send(AppEvent::RequestRedraw);
|
||||
}
|
||||
|
||||
@@ -36586,20 +36821,6 @@ impl WidgetRef for &ChatWidget<'_> {
|
||||
.fg(crate::colors::text());
|
||||
fill_rect(buf, history_area, Some(' '), base_style);
|
||||
|
||||
// Collect all content items into a single list.
|
||||
// The shared renderer cache (documented in docs/history_render_cache_bridge.md)
|
||||
// consumes this list via `RenderRequest` so we can reuse per-cell caches
|
||||
// while progressively moving the draw loop toward pure HistoryRecord state.
|
||||
let mut all_content: Vec<&dyn HistoryCell> = Vec::new();
|
||||
for cell in self.history_cells.iter() {
|
||||
all_content.push(cell);
|
||||
}
|
||||
|
||||
// Add active/streaming cell if present
|
||||
if let Some(ref cell) = self.active_exec_cell {
|
||||
all_content.push(cell as &dyn HistoryCell);
|
||||
}
|
||||
|
||||
// Add live streaming content if present
|
||||
let streaming_lines = self
|
||||
.live_builder
|
||||
@@ -36616,10 +36837,6 @@ impl WidgetRef for &ChatWidget<'_> {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(ref cell) = streaming_cell {
|
||||
all_content.push(cell);
|
||||
}
|
||||
|
||||
// Append any queued user messages as sticky preview cells at the very
|
||||
// end so they always render at the bottom until they are dispatched.
|
||||
let mut queued_preview_cells: Vec<crate::history_cell::PlainHistoryCell> = Vec::new();
|
||||
@@ -36628,101 +36845,66 @@ impl WidgetRef for &ChatWidget<'_> {
|
||||
let state = history_cell::new_queued_user_prompt(qm.display_text.clone());
|
||||
queued_preview_cells.push(crate::history_cell::PlainHistoryCell::from_state(state));
|
||||
}
|
||||
for c in &queued_preview_cells {
|
||||
all_content.push(c as &dyn HistoryCell);
|
||||
}
|
||||
}
|
||||
|
||||
let mut render_requests: Vec<RenderRequest> = Vec::with_capacity(all_content.len());
|
||||
for (idx, cell) in all_content.iter().enumerate() {
|
||||
let (history_id, has_record) = if idx < self.history_cell_ids.len() {
|
||||
if let Some(id) = self.history_cell_ids[idx] {
|
||||
let exists = self.history_state.index_of(id).is_some();
|
||||
(id, exists)
|
||||
} else {
|
||||
(HistoryId::ZERO, false)
|
||||
}
|
||||
} else {
|
||||
(HistoryId::ZERO, false)
|
||||
};
|
||||
|
||||
let cell_has_custom_render = cell.has_custom_render();
|
||||
let is_streaming = cell
|
||||
.as_any()
|
||||
.downcast_ref::<crate::history_cell::StreamingContentCell>()
|
||||
.is_some();
|
||||
|
||||
let mut cacheable = history_id != HistoryId::ZERO
|
||||
&& has_record
|
||||
&& !cell_has_custom_render
|
||||
&& !cell.is_animating()
|
||||
&& !is_streaming;
|
||||
self.ensure_render_request_cache();
|
||||
|
||||
let render_request_cache = self.render_request_cache.borrow();
|
||||
let mut render_requests: Vec<RenderRequest> =
|
||||
Vec::with_capacity(self.history_cells.len().saturating_add(3));
|
||||
for (cell, seed) in self
|
||||
.history_cells
|
||||
.iter()
|
||||
.zip(render_request_cache.iter())
|
||||
{
|
||||
let assistant = cell
|
||||
.as_any()
|
||||
.downcast_ref::<crate::history_cell::AssistantMarkdownCell>();
|
||||
|
||||
let mut fallback_lines = None;
|
||||
let mut kind = RenderRequestKind::Legacy;
|
||||
if history_id != HistoryId::ZERO {
|
||||
if let Some(record) = self.history_state.record(history_id) {
|
||||
match record {
|
||||
HistoryRecord::Exec(_) => {
|
||||
kind = RenderRequestKind::Exec { id: history_id };
|
||||
}
|
||||
HistoryRecord::MergedExec(_) => {
|
||||
kind = RenderRequestKind::MergedExec { id: history_id };
|
||||
}
|
||||
HistoryRecord::Explore(_) => {
|
||||
let hold_header = if idx < self.history_cells.len() {
|
||||
self.rendered_explore_should_hold(idx)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
let full_detail = self.is_reasoning_shown();
|
||||
kind = RenderRequestKind::Explore {
|
||||
id: history_id,
|
||||
hold_header,
|
||||
full_detail,
|
||||
};
|
||||
}
|
||||
HistoryRecord::Diff(_) => {
|
||||
kind = RenderRequestKind::Diff { id: history_id };
|
||||
}
|
||||
HistoryRecord::AssistantStream(stream_state) => {
|
||||
kind = RenderRequestKind::Streaming { id: history_id };
|
||||
if stream_state.in_progress {
|
||||
cacheable = false;
|
||||
}
|
||||
}
|
||||
HistoryRecord::AssistantMessage(_) => {
|
||||
kind = RenderRequestKind::Assistant { id: history_id };
|
||||
}
|
||||
other => {
|
||||
fallback_lines =
|
||||
self.fallback_lines_for_record(*cell, other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fallback_lines.is_none() && !cell_has_custom_render && history_id == HistoryId::ZERO {
|
||||
let lines = cell.display_lines_trimmed();
|
||||
if !lines.is_empty() {
|
||||
fallback_lines = Some(lines);
|
||||
}
|
||||
}
|
||||
|
||||
render_requests.push(RenderRequest {
|
||||
history_id,
|
||||
cell: Some(*cell),
|
||||
history_id: seed.history_id,
|
||||
cell: Some(cell.as_ref()),
|
||||
assistant,
|
||||
use_cache: cacheable,
|
||||
fallback_lines,
|
||||
kind,
|
||||
use_cache: seed.use_cache,
|
||||
fallback_lines: seed.fallback_lines.clone(),
|
||||
kind: seed.kind.clone(),
|
||||
config: &self.config,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(ref cell) = self.active_exec_cell {
|
||||
render_requests.push(RenderRequest {
|
||||
history_id: HistoryId::ZERO,
|
||||
cell: Some(cell as &dyn HistoryCell),
|
||||
assistant: None,
|
||||
use_cache: false,
|
||||
fallback_lines: None,
|
||||
kind: RenderRequestKind::Legacy,
|
||||
config: &self.config,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(ref cell) = streaming_cell {
|
||||
render_requests.push(RenderRequest {
|
||||
history_id: HistoryId::ZERO,
|
||||
cell: Some(cell as &dyn HistoryCell),
|
||||
assistant: None,
|
||||
use_cache: false,
|
||||
fallback_lines: None,
|
||||
kind: RenderRequestKind::Legacy,
|
||||
config: &self.config,
|
||||
});
|
||||
}
|
||||
|
||||
for c in &queued_preview_cells {
|
||||
render_requests.push(RenderRequest {
|
||||
history_id: HistoryId::ZERO,
|
||||
cell: Some(c as &dyn HistoryCell),
|
||||
assistant: None,
|
||||
use_cache: false,
|
||||
fallback_lines: None,
|
||||
kind: RenderRequestKind::Legacy,
|
||||
config: &self.config,
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate total content height using prefix sums; build if needed
|
||||
@@ -36749,6 +36931,15 @@ impl WidgetRef for &ChatWidget<'_> {
|
||||
let perf_enabled = self.perf_state.enabled;
|
||||
let mut rendered_cells_full: Option<Vec<VisibleCell>> = None;
|
||||
if needs_prefix_rebuild {
|
||||
let mut used_fast_append = false;
|
||||
if self.try_append_prefix_fast(&render_requests, render_settings, content_area.width) {
|
||||
used_fast_append = true;
|
||||
self.history_prefix_append_only.set(true);
|
||||
}
|
||||
if used_fast_append {
|
||||
// Prefix sums already updated; skip the full rebuild path.
|
||||
rendered_cells_full = None;
|
||||
} else {
|
||||
if perf_enabled {
|
||||
let mut p = self.perf_state.stats.borrow_mut();
|
||||
p.prefix_rebuilds = p.prefix_rebuilds.saturating_add(1);
|
||||
@@ -36835,10 +37026,17 @@ impl WidgetRef for &ChatWidget<'_> {
|
||||
let mut p = self.perf_state.stats.borrow_mut();
|
||||
p.ns_total_height = p.ns_total_height.saturating_add(elapsed);
|
||||
}
|
||||
self.history_render
|
||||
.update_prefix_cache(content_area.width, prefix, total_height, cells.len());
|
||||
self.history_render.update_prefix_cache(
|
||||
content_area.width,
|
||||
prefix,
|
||||
total_height,
|
||||
cells.len(),
|
||||
self.history_cells.len(),
|
||||
);
|
||||
self.history_render.update_spacing_ranges(spacing_ranges);
|
||||
rendered_cells_full = Some(cells);
|
||||
self.history_prefix_append_only.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
let mut total_height = self.history_render.last_total_height();
|
||||
|
||||
@@ -32,11 +32,13 @@ use unicode_width::UnicodeWidthStr;
|
||||
pub(crate) struct HistoryRenderState {
|
||||
pub(crate) layout_cache: RefCell<HashMap<CacheKey, Rc<CachedLayout>>>,
|
||||
pub(crate) height_cache: RefCell<HashMap<CacheKey, u16>>,
|
||||
fallback_cache: RefCell<HashMap<HistoryId, Rc<Vec<Line<'static>>>>>,
|
||||
pub(crate) height_cache_last_width: Cell<u16>,
|
||||
pub(crate) prefix_sums: RefCell<Vec<u16>>,
|
||||
pub(crate) last_prefix_width: Cell<u16>,
|
||||
pub(crate) last_prefix_count: Cell<usize>,
|
||||
pub(crate) last_total_height: Cell<u16>,
|
||||
pub(crate) last_history_count: Cell<usize>,
|
||||
pub(crate) prefix_valid: Cell<bool>,
|
||||
// Row intervals that correspond to inter-cell spacing so we can avoid
|
||||
// landing the viewport on empty gaps when scrolling.
|
||||
@@ -51,11 +53,13 @@ impl HistoryRenderState {
|
||||
Self {
|
||||
layout_cache: RefCell::new(HashMap::new()),
|
||||
height_cache: RefCell::new(HashMap::new()),
|
||||
fallback_cache: RefCell::new(HashMap::new()),
|
||||
height_cache_last_width: Cell::new(0),
|
||||
prefix_sums: RefCell::new(Vec::new()),
|
||||
last_prefix_width: Cell::new(0),
|
||||
last_prefix_count: Cell::new(0),
|
||||
last_total_height: Cell::new(0),
|
||||
last_history_count: Cell::new(0),
|
||||
prefix_valid: Cell::new(false),
|
||||
spacing_ranges: RefCell::new(Vec::new()),
|
||||
bottom_spacer_range: Cell::new(None),
|
||||
@@ -67,8 +71,10 @@ impl HistoryRenderState {
|
||||
pub(crate) fn invalidate_height_cache(&self) {
|
||||
self.layout_cache.borrow_mut().clear();
|
||||
self.height_cache.borrow_mut().clear();
|
||||
self.fallback_cache.borrow_mut().clear();
|
||||
self.prefix_sums.borrow_mut().clear();
|
||||
self.last_total_height.set(0);
|
||||
self.last_history_count.set(0);
|
||||
self.prefix_valid.set(false);
|
||||
self.spacing_ranges.borrow_mut().clear();
|
||||
self.bottom_spacer_range.set(None);
|
||||
@@ -84,8 +90,10 @@ impl HistoryRenderState {
|
||||
self.height_cache
|
||||
.borrow_mut()
|
||||
.retain(|key, _| key.width == width);
|
||||
self.fallback_cache.borrow_mut().clear();
|
||||
self.prefix_sums.borrow_mut().clear();
|
||||
self.last_total_height.set(0);
|
||||
self.last_history_count.set(0);
|
||||
self.prefix_valid.set(false);
|
||||
self.height_cache_last_width.set(width);
|
||||
self.spacing_ranges.borrow_mut().clear();
|
||||
@@ -105,8 +113,10 @@ impl HistoryRenderState {
|
||||
self.height_cache
|
||||
.borrow_mut()
|
||||
.retain(|key, _| key.history_id != id);
|
||||
self.fallback_cache.borrow_mut().remove(&id);
|
||||
self.prefix_sums.borrow_mut().clear();
|
||||
self.last_total_height.set(0);
|
||||
self.last_history_count.set(0);
|
||||
self.prefix_valid.set(false);
|
||||
self.spacing_ranges.borrow_mut().clear();
|
||||
self.bottom_spacer_range.set(None);
|
||||
@@ -117,8 +127,21 @@ impl HistoryRenderState {
|
||||
pub(crate) fn invalidate_all(&self) {
|
||||
self.layout_cache.borrow_mut().clear();
|
||||
self.height_cache.borrow_mut().clear();
|
||||
self.fallback_cache.borrow_mut().clear();
|
||||
self.prefix_sums.borrow_mut().clear();
|
||||
self.last_total_height.set(0);
|
||||
self.last_history_count.set(0);
|
||||
self.prefix_valid.set(false);
|
||||
self.spacing_ranges.borrow_mut().clear();
|
||||
self.bottom_spacer_range.set(None);
|
||||
self.bottom_spacer_lines.set(0);
|
||||
self.pending_bottom_spacer_lines.set(None);
|
||||
}
|
||||
|
||||
pub(crate) fn invalidate_prefix_only(&self) {
|
||||
self.prefix_sums.borrow_mut().clear();
|
||||
self.last_total_height.set(0);
|
||||
self.last_history_count.set(0);
|
||||
self.prefix_valid.set(false);
|
||||
self.spacing_ranges.borrow_mut().clear();
|
||||
self.bottom_spacer_range.set(None);
|
||||
@@ -145,6 +168,7 @@ impl HistoryRenderState {
|
||||
prefix: Vec<u16>,
|
||||
total_height: u16,
|
||||
count: usize,
|
||||
history_count: usize,
|
||||
) {
|
||||
{
|
||||
let mut ps = self.prefix_sums.borrow_mut();
|
||||
@@ -153,9 +177,27 @@ impl HistoryRenderState {
|
||||
self.last_prefix_width.set(width);
|
||||
self.last_prefix_count.set(count);
|
||||
self.last_total_height.set(total_height);
|
||||
self.last_history_count.set(history_count);
|
||||
self.prefix_valid.set(true);
|
||||
}
|
||||
|
||||
pub(crate) fn cached_fallback_lines<F>(&self, history_id: HistoryId, build: F) -> Rc<Vec<Line<'static>>>
|
||||
where
|
||||
F: FnOnce() -> Vec<Line<'static>>,
|
||||
{
|
||||
if history_id == HistoryId::ZERO {
|
||||
return Rc::new(build());
|
||||
}
|
||||
if let Some(lines) = self.fallback_cache.borrow().get(&history_id) {
|
||||
return Rc::clone(lines);
|
||||
}
|
||||
let lines = Rc::new(build());
|
||||
self.fallback_cache
|
||||
.borrow_mut()
|
||||
.insert(history_id, Rc::clone(&lines));
|
||||
lines
|
||||
}
|
||||
|
||||
pub(crate) fn update_spacing_ranges(&self, ranges: Vec<(u16, u16)>) {
|
||||
*self.spacing_ranges.borrow_mut() = ranges;
|
||||
}
|
||||
@@ -239,6 +281,63 @@ impl HistoryRenderState {
|
||||
self.last_total_height.get()
|
||||
}
|
||||
|
||||
pub(crate) fn last_prefix_count(&self) -> usize {
|
||||
self.last_prefix_count.get()
|
||||
}
|
||||
|
||||
pub(crate) fn last_history_count(&self) -> usize {
|
||||
self.last_history_count.get()
|
||||
}
|
||||
|
||||
pub(crate) fn can_append_prefix(&self, width: u16, count: usize) -> bool {
|
||||
self.prefix_valid.get()
|
||||
&& self.last_prefix_width.get() == width
|
||||
&& count == self.last_prefix_count.get().saturating_add(1)
|
||||
}
|
||||
|
||||
pub(crate) fn extend_prefix_for_append(
|
||||
&self,
|
||||
width: u16,
|
||||
spacing: u16,
|
||||
new_height: u16,
|
||||
new_history_count: usize,
|
||||
) -> Option<(u16, u16)> {
|
||||
if !self.prefix_valid.get() || self.last_prefix_width.get() != width {
|
||||
return None;
|
||||
}
|
||||
let prev_count = self.last_prefix_count.get();
|
||||
if prev_count == 0 {
|
||||
return None;
|
||||
}
|
||||
if new_history_count != self.last_history_count.get().saturating_add(1) {
|
||||
return None;
|
||||
}
|
||||
let mut ps = self.prefix_sums.borrow_mut();
|
||||
if ps.len() != prev_count.saturating_add(1) {
|
||||
return None;
|
||||
}
|
||||
let old_total = *ps.last().unwrap_or(&0);
|
||||
let spacing_start = old_total;
|
||||
let spacing_end = spacing_start.saturating_add(spacing);
|
||||
if let Some(last) = ps.last_mut() {
|
||||
*last = spacing_end;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
let new_total = spacing_end.saturating_add(new_height);
|
||||
ps.push(new_total);
|
||||
self.last_total_height.set(new_total);
|
||||
self.last_prefix_count.set(prev_count.saturating_add(1));
|
||||
self.last_history_count.set(new_history_count);
|
||||
self.last_prefix_width.set(width);
|
||||
self.prefix_valid.set(true);
|
||||
if spacing > 0 { Some((spacing_start, spacing_end)) } else { None }
|
||||
}
|
||||
|
||||
pub(crate) fn append_spacing_range(&self, range: (u16, u16)) {
|
||||
self.spacing_ranges.borrow_mut().push(range);
|
||||
}
|
||||
|
||||
pub(crate) fn visible_cells<'a>(
|
||||
&self,
|
||||
history_state: &HistoryState,
|
||||
@@ -557,7 +656,7 @@ pub(crate) struct RenderRequest<'a> {
|
||||
pub cell: Option<&'a dyn HistoryCell>,
|
||||
pub assistant: Option<&'a AssistantMarkdownCell>,
|
||||
pub use_cache: bool,
|
||||
pub fallback_lines: Option<Vec<Line<'static>>>,
|
||||
pub fallback_lines: Option<Rc<Vec<Line<'static>>>>,
|
||||
pub kind: RenderRequestKind,
|
||||
pub config: &'a Config,
|
||||
}
|
||||
@@ -616,7 +715,7 @@ impl<'a> RenderRequest<'a> {
|
||||
}
|
||||
|
||||
if let Some(lines) = &self.fallback_lines {
|
||||
return lines.clone();
|
||||
return lines.as_ref().clone();
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user