using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Windows.Threading; using WeldingCommon.Data.Services.History.Collection; namespace WeldingCommon.Data.Services.History { public class HistoryService : IHistoryWriter, IHistoryReader, IDisposable { private readonly int _maxCount; private readonly DispatcherTimer _pump; protected readonly object _lock = new object(); protected readonly Dictionary> _collections = new Dictionary>(); protected readonly Dictionary> _readOnly = new Dictionary>(); protected readonly Dictionary> _queues = new Dictionary>(); public HistoryService(int maxCount = 50, int pumpMs = 100) { _maxCount = maxCount; _pump = new DispatcherTimer(DispatcherPriority.Background) { Interval = TimeSpan.FromMilliseconds(pumpMs) }; _pump.Tick += Pump_Tick; _pump.Start(); } public IEnumerable Keys { get { lock (_lock) return _collections.Keys.ToList(); } } public ReadOnlyObservableCollection GetHistory(TKey key) { lock (_lock) { EnsureKey(key); return _readOnly[key]; } } public virtual void Append(TKey key, T snapshot) { lock (_lock) EnsureKey(key); _queues[key].Enqueue(snapshot); } public virtual void Clear(TKey key) { lock (_lock) { EnsureKey(key); _collections[key].Clear(); while (_queues[key].TryDequeue(out _)) ; } } /// /// 파생 클래스가 로드/초기화 시 컬렉션을 직접 채울 수 있게 제공 /// protected void Seed(TKey key, IEnumerable itemsNewestFirst) { lock (_lock) { EnsureKey(key); _collections[key].Clear(); // itemsNewestFirst: 최신이 앞이라고 가정 foreach (var item in itemsNewestFirst.Take(_maxCount)) { _collections[key].AddRolling(item); // RollingCollection 구현에 따라 PrependRolling로 바꿔도 됨 } } } private void EnsureKey(TKey key) { if (_collections.ContainsKey(key)) return; var col = new RollingCollection(_maxCount); _collections[key] = col; _readOnly[key] = new ReadOnlyObservableCollection(col); _queues[key] = new ConcurrentQueue(); } private void Pump_Tick(object sender, EventArgs e) { // UI thread에서 ObservableCollection 변경하도록 타이머 사용 List keys; lock (_lock) keys = _queues.Keys.ToList(); var anyAdded = false; foreach (var key in keys) { anyAdded |= Drain(_queues[key], _collections[key], maxPerTick: 30); } if (anyAdded) OnChanged(); // 파생 클래스 저장 트리거 포인트 } private bool Drain(ConcurrentQueue q, RollingCollection col, int maxPerTick) { var n = 0; var added = false; while (n < maxPerTick && q.TryDequeue(out var item)) { col.PrependRolling(item); // 최신이 위로 n++; added = true; } return added; } /// /// 변경 발생 시 호출됨 (Pump에서 실제 추가된 경우 + Clear 시) /// 파생 클래스에서 override 해서 저장/로그 등을 수행 /// protected virtual void OnChanged() { } public virtual void Dispose() { _pump.Stop(); _pump.Tick -= Pump_Tick; } } }