using System.Linq; namespace AxCopilot.Views; public partial class ChatWindow { /// 최대 캐시 보유 수. 화면 표시 항목 + 여유분. private const int TranscriptCacheRetentionCount = 120; private void PruneTranscriptElementCache(IReadOnlyCollection visibleKeys) { // 캐시가 보유 한도의 1.5배 이상일 때만 정리 (빈번한 정리 방지) if (_elementCache.Count <= TranscriptCacheRetentionCount * 3 / 2) return; var keep = new HashSet(visibleKeys, StringComparer.Ordinal); // 최근 렌더링된 키 중 보유 한도만큼만 유지 var retainFromRendered = Math.Min(TranscriptCacheRetentionCount, _lastRenderedTimelineKeys.Count); foreach (var key in _lastRenderedTimelineKeys.TakeLast(retainFromRendered)) keep.Add(key); var removable = _elementCache.Keys .Where(key => !keep.Contains(key)) .ToList(); foreach (var key in removable) _elementCache.Remove(key); // _transcriptElementMap도 동기 정리 — 더 이상 transcript에 없는 element 참조 제거 PruneTranscriptElementMap(); } /// /// _transcriptElementMap에서 현재 _transcriptElements에 없는 항목을 제거합니다. /// ObservableCollection 변경 시 자동으로 정리되지 않는 stale 참조를 회수합니다. /// private void PruneTranscriptElementMap() { if (_transcriptElementMap.Count <= _transcriptElements.Count * 2) return; // 맵이 실제 요소의 2배 미만이면 정리 불필요 var activeElements = new HashSet( _transcriptElements .Where(item => item.IsMaterialized && item.Element != null) .Select(item => item.Element!)); var staleKeys = _transcriptElementMap.Keys .Where(key => !activeElements.Contains(key)) .ToList(); foreach (var key in staleKeys) _transcriptElementMap.Remove(key); } }