DataArray, DataObject pool 적용. 버그 수정. AGV 움직임 튀는 거 수정 필요

This commit is contained in:
logonkhi
2025-06-26 19:46:13 +09:00
parent aa9caab761
commit ad034d8246
19 changed files with 1420 additions and 497 deletions

View File

@@ -11,6 +11,13 @@ namespace UVC.Data
/// </summary>
public class DataArray : List<DataObject>, IDataObject
{
/// <summary>
/// 이 객체가 객체 풀에 있는지 여부를 나타냅니다.
/// 중복 반환을 방지하기 위해 DataArrayPool에서 내부적으로 사용됩니다.
/// </summary>
internal bool IsInPool { get; set; } = false;
// 추가 된 항목 목록
protected List<DataObject> addedList = new List<DataObject>();
// 제거 된 항목 목록
@@ -28,12 +35,6 @@ namespace UVC.Data
// 제거된 항목에 접근할 수 있는 읽기 전용 컬렉션
public ReadOnlyCollection<DataObject> ModifiedList => modifiedList.AsReadOnly();
/// <summary>
/// 변경 추적 활성화 여부
/// </summary>
public bool IsChangeTracking { get; set; } = false;
/// <summary>
/// 기본 생성자
/// </summary>
@@ -82,13 +83,43 @@ namespace UVC.Data
/// <param name="jArray">JSON 배열</param>
public DataArray(JArray jArray) : base()
{
if (jArray != null)
FromJArray(jArray);
}
public DataArray FromCapacity(int capacity)
{
Capacity = capacity;
return this;
}
public DataArray FromCollection(IEnumerable<DataObject> collection)
{
if (collection == null) return this;
foreach (var item in collection)
{
foreach (var item in jArray)
{
Add(ConvertToDataObject(item));
}
// base.Add를 사용하여 추적 로직을 우회하고 직접 추가합니다.
base.Add(item);
}
return this;
}
/// <summary>
/// JArray로부터 DataArray의 내용을 채웁니다.
/// </summary>
/// <param name="jArray">JSON 배열</param>
public DataArray FromJArray(JArray jArray)
{
if (jArray == null) return this;
foreach (var item in jArray)
{
// base.Add를 사용하여 추적 로직을 우회하고 직접 추가합니다.
base.Add(ConvertToDataObject(item));
}
return this;
}
/// <summary>
@@ -98,12 +129,15 @@ namespace UVC.Data
{
if (token.Type == JTokenType.Object)
{
return new DataObject((JObject)token);
// 풀에서 객체를 가져와 JObject로 초기화합니다.
var dataObject = DataObjectPool.Get();
dataObject.FromJObject((JObject)token);
return dataObject;
}
else
{
// JObject가 아닌 경우, 새 DataObject를 만들고 값을 넣어줍니다
var dataObject = new DataObject();
// JObject가 아닌 경우, 풀에서 새 DataObject를 가져와 값을 넣어줍니다.
var dataObject = DataObjectPool.Get();
dataObject.Add("value", ConvertJTokenToObject(token));
return dataObject;
}
@@ -122,10 +156,14 @@ namespace UVC.Data
case JTokenType.Boolean:
return token.ToObject<bool>();
case JTokenType.Object:
return new DataObject((JObject)token);
var dataObject = DataObjectPool.Get();
dataObject.FromJObject((JObject)token);
return dataObject;
case JTokenType.Array:
JArray array = (JArray)token;
return new DataArray(array);
var dataArray = DataArrayPool.Get();
dataArray.FromJArray(array);
return dataArray;
default:
return token.ToString();
}
@@ -135,7 +173,7 @@ namespace UVC.Data
/// 모든 아이템이 추가 된것으로 표시합니다.
/// 전체 데이터가 갱신되었을 때 사용합니다.
/// </summary>
public void InitData()
public void MarkAllAsUpdated()
{
addedList.Clear();
addedList.AddRange(this);
@@ -149,56 +187,57 @@ namespace UVC.Data
public void UpdateDifferent(IDataObject other)
{
if (other == null) return;
if (other is DataArray otherArray)
if (!(other is DataArray otherArray)) return;
// 기존 변경 추적 목록을 초기화합니다.
ClearTrackedChanges();
// 성능 향상을 위해 ID를 키로 사용하는 사전을 생성합니다.
var thisDict = this.ToDictionary(item => item.Id);
var otherDict = otherArray.ToDictionary(item => item.Id);
var thisIds = new HashSet<string>(thisDict.Keys);
var otherIds = new HashSet<string>(otherDict.Keys);
// 제거된 항목 확인 (현재 배열에는 있지만 다른 배열에는 없는 항목)
foreach (var id in thisIds.Where(id => !otherIds.Contains(id)))
{
addedList.Clear();
removedList.Clear();
modifiedList.Clear();
removedList.Add(thisDict[id]);
}
// Id 기준으로 객체들을 비교하기 위한 사전 생성
var thisDict = this.ToDictionary(item => item.Id, item => item);
var otherDict = otherArray.ToDictionary(item => item.Id, item => item);
// 추가된 항목 확인 (다른 배열에는 있지만 현재 배열에는 없는 항목)
foreach (var id in otherIds.Where(id => !thisIds.Contains(id)))
{
addedList.Add(otherDict[id]);
}
// 제거된 항목 확인 (현재 배열에는 있지만 other에는 없는 항목)
foreach (var id in thisDict.Keys.Where(id => !otherDict.ContainsKey(id)))
// 수정된 항목 확인 (양쪽 모두에 있지만 내용이 다른 항목)
foreach (var id in thisIds.Where(id => otherIds.Contains(id)))
{
var thisItem = thisDict[id];
var otherItem = otherDict[id];
// [성능 개선] ToString() 비교는 매우 비효율적입니다.
// DataObject.UpdateDifferent를 직접 호출하여 변경 사항을 적용하고,
// UpdatedCount를 통해 실제 변경 여부를 확인합니다.
thisItem.UpdateDifferent(otherItem);
if (thisItem.UpdatedCount > 0)
{
removedList.Add(thisDict[id]);
modifiedList.Add(thisItem);
}
}
// 추가된 항목 확인 (other에는 있지만 현재 배열에는 없는 항목)
foreach (var id in otherDict.Keys.Where(id => !thisDict.ContainsKey(id)))
{
addedList.Add(otherDict[id]);
}
// [성능 개선] RemoveAll과 HashSet을 사용하여 제거 작업의 효율성을 높입니다.
if (removedList.Count > 0)
{
var removedItemIds = new HashSet<string>(removedList.Select(i => i.Id));
this.RemoveAll(item => removedItemIds.Contains(item.Id));
}
// 수정된 항목 확인 (양쪽 모두에 있지만 내용이 다른 항목)
foreach (var id in thisDict.Keys.Where(id => otherDict.ContainsKey(id)))
{
var thisItem = thisDict[id];
var otherItem = otherDict[id];
if (!thisItem.ToString().Equals(otherItem.ToString()))
{
modifiedList.Add(thisItem);
thisItem.UpdateDifferent(otherItem);
}
}
// 실제 컬렉션 업데이트
// 현재 배열에서 제거된 항목들을 제거
for (int i = this.Count - 1; i >= 0; i--)
{
if (removedList.Contains(this[i]))
{
this.RemoveAt(i);
}
}
// 추가된 항목들을 현재 배열에 추가
foreach (var item in addedList)
{
this.Add(item);
}
// [성능 개선] base.AddRange를 사용하여 추적 로직을 우회하고 효율적으로 추가합니다.
if (addedList.Count > 0)
{
base.AddRange(addedList);
}
}
@@ -209,7 +248,34 @@ namespace UVC.Data
/// <returns></returns>
public IDataObject GetUpdatedObject()
{
return this;
// 풀에서 새 DataArray 인스턴스를 가져옵니다.
var clone = DataArrayPool.Get();
clone.FromCapacity(this.Count);
// 배열의 모든 DataObject를 순회하며 각각을 복제합니다.
foreach (var item in this)
{
// DataObject의 Clone 메서드를 호출하여 깊은 복사를 수행하고,
// base.Add를 사용해 추적 로직 없이 직접 추가합니다.
if (item.GetUpdatedObject() is DataObject updatedObject)
{
clone.Add(updatedObject);
if (addedList.Contains(item))
{
clone.addedList.Add(updatedObject);
}
else if (removedList.Contains(item))
{
clone.removedList.Add(updatedObject);
}
else if (modifiedList.Contains(item))
{
clone.modifiedList.Add(updatedObject);
}
}
}
return clone;
}
/// <summary>
@@ -217,57 +283,6 @@ namespace UVC.Data
/// </summary>
/// <returns>업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면 0을 반환합니다.</returns>
public int UpdatedCount { get => addedList.Count + modifiedList.Count + removedList.Count; }
/// <summary>
/// 항목을 추가합니다.
/// </summary>
/// <param name="item">추가할 항목</param>
public new void Add(DataObject item)
{
base.Add(item);
// 변경 사항을 추적하고 이벤트 발생
if (IsChangeTracking)
{
int index = Count - 1;
addedList.Add(item);
}
}
/// <summary>
/// 항목을 제거합니다.
/// </summary>
/// <param name="item">제거할 항목</param>
/// <returns>제거 성공 여부</returns>
public new bool Remove(DataObject item)
{
int index = IndexOf(item);
if (index >= 0)
{
RemoveAt(index);
return true;
}
return false;
}
/// <summary>
/// 지정된 인덱스의 항목을 제거합니다.
/// </summary>
/// <param name="index">제거할 항목의 인덱스</param>
public new void RemoveAt(int index)
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
DataObject removedItem = this[index];
base.RemoveAt(index);
if (IsChangeTracking)
{
removedList.Add(removedItem);
}
}
/// <summary>
/// 컬렉션의 모든 항목을 제거합니다.
@@ -276,101 +291,87 @@ namespace UVC.Data
{
if (Count > 0)
{
var oldItems = new List<DataObject>(this);
ReturnToDataObjectPool();
base.Clear();
if (IsChangeTracking)
{
// 모든 항목을 제거 목록에 추가
removedList.AddRange(oldItems);
}
}
}
/// <summary>
/// 지정된 인덱스에 항목을 삽입합니다.
/// 객체를 풀에 반환하기 전에 초기 상태로 리셋합니다.
/// </summary>
/// <param name="index">삽입할 위치</param>
/// <param name="item">삽입할 항목</param>
public new void Insert(int index, DataObject item)
public void Reset()
{
base.Insert(index, item);
// 포함된 DataObject들을 먼저 풀에 반환합니다.
ReturnToDataObjectPool();
if (IsChangeTracking)
{
addedList.Add(item);
}
// 내부 리스트와 변경 추적 리스트를 모두 비웁니다.
base.Clear();
ClearTrackedChanges();
}
public void ReturnToPool()
{
DataArrayPool.Return(this);
}
/// <summary>
/// 지정된 인덱스의 항목을 교체합니다.
/// 이 DataArray에 포함된 모든 DataObject를 풀에 반환하고 리스트를 비웁니다.
/// DataArray의 사용이 끝났을 때 호출해야 합니다.
/// </summary>
/// <param name="index">교체할 위치</param>
/// <param name="item">새 항목</param>
public new DataObject this[int index]
private void ReturnToDataObjectPool()
{
get { return base[index]; }
set
foreach (var item in this)
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
DataObject oldItem = base[index];
base[index] = value;
if (IsChangeTracking)
{
addedList.Add(value);
removedList.Add(oldItem);
}
DataObjectPool.Return(item);
}
}
/// <summary>
/// 범위의 항목을 추가합니다.
/// </summary>
/// <param name="collection">추가할 항목의 컬렉션</param>
public new void AddRange(IEnumerable<DataObject> collection)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
int startIndex = Count;
base.AddRange(collection);
if (IsChangeTracking)
{
var items = collection.ToList();
for (int i = 0; i < items.Count; i++)
{
addedList.Add(items[i]);
}
}
}
/// <summary>
/// 변경된 인덱스 목록을 초기화합니다.
/// </summary>
public void ResetChangedIndices()
public void ClearTrackedChanges()
{
addedList.Clear();
removedList.Clear();
modifiedList.Clear();
}
/// <summary>
/// 변경 내역을 저장하지 않고 항목을 추가합니다.
/// 동일한 상태와 값을 가진 현재 데이터 객체의 새 인스턴스를 생성합니다.
/// </summary>
/// <param name="item">추가할 항목</param>
public void AddWithoutTracking(DataObject item)
/// <remarks>복제된 객체는 원본 객체와 독립적이므로, 한 객체를 변경해도 다른 객체에는 영향을 미치지 않습니다.
///</remarks>
/// <returns>현재 객체의 복사본인 새 <see cref="IDataObject"/> 인스턴스를 반환합니다.</returns>
public IDataObject Clone()
{
bool oldTracking = IsChangeTracking;
IsChangeTracking = false;
Add(item);
IsChangeTracking = oldTracking;
// 풀에서 새 DataArray 인스턴스를 가져옵니다.
var clone = DataArrayPool.Get();
clone.FromCapacity(this.Count);
// 배열의 모든 DataObject를 순회하며 각각을 복제합니다.
foreach (var item in this)
{
// DataObject의 Clone 메서드를 호출하여 깊은 복사를 수행하고,
// base.Add를 사용해 추적 로직 없이 직접 추가합니다.
if (item.Clone() is DataObject clonedItem)
{
clone.Add(clonedItem);
if (addedList.Contains(item))
{
clone.addedList.Add(clonedItem);
}
else if (removedList.Contains(item))
{
clone.removedList.Add(clonedItem);
}
else if (modifiedList.Contains(item))
{
clone.modifiedList.Add(clonedItem);
}
}
}
return clone;
}
@@ -400,7 +401,9 @@ namespace UVC.Data
/// </summary>
public static implicit operator DataArray(JArray jArray)
{
return new DataArray(jArray);
var dataArray = DataArrayPool.Get();
dataArray.FromJArray(jArray);
return dataArray;
}
}
}