130 lines
4.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|