DataArray, DataObject pool 적용. 버그 수정. AGV 움직임 튀는 거 수정 필요
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user