코드 정리 및 DataValidator 추가, HttpPipeLine 쓰레드에서 실행되도록 수정

This commit is contained in:
logonkhi
2025-06-24 19:29:37 +09:00
parent 3acff06eca
commit b3bf7e6eff
50 changed files with 1544 additions and 3064 deletions

View File

@@ -12,6 +12,7 @@ using UnityEngine;
using UVC.Log;
using UVC.Network;
using UVC.Tests;
using UVC.Threading;
namespace UVC.Data
{
@@ -29,6 +30,11 @@ namespace UVC.Data
/// - 안전한 요청 취소 및 자원 정리
/// - 테스트를 위한 목업 기능 지원
///
/// 모든 HTTP 요청은 백그라운드 스레드(스레드풀)에서 처리되어 메인 스레드 차단을 방지합니다.
/// 요청 결과 처리 시 핸들러(SuccessHandler, FailHandler)는 자동으로 메인 스레드에서 호출됩니다.
/// 이를 통해 UI 스레드 차단 없이 효율적인 네트워크 작업을 수행하면서도,
/// UI 업데이트는 안전하게 메인 스레드에서 처리할 수 있습니다.
///
/// 모든 반복 실행은 CancellationTokenSource를 통해 취소할 수 있으며,
/// 취소 후 현재 진행 중인 모든 요청이 안전하게 완료되는 것을 보장합니다.
/// </remarks>
@@ -142,22 +148,33 @@ namespace UVC.Data
/// 지정한 키의 HTTP 요청을 실행합니다.
/// </summary>
/// <param name="key">실행할 요청의 키</param>
/// <param name="switchToMainThread">메인 스레드로 전환할지 여부</param>
/// <returns>비동기 작업</returns>
/// <remarks>
/// 요청 정보의 repeat 속성에 따라 단일 실행 또는 반복 실행을 시작합니다.
/// 이미 실행 중인 반복 작업이 있다면 먼저 중지하고 완료를 대기한 후 새로운 요청을 시작합니다.
/// 단일 실행의 경우 완료될 때까지 대기하지만, 반복 실행은 백그라운드에서 실행됩니다.
///
/// 모든 HTTP 요청 처리는 백그라운드 스레드에서 수행되며, 핸들러만 메인 스레드에서 호출됩니다.
/// </remarks>
/// <exception cref="KeyNotFoundException">지정된 키가 등록되어 있지 않은 경우</exception>
public async UniTask Excute(string key)
public async UniTask Excute(string key, bool switchToMainThread = false)
{
if (infoList.ContainsKey(key))
if (!infoList.ContainsKey(key))
{
HttpPipeLineInfo info = infoList[key];
throw new KeyNotFoundException($"No HTTP request found with key '{key}'.");
}
// 반복 설정에 관계없이 이전에 실행 중인 반복 작업이 있다면 중지
await StopRepeat(key);
HttpPipeLineInfo info = infoList[key];
// 반복 설정에 관계없이 이전에 실행 중인 반복 작업이 있다면 중지
await StopRepeat(key);
// 스레드풀에서 요청 처리 실행
await UniTask.SwitchToThreadPool();
try
{
if (!info.Repeat)
{
// 단일 실행 로직 호출
@@ -166,12 +183,19 @@ namespace UVC.Data
else
{
// 반복 설정이 있는 경우에만 StartRepeat 호출
// Forget()을 호출하지 않고 StartRepeat가 스레드풀에서 계속 실행되도록 함
StartRepeat(key).Forget();
}
}
else
finally
{
throw new KeyNotFoundException($"No HTTP request found with key '{key}'.");
// ExecuteSingle 또는 StartRepeat.Forget() 후 메인 스레드로 복귀하는 것은 선택 사항
// 여기서는 원래 실행 컨텍스트로 돌아가기 위해 메인 스레드로 전환
// 만약 계속해서 백그라운드 스레드에서 실행하려면 이 코드를 제거할 수 있음
if (switchToMainThread)
{
await UniTask.SwitchToMainThread();
}
}
}
@@ -183,8 +207,9 @@ namespace UVC.Data
/// <param name="cancellationToken">요청 취소를 위한 취소 토큰</param>
/// <returns>비동기 작업</returns>
/// <remarks>
/// 이 메서드는 HTTP 요청을 보내고, 응답 데이터를 파싱하여 IDataObject로 변환합니다.
/// 이 메서드는 백그라운드 스레드에서 HTTP 요청을 보내고, 응답 데이터를 파싱하여 IDataObject로 변환합니다.
/// JSON 객체 또는 배열 형식의 응답을 처리할 수 있으며, 취소 토큰을 통해 언제든지 작업을 취소할 수 있습니다.
/// 모든 핸들러 호출은 메인 스레드에서 이루어져 UI 업데이트를 안전하게 수행할 수 있습니다.
/// </remarks>
/// <exception cref="OperationCanceledException">작업이 취소된 경우 발생</exception>
/// <exception cref="JsonException">JSON 응답 파싱 중 오류가 발생한 경우</exception>
@@ -237,7 +262,16 @@ namespace UVC.Data
// 응답 마스크 적용 결과가 성공이 아니면 실패 핸들러 호출 후 반환
if (!responseResult.IsSuccess)
{
info.FailHandler?.Invoke(responseResult.Message!);
if (info.FailHandler != null)
{
string errorMessage = responseResult.Message!;
// UI 스레드에서 실패 핸들러 호출
MainThreadDispatcher.Instance.SendToMainThread(() =>
{
info.FailHandler.Invoke(errorMessage);
});
}
return;
}
else
@@ -248,6 +282,44 @@ namespace UVC.Data
if (result.StartsWith("{"))
{
if (info.Validator != null)
{
if (info.Validator.SupportsStreamParsing && result.Length > info.Validator.SupportsStreamLength)
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(result)))
{
if (info.Validator != null && !info.Validator.IsValid(stream))
{
if (info.FailHandler != null)
{
// UI 스레드에서 실패 핸들러 호출
MainThreadDispatcher.Instance.SendToMainThread(() =>
{
info.FailHandler.Invoke("Data is not Valid");
});
}
return;
}
}
}
else
{
JObject source = JObject.Parse(result);
if (info.Validator != null && !info.Validator.IsValid(source))
{
if (info.FailHandler != null)
{
// UI 스레드에서 실패 핸들러 호출
MainThreadDispatcher.Instance.SendToMainThread(() =>
{
info.FailHandler.Invoke("Data is not Valid");
});
}
return;
}
}
}
if (info.DataMapper != null)
{
if (info.DataMapper.SupportsStreamParsing && result.Length > info.DataMapper.SupportsStreamLength)
@@ -266,19 +338,81 @@ namespace UVC.Data
}
else if (result.StartsWith("["))
{
if (info.Validator != null)
{
if (info.Validator.SupportsStreamParsing && result.Length > info.Validator.SupportsStreamLength)
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(result)))
{
JArray? validSource = info.Validator.GetValidData(stream);
if (validSource == null)
{
if (info.FailHandler != null)
{
// UI 스레드에서 실패 핸들러 호출
MainThreadDispatcher.Instance.SendToMainThread(() =>
{
info.FailHandler.Invoke("Data is not Valid");
});
}
return;
}
}
}
else
{
JArray source = JArray.Parse(result);
JArray? validSource = info.Validator.GetValidData(source);
if (validSource == null)
{
if (info.FailHandler != null)
{
// UI 스레드에서 실패 핸들러 호출
MainThreadDispatcher.Instance.SendToMainThread(() =>
{
info.FailHandler.Invoke("Data is not Valid");
});
}
return;
}
}
}
if (info.DataMapper != null)
{
if (info.DataMapper.SupportsStreamParsing && result.Length > info.DataMapper.SupportsStreamLength)
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(result)))
{
dataObject = info.DataMapper.MapArrayStream(stream);
if (info.Validator != null)
{
JArray? validSource = info.Validator.GetValidData(stream);
if (validSource != null)
{
dataObject = info.DataMapper.Map(validSource);
}
}
else
{
dataObject = info.DataMapper.MapArrayStream(stream);
}
}
}
else
{
JArray source = JArray.Parse(result);
dataObject = info.DataMapper.Map(source);
if (info.Validator != null)
{
JArray? validSource = info.Validator.GetValidData(source);
if (validSource != null)
{
dataObject = info.DataMapper.Map(validSource);
}
}
else
{
dataObject = info.DataMapper.Map(source);
}
}
}
}
@@ -300,12 +434,47 @@ namespace UVC.Data
// 갱신 된 데이터가 있는 경우 핸들러 호출
if (info.UpdatedDataOnly)
{
if (dataObject != null && dataObject.UpdatedCount > 0) info.SuccessHandler?.Invoke(dataObject);
if (dataObject != null && dataObject.UpdatedCount > 0)
{
if (info.SuccessHandler != null)
{
// 로컬 변수로 복사하여 클로저에서 안전하게 사용
var handlerData = dataObject;
// UI 스레드에서 성공 핸들러 호출
MainThreadDispatcher.Instance.SendToMainThread(() =>
{
info.SuccessHandler.Invoke(handlerData);
});
}
return;
}
}
if (dataObject != null)
{
if (info.SuccessHandler != null)
{
// 로컬 변수로 복사하여 클로저에서 안전하게 사용
var handlerData = dataObject;
// UI 스레드에서 성공 핸들러 호출
MainThreadDispatcher.Instance.SendToMainThread(() =>
{
info.SuccessHandler.Invoke(handlerData);
});
}
}
else
{
info.SuccessHandler?.Invoke(dataObject);
if (info.FailHandler != null)
{
// UI 스레드에서 실패 핸들러 호출
MainThreadDispatcher.Instance.SendToMainThread(() =>
{
info.FailHandler.Invoke("Data is Null");
});
}
}
return;
}
catch (OperationCanceledException)
@@ -349,10 +518,12 @@ namespace UVC.Data
/// <param name="key">반복 실행할 요청의 키</param>
/// <returns>비동기 작업</returns>
/// <remarks>
/// 지정된 간격(repeatInterval)으로 HTTP 요청을 반복 실행합니다.
/// 지정된 간격(repeatInterval)으로 HTTP 요청을 백그라운드 스레드에서 반복 실행합니다.
/// repeatCount가 0인 경우 무한 반복하며, 0보다 큰 경우 지정된 횟수만큼만 실행합니다.
/// 작업 실행 중 예외가 발생하면 로그를 기록하고 다음 실행을 시도합니다.
/// 취소 요청이 있거나 최대 실행 횟수에 도달하면 반복이 종료됩니다.
///
/// 이 메서드는 백그라운드 스레드에서 실행되며, 모든 핸들러 호출은 메인 스레드에서 이루어집니다.
/// </remarks>
/// <exception cref="KeyNotFoundException">지정된 키가 등록되어 있지 않은 경우</exception>
private async UniTask StartRepeat(string key)