DataArray, DataObject pool 적용. 버그 수정. AGV 움직임 튀는 거 수정 필요
This commit is contained in:
@@ -55,10 +55,10 @@ namespace SampleProject
|
||||
mqttPipeLine.Execute();
|
||||
|
||||
//10초 후 정지
|
||||
UniTask.Delay(TimeSpan.FromSeconds(10)).ContinueWith(() =>
|
||||
{
|
||||
mqttPipeLine.Stop();
|
||||
});
|
||||
//UniTask.Delay(TimeSpan.FromSeconds(10)).ContinueWith(() =>
|
||||
//{
|
||||
// mqttPipeLine.Stop();
|
||||
//});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Assets/Scripts/UVC/Data/DataArrayPool.cs
Normal file
111
Assets/Scripts/UVC/Data/DataArrayPool.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using RTG;
|
||||
using System.Collections.Concurrent;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// DataArray 객체의 재사용을 위한 풀링 클래스입니다.
|
||||
/// </summary>
|
||||
public static class DataArrayPool
|
||||
{
|
||||
private static readonly ConcurrentQueue<DataArray> _pool = new ConcurrentQueue<DataArray>();
|
||||
private static int _maxPoolSize = 500; // DataArray는 DataObject보다 수가 적을 것이므로 기본값 조정
|
||||
|
||||
// --- 통계용 필드 ---
|
||||
private static int _inUseCount = 0;
|
||||
private static int _peakUsage = 0;
|
||||
private static int _poolMisses = 0;
|
||||
private static readonly object _statsLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 풀의 최대 크기를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="size">설정할 최대 크기</param>
|
||||
public static void SetMaxPoolSize(int size)
|
||||
{
|
||||
_maxPoolSize = size > 0 ? size : 500;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀에서 DataArray 객체를 가져옵니다. 풀이 비어있으면 새로 생성합니다.
|
||||
/// </summary>
|
||||
/// <returns>재사용 가능한 DataArray 객체</returns>
|
||||
public static DataArray Get()
|
||||
{
|
||||
bool fromPool = _pool.TryDequeue(out var array);
|
||||
|
||||
lock (_statsLock)
|
||||
{
|
||||
_inUseCount++;
|
||||
if (_inUseCount > _peakUsage)
|
||||
{
|
||||
_peakUsage = _inUseCount;
|
||||
}
|
||||
|
||||
if (!fromPool)
|
||||
{
|
||||
_poolMisses++;
|
||||
if (_inUseCount > _maxPoolSize)
|
||||
{
|
||||
int oldSize = _maxPoolSize;
|
||||
_maxPoolSize += 100; // 증가량 조정
|
||||
Debug.Log($"DataArrayPool size automatically increased from {oldSize} to {_maxPoolSize}. Peak usage: {_peakUsage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(_peakUsage % 100 == 0) Debug.Log($"DataArrayPool stats: {GetStats()}");
|
||||
|
||||
if (fromPool)
|
||||
{
|
||||
array.IsInPool = false;
|
||||
array.Reset();
|
||||
}
|
||||
return fromPool ? array : new DataArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 사용이 끝난 DataArray 객체를 풀에 반환합니다.
|
||||
/// </summary>
|
||||
/// <param name="array">반환할 DataArray 객체</param>
|
||||
public static void Return(DataArray array)
|
||||
{
|
||||
if (array == null || array.IsInPool) return;
|
||||
|
||||
lock (_statsLock)
|
||||
{
|
||||
_inUseCount--;
|
||||
}
|
||||
|
||||
if (_pool.Count < _maxPoolSize)
|
||||
{
|
||||
array.Reset();
|
||||
array.IsInPool = true;
|
||||
_pool.Enqueue(array);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 풀의 현재 성능 통계를 문자열로 반환합니다.
|
||||
/// </summary>
|
||||
/// <returns>풀 통계 (최대 사용량, 현재 사용량, 풀 비어있을 때 생성 횟수, 현재 풀 크기)</returns>
|
||||
public static string GetStats()
|
||||
{
|
||||
return $"Peak Usage: {_peakUsage}, In Use: {_inUseCount}, Misses: {_poolMisses}, Pooled: {_pool.Count}, Max Size: {_maxPoolSize}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀 통계를 초기화합니다.
|
||||
/// </summary>
|
||||
public static void ResetStats()
|
||||
{
|
||||
lock (_statsLock)
|
||||
{
|
||||
_peakUsage = 0;
|
||||
_poolMisses = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Data/DataArrayPool.cs.meta
Normal file
2
Assets/Scripts/UVC/Data/DataArrayPool.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b61a9a50cc22ac946aa9e175cb06451c
|
||||
@@ -249,12 +249,21 @@ namespace UVC.Data
|
||||
/// </example>
|
||||
private DataObject MapObject(JObject sourceObject, DataMask maskObject, int depth = 0, string path = "")
|
||||
{
|
||||
if (maskObject == null) return new DataObject(sourceObject);
|
||||
if (maskObject == null)
|
||||
{
|
||||
var dataObj = DataObjectPool.Get();
|
||||
dataObj.FromJObject(sourceObject);
|
||||
return dataObj;
|
||||
}
|
||||
|
||||
// Mask가 비어있으면 원본 객체를 그대로 사용, 깊이 제한에 도달하면 간소화된 처리
|
||||
if (maskObject.Count == 0 || depth >= maxRecursionDepth)
|
||||
{
|
||||
DataObject dObj = new DataObject() { IdKey = maskObject.ObjectIdKey, Name = maskObject.ObjectName };
|
||||
DataObject dObj = DataObjectPool.Get();
|
||||
dObj.IdKey = maskObject.ObjectIdKey;
|
||||
dObj.Name = maskObject.ObjectName;
|
||||
|
||||
|
||||
// 속성 이름 변환 처리
|
||||
foreach (var property in sourceObject.Properties())
|
||||
{
|
||||
@@ -268,7 +277,9 @@ namespace UVC.Data
|
||||
return dObj;
|
||||
}
|
||||
|
||||
DataObject target = new DataObject() { IdKey = maskObject.ObjectIdKey, Name = maskObject.ObjectName };
|
||||
DataObject target = DataObjectPool.Get();
|
||||
target.IdKey = maskObject.ObjectIdKey;
|
||||
target.Name = maskObject.ObjectName;
|
||||
|
||||
foreach (var property in sourceObject.Properties())
|
||||
{
|
||||
@@ -352,11 +363,11 @@ namespace UVC.Data
|
||||
}
|
||||
else if (maskValue is DataMask)
|
||||
{
|
||||
target[propertyName] = new DataObject();
|
||||
target[propertyName] = DataObjectPool.Get();
|
||||
}
|
||||
else if (maskValue is List<DataMask>)
|
||||
{
|
||||
target[propertyName] = new DataArray();
|
||||
target[propertyName] = DataArrayPool.Get();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -380,12 +391,14 @@ namespace UVC.Data
|
||||
switch (token.Type)
|
||||
{
|
||||
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;
|
||||
if (array.All(item => item.Type == JTokenType.Object))
|
||||
{
|
||||
return new DataArray(array);
|
||||
return DataArrayPool.Get().FromJArray(array);
|
||||
}
|
||||
return array.ToObject<object[]>();
|
||||
case JTokenType.Integer:
|
||||
@@ -445,7 +458,7 @@ namespace UVC.Data
|
||||
private DataArray MapArray(JArray sourceArray, List<DataMask> maskTemplates, string path = "")
|
||||
{
|
||||
// 빠른 초기 크기 할당으로 재할당 방지
|
||||
DataArray targetArray = new DataArray(sourceArray.Count);
|
||||
DataArray targetArray = DataArrayPool.Get().FromCapacity(sourceArray.Count);
|
||||
|
||||
// 특정 크기 이상일 경우 병렬 처리 적용
|
||||
if (sourceArray.Count > parallelProcessingThreshold && maskTemplates.Count <= 1)
|
||||
@@ -465,12 +478,14 @@ namespace UVC.Data
|
||||
{
|
||||
if (sourceItem.Type == JTokenType.Object)
|
||||
{
|
||||
targetArray.Add(new DataObject((JObject)sourceItem));
|
||||
var dataObject = DataObjectPool.Get();
|
||||
dataObject.FromJObject((JObject)sourceItem);
|
||||
targetArray.Add(dataObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
// DataObject가 아닌 경우, 새 DataObject를 만들고 값을 넣어줍니다
|
||||
var dataObject = new DataObject();
|
||||
var dataObject = DataObjectPool.Get();
|
||||
dataObject.Add("value", ConvertJTokenToObject(sourceItem));
|
||||
targetArray.Add(dataObject);
|
||||
}
|
||||
@@ -481,7 +496,7 @@ namespace UVC.Data
|
||||
ULog.Warning($"배열 항목 변환 오류 - 경로: {itemPath}", ex);
|
||||
|
||||
// 오류 발생 시 빈 객체 추가
|
||||
targetArray.Add(new DataObject());
|
||||
targetArray.Add(DataObjectPool.Get());
|
||||
}
|
||||
}
|
||||
return targetArray;
|
||||
@@ -524,14 +539,14 @@ namespace UVC.Data
|
||||
{
|
||||
// DataArray는 DataObject만 담을 수 있으므로, 배열을 처리하는 방식을 변경해야 함
|
||||
var nestedArray = MapArray((JArray)sourceItem, maskTemplates, itemPath);
|
||||
var container = new DataObject();
|
||||
var container = DataObjectPool.Get();
|
||||
container.Add("items", nestedArray);
|
||||
targetArray.Add(container);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 단순 값을 DataObject로 래핑
|
||||
var dataObject = new DataObject();
|
||||
var dataObject = DataObjectPool.Get();
|
||||
dataObject.Add("value", ConvertJTokenToObject(sourceItem));
|
||||
targetArray.Add(dataObject);
|
||||
}
|
||||
@@ -542,7 +557,7 @@ namespace UVC.Data
|
||||
ULog.Warning($"배열 항목 변환 오류 - 경로: {itemPath}", ex);
|
||||
|
||||
// 오류 발생 시 빈 객체 추가
|
||||
targetArray.Add(new DataObject());
|
||||
targetArray.Add(DataObjectPool.Get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,7 +569,7 @@ namespace UVC.Data
|
||||
/// </summary>
|
||||
private DataArray MapArrayInParallel(JArray sourceArray, List<DataMask> maskTemplates, string path)
|
||||
{
|
||||
DataArray targetArray = new DataArray(sourceArray.Count);
|
||||
DataArray targetArray = DataArrayPool.Get().FromCapacity(sourceArray.Count);
|
||||
DataMask maskTemplate = maskTemplates.Count > 0 ? maskTemplates[0] : new DataMask();
|
||||
|
||||
var parallelOptions = new ParallelOptions
|
||||
@@ -581,7 +596,7 @@ namespace UVC.Data
|
||||
else
|
||||
{
|
||||
// 단순 값 처리
|
||||
var dataObject = new DataObject();
|
||||
var dataObject = DataObjectPool.Get();
|
||||
dataObject.Add("value", ConvertJTokenToObject(sourceItem));
|
||||
results[i] = dataObject;
|
||||
}
|
||||
@@ -595,7 +610,7 @@ namespace UVC.Data
|
||||
ULog.Warning($"병렬 배열 처리 오류 - 경로: {itemPath}", ex);
|
||||
|
||||
// 오류 발생 시 빈 객체 생성
|
||||
results[i] = new DataObject();
|
||||
results[i] = DataObjectPool.Get();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -618,7 +633,7 @@ namespace UVC.Data
|
||||
/// </summary>
|
||||
private DataArray MapArraySequential(JArray sourceArray, List<DataMask> maskTemplates, string path)
|
||||
{
|
||||
DataArray targetArray = new DataArray(sourceArray.Count);
|
||||
DataArray targetArray = DataArrayPool.Get().FromCapacity(sourceArray.Count);
|
||||
DataMask maskTemplate = maskTemplates.Count > 0 ? maskTemplates[0] : new DataMask();
|
||||
|
||||
for (int i = 0; i < sourceArray.Count; i++)
|
||||
@@ -634,7 +649,7 @@ namespace UVC.Data
|
||||
}
|
||||
else
|
||||
{
|
||||
var dataObject = new DataObject();
|
||||
var dataObject = DataObjectPool.Get();
|
||||
dataObject.Add("value", ConvertJTokenToObject(sourceItem));
|
||||
targetArray.Add(dataObject);
|
||||
}
|
||||
@@ -643,7 +658,7 @@ namespace UVC.Data
|
||||
{
|
||||
conversionErrors[itemPath] = ex;
|
||||
ULog.Warning($"순차 배열 처리 오류 - 경로: {itemPath}", ex);
|
||||
targetArray.Add(new DataObject());
|
||||
targetArray.Add(DataObjectPool.Get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -978,9 +993,9 @@ namespace UVC.Data
|
||||
catch
|
||||
{
|
||||
// 배열 파싱 실패시 단일 항목 배열로 처리
|
||||
var dataObject = new DataObject();
|
||||
var dataObject = DataObjectPool.Get();
|
||||
dataObject.Add("value", sourceValue.ToString());
|
||||
var array = new DataArray(1);
|
||||
var array = DataArrayPool.Get().FromCapacity(1);
|
||||
array.Add(dataObject);
|
||||
target[propertyName] = array;
|
||||
}
|
||||
@@ -988,9 +1003,9 @@ namespace UVC.Data
|
||||
else
|
||||
{
|
||||
// 다른 타입은 단일 항목 배열로 변환
|
||||
var dataObject = new DataObject();
|
||||
var dataObject = DataObjectPool.Get();
|
||||
dataObject.Add("value", ConvertJTokenToObject(sourceValue));
|
||||
var array = new DataArray(1);
|
||||
var array = DataArrayPool.Get().FromCapacity(1);
|
||||
array.Add(dataObject);
|
||||
target[propertyName] = array;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,9 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UVC.Extention;
|
||||
using UVC.Log;
|
||||
|
||||
namespace UVC.Data
|
||||
{
|
||||
@@ -50,13 +47,33 @@ namespace UVC.Data
|
||||
/// // - password는 제외됨
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class DataObject : OrderedDictionary<string, object>, IDataObject
|
||||
public class DataObject : OrderedDictionary<string, object?>, IDataObject
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 이 객체가 객체 풀에 있는지 여부를 나타냅니다.
|
||||
/// 중복 반환을 방지하기 위해 DataObjectPool에서 내부적으로 사용됩니다.
|
||||
/// </summary>
|
||||
internal bool IsInPool { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 객체의 고유 식별자를 나타내는 속성입니다. DataArray에서 데이터를 식별하는 데 사용됩니다.
|
||||
/// </summary>
|
||||
public string Id { get => (IdKey != null && ContainsKey(IdKey)) ? this[IdKey].ToString() : this.First().Value.ToString(); }
|
||||
public string Id
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IdKey != null && ContainsKey(IdKey))
|
||||
{
|
||||
return this[IdKey]?.ToString() ?? string.Empty;
|
||||
}
|
||||
if (Count > 0)
|
||||
{
|
||||
return this.First().Value?.ToString() ?? string.Empty;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Id에 해당하는 key 문자열
|
||||
@@ -72,15 +89,15 @@ namespace UVC.Data
|
||||
/// <summary>
|
||||
/// 직접적인 변경이 있었던 키를 저장하는 리스트입니다.
|
||||
/// </summary>
|
||||
protected List<string> changedProperies = new List<string>();
|
||||
protected HashSet<string> changedProperies = new HashSet<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 변경된 속성의 키 목록을 읽기 전용으로 반환합니다.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<string> ChangedProperies => changedProperies.AsReadOnly();
|
||||
public IReadOnlyCollection<string> ChangedProperies => changedProperies;
|
||||
|
||||
// 기존에 있던 속성 키를 추적하기 위한 집합
|
||||
private HashSet<string> existingProperties = new HashSet<string>();
|
||||
// 객체 생성 중에는 변경 추적을 비활성화하기 위한 플래그
|
||||
private bool _isInitializing = false;
|
||||
|
||||
/// <summary>
|
||||
/// 기본 생성자입니다. 빈 데이터 객체를 생성합니다.
|
||||
@@ -93,12 +110,19 @@ namespace UVC.Data
|
||||
/// <param name="other">복사할 JObject 객체</param>
|
||||
public DataObject(JObject other)
|
||||
{
|
||||
// JObject로부터 속성을 복사
|
||||
foreach (var prop in other.Properties())
|
||||
_isInitializing = true;
|
||||
try
|
||||
{
|
||||
// JToken을 object로 변환
|
||||
this[prop.Name] = ConvertJTokenToObject(prop.Value);
|
||||
existingProperties.Add(prop.Name);
|
||||
// JObject로부터 속성을 복사
|
||||
foreach (var prop in other.Properties())
|
||||
{
|
||||
// 인덱서를 사용하여 JToken을 객체로 변환하고 추가
|
||||
this[prop.Name] = ConvertJTokenToObject(prop.Value);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isInitializing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,14 +130,78 @@ namespace UVC.Data
|
||||
/// Dictionary로 데이터 객체를 초기화합니다.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">초기화에 사용할 Dictionary 객체</param>
|
||||
public DataObject(Dictionary<string, object> dictionary) : base()
|
||||
public DataObject(Dictionary<string, object?> dictionary) : base()
|
||||
{
|
||||
// 생성자에서 초기 속성들을 기존 속성으로 등록
|
||||
foreach (var key in dictionary.Keys)
|
||||
_isInitializing = true;
|
||||
try
|
||||
{
|
||||
Add(key, dictionary[key]);
|
||||
existingProperties.Add(key);
|
||||
// 생성자에서 초기 속성들을 등록
|
||||
foreach (var item in dictionary)
|
||||
{
|
||||
this[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isInitializing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 <see cref="JObject"/>의 속성과 값으로 현재 객체를 채웁니다.
|
||||
/// </summary>
|
||||
/// <remarks>이 메서드는 제공된 <see cref="JObject"/>의 모든 속성을 반복하고
|
||||
/// 해당 값을 현재 객체의 해당 속성에 할당합니다. 현재 객체에 속성이 없는 경우
|
||||
/// 동적으로 추가됩니다. 이 메서드는 채우기 프로세스 중 적절한 처리를 보장하기 위해 내부 초기화
|
||||
/// 플래그를 임시로 설정합니다.</remarks>
|
||||
/// <param name="other">복사할 속성과 값이 포함된 <see cref="JObject"/>입니다.</param>
|
||||
public DataObject FromJObject(JObject other)
|
||||
{
|
||||
IdKey = null; // IdKey를 초기화합니다.
|
||||
Name = string.Empty; // Name을 초기화합니다.
|
||||
_isInitializing = true;
|
||||
try
|
||||
{
|
||||
// JObject로부터 속성을 복사
|
||||
foreach (var prop in other.Properties())
|
||||
{
|
||||
// 인덱서를 사용하여 JToken을 객체로 변환하고 추가
|
||||
this[prop.Name] = ConvertJTokenToObject(prop.Value);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isInitializing = false;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 사전의 키-값 쌍으로 현재 객체를 채웁니다.
|
||||
/// </summary>
|
||||
/// <remarks>이 메서드는 제공된 사전을 기반으로 객체의 속성을 업데이트합니다.
|
||||
/// 사전의 키가 속성 이름과 일치하면 속성 값이 해당 값으로 설정됩니다.
|
||||
/// 사전에 예상되는 속성 유형과 일치하는 유효한 키와 값이 포함되어 있는지 확인합니다.</remarks>
|
||||
/// <param name="dictionary">객체의 속성을 초기화하는 키-값 쌍을 포함하는 사전입니다. 키는 속성
|
||||
/// 이름을 나타내고 값은 해당 속성 값을 나타냅니다.</param>
|
||||
public DataObject FromDictionary(Dictionary<string, object?> dictionary)
|
||||
{
|
||||
IdKey = null; // IdKey를 초기화합니다.
|
||||
Name = string.Empty; // Name을 초기화합니다.
|
||||
_isInitializing = true;
|
||||
try
|
||||
{
|
||||
// 생성자에서 초기 속성들을 등록
|
||||
foreach (var item in dictionary)
|
||||
{
|
||||
this[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isInitializing = false;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -121,7 +209,7 @@ namespace UVC.Data
|
||||
/// </summary>
|
||||
/// <param name="token">변환할 JToken 객체</param>
|
||||
/// <returns>변환된 C# 객체</returns>
|
||||
private object ConvertJTokenToObject(JToken token)
|
||||
private object? ConvertJTokenToObject(JToken token)
|
||||
{
|
||||
switch (token.Type)
|
||||
{
|
||||
@@ -134,10 +222,12 @@ 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);
|
||||
return DataArrayPool.Get().FromJArray(array);
|
||||
default:
|
||||
return token.ToString();
|
||||
}
|
||||
@@ -147,33 +237,10 @@ namespace UVC.Data
|
||||
/// 모든 프로퍼티를 변경된 것으로 표시합니다.
|
||||
/// 전체 데이터가 갱신되었을 때 사용합니다.
|
||||
/// </summary>
|
||||
public void InitData()
|
||||
public void MarkAllAsUpdated()
|
||||
{
|
||||
changedProperies.Clear();
|
||||
changedProperies.AddRange(this.Keys);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 속성이 변경될 때 호출되는 메서드입니다.
|
||||
/// 파생 클래스에서 오버라이드하여 추가 동작을 정의할 수 있습니다.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">변경된 속성의 이름</param>
|
||||
protected virtual void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
// 기존에 존재하던 속성인 경우에만 변경된 것으로 추적
|
||||
if (existingProperties.Contains(propertyName))
|
||||
{
|
||||
if (!changedProperies.Contains(propertyName))
|
||||
{
|
||||
changedProperies.Add(propertyName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 새로 추가된 속성은 기존 속성 목록에 추가
|
||||
existingProperties.Add(propertyName);
|
||||
}
|
||||
changedProperies.UnionWith(this.Keys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -183,17 +250,8 @@ namespace UVC.Data
|
||||
/// <param name="value">속성의 값</param>
|
||||
public new void Add(string propertyName, object value)
|
||||
{
|
||||
// 추가하기 전에 확인 - 속성이 이미 있는지 확인
|
||||
bool isExisting = ContainsKey(propertyName);
|
||||
|
||||
// 기본 구현 호출
|
||||
base.Add(propertyName, value);
|
||||
|
||||
// 새로 추가된 속성이면 기존 속성 목록에 추가
|
||||
if (!isExisting)
|
||||
{
|
||||
existingProperties.Add(propertyName);
|
||||
}
|
||||
// 인덱서를 사용하여 일관된 변경 추적 로직을 보장합니다.
|
||||
this[propertyName] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,32 +260,26 @@ namespace UVC.Data
|
||||
/// </summary>
|
||||
/// <param name="key">접근할 속성의 키</param>
|
||||
/// <returns>속성값</returns>
|
||||
public new object this[string key]
|
||||
public new object? this[string key]
|
||||
{
|
||||
get => base[key];
|
||||
set
|
||||
{
|
||||
// 속성 설정 전에 기존에 있는 속성인지 확인
|
||||
bool isExisting = ContainsKey(key) || existingProperties.Contains(key);
|
||||
// 키가 새로 추가되거나 기존 값이 변경되었는지 확인합니다.
|
||||
bool hasChanged = !TryGetValue(key, out object? oldValue) || !Equals(oldValue, value);
|
||||
|
||||
// 기본 구현 호출
|
||||
base[key] = value;
|
||||
|
||||
// 기존 속성이었다면 변경된 것으로 처리
|
||||
if (isExisting)
|
||||
if (hasChanged)
|
||||
{
|
||||
if (!changedProperies.Contains(key))
|
||||
// 기본 딕셔너리에 값을 설정합니다.
|
||||
base[key] = value;
|
||||
|
||||
// 객체 초기화 중이 아닐 때만 변경 사항을 추적합니다.
|
||||
if (!_isInitializing)
|
||||
{
|
||||
changedProperies.Add(key);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 새로 추가된 속성은 기존 속성으로 등록
|
||||
existingProperties.Add(key);
|
||||
}
|
||||
|
||||
OnPropertyChanged(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +291,7 @@ namespace UVC.Data
|
||||
/// <returns>변환된 정수 값 또는 기본값</returns>
|
||||
public int GetInt(string propertyName, int defaultValue = 0)
|
||||
{
|
||||
if (TryGetValue(propertyName, out object value) && value != null)
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
if (value is int intValue)
|
||||
return intValue;
|
||||
@@ -256,7 +308,7 @@ namespace UVC.Data
|
||||
/// <returns>변환된 문자열 값 또는 기본값</returns>
|
||||
public string? GetString(string propertyName, string? defaultValue = null)
|
||||
{
|
||||
if (TryGetValue(propertyName, out object value) && value != null)
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
return value.ToString();
|
||||
}
|
||||
@@ -271,7 +323,7 @@ namespace UVC.Data
|
||||
/// <returns>변환된 불리언 값 또는 기본값</returns>
|
||||
public bool GetBool(string propertyName, bool defaultValue = false)
|
||||
{
|
||||
if (TryGetValue(propertyName, out object value) && value != null)
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
return boolValue;
|
||||
@@ -288,7 +340,7 @@ namespace UVC.Data
|
||||
/// <returns>변환된 부동 소수점 값 또는 기본값</returns>
|
||||
public float GetFloat(string propertyName, float defaultValue = 0f)
|
||||
{
|
||||
if (TryGetValue(propertyName, out object value) && value != null)
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
if (value is float floatValue)
|
||||
return floatValue;
|
||||
@@ -305,7 +357,7 @@ namespace UVC.Data
|
||||
/// <returns>변환된 더블 값 또는 기본값</returns>
|
||||
public double GetDouble(string propertyName, double defaultValue = 0.0)
|
||||
{
|
||||
if (TryGetValue(propertyName, out object value) && value != null)
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
if (value is double doubleValue)
|
||||
return doubleValue;
|
||||
@@ -315,14 +367,20 @@ namespace UVC.Data
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 문자열을 배정밀도 부동 소수점 숫자로 변환합니다.
|
||||
/// 지정된 속성의 값을 long으로 변환하여 반환합니다.
|
||||
/// </summary>
|
||||
/// <param name="v">변환할 숫자의 문자열 표현입니다.</param>
|
||||
/// <returns>입력 문자열에서 구문 분석된 배정밀도 부동 소수점 숫자 또는 변환이 실패하면 <c>0.0</c>을 반환합니다.
|
||||
///</returns>
|
||||
public double GetLong(string v)
|
||||
/// <param name="propertyName">속성 이름</param>
|
||||
/// <param name="defaultValue">속성이 없거나 변환할 수 없는 경우 반환할 기본값</param>
|
||||
/// <returns>변환된 long 값 또는 기본값</returns>
|
||||
public long GetLong(string propertyName, long defaultValue = 0L)
|
||||
{
|
||||
return GetDouble(v, 0.0);
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
if (value is long longValue)
|
||||
return longValue;
|
||||
return Convert.ToInt64(value);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -333,7 +391,7 @@ namespace UVC.Data
|
||||
/// <returns>변환된 DateTime 값 또는 기본값</returns>
|
||||
public DateTime? GetDateTime(string propertyName, DateTime? defaultValue = null)
|
||||
{
|
||||
if (TryGetValue(propertyName, out object value) && value != null)
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
if (value is DateTime dateTime)
|
||||
return dateTime;
|
||||
@@ -351,7 +409,7 @@ namespace UVC.Data
|
||||
/// <returns>변환된 열거형 값 또는 기본값</returns>
|
||||
public T GetEnum<T>(string propertyName, T defaultValue = default) where T : Enum
|
||||
{
|
||||
if (TryGetValue(propertyName, out object value) && value != null)
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
if (value is T enumValue)
|
||||
return enumValue;
|
||||
@@ -368,7 +426,7 @@ namespace UVC.Data
|
||||
/// <returns>IDataObject 인터페이스를 구현한 객체 또는 기본값</returns>
|
||||
public IDataObject? Get(string propertyName, IDataObject? defaultValue = null)
|
||||
{
|
||||
if (TryGetValue(propertyName, out object value) && value != null)
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
if (value is IDataObject dataObject) return dataObject;
|
||||
}
|
||||
@@ -383,54 +441,130 @@ namespace UVC.Data
|
||||
/// <returns>DataArray 객체 또는 기본값</returns>
|
||||
public DataArray? GetDataArray(string propertyName, DataArray? defaultValue = null)
|
||||
{
|
||||
if (TryGetValue(propertyName, out object value) && value != null)
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
if (value is DataArray dataArray)
|
||||
return dataArray;
|
||||
if (value is JArray jArray)
|
||||
return new DataArray(jArray);
|
||||
return DataArrayPool.Get().FromJArray(jArray);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 속성의 값을 DataObject로 변환하여 반환합니다.
|
||||
/// <para>이 메서드는 먼저 현재 객체에서 속성을 찾습니다. 찾지 못하면 중첩된 DataObject를 재귀적으로 탐색합니다.</para>
|
||||
/// <para>과도한 재귀로 인한 문제를 방지하기 위해 최대 탐색 깊이가 제한됩니다.</para>
|
||||
/// </summary>
|
||||
/// <param name="propertyName">속성 이름</param>
|
||||
/// <param name="defaultValue">속성이 없거나 변환할 수 없는 경우 반환할 기본값</param>
|
||||
/// <param name="maxDepth">재귀 탐색의 최대 깊이</param>
|
||||
/// <returns>DataObject 객체 또는 기본값</returns>
|
||||
public DataObject? GetDataObject(string propertyName, DataObject? defaultValue = null)
|
||||
public DataObject? GetDataObject(string propertyName, DataObject? defaultValue = null, int maxDepth = 8)
|
||||
{
|
||||
if (TryGetValue(propertyName, out object value) && value != null)
|
||||
// 내부 재귀 탐색을 시작하고, 결과를 찾지 못하면 기본값을 반환합니다.
|
||||
return GetDataObjectInternal(propertyName, 0, maxDepth) ?? defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 재귀 깊이 제한을 두고 내부적으로 DataObject를 탐색하는 private 헬퍼 메서드입니다.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">찾을 속성 이름</param>
|
||||
/// <param name="currentDepth">현재 재귀 깊이</param>
|
||||
/// <param name="maxDepth">최대 재귀 깊이</param>
|
||||
private DataObject? GetDataObjectInternal(string propertyName, int currentDepth, int maxDepth)
|
||||
{
|
||||
// StackOverflow 방지를 위한 최대 재귀 깊이
|
||||
if (currentDepth >= maxDepth)
|
||||
{
|
||||
return null; // 깊이 제한 초과 시 탐색 중단
|
||||
}
|
||||
|
||||
// 1. 현재 객체에서 직접 속성 검색
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
if (value is DataObject dataObject)
|
||||
return dataObject;
|
||||
if (value is JObject jObject)
|
||||
return new DataObject(jObject);
|
||||
if (value is Dictionary<string, object> dict)
|
||||
return new DataObject(dict);
|
||||
}
|
||||
|
||||
// propertyName으로 직접 객체를 찾지 못했거나, 찾았지만 적절한 타입이 아닌 경우,
|
||||
// 현재 DataObject의 값들 중 DataObject 타입인 것들을 순회하며 내부에서 propertyName을 다시 검색합니다.
|
||||
foreach (KeyValuePair<string, object> keyValue in this)
|
||||
{
|
||||
var dataObj = DataObjectPool.Get();
|
||||
dataObj.FromJObject(jObject);
|
||||
return dataObj;
|
||||
}
|
||||
if (value is Dictionary<string, object?> dict)
|
||||
{
|
||||
var dataObj = DataObjectPool.Get();
|
||||
dataObj.FromDictionary(dict);
|
||||
return dataObj;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 중첩된 DataObject에서 재귀적으로 검색
|
||||
foreach (KeyValuePair<string, object?> keyValue in this)
|
||||
{
|
||||
if (keyValue.Value is DataObject nestedDataObject)
|
||||
{
|
||||
// 중첩된 DataObject에서 propertyName을 검색합니다.
|
||||
// 여기서 defaultValue로 null을 전달하여, 이 탐색 단계에서 찾지 못하면
|
||||
// 외부 호출의 defaultValue가 사용되도록 합니다.
|
||||
DataObject? foundInNested = nestedDataObject.GetDataObject(propertyName, null);
|
||||
// 재귀 호출 시 깊이를 1 증가
|
||||
DataObject? foundInNested = nestedDataObject.GetDataObjectInternal(propertyName, currentDepth + 1, maxDepth);
|
||||
if (foundInNested != null)
|
||||
{
|
||||
return foundInNested; // 중첩된 객체에서 찾았으면 반환
|
||||
return foundInNested;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
|
||||
return null; // 이 경로에서 객체를 찾지 못함
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 경로를 사용하여 중첩된 DataObject를 찾습니다.
|
||||
/// 경로는 점(.)으로 구분된 속성 이름과 배열 인덱스로 구성됩니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var userProfile = data.FindDataObjectByPath("users.0.profile");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="path">"data.users.0.profile"과 같은 객체 경로</param>
|
||||
/// <param name="defaultValue">경로를 찾을 수 없는 경우 반환할 기본값</param>
|
||||
/// <returns>찾은 DataObject 또는 기본값</returns>
|
||||
public DataObject? FindDataObjectByPath(string path, DataObject? defaultValue = null)
|
||||
{
|
||||
var segments = path.Split('.');
|
||||
object? current = this;
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (current == null) return defaultValue;
|
||||
|
||||
switch (current)
|
||||
{
|
||||
case DataObject currentObject:
|
||||
if (!currentObject.TryGetValue(segment, out current))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
break;
|
||||
case DataArray currentArray:
|
||||
if (int.TryParse(segment, out int index) && index >= 0 && index < currentArray.Count)
|
||||
{
|
||||
current = currentArray[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Path leads to a primitive value, cannot traverse further.
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return current as DataObject ?? defaultValue;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 속성이 제거될 때 기존 속성 목록에서도 제거합니다.
|
||||
/// </summary>
|
||||
@@ -441,7 +575,7 @@ namespace UVC.Data
|
||||
bool result = base.Remove(key);
|
||||
if (result)
|
||||
{
|
||||
existingProperties.Remove(key);
|
||||
// 변경 추적 목록에서도 제거합니다.
|
||||
changedProperies.Remove(key);
|
||||
}
|
||||
return result;
|
||||
@@ -453,10 +587,59 @@ namespace UVC.Data
|
||||
public void RemoveAll()
|
||||
{
|
||||
base.Clear();
|
||||
existingProperties.Clear();
|
||||
changedProperies.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 객체를 풀에 반환하기 전에 초기 상태로 리셋합니다.
|
||||
/// 모든 속성을 제거하고, 이름과 ID 키를 기본값으로 설정합니다.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
RemoveAll();
|
||||
Name = string.Empty;
|
||||
IdKey = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 동일한 상태와 값을 가진 현재 데이터 객체의 새 인스턴스를 생성합니다.
|
||||
/// </summary>
|
||||
/// <remarks>복제된 객체는 원본 객체와 독립적이므로, 한 객체를 변경해도 다른 객체에는 영향을 미치지 않습니다.
|
||||
///</remarks>
|
||||
/// <returns>현재 객체의 복사본인 새 <see cref="IDataObject"/> 인스턴스를 반환합니다.</returns>
|
||||
public IDataObject Clone()
|
||||
{
|
||||
var clone = DataObjectPool.Get();
|
||||
clone.Name = Name;
|
||||
clone.IdKey = IdKey;
|
||||
|
||||
// 모든 키-값 쌍을 순회하며 깊은 복사를 수행합니다.
|
||||
foreach (var pair in this)
|
||||
{
|
||||
object? clonedValue;
|
||||
switch (pair.Value)
|
||||
{
|
||||
// 값이 DataObject인 경우, 재귀적으로 Clone을 호출합니다.
|
||||
case DataObject dataObjectValue:
|
||||
clonedValue = dataObjectValue.Clone();
|
||||
break;
|
||||
// 값이 DataArray인 경우, 재귀적으로 Clone을 호출합니다.
|
||||
case DataArray dataArrayValue:
|
||||
clonedValue = dataArrayValue.Clone();
|
||||
break;
|
||||
// 그 외의 경우 (primitive 타입 등)는 그대로 복사합니다.
|
||||
default:
|
||||
clonedValue = pair.Value;
|
||||
break;
|
||||
}
|
||||
// 복제된 값을 새 DataObject에 추가합니다.
|
||||
clone.Add(pair.Key, clonedValue);
|
||||
}
|
||||
|
||||
clone.changedProperies = new HashSet<string>(changedProperies);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 DataObject를 JObject로 변환합니다.
|
||||
/// </summary>
|
||||
@@ -471,6 +654,18 @@ namespace UVC.Data
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 변경된 것으로 표시된 속성 목록을 지웁니다.
|
||||
/// </summary>
|
||||
/// <remarks>이 메서드는 변경된 속성의 내부 추적을 재설정하여
|
||||
/// 기록된 모든 수정 사항을 효과적으로 지웁니다. 모든 변경 내용 추적을 삭제하고
|
||||
/// 새로 시작하려면 이 메서드를 사용하세요.</remarks>
|
||||
public void ClearChangedProperties()
|
||||
{
|
||||
// 변경 추적 목록을 초기화합니다.
|
||||
changedProperies.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 다른 DataObject와 현재 객체를 비교하여 다른 부분만 설정합니다.
|
||||
/// 변경된 키는 자동으로 추적됩니다.
|
||||
@@ -478,16 +673,28 @@ namespace UVC.Data
|
||||
/// <param name="other">비교할 DataObject</param>
|
||||
public void UpdateDifferent(IDataObject other)
|
||||
{
|
||||
if (other == null || other is not DataObject) return;
|
||||
if (other == null || other is not DataObject otherDataObject) return;
|
||||
|
||||
// 변경 추적 목록을 초기화하여 'other' 객체의 상태를 기준으로 새로 설정합니다.
|
||||
changedProperies.Clear();
|
||||
foreach (var keyValue in (DataObject)other)
|
||||
|
||||
foreach (var keyValue in otherDataObject)
|
||||
{
|
||||
if (!this.ContainsKey(keyValue.Key) || this[keyValue.Key] != keyValue.Value)
|
||||
if (!this.ContainsKey(keyValue.Key) || (this[keyValue.Key] == null && keyValue.Value != null)
|
||||
|| (this[keyValue.Key] != null && keyValue.Value == null)
|
||||
|| (this[keyValue.Key] != null && keyValue.Value != null && !this[keyValue.Key]!.Equals(keyValue.Value)))
|
||||
{
|
||||
this[keyValue.Key] = keyValue.Value;
|
||||
changedProperies.Add(keyValue.Key);
|
||||
}
|
||||
}
|
||||
// 현재 객체에만 있는 속성은 제거합니다.
|
||||
var keysToRemove = this.Keys.Except(otherDataObject.Keys).ToList();
|
||||
foreach (var key in keysToRemove)
|
||||
{
|
||||
this.Remove(key);
|
||||
changedProperies.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -496,7 +703,7 @@ namespace UVC.Data
|
||||
/// <returns>업데이트 된 항목만 가지고 있는 DataObject</returns>
|
||||
public IDataObject GetUpdatedObject()
|
||||
{
|
||||
DataObject updated = new DataObject();
|
||||
DataObject updated = DataObjectPool.Get();
|
||||
foreach (var key in changedProperies)
|
||||
{
|
||||
if (this.ContainsKey(key))
|
||||
@@ -504,9 +711,28 @@ namespace UVC.Data
|
||||
updated[key] = this[key];
|
||||
}
|
||||
}
|
||||
updated.IdKey = IdKey; // ID 키를 복사합니다.
|
||||
updated.Name = Name; // 이름을 복사합니다.
|
||||
|
||||
//id에 해당하는 속성이 있다면, 업데이트된 객체에도 포함시킵니다.
|
||||
if (IdKey != null && ContainsKey(IdKey))
|
||||
{
|
||||
updated[IdKey] = this[IdKey];
|
||||
}
|
||||
else if (Count > 0)
|
||||
{
|
||||
var keyValue = this.First();
|
||||
updated[keyValue.Key] = keyValue.Value;
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
public void ReturnToPool()
|
||||
{
|
||||
DataObjectPool.Return(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 업데이트 된 속성의 수.
|
||||
/// </summary>
|
||||
@@ -522,68 +748,7 @@ namespace UVC.Data
|
||||
{
|
||||
return string.Join(", ", this.Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DataObject 인스턴스를 재사용하기 위한 객체 풀 클래스입니다.
|
||||
/// 메모리 할당과 가비지 컬렉션을 최소화하기 위해 DataObject 인스턴스를 관리합니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 객체 풀링은 자주 생성 및 삭제되는 객체의 성능을 향상시키는 패턴입니다.
|
||||
/// 사용이 끝난 객체를 삭제하는 대신 풀에 반환하고, 새 객체가 필요할 때 풀에서 꺼내 재사용합니다.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 객체 풀에서 DataObject 가져오기
|
||||
/// DataObject obj = DataObjectPool.GetDataObjectFromPool();
|
||||
///
|
||||
/// // 객체 사용
|
||||
/// obj["name"] = "홍길동";
|
||||
/// obj["age"] = 30;
|
||||
///
|
||||
/// // 작업 완료 후 풀에 반환
|
||||
/// DataObjectPool.ReturnToPool(obj);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static class DataObjectPool
|
||||
{
|
||||
/// <summary>
|
||||
/// DataObject 인스턴스를 저장하는 큐입니다.
|
||||
/// </summary>
|
||||
private static Queue<DataObject> dataObjectPool = new Queue<DataObject>();
|
||||
/// <summary>
|
||||
/// 풀의 최대 크기입니다. 이 크기를 초과하는 객체는 풀에 저장되지 않습니다.
|
||||
/// </summary>
|
||||
private static int maxPoolSize = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// 풀에서 DataObject 인스턴스를 가져옵니다.
|
||||
/// 풀이 비어있으면 새 인스턴스를 생성하여 반환합니다.
|
||||
/// </summary>
|
||||
/// <returns>재사용 가능한 DataObject 인스턴스</returns>
|
||||
public static DataObject GetDataObjectFromPool()
|
||||
{
|
||||
if (dataObjectPool.Count > 0)
|
||||
{
|
||||
return dataObjectPool.Dequeue();
|
||||
}
|
||||
return new DataObject();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 사용이 완료된 DataObject를 풀에 반환합니다.
|
||||
/// 객체는 반환 전에 초기화되어 모든 속성이 제거됩니다.
|
||||
/// </summary>
|
||||
/// <param name="obj">풀에 반환할 DataObject 인스턴스</param>
|
||||
public static void ReturnToPool(DataObject obj)
|
||||
{
|
||||
if (obj != null && dataObjectPool.Count < maxPoolSize)
|
||||
{
|
||||
obj.RemoveAll(); // 재사용 전 정리
|
||||
dataObjectPool.Enqueue(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
144
Assets/Scripts/UVC/Data/DataObjectPool.cs
Normal file
144
Assets/Scripts/UVC/Data/DataObjectPool.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// DataObject 인스턴스를 재사용하기 위한 객체 풀 클래스입니다.
|
||||
/// 메모리 할당과 가비지 컬렉션을 최소화하기 위해 DataObject 인스턴스를 관리합니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 객체 풀링은 자주 생성 및 삭제되는 객체의 성능을 향상시키는 패턴입니다.
|
||||
/// 사용이 끝난 객체를 삭제하는 대신 풀에 반환하고, 새 객체가 필요할 때 풀에서 꺼내 재사용합니다.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 객체 풀에서 DataObject 가져오기
|
||||
/// DataObject obj = DataObjectPool.Get();
|
||||
///
|
||||
/// // 객체 사용
|
||||
/// obj["name"] = "홍길동";
|
||||
/// obj["age"] = 30;
|
||||
///
|
||||
/// // 작업 완료 후 풀에 반환
|
||||
/// DataObjectPool.Return(obj);
|
||||
///
|
||||
/// // 풀 통계 확인
|
||||
/// ULog.Debug(DataObjectPool.GetStats());
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static class DataObjectPool
|
||||
{
|
||||
/// <summary>
|
||||
/// DataObject 인스턴스를 저장하는 스레드 안전 큐입니다.
|
||||
/// </summary>
|
||||
private static ConcurrentQueue<DataObject> dataObjectPool = new ConcurrentQueue<DataObject>();
|
||||
|
||||
/// <summary>
|
||||
/// 풀의 최대 크기입니다. 이 크기를 초과하는 객체는 풀에 저장되지 않습니다.
|
||||
/// </summary>
|
||||
private static int maxPoolSize = 1000;
|
||||
|
||||
// --- 통계용 필드 ---
|
||||
private static int _inUseCount = 0;
|
||||
private static int _peakUsage = 0;
|
||||
private static int _poolMisses = 0;
|
||||
private static readonly object _statsLock = new object();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 풀의 최대 크기를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="size">설정할 최대 크기</param>
|
||||
public static void SetMaxPoolSize(int size)
|
||||
{
|
||||
maxPoolSize = size > 0 ? size : 1000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀에서 DataObject 인스턴스를 가져옵니다.
|
||||
/// 풀이 비어있으면 새 인스턴스를 생성하여 반환합니다.
|
||||
/// </summary>
|
||||
/// <returns>재사용 가능한 DataObject 인스턴스</returns>
|
||||
public static DataObject Get()
|
||||
{
|
||||
bool fromPool = dataObjectPool.TryDequeue(out var obj);
|
||||
|
||||
lock (_statsLock)
|
||||
{
|
||||
_inUseCount++;
|
||||
if (_inUseCount > _peakUsage)
|
||||
{
|
||||
_peakUsage = _inUseCount;
|
||||
}
|
||||
|
||||
// 풀이 비어있어서 새 객체를 만들어야 하는 경우
|
||||
if (!fromPool)
|
||||
{
|
||||
_poolMisses++;
|
||||
// 현재 사용량이 최대 풀 크기에 도달했는지 확인
|
||||
if (_inUseCount > maxPoolSize)
|
||||
{
|
||||
int oldSize = maxPoolSize;
|
||||
maxPoolSize += 1000;
|
||||
Debug.Log($"DataObjectPool size automatically increased from {oldSize} to {maxPoolSize}. Peak usage: {_peakUsage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_peakUsage % 100 == 0) Debug.Log($"DataObjectPool stats: {GetStats()}");
|
||||
|
||||
if (fromPool)
|
||||
{
|
||||
obj.IsInPool = false;
|
||||
obj.Reset();
|
||||
}
|
||||
return fromPool ? obj : new DataObject();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 사용이 완료된 DataObject를 풀에 반환합니다.
|
||||
/// 객체는 반환 전에 초기화되어 모든 속성이 제거됩니다.
|
||||
/// </summary>
|
||||
/// <param name="obj">풀에 반환할 DataObject 인스턴스</param>
|
||||
public static void Return(DataObject obj)
|
||||
{
|
||||
if (obj == null || obj.IsInPool) return;
|
||||
|
||||
lock (_statsLock)
|
||||
{
|
||||
_inUseCount--;
|
||||
}
|
||||
|
||||
if (dataObjectPool.Count < maxPoolSize)
|
||||
{
|
||||
obj.Reset(); // 재사용 전 완벽한 초기화
|
||||
obj.IsInPool = true;
|
||||
dataObjectPool.Enqueue(obj);
|
||||
}
|
||||
// 풀이 가득 차면 객체는 풀에 추가되지 않고 GC 대상이 됩니다.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀의 현재 성능 통계를 문자열로 반환합니다.
|
||||
/// </summary>
|
||||
/// <returns>풀 통계 (최대 사용량, 현재 사용량, 풀 비어있을 때 생성 횟수, 현재 풀 크기)</returns>
|
||||
public static string GetStats()
|
||||
{
|
||||
return $"Peak Usage: {_peakUsage}, In Use: {_inUseCount}, Misses: {_poolMisses}, Pooled: {dataObjectPool.Count}, Max Size: {maxPoolSize}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀 통계를 초기화합니다.
|
||||
/// </summary>
|
||||
public static void ResetStats()
|
||||
{
|
||||
lock (_statsLock)
|
||||
{
|
||||
_peakUsage = 0;
|
||||
_poolMisses = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Data/DataObjectPool.cs.meta
Normal file
2
Assets/Scripts/UVC/Data/DataObjectPool.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ccfcb80fa0900584b8569695e4fb8f21
|
||||
@@ -69,17 +69,26 @@ namespace UVC.Data
|
||||
{
|
||||
if (!dataObjects.ContainsKey(key))
|
||||
{
|
||||
dataObject.InitData();
|
||||
dataObjects.Add(key, dataObject);
|
||||
var newData = dataObject.Clone();
|
||||
dataObjects.Add(key, newData);
|
||||
dataObject.MarkAllAsUpdated();
|
||||
//UniTask.Post(() => NotifyDataUpdate(key, newData));
|
||||
return dataObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
IDataObject obj = dataObjects[key];
|
||||
obj.UpdateDifferent(dataObject);
|
||||
var newDataObject = dataObject;
|
||||
if (updatedDataOnly) newDataObject = obj.GetUpdatedObject();
|
||||
UniTask.Post(() => NotifyDataUpdate(key, newDataObject));
|
||||
IDataObject newDataObject;
|
||||
if (updatedDataOnly)
|
||||
{
|
||||
newDataObject = obj.GetUpdatedObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
newDataObject = dataObject;
|
||||
}
|
||||
//UniTask.Post(() => NotifyDataUpdate(key, obj));
|
||||
return newDataObject;
|
||||
}
|
||||
}
|
||||
@@ -131,25 +140,26 @@ namespace UVC.Data
|
||||
/// <remarks>
|
||||
/// 같은 키에 대해 여러 핸들러를 등록할 수 있습니다.
|
||||
/// 핸들러는 해당 키의 데이터가 AddData를 통해 업데이트될 때마다 호출됩니다.
|
||||
/// 전달 되는 데이터 객체는 업데이트된 상태고 DataRepository에 저장된 객체이기에 pool.ReturnToPool()을 호출하면 않됩니다.
|
||||
/// </remarks>
|
||||
public void AddDataUpdateHandler(string key, Action<IDataObject> handler)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException(nameof(handler), "핸들러는 null일 수 없습니다.");
|
||||
lock (syncLock)
|
||||
{
|
||||
if (!dataUpdateHandlers.ContainsKey(key))
|
||||
{
|
||||
dataUpdateHandlers[key] = handler;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataUpdateHandlers[key] += handler; // 기존 핸들러에 추가
|
||||
}
|
||||
}
|
||||
}
|
||||
//public void AddDataUpdateHandler(string key, Action<IDataObject> handler)
|
||||
//{
|
||||
// if (string.IsNullOrEmpty(key))
|
||||
// throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");
|
||||
// if (handler == null)
|
||||
// throw new ArgumentNullException(nameof(handler), "핸들러는 null일 수 없습니다.");
|
||||
// lock (syncLock)
|
||||
// {
|
||||
// if (!dataUpdateHandlers.ContainsKey(key))
|
||||
// {
|
||||
// dataUpdateHandlers[key] = handler;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// dataUpdateHandlers[key] += handler; // 기존 핸들러에 추가
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 키에서 데이터 업데이트 핸들러를 제거합니다.
|
||||
@@ -160,24 +170,24 @@ namespace UVC.Data
|
||||
/// 지정된 키에 연결된 핸들러가 없거나, 제거하려는 핸들러가 등록되지 않은 경우 아무 작업도 수행하지 않습니다.
|
||||
/// 키에 연결된 마지막 핸들러가 제거되면 해당 키는 dataUpdateHandlers 딕셔너리에서 삭제됩니다.
|
||||
/// </remarks>
|
||||
public void RemoveDataUpdateHandler(string key, Action<IDataObject> handler)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException(nameof(handler), "핸들러는 null일 수 없습니다.");
|
||||
lock (syncLock)
|
||||
{
|
||||
if (dataUpdateHandlers.ContainsKey(key))
|
||||
{
|
||||
dataUpdateHandlers[key] -= handler; // 기존 핸들러에서 제거
|
||||
if (dataUpdateHandlers[key] == null)
|
||||
{
|
||||
dataUpdateHandlers.Remove(key); // 핸들러가 없으면 제거
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//public void RemoveDataUpdateHandler(string key, Action<IDataObject> handler)
|
||||
//{
|
||||
// if (string.IsNullOrEmpty(key))
|
||||
// throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");
|
||||
// if (handler == null)
|
||||
// throw new ArgumentNullException(nameof(handler), "핸들러는 null일 수 없습니다.");
|
||||
// lock (syncLock)
|
||||
// {
|
||||
// if (dataUpdateHandlers.ContainsKey(key))
|
||||
// {
|
||||
// dataUpdateHandlers[key] -= handler; // 기존 핸들러에서 제거
|
||||
// if (dataUpdateHandlers[key] == null)
|
||||
// {
|
||||
// dataUpdateHandlers.Remove(key); // 핸들러가 없으면 제거
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 데이터 업데이트 핸들러를 제거합니다.
|
||||
@@ -187,13 +197,13 @@ namespace UVC.Data
|
||||
/// 예를 들어, 씬이 변경되거나 애플리케이션이 종료될 때
|
||||
/// 모든 핸들러를 정리하는 데 유용합니다.
|
||||
/// </remarks>
|
||||
public void ClearDataUpdateHandlers()
|
||||
{
|
||||
lock (syncLock)
|
||||
{
|
||||
dataUpdateHandlers.Clear();
|
||||
}
|
||||
}
|
||||
//public void ClearDataUpdateHandlers()
|
||||
//{
|
||||
// lock (syncLock)
|
||||
// {
|
||||
// dataUpdateHandlers.Clear();
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 키의 데이터가 업데이트되었음을 알리고 등록된 핸들러들을 호출합니다.
|
||||
@@ -204,20 +214,20 @@ namespace UVC.Data
|
||||
/// 이 메서드는 주로 내부적으로 AddData 메서드에서 호출되어
|
||||
/// 특정 키의 데이터가 변경되었을 때 등록된 핸들러들에게 알립니다.
|
||||
/// </remarks>
|
||||
private void NotifyDataUpdate(string key, IDataObject dataObject)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");
|
||||
if (dataObject == null)
|
||||
throw new ArgumentNullException(nameof(dataObject), "데이터 객체는 null일 수 없습니다.");
|
||||
lock (syncLock)
|
||||
{
|
||||
if (dataUpdateHandlers.ContainsKey(key))
|
||||
{
|
||||
dataUpdateHandlers[key]?.Invoke(dataObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
//private void NotifyDataUpdate(string key, IDataObject dataObject)
|
||||
//{
|
||||
// if (string.IsNullOrEmpty(key))
|
||||
// throw new ArgumentNullException(nameof(key), "키는 null이거나 빈 문자열일 수 없습니다.");
|
||||
// if (dataObject == null)
|
||||
// throw new ArgumentNullException(nameof(dataObject), "데이터 객체는 null일 수 없습니다.");
|
||||
// lock (syncLock)
|
||||
// {
|
||||
// if (dataUpdateHandlers.ContainsKey(key))
|
||||
// {
|
||||
// dataUpdateHandlers[key]?.Invoke(dataObject);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 저장소에 저장된 모든 데이터 객체의 키 목록을 반환합니다.
|
||||
@@ -380,12 +390,13 @@ namespace UVC.Data
|
||||
|
||||
if (value is JObject jObject)
|
||||
{
|
||||
DataObject dataObject = new DataObject(jObject);
|
||||
DataObject dataObject = DataObjectPool.Get();
|
||||
dataObject.FromJObject(jObject);
|
||||
AddData(key, dataObject, false);
|
||||
}
|
||||
else if (value is JArray jArray)
|
||||
{
|
||||
DataArray dataArray = new DataArray(jArray);
|
||||
DataArray dataArray = DataArrayPool.Get().FromJArray(jArray);
|
||||
AddData(key, dataArray, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +230,8 @@ namespace UVC.Data
|
||||
requestInProgress[key] = true;
|
||||
}
|
||||
|
||||
IDataObject? mappedObject = null;
|
||||
|
||||
try
|
||||
{
|
||||
string result = string.Empty;
|
||||
@@ -247,8 +249,7 @@ namespace UVC.Data
|
||||
{
|
||||
throw new OperationCanceledException("Operation cancelled", cancellationToken);
|
||||
}
|
||||
|
||||
IDataObject? dataObject = null;
|
||||
|
||||
result = result.Trim();
|
||||
|
||||
if (!string.IsNullOrEmpty(result))
|
||||
@@ -317,13 +318,13 @@ namespace UVC.Data
|
||||
{
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(result)))
|
||||
{
|
||||
dataObject = info.DataMapper.MapObjectStream(stream);
|
||||
mappedObject = info.DataMapper.MapObjectStream(stream);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JObject source = JObject.Parse(result);
|
||||
dataObject = info.DataMapper.Map(source);
|
||||
mappedObject = info.DataMapper.Map(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,12 +377,12 @@ namespace UVC.Data
|
||||
JArray? validSource = info.Validator.GetValidData(stream);
|
||||
if (validSource != null)
|
||||
{
|
||||
dataObject = info.DataMapper.Map(validSource);
|
||||
mappedObject = info.DataMapper.Map(validSource);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dataObject = info.DataMapper.MapArrayStream(stream);
|
||||
mappedObject = info.DataMapper.MapArrayStream(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,12 +394,12 @@ namespace UVC.Data
|
||||
JArray? validSource = info.Validator.GetValidData(source);
|
||||
if (validSource != null)
|
||||
{
|
||||
dataObject = info.DataMapper.Map(validSource);
|
||||
mappedObject = info.DataMapper.Map(validSource);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dataObject = info.DataMapper.Map(source);
|
||||
mappedObject = info.DataMapper.Map(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,17 +417,24 @@ namespace UVC.Data
|
||||
{
|
||||
throw new OperationCanceledException("Operation cancelled", cancellationToken);
|
||||
}
|
||||
if (dataObject != null) dataObject = DataRepository.Instance.AddData(key, dataObject, info.UpdatedDataOnly);
|
||||
|
||||
var repoObject = mappedObject;
|
||||
if (mappedObject != null)
|
||||
{
|
||||
repoObject = DataRepository.Instance.AddData(key, mappedObject, info.UpdatedDataOnly);
|
||||
if (repoObject == mappedObject) repoObject = mappedObject.Clone();
|
||||
}
|
||||
|
||||
|
||||
// 갱신 된 데이터가 있는 경우 핸들러 호출
|
||||
if (info.UpdatedDataOnly)
|
||||
{
|
||||
if (dataObject != null && dataObject.UpdatedCount > 0)
|
||||
if (repoObject != null && repoObject.UpdatedCount > 0)
|
||||
{
|
||||
if (info.SuccessHandler != null)
|
||||
{
|
||||
// 로컬 변수로 복사하여 클로저에서 안전하게 사용
|
||||
var handlerData = dataObject;
|
||||
var handlerData = repoObject;
|
||||
// UI 스레드에서 성공 핸들러 호출
|
||||
await UniTask.SwitchToMainThread();
|
||||
info.SuccessHandler.Invoke(handlerData);
|
||||
@@ -436,12 +444,12 @@ namespace UVC.Data
|
||||
}
|
||||
|
||||
|
||||
if (dataObject != null)
|
||||
if (repoObject != null)
|
||||
{
|
||||
if (info.SuccessHandler != null)
|
||||
{
|
||||
// 로컬 변수로 복사하여 클로저에서 안전하게 사용
|
||||
var handlerData = dataObject;
|
||||
var handlerData = repoObject;
|
||||
// UI 스레드에서 성공 핸들러 호출
|
||||
await UniTask.SwitchToMainThread();
|
||||
info.SuccessHandler.Invoke(handlerData);
|
||||
@@ -482,6 +490,12 @@ namespace UVC.Data
|
||||
}
|
||||
finally
|
||||
{
|
||||
//DataMapper가 생성한 임시 객체를 풀에 반환합니다.
|
||||
if (mappedObject != null)
|
||||
{
|
||||
mappedObject.ReturnToPool();
|
||||
}
|
||||
|
||||
lock (requestInProgress)
|
||||
{
|
||||
requestInProgress[key] = false;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
/// 모든 프로퍼티를 변경된 것으로 표시합니다.
|
||||
/// 전체 데이터가 갱신되었을 때 사용합니다.
|
||||
/// </summary>
|
||||
public void InitData();
|
||||
public void MarkAllAsUpdated();
|
||||
|
||||
/// <summary>
|
||||
/// 다른 DataObject와 현재 객체를 비교하여 다른 부분만 설정합니다.
|
||||
@@ -36,5 +36,21 @@
|
||||
/// <returns>업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면 0을 반환합니다.</returns>
|
||||
public int UpdatedCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 동일한 상태와 값을 가진 현재 데이터 객체의 새 인스턴스를 생성합니다.
|
||||
/// </summary>
|
||||
/// <remarks>복제된 객체는 원본 객체와 독립적이므로, 한 객체를 변경해도 다른 객체에는 영향을 미치지 않습니다.
|
||||
///</remarks>
|
||||
/// <returns>현재 객체의 복사본인 새 <see cref="IDataObject"/> 인스턴스를 반환합니다.</returns>
|
||||
public IDataObject Clone();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 재사용을 위해 객체를 풀로 반환합니다.
|
||||
/// </summary>
|
||||
/// <remarks>객체가 더 이상 필요하지 않을 때 이 메서드를 호출하여
|
||||
/// 풀에서 재사용할 수 있도록 해야 합니다. 객체를 풀로 반환하기 전에 객체가 유효한 상태인지 확인하십시오.</remarks>
|
||||
public void ReturnToPool();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,8 +201,7 @@ namespace UVC.Data
|
||||
if (infoList.ContainsKey(topic))
|
||||
{
|
||||
MQTTPipeLineInfo info = infoList[topic];
|
||||
IDataObject? dataObject = null;
|
||||
|
||||
IDataObject? mappedObject = null;
|
||||
message = message.Trim();
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
@@ -211,11 +210,8 @@ namespace UVC.Data
|
||||
if (message.StartsWith("{"))
|
||||
{
|
||||
JObject source = JObject.Parse(message);
|
||||
if (info.Validator != null && !info.Validator.IsValid(source))
|
||||
{
|
||||
return; // 유효성 검사 실패 시 핸들러 호출을 중단
|
||||
}
|
||||
if (info.DataMapper != null) dataObject = info.DataMapper.Map(source);
|
||||
if (info.Validator != null && !info.Validator.IsValid(source)) return;
|
||||
if (info.DataMapper != null) mappedObject = info.DataMapper.Map(source);
|
||||
}
|
||||
else if (message.StartsWith("["))
|
||||
{
|
||||
@@ -223,38 +219,40 @@ namespace UVC.Data
|
||||
if (info.Validator != null)
|
||||
{
|
||||
JArray? validSource = info.Validator.GetValidData(source);
|
||||
if (validSource != null && validSource.Count > 0)
|
||||
{
|
||||
// 유효한 데이터가 있는 경우에만 매핑
|
||||
if (info.DataMapper != null) dataObject = info.DataMapper.Map(validSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
return; // 유효성 검사 실패 시 핸들러 호출을 중단
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (info.DataMapper != null) dataObject = info.DataMapper.Map(source);
|
||||
// 유효성 검사 실패 시 핸들러 호출을 중단
|
||||
if (validSource == null || validSource.Count == 0) return;
|
||||
source = validSource;
|
||||
}
|
||||
if (info.DataMapper != null) mappedObject = info.DataMapper.Map(source);
|
||||
}
|
||||
|
||||
if (dataObject != null) dataObject = DataRepository.Instance.AddData(topic, dataObject, info.UpdatedDataOnly);
|
||||
|
||||
if (mappedObject == null) return;
|
||||
// DataRepository는 내부적으로 데이터를 복사/업데이트하므로, mappedObject는 여기서 임시 객체가 됩니다.
|
||||
var repoObject = DataRepository.Instance.AddData(topic, mappedObject, info.UpdatedDataOnly);
|
||||
if(repoObject == mappedObject) repoObject = mappedObject.Clone();
|
||||
// 핸들러 호출이 필요한지 확인
|
||||
bool shouldInvoke = !info.UpdatedDataOnly || (dataObject != null && dataObject.UpdatedCount > 0);
|
||||
|
||||
bool shouldInvoke = !info.UpdatedDataOnly || (repoObject != null && repoObject.UpdatedCount > 0);
|
||||
if (shouldInvoke)
|
||||
{
|
||||
var handlerData = repoObject;
|
||||
// 핸들러를 메인 스레드에서 안전하게 호출
|
||||
UniTask.Post(() => info.Handler?.Invoke(dataObject));
|
||||
UniTask.Post(() => info.Handler?.Invoke(handlerData));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 예외 발생 시 로깅 또는 처리
|
||||
// 예외 로깅도 메인 스레드에서 처리하여 Unity API 호출에 대한 스레드 안정성 확보
|
||||
UniTask.Post(() => ULog.Error($"Error processing message for topic '{topic}': {ex.Message}", ex));
|
||||
//UniTask.Post(() => ULog.Error($"Error processing message for topic '{topic}': {ex.Message}", ex));
|
||||
ULog.Error($"Error processing message for topic '{topic}': {ex.Message}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// DataMapper가 생성한 임시 객체를 풀에 반환합니다.
|
||||
if (mappedObject != null)
|
||||
{
|
||||
mappedObject.ReturnToPool();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ namespace UVC.Factory.Component
|
||||
data[keyValue.Key] = keyValue.Value;
|
||||
}
|
||||
}
|
||||
newData.ReturnToPool(); // 사용이 끝난 데이터 객체를 풀에 반환합니다.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,9 +117,10 @@ namespace UVC.Factory.Component
|
||||
float y = data.GetFloat("Y") * scaleFactor;
|
||||
Quaternion rotation = Quaternion.Euler(0, data.GetFloat("DEGREE"), 0);
|
||||
|
||||
float newX = newData.GetFloat("X") * scaleFactor;
|
||||
float newY = newData.GetFloat("Y") * scaleFactor;
|
||||
Quaternion newRotation = Quaternion.Euler(0, newData.GetFloat("DEGREE"), 0);
|
||||
float newX = (newData.ContainsKey("X") ? newData.GetFloat("X") : data.GetFloat("X")) * scaleFactor;
|
||||
float newY = (newData.ContainsKey("Y") ? newData.GetFloat("Y") : data.GetFloat("Y")) * scaleFactor;
|
||||
float newDegree = (newData.ContainsKey("DEGREE") ? newData.GetFloat("DEGREE") : data.GetFloat("DEGREE"));
|
||||
Quaternion newRotation = Quaternion.Euler(0, newDegree, 0);
|
||||
|
||||
Vector3 newTargetPosition = new Vector3(x, transform.position.y, y);
|
||||
Quaternion newTargetRotation = rotation;
|
||||
@@ -141,6 +143,7 @@ namespace UVC.Factory.Component
|
||||
this.targetPosition = newTargetPosition;
|
||||
this.targetRotation = newTargetRotation;
|
||||
|
||||
newData.ReturnToPool(); // 사용이 끝난 데이터 객체를 풀에 반환합니다.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,8 @@ namespace UVC.Factory.Component
|
||||
await InitializePoolAsync();
|
||||
//데이터를 어떤 형식으로 받을지 정의합니다.
|
||||
var dataMask = new DataMask();
|
||||
dataMask.ObjectName = "AGV"; // AGV 객체의 이름을 설정합니다.
|
||||
dataMask.ObjectIdKey = "VHL_NAME"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
|
||||
dataMask["VHL_NAME"] = "";
|
||||
dataMask["AGV_IDX"] = "";
|
||||
dataMask["B_INSTALL"] = "";
|
||||
@@ -123,11 +125,12 @@ namespace UVC.Factory.Component
|
||||
// 데이터가 업데이트되면 OnUpdateData 메서드를 호출하여 처리합니다.
|
||||
|
||||
DataValidator validator = new DataValidator();
|
||||
validator.AddValidator("JOB_ID", value => value != null);
|
||||
//validator.AddValidator("JOB_ID", value => value != null);
|
||||
validator.AddValidator("VHL_NAME", value => value != null && value!.ToString() == "HFF09CNA8016");
|
||||
|
||||
var pipelineInfo = new MQTTPipeLineInfo("AGV")
|
||||
.setDataMapper(new DataMapper(dataMask))
|
||||
.setValidator(validator)
|
||||
//.setValidator(validator)
|
||||
.setHandler(OnUpdateData);
|
||||
|
||||
// 생성한 파이프라인 정보를 전역 MQTT 파이프라인에 추가합니다.
|
||||
@@ -157,18 +160,23 @@ namespace UVC.Factory.Component
|
||||
/// </summary>
|
||||
/// <param name="data">수신된 데이터 객체 (DataArray 형태)</param>
|
||||
public void OnUpdateData(IDataObject? data)
|
||||
{
|
||||
{
|
||||
if (data == null || agvPool == null) return;
|
||||
|
||||
DataArray? arr = data as DataArray;
|
||||
if (arr == null || arr.Count == 0) return;
|
||||
if (arr == null) return;
|
||||
if (arr.Count == 0)
|
||||
{
|
||||
arr.ReturnToPool();
|
||||
return;
|
||||
}
|
||||
|
||||
// 데이터 배열에서 추가, 제거, 수정된 항목 리스트를 가져옵니다.
|
||||
var AddedItems = arr.AddedItems;
|
||||
var RemovedItems = arr.RemovedItems;
|
||||
var ModifiedList = arr.ModifiedList;
|
||||
|
||||
//Debug.Log($"AGVManager received data: Added={AddedItems.Count}, Removed={RemovedItems.Count}, Modified={ModifiedList.Count}");
|
||||
Debug.Log($"AGVManager received data: Added={AddedItems.Count}, Removed={RemovedItems.Count}, Modified={ModifiedList.Count}");
|
||||
|
||||
// 새로 추가된 AGV 처리
|
||||
foreach (var item in AddedItems.ToList())
|
||||
@@ -186,7 +194,7 @@ namespace UVC.Factory.Component
|
||||
"",
|
||||
item.GetString("MODE")
|
||||
);
|
||||
agv.UpdateData(item);
|
||||
agv.UpdateData(item.Clone());
|
||||
}
|
||||
|
||||
// 제거된 AGV 처리
|
||||
@@ -208,10 +216,11 @@ namespace UVC.Factory.Component
|
||||
AGV? agv = agvPool.FindActiveItem(vhlName);
|
||||
if (agv != null)
|
||||
{
|
||||
agv.UpdateData(item);
|
||||
agv.UpdateData(item.Clone());
|
||||
}
|
||||
}
|
||||
|
||||
arr.ReturnToPool();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
161
Assets/Scripts/UVC/Tests/Data/DataArrayTests.cs
Normal file
161
Assets/Scripts/UVC/Tests/Data/DataArrayTests.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
#nullable enable
|
||||
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UVC.Data;
|
||||
|
||||
namespace UVC.Tests.Data
|
||||
{
|
||||
[TestFixture]
|
||||
public class DataArrayTests
|
||||
{
|
||||
private DataArray _dataArray = new DataArray();
|
||||
private DataObject _item1 = new DataObject();
|
||||
private DataObject _item2 = new DataObject();
|
||||
private DataObject _item3 = new DataObject();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 모든 테스트 메서드를 실행하는 메서드입니다.
|
||||
/// </summary>
|
||||
public void TestAll()
|
||||
{
|
||||
Debug.Log("===== DataArray 테스트 시작 =====");
|
||||
|
||||
SetUp();
|
||||
RunTest(nameof(Add_AddsItemToList), Add_AddsItemToList);
|
||||
SetUp();
|
||||
RunTest(nameof(Remove_RemovesItemFromList), Remove_RemovesItemFromList);
|
||||
SetUp();
|
||||
RunTest(nameof(Clear_RemovesAllItems), Clear_RemovesAllItems);
|
||||
SetUp();
|
||||
RunTest(nameof(UpdateDifferent_TracksAddsDeletesAndModifies), UpdateDifferent_TracksAddsDeletesAndModifies);
|
||||
SetUp();
|
||||
RunTest(nameof(ClearTrackedChanges_ResetsAllChangeLists), ClearTrackedChanges_ResetsAllChangeLists);
|
||||
|
||||
Debug.Log("===== DataArray 테스트 완료 =====");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 단일 테스트 메서드를 실행하고 결과를 로그로 출력합니다.
|
||||
/// </summary>
|
||||
private void RunTest(string testName, Action testAction)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.Log($"테스트 시작: {testName}");
|
||||
testAction();
|
||||
Debug.Log($"테스트 성공: {testName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"테스트 실패: {testName}\n{ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
// 테스트에 사용할 DataObject 아이템들을 초기화합니다.
|
||||
// UpdateDifferent 메서드가 Id를 기준으로 동작하므로 Id를 설정합니다.
|
||||
_item1 = new DataObject { { "id", "1" }, { "value", "A" } };
|
||||
_item2 = new DataObject { { "id", "2" }, { "value", "B" } };
|
||||
_item3 = new DataObject { { "id", "3" }, { "value", "C" } };
|
||||
|
||||
// 각 테스트 전에 DataArray를 초기화합니다.
|
||||
_dataArray = new DataArray(new[] { _item1, _item2 });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Add_AddsItemToList()
|
||||
{
|
||||
// Act
|
||||
_dataArray.Add(_item3);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(3, _dataArray.Count, "DataArray의 전체 개수는 3이어야 합니다.");
|
||||
Assert.Contains(_item3, _dataArray.ToList());
|
||||
Assert.AreEqual(0, _dataArray.AddedItems.Count, "Add 메서드는 더 이상 AddedItems를 직접 채우지 않아야 합니다.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Remove_RemovesItemFromList()
|
||||
{
|
||||
// Act
|
||||
bool result = _dataArray.Remove(_item1);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result, "항목 제거는 성공해야 합니다.");
|
||||
Assert.AreEqual(1, _dataArray.Count, "DataArray의 전체 개수는 1이어야 합니다.");
|
||||
Assert.IsFalse(_dataArray.Contains(_item1));
|
||||
Assert.AreEqual(0, _dataArray.RemovedItems.Count, "Remove 메서드는 더 이상 RemovedItems를 직접 채우지 않아야 합니다.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Clear_RemovesAllItems()
|
||||
{
|
||||
// Act
|
||||
_dataArray.Clear();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, _dataArray.Count, "DataArray는 비어있어야 합니다.");
|
||||
Assert.AreEqual(0, _dataArray.RemovedItems.Count, "Clear 메서드는 더 이상 RemovedItems를 직접 채우지 않아야 합니다.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateDifferent_TracksAddsDeletesAndModifies()
|
||||
{
|
||||
// Arrange
|
||||
// 기존 항목 수정, 새 항목 추가를 포함하는 다른 DataArray 생성
|
||||
var item2Modified = new DataObject { { "id", "2" }, { "value", "B_modified" } };
|
||||
var item4New = new DataObject { { "id", "4" }, { "value", "D" } };
|
||||
var otherArray = new DataArray(new[] { item2Modified, item4New });
|
||||
|
||||
// Act
|
||||
_dataArray.UpdateDifferent(otherArray);
|
||||
|
||||
// Assert
|
||||
// 제거된 항목(_item1) 확인
|
||||
Assert.AreEqual(1, _dataArray.RemovedItems.Count, "제거된 항목이 1개여야 합니다.");
|
||||
Assert.AreEqual("1", _dataArray.RemovedItems[0].Id);
|
||||
|
||||
// 추가된 항목(item4New) 확인
|
||||
Assert.AreEqual(1, _dataArray.AddedItems.Count, "추가된 항목이 1개여야 합니다.");
|
||||
Assert.AreEqual("4", _dataArray.AddedItems[0].Id);
|
||||
|
||||
// 수정된 항목(_item2) 확인
|
||||
Assert.AreEqual(1, _dataArray.ModifiedList.Count, "수정된 항목이 1개여야 합니다.");
|
||||
Assert.AreEqual("2", _dataArray.ModifiedList[0].Id);
|
||||
|
||||
// 최종 컬렉션 상태 확인
|
||||
Assert.AreEqual(2, _dataArray.Count, "최종 컬렉션의 크기는 2여야 합니다.");
|
||||
Assert.IsFalse(_dataArray.Any(i => i.Id == "1"), "제거된 항목이 컬렉션에 없어야 합니다.");
|
||||
Assert.IsTrue(_dataArray.Any(i => i.Id == "4"), "추가된 항목이 컬렉션에 있어야 합니다.");
|
||||
|
||||
var updatedItem = _dataArray.First(i => i.Id == "2");
|
||||
Assert.AreEqual("B_modified", updatedItem.GetString("value"), "수정된 항목의 값이 반영되어야 합니다.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClearTrackedChanges_ResetsAllChangeLists()
|
||||
{
|
||||
// Arrange
|
||||
// UpdateDifferent를 호출하여 변경 내역을 생성합니다.
|
||||
var otherArray = new DataArray(new[] { _item3 });
|
||||
_dataArray.UpdateDifferent(otherArray);
|
||||
Assert.Greater(_dataArray.UpdatedCount, 0, "테스트 준비 단계에서 변경 내역이 있어야 합니다.");
|
||||
|
||||
// Act
|
||||
_dataArray.ClearTrackedChanges();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, _dataArray.UpdatedCount, "UpdatedCount는 0이어야 합니다.");
|
||||
Assert.IsEmpty(_dataArray.AddedItems, "AddedItems 리스트는 비어있어야 합니다.");
|
||||
Assert.IsEmpty(_dataArray.RemovedItems, "RemovedItems 리스트는 비어있어야 합니다.");
|
||||
Assert.IsEmpty(_dataArray.ModifiedList, "ModifiedList 리스트는 비어있어야 합니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Tests/Data/DataArrayTests.cs.meta
Normal file
2
Assets/Scripts/UVC/Tests/Data/DataArrayTests.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b5f8fdb249f43b438e16aa4e70c8adf
|
||||
263
Assets/Scripts/UVC/Tests/Data/DataObjectTests.cs
Normal file
263
Assets/Scripts/UVC/Tests/Data/DataObjectTests.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
#nullable enable
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UVC.Data;
|
||||
|
||||
namespace UVC.Tests.Data
|
||||
{
|
||||
[TestFixture]
|
||||
public class DataObjectTests
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 모든 테스트 메서드를 실행하는 메서드입니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 이 메서드는 클래스의 모든 테스트 메서드를 순차적으로 호출하고
|
||||
/// 각 테스트의 성공 또는 실패 여부를 로그로 출력합니다.
|
||||
/// </remarks>
|
||||
public void TestAll()
|
||||
{
|
||||
|
||||
Debug.Log("===== DataMapper 테스트 시작 =====");
|
||||
|
||||
SetUp();
|
||||
RunTest(nameof(UpdateDifferent_WhenCalled_UpdatesAndTracksChanges), UpdateDifferent_WhenCalled_UpdatesAndTracksChanges);
|
||||
SetUp();
|
||||
RunTest(nameof(Indexer_WhenPropertyIsSet_TracksChange), Indexer_WhenPropertyIsSet_TracksChange);
|
||||
SetUp();
|
||||
RunTest(nameof(Indexer_WhenNewPropertyIsAdded_TracksAsChange), Indexer_WhenNewPropertyIsAdded_TracksAsChange);
|
||||
SetUp();
|
||||
RunTest(nameof(GetUpdatedObject_WhenCalledAfterChanges_ReturnsOnlyUpdatedProperties), GetUpdatedObject_WhenCalledAfterChanges_ReturnsOnlyUpdatedProperties);
|
||||
SetUp();
|
||||
RunTest(nameof(MarkAllAsUpdated_WhenCalled_MarksAllPropertiesAsChanged), MarkAllAsUpdated_WhenCalled_MarksAllPropertiesAsChanged);
|
||||
SetUp();
|
||||
RunTest(nameof(Remove_WhenCalled_RemovesPropertyAndChangeTracking), Remove_WhenCalled_RemovesPropertyAndChangeTracking);
|
||||
SetUp();
|
||||
RunTest(nameof(GetTypeMethods_ReturnCorrectTypes), GetTypeMethods_ReturnCorrectTypes);
|
||||
SetUp();
|
||||
RunTest(nameof(Constructor_FromJObject_CreatesCorrectObject), Constructor_FromJObject_CreatesCorrectObject);
|
||||
SetUp();
|
||||
RunTest(nameof(IdProperty_ReturnsCorrectId), IdProperty_ReturnsCorrectId);
|
||||
SetUp();
|
||||
RunTest(nameof(KeyOrder_IsPreservedByInsertion), KeyOrder_IsPreservedByInsertion);
|
||||
SetUp();
|
||||
RunTest(nameof(DataObjectPool_GetAndReturn_WorksCorrectly), DataObjectPool_GetAndReturn_WorksCorrectly);
|
||||
|
||||
|
||||
Debug.Log("===== DataMapper 테스트 완료 =====");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 단일 테스트 메서드를 실행하고 결과를 로그로 출력합니다.
|
||||
/// </summary>
|
||||
/// <param name="testName">테스트 메서드 이름</param>
|
||||
/// <param name="testAction">실행할 테스트 메서드</param>
|
||||
private void RunTest(string testName, Action testAction)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.Log($"테스트 시작: {testName}");
|
||||
testAction();
|
||||
Debug.Log($"테스트 성공: {testName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"테스트 실패: {testName}\n{ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
private DataObject _dataObject = new DataObject();
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
// 각 테스트가 실행되기 전에 DataObject를 초기화합니다.
|
||||
_dataObject = new DataObject
|
||||
{
|
||||
{ "id", 1 },
|
||||
{ "name", "Test" },
|
||||
{ "value", 100.5f },
|
||||
{ "isActive", true }
|
||||
};
|
||||
// 초기 상태에서는 변경된 속성이 없어야 합니다.
|
||||
_dataObject.ClearChangedProperties(); // 변경 목록 초기화
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateDifferent_WhenCalled_UpdatesAndTracksChanges()
|
||||
{
|
||||
// Arrange
|
||||
var otherDataObject = new DataObject
|
||||
{
|
||||
{ "id", 1 }, // 동일한 값
|
||||
{ "name", "Updated Test" }, // 변경된 값
|
||||
{ "newValue", "new" } // 새로 추가된 값
|
||||
};
|
||||
|
||||
// Act
|
||||
_dataObject.UpdateDifferent(otherDataObject);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("Updated Test", _dataObject["name"]);
|
||||
Assert.AreEqual("new", _dataObject["newValue"]);
|
||||
Assert.AreEqual(2, _dataObject.UpdatedCount);
|
||||
Assert.Contains("name", _dataObject.ChangedProperies.ToList());
|
||||
Assert.Contains("newValue", _dataObject.ChangedProperies.ToList());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Indexer_WhenPropertyIsSet_TracksChange()
|
||||
{
|
||||
// Act
|
||||
_dataObject["name"] = "New Name";
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, _dataObject.UpdatedCount);
|
||||
Assert.Contains("name", _dataObject.ChangedProperies.ToList());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Indexer_WhenNewPropertyIsAdded_TracksAsChange()
|
||||
{
|
||||
// Act
|
||||
_dataObject["newKey"] = "newValue";
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, _dataObject.UpdatedCount, "새로 추가된 속성은 변경으로 간주되어야 합니다.");
|
||||
Assert.IsTrue(_dataObject.ChangedProperies.Contains("newKey"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUpdatedObject_WhenCalledAfterChanges_ReturnsOnlyUpdatedProperties()
|
||||
{
|
||||
// Arrange
|
||||
_dataObject["name"] = "Updated Name";
|
||||
_dataObject["value"] = 200.0f;
|
||||
|
||||
// Act
|
||||
var updatedObject = _dataObject.GetUpdatedObject() as DataObject;
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(updatedObject);
|
||||
Assert.AreEqual(2, updatedObject.Count);
|
||||
Assert.AreEqual("Updated Name", updatedObject["name"]);
|
||||
Assert.AreEqual(200.0f, updatedObject["value"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MarkAllAsUpdated_WhenCalled_MarksAllPropertiesAsChanged()
|
||||
{
|
||||
// Act
|
||||
_dataObject.MarkAllAsUpdated();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(_dataObject.Count, _dataObject.UpdatedCount);
|
||||
CollectionAssert.AreEquivalent(_dataObject.Keys, _dataObject.ChangedProperies.ToList());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Remove_WhenCalled_RemovesPropertyAndChangeTracking()
|
||||
{
|
||||
// Arrange
|
||||
_dataObject["name"] = "A new name to be removed";
|
||||
Assert.Contains("name", _dataObject.ChangedProperies.ToList());
|
||||
|
||||
// Act
|
||||
bool result = _dataObject.Remove("name");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
Assert.IsFalse(_dataObject.ContainsKey("name"));
|
||||
Assert.IsFalse(_dataObject.ChangedProperies.Contains("name"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetTypeMethods_ReturnCorrectTypes()
|
||||
{
|
||||
// Assert
|
||||
Assert.AreEqual(1, _dataObject.GetInt("id"));
|
||||
Assert.AreEqual("Test", _dataObject.GetString("name"));
|
||||
Assert.AreEqual(100.5f, _dataObject.GetFloat("value"));
|
||||
Assert.IsTrue(_dataObject.GetBool("isActive"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Constructor_FromJObject_CreatesCorrectObject()
|
||||
{
|
||||
// Arrange
|
||||
var jObject = new JObject
|
||||
{
|
||||
{ "id", 10 },
|
||||
{ "user", "jobject_user" }
|
||||
};
|
||||
|
||||
// Act
|
||||
var dataObject = new DataObject(jObject);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(10, dataObject.GetInt("id"));
|
||||
Assert.AreEqual("jobject_user", dataObject.GetString("user"));
|
||||
Assert.AreEqual(0, dataObject.UpdatedCount, "생성자 호출 시에는 변경 추적을 하지 않아야 합니다.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IdProperty_ReturnsCorrectId()
|
||||
{
|
||||
// Arrange
|
||||
_dataObject.IdKey = "id";
|
||||
|
||||
// Act & Assert
|
||||
Assert.AreEqual("1", _dataObject.Id);
|
||||
|
||||
// Arrange
|
||||
var noIdKeyObject = new DataObject { { "name", "Test" }, { "value", 100 } };
|
||||
|
||||
// Act & Assert
|
||||
// IdKey가 설정되지 않은 경우 첫 번째 항목의 값을 Id로 사용해야 합니다.
|
||||
Assert.AreEqual("Test", noIdKeyObject.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void KeyOrder_IsPreservedByInsertion()
|
||||
{
|
||||
// Arrange
|
||||
var orderedDataObject = new DataObject();
|
||||
|
||||
// Act
|
||||
orderedDataObject.Add("first", 1);
|
||||
orderedDataObject.Add("second", 2);
|
||||
orderedDataObject.Add("third", 3);
|
||||
|
||||
// Assert
|
||||
var keys = orderedDataObject.Keys.ToList();
|
||||
Assert.AreEqual("first", keys[0]);
|
||||
Assert.AreEqual("second", keys[1]);
|
||||
Assert.AreEqual("third", keys[2]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DataObjectPool_GetAndReturn_WorksCorrectly()
|
||||
{
|
||||
// Act
|
||||
var obj = DataObjectPool.Get();
|
||||
obj["test"] = "value";
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(obj);
|
||||
Assert.AreEqual(1, obj.Count);
|
||||
|
||||
// Act
|
||||
DataObjectPool.Return(obj);
|
||||
var retrievedObj = DataObjectPool.Get();
|
||||
|
||||
// Assert
|
||||
// 풀에서 다시 가져온 객체는 비어 있어야 합니다.
|
||||
Assert.AreEqual(0, retrievedObj.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Tests/Data/DataObjectTests.cs.meta
Normal file
2
Assets/Scripts/UVC/Tests/Data/DataObjectTests.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ac90569e2576f04fbdf6394a36db8d8
|
||||
@@ -8,7 +8,9 @@ namespace UVC.Tests
|
||||
{
|
||||
//new DataMapperTests().TestAll();
|
||||
//new HttpPipeLineTests().TestAll();
|
||||
new MQTTPipeLineTests().TestAll();
|
||||
//new MQTTPipeLineTests().TestAll();
|
||||
//new DataObjectTests().TestAll();
|
||||
new DataArrayTests().TestAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user