2026-06-05 13:44:39 +09:00

130 lines
4.3 KiB
C#

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<TKey, T> : IHistoryWriter<TKey, T>, IHistoryReader<TKey, T>, IDisposable
{
private readonly int _maxCount;
private readonly DispatcherTimer _pump;
protected readonly object _lock = new object();
protected readonly Dictionary<TKey, RollingCollection<T>> _collections = new Dictionary<TKey, RollingCollection<T>>();
protected readonly Dictionary<TKey, ReadOnlyObservableCollection<T>> _readOnly = new Dictionary<TKey, ReadOnlyObservableCollection<T>>();
protected readonly Dictionary<TKey, ConcurrentQueue<T>> _queues = new Dictionary<TKey, ConcurrentQueue<T>>();
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<TKey> Keys {
get {
lock (_lock)
return _collections.Keys.ToList();
}
}
public ReadOnlyObservableCollection<T> 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 _)) ;
}
}
/// <summary>
/// 파생 클래스가 로드/초기화 시 컬렉션을 직접 채울 수 있게 제공
/// </summary>
protected void Seed(TKey key, IEnumerable<T> 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<T>(_maxCount);
_collections[key] = col;
_readOnly[key] = new ReadOnlyObservableCollection<T>(col);
_queues[key] = new ConcurrentQueue<T>();
}
private void Pump_Tick(object sender, EventArgs e)
{
// UI thread에서 ObservableCollection 변경하도록 타이머 사용
List<TKey> 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<T> q, RollingCollection<T> 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;
}
/// <summary>
/// 변경 발생 시 호출됨 (Pump에서 실제 추가된 경우 + Clear 시)
/// 파생 클래스에서 override 해서 저장/로그 등을 수행
/// </summary>
protected virtual void OnChanged() { }
public virtual void Dispose()
{
_pump.Stop();
_pump.Tick -= Pump_Tick;
}
}
}