테스트 중
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace UVC.Data.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// DataObject 객체 컬렉션의 변경사항을 추적하는 데이터 배열 클래스
|
||||
/// </summary>
|
||||
public class DataArray : List<DataObject>, IDataObject
|
||||
public partial class DataArray : List<DataObject>, IDataObject
|
||||
{
|
||||
private bool isInPool = false;
|
||||
/// <summary>
|
||||
@@ -164,6 +167,77 @@ namespace UVC.Data.Core
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UniTask 기반 비차단(코루틴 대체) 방식으로 JArray로부터 데이터를 채웁니다. WebGL 같은 싱글스레드 환경에서
|
||||
/// UI 블로킹을 피하기 위해 일정 수의 항목마다 await로 제어권을 양보합니다.
|
||||
/// await myDataArray.FromJArrayIncrementalAsync(jArray, 100, cancellationToken);
|
||||
/// </summary>
|
||||
/// <param name="jArray">JArray 소스</param>
|
||||
/// <param name="batchSize">한 번에 처리할 항목 수</param>
|
||||
/// <param name="cancellationToken">취소 토큰</param>
|
||||
public async UniTask FromJArrayIncrementalAsync(JArray jArray, int batchSize = 50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (jArray == null) return;
|
||||
int count = 0;
|
||||
foreach (var item in jArray)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
base.Add(ConvertToDataObject(item));
|
||||
count++;
|
||||
if (count % batchSize == 0)
|
||||
{
|
||||
await UniTask.Yield(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UniTask 기반 비차단 방식으로 JSON 문자열을 스트리밍하여 파싱합니다.
|
||||
/// JsonTextReader를 사용해 토큰 단위로 읽고, 일정 수의 항목마다 await로 제어권을 양보합니다.
|
||||
/// await myDataArray.FromJsonStringIncrementalAsync(jArray, 100, cancellationToken);
|
||||
/// </summary>
|
||||
/// <param name="jsonString">JSON 문자열</param>
|
||||
/// <param name="batchSize">한 번에 처리할 항목 수</param>
|
||||
/// <param name="cancellationToken">취소 토큰</param>
|
||||
public async UniTask FromJsonStringIncrementalAsync(string jsonString, int batchSize = 50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(jsonString)) return;
|
||||
|
||||
using (var sr = new System.IO.StringReader(jsonString))
|
||||
using (var reader = new Newtonsoft.Json.JsonTextReader(sr))
|
||||
{
|
||||
reader.SupportMultipleContent = false;
|
||||
|
||||
// 기대: StartArray
|
||||
if (!reader.Read() || reader.TokenType != Newtonsoft.Json.JsonToken.StartArray) return;
|
||||
|
||||
int count = 0;
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (reader.TokenType == Newtonsoft.Json.JsonToken.StartObject || reader.TokenType == Newtonsoft.Json.JsonToken.StartArray ||
|
||||
reader.TokenType == Newtonsoft.Json.JsonToken.String || reader.TokenType == Newtonsoft.Json.JsonToken.Integer ||
|
||||
reader.TokenType == Newtonsoft.Json.JsonToken.Float || reader.TokenType == Newtonsoft.Json.JsonToken.Boolean)
|
||||
{
|
||||
// JToken.ReadFrom는 현재 토큰에서 전체 값(객체나 배열 등)을 읽습니다.
|
||||
JToken token = JToken.ReadFrom(reader);
|
||||
base.Add(ConvertToDataObject(token));
|
||||
count++;
|
||||
if (count % batchSize == 0)
|
||||
{
|
||||
await UniTask.Yield(cancellationToken);
|
||||
}
|
||||
}
|
||||
else if (reader.TokenType == Newtonsoft.Json.JsonToken.EndArray)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DataArray FromCapacity(int capacity)
|
||||
{
|
||||
Capacity = capacity;
|
||||
@@ -333,7 +407,7 @@ namespace UVC.Data.Core
|
||||
|
||||
/// <summary>
|
||||
/// 현재 데이터 객체의 업데이트된 버전을 생성하고 반환합니다. 선택적으로 메모리 할당 최적화를 위해 풀을 사용합니다.
|
||||
// </summary>
|
||||
/// </summary>
|
||||
/// <remarks>이 메서드는 현재 데이터 객체와 관련 요소의 깊은 복사를 수행합니다.
|
||||
/// <paramref name="fromPool"/>이 <see langword="true"/>이면 메서드는 메모리 사용을 최적화하기 위해 객체 풀에서 새 인스턴스를 검색합니다. 그렇지 않으면 새 인스턴스가 직접 생성됩니다. 반환된
|
||||
/// 객체는 추가, 제거 및 수정을 포함하여 현재 객체의 상태에 대한 업데이트를 반영합니다.
|
||||
@@ -377,7 +451,7 @@ namespace UVC.Data.Core
|
||||
/// <summary>
|
||||
/// 업데이트 된 속성의 수.
|
||||
/// </summary>
|
||||
/// <returns>업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면 0을 반환합니다.</returns>
|
||||
/// <returns>업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면0을 반환합니다.</returns>
|
||||
public int UpdatedCount { get => addedList.Count + modifiedList.Count + removedList.Count; }
|
||||
|
||||
private bool isUpdateImmediately = false;
|
||||
@@ -471,12 +545,11 @@ namespace UVC.Data.Core
|
||||
/// 현재 <see cref="DataArray"/> 인스턴스의 요소 및 관련 상태를 포함한 깊은 복사본을 생성합니다.
|
||||
///
|
||||
/// </summary>
|
||||
/// <remarks>이 메서드는 현재 배열에 있는 모든 <see cref="DataObject"/> 요소의 깊은 복사본을 포함하는 새로운 <see cref="DataArray"/> 인스턴스를 반환합니다.
|
||||
/// <remarks>이 메서드는 현재 배열에 있는 모든 <see cref="DataObject"/> 요소의 깊은 복사본을 포함하는 새로운 <see cref=\"DataArray\"/> 인스턴스를 반환합니다.
|
||||
/// 복사된 인스턴스는 내부 목록에서 추적하는 추가, 제거 또는 수정된 요소를 포함하여 원본 인스턴스의 상태도 복제합니다.
|
||||
/// </remarks>
|
||||
/// <param name="fromPool">객체 풀에서 복제할지 여부를 지정합니다. 기본값은 true입니다.</param>
|
||||
/// <returns>현재 인스턴스의 깊은 복사본인 새로운 <see cref="DataArray"/> 인스턴스를 반환합니다.</returns>
|
||||
|
||||
public DataArray Copy(bool fromPool = true)
|
||||
{
|
||||
// 풀에서 새 DataArray 인스턴스를 가져옵니다.
|
||||
@@ -540,5 +613,96 @@ namespace UVC.Data.Core
|
||||
dataArray.FromJArray(jArray);
|
||||
return dataArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JToken을 DataObject로 비동기 재질화하여 반환합니다. 이름: ConvertTokenToDataObjectAsync
|
||||
/// </summary>
|
||||
public async UniTask<DataObject> ConvertTokenToDataObjectAsync(JToken token, int batchSize =50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (token.Type == JTokenType.Object)
|
||||
{
|
||||
var dataObject = DataObjectPool.Get();
|
||||
await dataObject.FromJObjectIncrementalAsync((JObject)token, batchSize, cancellationToken);
|
||||
return dataObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
var dataObject = DataObjectPool.Get();
|
||||
var value = await MaterializePrimitiveOrCollectionAsync(token, batchSize, cancellationToken);
|
||||
dataObject.Add("value", value);
|
||||
return dataObject;
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask<object?> MaterializePrimitiveOrCollectionAsync(JToken token, int batchSize, CancellationToken cancellationToken)
|
||||
{
|
||||
switch (token.Type)
|
||||
{
|
||||
case JTokenType.String:
|
||||
return token.ToString();
|
||||
case JTokenType.Integer:
|
||||
return token.ToObject<int>();
|
||||
case JTokenType.Float:
|
||||
return token.ToObject<float>();
|
||||
case JTokenType.Boolean:
|
||||
return token.ToObject<bool>();
|
||||
case JTokenType.Array:
|
||||
JArray array = (JArray)token;
|
||||
var dataArray = DataArrayPool.Get();
|
||||
await dataArray.FromJArrayIncrementalAsync(array, batchSize, cancellationToken);
|
||||
return dataArray;
|
||||
case JTokenType.Object:
|
||||
var dataObject = DataObjectPool.Get();
|
||||
await dataObject.FromJObjectIncrementalAsync((JObject)token, batchSize, cancellationToken);
|
||||
return dataObject;
|
||||
default:
|
||||
return token.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// Note: existing synchronous ConvertToDataObject remains for compatibility
|
||||
|
||||
/// <summary>
|
||||
/// 비동기 팩토리: JArray로부터 완전히 점진 재질화된 DataArray를 생성합니다.
|
||||
/// </summary>
|
||||
public static async UniTask<DataArray> CreateFromJArrayAsync(JArray jArray, int batchSize =50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var arr = new DataArray();
|
||||
await arr.FromJArrayIncrementalAsync(jArray, batchSize, cancellationToken);
|
||||
return arr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 비동기 팩토리: JSON 문자열로부터 완전히 점진 재질화된 DataArray를 생성합니다.
|
||||
/// </summary>
|
||||
public static async UniTask<DataArray> CreateFromJsonStringAsync(string jsonString, int batchSize =50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(jsonString)) return new DataArray();
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
var arr = new DataArray();
|
||||
await arr.FromJsonStringIncrementalAsync(jsonString, batchSize, cancellationToken);
|
||||
return arr;
|
||||
#else
|
||||
var jArr = await UniTask.RunOnThreadPool(() => JArray.Parse(jsonString));
|
||||
return await CreateFromJArrayAsync(jArr, batchSize, cancellationToken);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 비동기 팩토리: 스트림으로부터 완전히 점진 재질화된 DataArray를 생성합니다.
|
||||
/// </summary>
|
||||
public static async UniTask<DataArray> CreateFromStreamAsync(System.IO.Stream jsonStream, int batchSize =50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (jsonStream == null) throw new ArgumentNullException(nameof(jsonStream));
|
||||
using (var sr = new System.IO.StreamReader(jsonStream))
|
||||
using (var reader = new Newtonsoft.Json.JsonTextReader(sr))
|
||||
{
|
||||
reader.SupportMultipleContent = true;
|
||||
var serializer = new Newtonsoft.Json.JsonSerializer();
|
||||
var jArr = serializer.Deserialize<JArray>(reader);
|
||||
if (jArr == null) return new DataArray();
|
||||
return await CreateFromJArrayAsync(jArr, batchSize, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UVC.Extention;
|
||||
|
||||
namespace UVC.Data.Core
|
||||
@@ -26,8 +29,8 @@ namespace UVC.Data.Core
|
||||
/// // 필드 이름 변환 설정
|
||||
/// mask.NamesForReplace = new Dictionary<string, string>
|
||||
/// {
|
||||
/// { "userName", "name" },
|
||||
/// { "userEmail", "email" }
|
||||
/// { "userName", "name" },
|
||||
/// { "userEmail", "email" }
|
||||
/// };
|
||||
///
|
||||
/// // JObject 속성으로 마스킹 규칙 추가
|
||||
@@ -46,7 +49,7 @@ namespace UVC.Data.Core
|
||||
/// // - password는 제외됨
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class DataObject : OrderedDictionary<string, object?>, IDataObject
|
||||
public partial class DataObject : OrderedDictionary<string, object?>, IDataObject
|
||||
{
|
||||
private bool isInPool = false;
|
||||
/// <summary>
|
||||
@@ -118,7 +121,7 @@ namespace UVC.Data.Core
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Id에 해당하는 key 문자열
|
||||
/// Id에 해당하는 key 문자열
|
||||
/// </summary>
|
||||
public string? IdKey { get; set; } = null;
|
||||
|
||||
@@ -286,11 +289,142 @@ namespace UVC.Data.Core
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// UniTask 기반 비차단 방식으로 JObject로부터 데이터를 채웁니다. WebGL 같은 싱글스레드 환경에서
|
||||
/// UI 블로킹을 피하기 위해 일정 수의 키마다 await로 제어권을 양보합니다.
|
||||
/// await myDataObject.FromJObjectIncrementalAsync(jObject, 200, cancellationToken);
|
||||
/// </summary>
|
||||
/// <param name="other">JObject 소스</param>
|
||||
/// <param name="batchSize">한 번에 처리할 속성 수</param>
|
||||
/// <param name="cancellationToken">취소 토큰</param>
|
||||
public async UniTask FromJObjectIncrementalAsync(JObject other, int batchSize =50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (other == null) return;
|
||||
IdKey = null;
|
||||
Name = string.Empty;
|
||||
_isInitializing = true;
|
||||
int count =0;
|
||||
try
|
||||
{
|
||||
foreach (var prop in other.Properties())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
var value = await ConvertJTokenToObjectAsync(prop.Value, batchSize, cancellationToken);
|
||||
this[prop.Name] = value;
|
||||
#else
|
||||
this[prop.Name] = ConvertJTokenToObject(prop.Value);
|
||||
#endif
|
||||
count++;
|
||||
if (count % batchSize ==0)
|
||||
{
|
||||
await UniTask.Yield(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isInitializing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UniTask 기반 비차단 방식으로 JSON 문자열을 스트리밍하여 파싱합니다.
|
||||
/// JsonTextReader를 사용해 토큰 단위로 읽고, 일정 수의 항목마다 await로 제어권을 양보합니다.
|
||||
/// await myDataObject.FromJsonStringIncrementalAsync(jsonString, 200, cancellationToken);
|
||||
/// </summary>
|
||||
/// <param name="jsonString">JSON 문자열</param>
|
||||
/// <param name="batchSize">한 번에 처리할 속성 수</param>
|
||||
/// <param name="cancellationToken">취소 토큰</param>
|
||||
public async UniTask FromJsonStringIncrementalAsync(string jsonString, int batchSize =50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(jsonString)) return;
|
||||
|
||||
using (var sr = new System.IO.StringReader(jsonString))
|
||||
using (var reader = new Newtonsoft.Json.JsonTextReader(sr))
|
||||
{
|
||||
reader.SupportMultipleContent = false;
|
||||
|
||||
// 기대: StartObject
|
||||
if (!reader.Read() || reader.TokenType != Newtonsoft.Json.JsonToken.StartObject) return;
|
||||
|
||||
IdKey = null;
|
||||
Name = string.Empty;
|
||||
_isInitializing = true;
|
||||
int count =0;
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (reader.TokenType == Newtonsoft.Json.JsonToken.PropertyName)
|
||||
{
|
||||
string propName = (string)reader.Value!;
|
||||
|
||||
// 다음 토큰은 값의 시작이어야 함. JToken.ReadFrom를 사용하여 값 전체를 읽는다.
|
||||
if (!reader.Read()) break;
|
||||
|
||||
JToken token = JToken.ReadFrom(reader);
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
var value = await ConvertJTokenToObjectAsync(token, batchSize, cancellationToken);
|
||||
this[propName] = value;
|
||||
#else
|
||||
this[propName] = ConvertJTokenToObject(token);
|
||||
#endif
|
||||
|
||||
count++;
|
||||
if (count % batchSize ==0)
|
||||
{
|
||||
await UniTask.Yield(cancellationToken);
|
||||
}
|
||||
}
|
||||
else if (reader.TokenType == Newtonsoft.Json.JsonToken.EndObject)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_isInitializing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JToken을 적절한 C# 객체 타입으로 변환하는 헬퍼 메서드입니다.
|
||||
/// </summary>
|
||||
/// <param name="token">변환할 JToken 객체</param>
|
||||
/// <returns>변환된 C# 객체</returns>
|
||||
private async UniTask<object?> MaterializeJTokenAsync(JToken token, int batchSize =50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
switch (token.Type)
|
||||
{
|
||||
case JTokenType.String:
|
||||
return token.ToString();
|
||||
case JTokenType.Integer:
|
||||
return token.ToObject<int>();
|
||||
case JTokenType.Float:
|
||||
return token.ToObject<float>();
|
||||
case JTokenType.Boolean:
|
||||
return token.ToObject<bool>();
|
||||
case JTokenType.Object:
|
||||
// 점진적으로 DataObject로 채우기
|
||||
var dataObject = DataObjectPool.Get();
|
||||
await dataObject.FromJObjectIncrementalAsync((JObject)token, batchSize, cancellationToken);
|
||||
return dataObject;
|
||||
case JTokenType.Array:
|
||||
JArray array = (JArray)token;
|
||||
var dataArray = DataArrayPool.Get();
|
||||
await dataArray.FromJArrayIncrementalAsync(array, batchSize, cancellationToken);
|
||||
return dataArray;
|
||||
default:
|
||||
return token.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 기존 ConvertJTokenToObject는 동기 API를 유지하되, 호출자가 비동기 전환을 원하면
|
||||
/// MaterializeJTokenAsync를 직접 호출하도록 합니다. 기본 동작은 기존과 동일합니다.
|
||||
/// </summary>
|
||||
private object? ConvertJTokenToObject(JToken token)
|
||||
{
|
||||
switch (token.Type)
|
||||
@@ -315,6 +449,15 @@ namespace UVC.Data.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 새 비동기 전환용 헬퍼: 동기 ConvertJTokenToObject 대신 사용하여 비동기적으로 토큰을 재질화합니다.
|
||||
/// 메서드 이름: ConvertJTokenToObjectAsync
|
||||
/// </summary>
|
||||
public UniTask<object?> ConvertJTokenToObjectAsync(JToken token, int batchSize =50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return MaterializeJTokenAsync(token, batchSize, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 프로퍼티를 변경된 것으로 표시합니다.
|
||||
/// 전체 데이터가 갱신되었을 때 사용합니다.
|
||||
@@ -383,14 +526,13 @@ namespace UVC.Data.Core
|
||||
/// <returns>변환된 정수 값 또는 기본값</returns>
|
||||
public int? GetInt(string propertyName, int? defaultValue = null)
|
||||
{
|
||||
if (!TryGetValue(propertyName, out var v) || v == null) return defaultValue;
|
||||
return v switch
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
int i => i,
|
||||
long l when l >= int.MinValue && l <= int.MaxValue => (int)l,
|
||||
string s when int.TryParse(s, out var i2) => i2,
|
||||
_ => defaultValue
|
||||
};
|
||||
if (value is int intValue)
|
||||
return intValue;
|
||||
return Convert.ToInt32(value);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -573,7 +715,7 @@ namespace UVC.Data.Core
|
||||
return null; // 깊이 제한 초과 시 탐색 중단
|
||||
}
|
||||
|
||||
// 1. 현재 객체에서 직접 속성 검색
|
||||
//1. 현재 객체에서 직접 속성 검색
|
||||
if (TryGetValue(propertyName, out object? value) && value != null)
|
||||
{
|
||||
if (value is DataObject dataObject)
|
||||
@@ -592,12 +734,12 @@ namespace UVC.Data.Core
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 중첩된 DataObject에서 재귀적으로 검색
|
||||
//2. 중첩된 DataObject에서 재귀적으로 검색
|
||||
foreach (KeyValuePair<string, object?> keyValue in this)
|
||||
{
|
||||
if (keyValue.Value is DataObject nestedDataObject)
|
||||
{
|
||||
// 재귀 호출 시 깊이를 1 증가
|
||||
// 재귀 호출 시 깊이를1 증가
|
||||
DataObject? foundInNested = nestedDataObject.GetDataObjectInternal(propertyName, currentDepth + 1, maxDepth);
|
||||
if (foundInNested != null)
|
||||
{
|
||||
@@ -692,13 +834,7 @@ namespace UVC.Data.Core
|
||||
/// </summary>
|
||||
public void RemoveAll()
|
||||
{
|
||||
if (Count == 0)
|
||||
{
|
||||
changedProperies.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var value in Values)
|
||||
foreach (var value in Values.ToList())
|
||||
{
|
||||
if (value is DataObject dataObject)
|
||||
{
|
||||
@@ -823,8 +959,8 @@ namespace UVC.Data.Core
|
||||
foreach (var keyValue in otherDataObject)
|
||||
{
|
||||
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] != null && keyValue.Value == null)
|
||||
|| (this[keyValue.Key] != null && keyValue.Value != null && !this[keyValue.Key]!.Equals(keyValue.Value)))
|
||||
{
|
||||
//참조 타입과 값 타입 구분하여 복사
|
||||
object? valueToSet;
|
||||
@@ -866,7 +1002,7 @@ namespace UVC.Data.Core
|
||||
/// cref="DataArray"/> 유형의 속성은 깊이 복사되고, 다른 유형의 속성은 직접 복사되도록 합니다. <c>IdKey</c>가 없으면
|
||||
/// 현재 객체의 첫 번째 키-값 쌍이 업데이트된 객체에 포함됩니다.
|
||||
///</remarks>
|
||||
/// <param name="fromPool">업데이트된 객체를 풀에서 검색해야 하는지 여부를 나타내는 값입니다. <see langword="true"/>인 경우
|
||||
/// <param name="fromPool">업데이트된 객체가 풀에서 검색해야 하는지 여부를 나타내는 값입니다. <see langword="true"/>인 경우
|
||||
/// <see cref="DataObjectPool"/>에서 객체를 가져옵니다. 그렇지 않으면 새 <see cref="DataObject"/>
|
||||
/// 인스턴스가 생성됩니다.</param>
|
||||
/// <returns>현재 객체에서 복사된 속성과 값을 포함하는 업데이트된 <see cref="IDataObject"/> 인스턴스입니다.
|
||||
@@ -912,7 +1048,6 @@ namespace UVC.Data.Core
|
||||
|
||||
public void ReturnToPool()
|
||||
{
|
||||
if (IsInPool) return; // 중복 반환 방지
|
||||
if (CreatedFromPool)
|
||||
{
|
||||
Reset();
|
||||
@@ -926,7 +1061,7 @@ namespace UVC.Data.Core
|
||||
/// <summary>
|
||||
/// 업데이트 된 속성의 수.
|
||||
/// </summary>
|
||||
/// <returns>업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면 0을 반환합니다.</returns>
|
||||
/// <returns>업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면0을 반환합니다.</returns>
|
||||
public int UpdatedCount { get => changedProperies.Count; }
|
||||
|
||||
private bool isUpdateImmediately = false;
|
||||
@@ -975,6 +1110,49 @@ namespace UVC.Data.Core
|
||||
return ToJObject().ToString(Newtonsoft.Json.Formatting.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 비동기 팩토리: JObject로부터 완전히 점진 재질화된 DataObject를 생성합니다.
|
||||
/// </summary>
|
||||
public static async UniTask<DataObject> CreateFromJObjectAsync(JObject other, int batchSize =50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var obj = new DataObject();
|
||||
await obj.FromJObjectIncrementalAsync(other, batchSize, cancellationToken);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 비동기 팩토리: JSON 문자열로부터 완전히 점진 재질화된 DataObject를 생성합니다.
|
||||
/// </summary>
|
||||
public static async UniTask<DataObject> CreateFromJsonStringAsync(string jsonString, int batchSize =50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(jsonString)) return new DataObject();
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
var obj = new DataObject();
|
||||
await obj.FromJsonStringIncrementalAsync(jsonString, batchSize, cancellationToken);
|
||||
return obj;
|
||||
// WebGL: 스레드가 없으므로 incremental 파서를 사용해 메인스레드에서 점진 처리
|
||||
#else
|
||||
// non-WebGL: 스레드풀에서 전체 파싱 후 비동기 재질화
|
||||
var jObj = await UniTask.RunOnThreadPool(() => JObject.Parse(jsonString));
|
||||
return await CreateFromJObjectAsync(jObj, batchSize, cancellationToken);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 비동기 팩토리: 스트림으로부터 완전히 점진 재질화된 DataObject를 생성합니다.
|
||||
/// </summary>
|
||||
public static async UniTask<DataObject> CreateFromStreamAsync(System.IO.Stream jsonStream, int batchSize =50, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (jsonStream == null) throw new ArgumentNullException(nameof(jsonStream));
|
||||
using (var sr = new System.IO.StreamReader(jsonStream))
|
||||
using (var reader = new Newtonsoft.Json.JsonTextReader(sr))
|
||||
{
|
||||
reader.SupportMultipleContent = true;
|
||||
var serializer = new Newtonsoft.Json.JsonSerializer();
|
||||
var jObj = serializer.Deserialize<JObject>(reader);
|
||||
if (jObj == null) return new DataObject();
|
||||
return await CreateFromJObjectAsync(jObj, batchSize, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using UnityEngine;
|
||||
using UVC.Data.Core;
|
||||
using UVC.Data.Http;
|
||||
using UVC.Log;
|
||||
using UVC.network;
|
||||
using UVC.Tests;
|
||||
|
||||
namespace UVC.Data.Mqtt
|
||||
@@ -197,7 +198,7 @@ namespace UVC.Data.Mqtt
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
if (UseMockup)
|
||||
{
|
||||
ULog.Warn("WebGL Mockup 모드는 아직 구현되지 않았습니다.");
|
||||
Debug.LogWarning("WebGL Mockup 모드는 아직 구현되지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
if (webGlMqttService != null) return;
|
||||
|
||||
@@ -169,8 +169,9 @@ namespace UVC.Data
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static UserSetting? FromDataObject(DataObject dataObject)
|
||||
{
|
||||
if (dataObject == null) return null;
|
||||
|
||||
@@ -332,6 +332,10 @@ namespace UVC.Factory.Playback
|
||||
if (File.Exists(utcZipFilePath))
|
||||
{
|
||||
if (OnProgress != null) OnProgress.Invoke(50, 100, 0.5f);
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
string errorMessage = "Not Supported on WebGL";
|
||||
#else
|
||||
//압축해제 후
|
||||
var zipper = new Zipper();
|
||||
|
||||
@@ -354,7 +358,7 @@ namespace UVC.Factory.Playback
|
||||
}
|
||||
});
|
||||
Debug.Log($"zipper1 errorMessage:{errorMessage} utcSqlFilePath:{utcSqlFilePath} sqlFilePath:{sqlFilePath} utcZipFilePath:{utcZipFilePath}");
|
||||
|
||||
#endif
|
||||
////파일 접근 문제면 2회 0.5초 후에 다시 실행.
|
||||
//if (errorMessage == "Could not open input(7z) file")
|
||||
//{
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
#nullable enable
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UVC.UI.List;
|
||||
using UVC.UI.List.ComponentList;
|
||||
using UVC.UI.Tab;
|
||||
using static UnityEditor.PlayerSettings;
|
||||
|
||||
namespace UVC.Factory.Tab
|
||||
{
|
||||
@@ -78,7 +72,7 @@ namespace UVC.Factory.Tab
|
||||
/// </summary>
|
||||
public void Refresh()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -72,7 +72,13 @@ namespace UVC.network
|
||||
{
|
||||
ULog.Debug($"MQTT Domain:{MQTTDomain} , MQTTPORT:{MQTTPort}");
|
||||
var options = new ConnectionOptionsBuilder()
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
.WithWebSocket(MQTTDomain, MQTTPort)
|
||||
.WithPath("/mqtt") // 필요 시 "/ws" 등 브로커/프록시 설정과 일치하게 변경
|
||||
// .WithTLS() // wss://가 필요하면 활성화
|
||||
#else
|
||||
.WithTCP(MQTTDomain, MQTTPort)
|
||||
#endif
|
||||
.Build();
|
||||
|
||||
if (client != null) Disconnect();
|
||||
|
||||
@@ -3,7 +3,6 @@ using DG.Tweening;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEditor.Search;
|
||||
using UnityEngine;
|
||||
using UVC.Log;
|
||||
using UVC.Util;
|
||||
@@ -78,7 +77,7 @@ namespace UVC.UI.Modal.DatePicker
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
instance.gameObject.SetActive(true);
|
||||
instance._initTime = initialDate;
|
||||
@@ -136,7 +135,8 @@ namespace UVC.UI.Modal.DatePicker
|
||||
var blockerCG = currentBlockerInstance.GetComponent<CanvasGroup>();
|
||||
if (blockerCG != null)
|
||||
{
|
||||
blockerCG.DOFade(0f, 0.3f).OnComplete(() => {
|
||||
blockerCG.DOFade(0f, 0.3f).OnComplete(() =>
|
||||
{
|
||||
UnityEngine.Object.Destroy(currentBlockerInstance); // 완전히 제거
|
||||
}); // 부드럽게 사라지게
|
||||
}
|
||||
@@ -145,7 +145,7 @@ namespace UVC.UI.Modal.DatePicker
|
||||
|
||||
void Start()
|
||||
{
|
||||
|
||||
|
||||
Vector3 startPos = _item.transform.localPosition;
|
||||
_dateItems.Clear();
|
||||
_dateItems.Add(_item);
|
||||
@@ -169,10 +169,10 @@ namespace UVC.UI.Modal.DatePicker
|
||||
|
||||
void CreateCalendar()
|
||||
{
|
||||
|
||||
|
||||
DateTime firstDay = _dateTime.AddDays(-(_dateTime.Day - 1));
|
||||
int index = GetDays(firstDay.DayOfWeek);
|
||||
|
||||
|
||||
int date = 0;
|
||||
for (int i = 0; i < _totalDateNum; i++)
|
||||
{
|
||||
|
||||
@@ -63,6 +63,9 @@ namespace UVC.Util
|
||||
/// </example>
|
||||
public async UniTask<string> Decompress(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
|
||||
{
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
return "Decompression not supported on WebGL platform.";
|
||||
#else
|
||||
if (zipFilePath.ToLower().Contains(".zip"))
|
||||
{
|
||||
return await DecompressZip(zipFilePath, decompressFolderPath, OnProgress);
|
||||
@@ -72,6 +75,7 @@ namespace UVC.Util
|
||||
return await Decompress7Zip(zipFilePath, decompressFolderPath, OnProgress);
|
||||
}
|
||||
return "Unsupported compressed file";
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -98,6 +102,9 @@ namespace UVC.Util
|
||||
/// </example>
|
||||
public async UniTask<string> DecompressZip(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
|
||||
{
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
return "Decompression not supported on WebGL platform.";
|
||||
#else
|
||||
progress = new int[1];
|
||||
progress2 = new ulong[1];
|
||||
isComplete = false;
|
||||
@@ -108,22 +115,12 @@ namespace UVC.Util
|
||||
{
|
||||
//getFileInfo zip 내의 콘텐츠의 총 압축되지 않은 바이트를 반환한다.
|
||||
ulong totalBytes = lzip.getFileInfo(zipFilePath);
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
// WebGL: ThreadPool 사용 불가 → 동기 처리
|
||||
OnProgress?.Invoke(0, (long)totalBytes, 0f);
|
||||
int result = lzip.decompress_File(zipFilePath, decompressFolderPath, progress, null, progress2);
|
||||
isComplete = true;
|
||||
percent = 1f;
|
||||
OnProgress?.Invoke((long)totalBytes, (long)totalBytes, 1f);
|
||||
#else
|
||||
|
||||
CountPercentZipAsync(totalBytes).Forget();
|
||||
int result = await UniTask.RunOnThreadPool(() =>
|
||||
{
|
||||
return lzip.decompress_File(zipFilePath, decompressFolderPath, progress, null, progress2);
|
||||
});
|
||||
isComplete = true;
|
||||
#endif
|
||||
|
||||
if (result == 1) //success
|
||||
{
|
||||
@@ -143,6 +140,7 @@ namespace UVC.Util
|
||||
}
|
||||
|
||||
return "invalidate File";
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -151,7 +149,8 @@ namespace UVC.Util
|
||||
/// <param name="totalBytes">압축 해제 대상 파일의 총 바이트 수</param>
|
||||
private async UniTaskVoid CountPercentZipAsync(ulong totalBytes)
|
||||
{
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
#else
|
||||
percent = (float)progress2[0] / (float)totalBytes;
|
||||
|
||||
//Debug.Log($"CountPercentAsync totalBytes:{totalBytes} progress:{progress[0]} progress2:{progress2[0]} {percent}");
|
||||
@@ -162,6 +161,7 @@ namespace UVC.Util
|
||||
await UniTask.Delay(TimeSpan.FromMilliseconds(100));//0.1초에 한번씩
|
||||
CountPercentZipAsync(totalBytes).Forget();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -188,6 +188,9 @@ namespace UVC.Util
|
||||
/// </example>
|
||||
public async UniTask<string> Decompress7Zip(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
|
||||
{
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
return "Decompression not supported on WebGL platform.";
|
||||
#else
|
||||
progress = new int[1] { 0 };
|
||||
isComplete = false;
|
||||
this.zipFilePath = zipFilePath;
|
||||
@@ -204,22 +207,12 @@ namespace UVC.Util
|
||||
//fsecurity.AddAccessRule(new FileSystemAccessRule(new NTAccount(name[1]), FileSystemRights.FullControl, InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.None, AccessControlType.Allow));
|
||||
//fileInfo.SetAccessControl(fsecurity);
|
||||
long totalBytes = lzma.getFileSize(zipFilePath);
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
// WebGL: ThreadPool 미사용, 동기 처리
|
||||
OnProgress?.Invoke(0, totalBytes, 0f);
|
||||
int result = lzma.doDecompress7zip(zipFilePath, decompressFolderPath, progress, true, true);
|
||||
isComplete = true;
|
||||
percent = 1f;
|
||||
OnProgress?.Invoke(totalBytes, totalBytes, 1f);
|
||||
#else
|
||||
CountPercent7ZipAsync(totalBytes, true).Forget();
|
||||
int result = await UniTask.RunOnThreadPool(() =>
|
||||
{
|
||||
return lzma.doDecompress7zip(zipFilePath, decompressFolderPath, progress, true, true);
|
||||
});
|
||||
isComplete = true;
|
||||
#endif
|
||||
|
||||
if (result == 1) //success
|
||||
{
|
||||
@@ -242,6 +235,7 @@ namespace UVC.Util
|
||||
else if (result == -6) errorMessage = "File IO error";
|
||||
return errorMessage;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -251,6 +245,8 @@ namespace UVC.Util
|
||||
/// <param name="isFirst">첫 번째 호출 여부</param>
|
||||
private async UniTaskVoid CountPercent7ZipAsync(long totalBytes, bool isFirst = false)
|
||||
{
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
#else
|
||||
if (!isFirst)
|
||||
{
|
||||
long readByted = (long)lzma.getBytesRead();
|
||||
@@ -264,6 +260,7 @@ namespace UVC.Util
|
||||
await UniTask.Delay(TimeSpan.FromMilliseconds(100));//0.1초에 한번씩
|
||||
CountPercent7ZipAsync(totalBytes).Forget();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user