WebGL 용 코드 추가

This commit is contained in:
logonkhi
2025-11-10 16:38:43 +09:00
parent f3d950fce7
commit 9277bab6dd
14 changed files with 433 additions and 35 deletions

View File

@@ -3,7 +3,6 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using UnityEngine;
namespace UVC.Data.Core
{

View File

@@ -383,13 +383,14 @@ namespace UVC.Data.Core
/// <returns>변환된 정수 값 또는 기본값</returns>
public int? GetInt(string propertyName, int? defaultValue = null)
{
if (TryGetValue(propertyName, out object? value) && value != null)
if (!TryGetValue(propertyName, out var v) || v == null) return defaultValue;
return v switch
{
if (value is int intValue)
return intValue;
return Convert.ToInt32(value);
}
return defaultValue;
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
};
}
/// <summary>
@@ -691,7 +692,13 @@ namespace UVC.Data.Core
/// </summary>
public void RemoveAll()
{
foreach (var value in Values.ToList())
if (Count == 0)
{
changedProperies.Clear();
return;
}
foreach (var value in Values)
{
if (value is DataObject dataObject)
{
@@ -841,7 +848,7 @@ namespace UVC.Data.Core
}
if (updatedDataOnly) return;
// 현재 객체에만 있는 속성은 제거합니다.
var keysToRemove = this.Keys.Except(otherDataObject.Keys).ToList();
foreach (var key in keysToRemove)
@@ -849,7 +856,7 @@ namespace UVC.Data.Core
this.Remove(key);
changedProperies.Remove(key);
}
}
/// <summary>
@@ -905,11 +912,12 @@ namespace UVC.Data.Core
public void ReturnToPool()
{
if (IsInPool) return; // 중복 반환 방지
if (CreatedFromPool)
{
Reset();
}
else
else
{
DataObjectPool.Return(this);
}

View File

@@ -194,8 +194,13 @@ namespace UVC.Data.Http
{
if (!info.Repeat)
{
// WebGL 환경에서는 ThreadPool 사용 불가 -> 메인 스레드에서 비동기 실행
#if UNITY_WEBGL && !UNITY_EDITOR
await ExecuteSingle(key, info);
#else
// 단일 실행 로직 호출
await UniTask.RunOnThreadPool(() => ExecuteSingle(key, info));
#endif
Debug.Log($"HTTP request '{key}' executed successfully.");
}
else
@@ -340,9 +345,12 @@ namespace UVC.Data.Http
{
try
{
#if UNITY_WEBGL && !UNITY_EDITOR
await ExecuteSingle(key, info, cts.Token);
#else
// 단일 실행 로직 호출
await UniTask.RunOnThreadPool(() => ExecuteSingle(key, info, cts.Token));
#endif
// 지정된 횟수만큼 반복한 경우 중지
if (info.RepeatCount > 0)
{

View File

@@ -107,6 +107,13 @@ namespace UVC.Data.Mqtt
private MqttDataPicker? defaultDataPicker;
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: Thread 기반 MqttWorker 대체
private MQTTService? webGlMqttService;
// WebGL 대용량(JSON 배열) 처리 시 프레임 분할 청크 크기
private const int WebGlArrayChunkSize = 256;
#endif
/// <summary>
/// MqttDataReceiver 인스턴스를 생성합니다.
/// </summary>
@@ -125,7 +132,11 @@ namespace UVC.Data.Mqtt
{
this.domain = string.IsNullOrEmpty(domain) ? Constants.MQTT_DOMAIN : domain;
this.port = port;
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL에서는 mqttWorker 사용 안 함
#else
mqttWorker.SetDomainPort(this.domain, this.port);
#endif
}
/// <summary>
@@ -136,7 +147,11 @@ namespace UVC.Data.Mqtt
public void SetDataPicker(MqttDataPicker dataPicker)
{
defaultDataPicker = dataPicker;
#if UNITY_WEBGL && !UNITY_EDITOR
// MessagePack 설정은 필요 시 MQTTService 확장
#else
mqttWorker.SetEnableMessagePack(dataPicker.EnableMessagePack);
#endif
}
/// <summary>
@@ -179,6 +194,20 @@ namespace UVC.Data.Mqtt
/// </summary>
public void Start()
{
#if UNITY_WEBGL && !UNITY_EDITOR
if (UseMockup)
{
ULog.Warn("WebGL Mockup 모드는 아직 구현되지 않았습니다.");
return;
}
if (webGlMqttService != null) return;
webGlMqttService = new MQTTService(domain, port, autoReconnect: true, onBackground: false);
foreach (var topic in topics)
{
webGlMqttService.AddTopicHandler(topic, OnWebGlRawTopicMessage);
}
webGlMqttService.Connect();
#else
if (!UseMockup)
{
if (mqttWorker.IsRunning) return;
@@ -198,8 +227,35 @@ namespace UVC.Data.Mqtt
//}
//mockupMQTT.Connect();
}
#endif
}
/// <summary>
/// WebGL 환경에서 MQTTService가 직접 호출하는 Raw 메시지 처리 진입점
/// </summary>
private void OnWebGlRawTopicMessage(string topic, string message)
{
// WebGL: 스레드풀 없음 → 메인 스레드에서 부하 분산을 위해 한 프레임 양보
RunBackground(() => OnTopicMessageLogic(topic, message));
}
/// <summary>
/// 플랫폼별 백그라운드 실행 셔임
/// </summary>
private static void RunBackground(Action action)
{
#if UNITY_WEBGL && !UNITY_EDITOR
UniTask.Void(async () =>
{
await UniTask.NextFrame();
action();
});
#else
UniTask.RunOnThreadPool(action).Forget();
#endif
}
/// <summary>
/// 토픽에서 수신된 MQTT 데이터 패킷 목록을 처리합니다.
/// </summary>
@@ -317,11 +373,21 @@ namespace UVC.Data.Mqtt
}
}
/// <summary>
/// 파이프라인을 중지하고 모든 토픽 구독을 해제한 후 MQTT 브로커와의 연결을 종료합니다.
/// </summary>
public void Stop()
{
#if UNITY_WEBGL && !UNITY_EDITOR
if (webGlMqttService != null)
{
webGlMqttService.Disconnect();
webGlMqttService.ClearTopicHandlers();
webGlMqttService = null;
}
firstMessageReceived.Clear();
#else
if (!UseMockup)
{
if (!mqttWorker.IsRunning) return;
@@ -337,6 +403,7 @@ namespace UVC.Data.Mqtt
// Mockup 모드인 경우 MockMQTTService를 사용하여 연결을 종료합니다.
//mockupMQTT?.Disconnect();
}
#endif
}
/// <summary>
@@ -346,10 +413,15 @@ namespace UVC.Data.Mqtt
/// <see cref="Dispose"/>를 호출한 후에는 해당 인스턴스를 더 이상 사용할 수 없습니다.</remarks>
public void Dispose()
{
#if UNITY_WEBGL && !UNITY_EDITOR
webGlMqttService?.Dispose();
webGlMqttService = null;
#else
if (!UseMockup) mqttWorker.Dispose();
//else mockupMQTT?.Disconnect();
#endif
configList.Clear();
firstMessageReceived.Clear();
topics.Clear();
}
}

View File

@@ -77,6 +77,25 @@ namespace UVC.Data
public static async UniTask LoadFromAppData()
{
string persistentDataPath = UnityEngine.Application.persistentDataPath;
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: 스레드 사용 불가. 메인 스레드에서 파일 작업 수행하되 프레임 양보로 스톨 방지.
string folderPath = System.IO.Path.Combine(persistentDataPath, "user");
DirectoryInfo directory = new DirectoryInfo(folderPath);
if (!directory.Exists) return;
FileInfo[] files = directory.GetFiles("*.json");
foreach (FileInfo file in files)
{
// 프레임 양보
await UniTask.Yield();
string filePath = file.FullName;
string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath);
if (string.IsNullOrEmpty(fileName)) continue;
string jsonString = System.IO.File.ReadAllText(filePath);
AddSetting(fileName, new UserSetting(jsonString));
}
#else
await UniTask.RunOnThreadPool(() =>
{
string folderPath = System.IO.Path.Combine(persistentDataPath, "user");
@@ -92,6 +111,7 @@ namespace UVC.Data
AddSetting(fileName, new UserSetting(jsonString));
}
});
#endif
}
/// <summary>
@@ -104,7 +124,30 @@ namespace UVC.Data
/// <returns></returns>
public static async UniTask SaveToAppData()
{
string persistentDataPath = UnityEngine.Application.persistentDataPath;
#if UNITY_WEBGL && !UNITY_EDITOR
foreach (var kv in _userDatas)
{
await UniTask.Yield(); // 긴 루프 스톨 방지
string key = kv.Key;
if (string.IsNullOrEmpty(key)) continue;
if (!_userDatas.TryGetValue(key, out var setting)) continue;
try
{
string folderPath = Path.Combine(persistentDataPath, "user");
if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath);
string filePath = Path.Combine(folderPath, $"{key}.json");
File.WriteAllText(filePath, setting.ToJsonString());
}
catch (Exception ex)
{
Debug.LogError($"SaveToAppData Error(WebGL): {ex.Message}");
}
}
#else
await UniTask.RunOnThreadPool(() =>
{
foreach (var keyValue in _userDatas)
@@ -127,7 +170,7 @@ namespace UVC.Data
}
});
}
#endif
public static UserSetting? FromDataObject(DataObject dataObject)
{
if (dataObject == null) return null;

View File

@@ -43,6 +43,15 @@ namespace UVC.Factory.Playback
try
{
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: ThreadPool 사용 없이 직접 실행
var response = await HttpRequester.RequestGet<HttpResponseModel<Dictionary<string, Dictionary<string, string>>>>(URLList.Get("playbackList"));
if (string.Equals(response.message, "success", StringComparison.OrdinalIgnoreCase))
{
return new Dictionary<string, Dictionary<string, string>>(response.data);
}
return null;
#else
return await UniTask.RunOnThreadPool<Dictionary<string, Dictionary<string, string>>?>(async () =>
{
var response = await HttpRequester.RequestGet<HttpResponseModel<Dictionary<string, Dictionary<string, string>>>>(URLList.Get("playbackList"));
@@ -52,7 +61,7 @@ namespace UVC.Factory.Playback
}
return null;
});
#endif
}
catch (Exception e)
{

View File

@@ -96,6 +96,32 @@ namespace UVC.Factory
/// <returns>조회된 데이터 리스트</returns>
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecond(string selectTime, int second, bool orderAsc = true, int limit = 0)
{
#if UNITY_WEBGL && !UNITY_EDITOR
DateTime target = DateTimeUtil.UtcParse(selectTime).AddSeconds(second);
string targetTime = DateTimeUtil.FormatTime(target);
queryBuilder.Append(queryParts[0]);
if (second > 0)
{
queryBuilder.Append($"{queryParts[1]}{selectTime}{queryParts[2]}{targetTime}'");
}
else
{
queryBuilder.Append($"{queryParts[3]}{selectTime}{queryParts[4]}{targetTime}'");
}
queryBuilder.Append($"{queryParts[5]}{(orderAsc ? "asc" : "desc")}");
if (limit > 0)
queryBuilder.Append($"{queryParts[6]}{limit}");
queryBuilder.Append(";");
var query = queryBuilder.ToString();
queryBuilder.Clear();
// 동기 실행 (WebGL은 ThreadPool 사용 불가)
var list = dbConnection.Query<PlaybackSQLiteDataEntity>(query);
return list;
#else
bool isMainThread = PlayerLoopHelper.IsMainThread;
List<PlaybackSQLiteDataEntity> result = await UniTask.RunOnThreadPool(() =>
{
@@ -126,6 +152,7 @@ namespace UVC.Factory
});
if (!isMainThread) await UniTask.SwitchToThreadPool();
return result;
#endif
}
StringBuilder queryBuilder = new();
@@ -143,6 +170,29 @@ namespace UVC.Factory
/// <returns>조회된 데이터 리스트</returns>
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecondBaseInfo(string selectTime, int second, bool orderAsc = false, int limit = 1)
{
#if UNITY_WEBGL && !UNITY_EDITOR
DateTime target = DateTimeUtil.UtcParse(selectTime).AddSeconds(second);
string targetTime = DateTimeUtil.FormatTime(target);
queryBuilder.Append("SELECT * FROM baseInfo WHERE ");
if (second > 0)
{
queryBuilder.Append($"timestamp >= '{selectTime}' AND timestamp < '{targetTime}'");
}
else
{
queryBuilder.Append($"timestamp <= '{selectTime}' AND timestamp > '{targetTime}'");
}
queryBuilder.Append($" ORDER BY timestamp {(orderAsc ? "asc" : "desc")}");
if (limit > 0) queryBuilder.Append($" LIMIT {limit}");
queryBuilder.Append(";");
var query = queryBuilder.ToString();
queryBuilder.Clear();
var list = dbConnection.Query<PlaybackSQLiteDataEntity>(query);
return list;
#else
bool isMainThread = PlayerLoopHelper.IsMainThread;
List<PlaybackSQLiteDataEntity> result = await UniTask.RunOnThreadPool(() =>
{
@@ -171,6 +221,7 @@ namespace UVC.Factory
});
if (!isMainThread) await UniTask.SwitchToThreadPool();
return result;
#endif
}
}

View File

@@ -125,6 +125,28 @@ namespace UVC.Factory.Playback
/// </example>
public async UniTask DispatchBaseInfoData(string date, string time, string fileName, string minute = "00", string second = "00")
{
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: ThreadPool 미사용, 직접 실행
this.date = date;
this.time = time;
this.fileName = fileName;
DateTime dateTime = DateTimeUtil.UtcParse($"{date}T{int.Parse(time).ToString("00")}:{minute}:{second}.000Z");
string formatTime = DateTimeUtil.FormatTime(dateTime);
List<PlaybackSQLiteDataEntity> list = await repository.SelectBySecondBaseInfo(date, fileName, formatTime);
if (list.Count > 0)
{
OnChangedTime?.Invoke();
HttpRequestConfig httpRequestConfig = new HttpRequestConfig(string.Empty);
httpRequestConfig.SetUpdatedDataOnly(true);
httpRequestConfig.SetSplitResponseByKey(true);
httpRequestConfig.AddSplitConfig("AGV", DataMapperValidator.Get("AGV"));
httpRequestConfig.AddSplitConfig("ALARM", DataMapperValidator.Get("ALARM"));
foreach (var item in list)
{
HttpDataProcessor.ProcessSplitResponse(httpRequestConfig, item.data, null, true);
}
}
#else
await UniTask.RunOnThreadPool(async () =>
{
//헝가리 시간임
@@ -149,6 +171,7 @@ namespace UVC.Factory.Playback
}
}
});
#endif
}
/// <summary>
@@ -163,6 +186,25 @@ namespace UVC.Factory.Playback
/// </example>
public async UniTask DispatchRealTimeData(int second, int speed)
{
#if UNITY_WEBGL && !UNITY_EDITOR
int newSecond = second;
if (newSecond > 36000) newSecond = 36000;
DateTime dateTime = DateTimeUtil.UtcParse($"{date}T{int.Parse(time).ToString("00")}:00:00.000Z").AddSeconds(newSecond);
string formatTime = DateTimeUtil.FormatTime(dateTime);
List<PlaybackSQLiteDataEntity> list = await repository.SelectBySecondAsync(date, fileName, formatTime, 1);
if (list.Count > 0)
{
HttpRequestConfig httpRequestConfig = new HttpRequestConfig(string.Empty);
httpRequestConfig.SetUpdatedDataOnly(true);
httpRequestConfig.SetSplitResponseByKey(true);
httpRequestConfig.AddSplitConfig("AGV", DataMapperValidator.Get("AGV"));
httpRequestConfig.AddSplitConfig("ALARM", DataMapperValidator.Get("ALARM"));
foreach (var item in list)
{
HttpDataProcessor.ProcessSplitResponse(httpRequestConfig, item.data);
}
}
#else
await UniTask.RunOnThreadPool(async () =>
{
int newSecond = second;
@@ -186,6 +228,7 @@ namespace UVC.Factory.Playback
}
}
});
#endif
}
/// <summary>
@@ -333,6 +376,16 @@ namespace UVC.Factory.Playback
// Debug.Log($"zipper3 errorMessage:{errorMessage} utcSqlFilePath:{utcSqlFilePath} sqlFilePath:{sqlFilePath} utcZipFilePath:{utcZipFilePath}");
//}
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: 메인 스레드에서 직접 파일 이동/삭제
if (File.Exists(utcSqlFilePath))
{
if (File.Exists(sqlFilePath)) File.Delete(sqlFilePath);
File.Copy(utcSqlFilePath, sqlFilePath);
File.Delete(utcSqlFilePath);
}
if (File.Exists(utcZipFilePath)) File.Delete(utcZipFilePath);
#else
await UniTask.RunOnThreadPool(() =>
{
//압축해제 한 파일 이동
@@ -346,7 +399,7 @@ namespace UVC.Factory.Playback
//zip 파일 삭제
File.Delete(utcZipFilePath);
});
#endif
if (OnComplete != null) OnComplete.Invoke(errorMessage);
}
},

View File

@@ -27,6 +27,11 @@ namespace UVC.Log
{
try
{
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: 백그라운드(ThreadPool) 사용 불가 → 동기 초기화
EnsureDatabaseConnectionWebGL();
CleanupOldLogsWebGL();
#else
CheckDatabaseConnectionAsync().ContinueWith((_) =>
{
lock (_dbLock)
@@ -45,6 +50,7 @@ namespace UVC.Log
}
}
});
#endif
}
catch (Exception ex)
{
@@ -53,6 +59,56 @@ namespace UVC.Log
}
}
#if UNITY_WEBGL && !UNITY_EDITOR
/// <summary>
/// WebGL 환경에서 데이터베이스 연결을 동기적으로 초기화합니다.
/// </summary>
private static void EnsureDatabaseConnectionWebGL()
{
if (db != null) return;
string appDataPath = Application.persistentDataPath;
DirectoryInfo di = new DirectoryInfo(Path.Combine(appDataPath, "unityLogs"));
if (!di.Exists) di.Create();
// WebGL Player 환경 구분
string dbPath = Application.platform == RuntimePlatform.WebGLPlayer
? Path.Combine(di.FullName, "serverWebGLLog.db")
: Path.Combine(di.FullName, "serverEditorLog.db");
db = new SQLiteConnection(dbPath);
try
{
db.CreateTable<HttpLogEntry>();
db.CreateTable<MqttLogEntry>();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
/// <summary>
/// WebGL에서 오래된 로그를 동기적으로 정리합니다.
/// </summary>
private static void CleanupOldLogsWebGL()
{
lock (_dbLock)
{
if (!doDeletedHttp)
{
db?.Execute("DELETE FROM HttpLogEntry WHERE RequestDate < datetime('now', '-1 month', 'localtime');");
doDeletedHttp = true;
}
if (!doDeletedMqtt)
{
db?.Execute("DELETE FROM MqttLogEntry WHERE Date < datetime('now', '-1 month', 'localtime');");
doDeletedMqtt = true;
}
}
}
#endif
/// <summary>
/// HTTP 요청을 로깅하여 데이터베이스에 로그 항목을 생성하고 저장합니다.
/// </summary>
@@ -67,6 +123,10 @@ namespace UVC.Log
/// 요청 URL, 메소드, 헤더, 본문, 날짜와 같은 세부 정보를 포함합니다.</returns>
public static HttpLogEntry LogHttpRequest(string url, string method, string headerJson, string bodyString, string date)
{
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: 초기화 보장
if (db == null) EnsureDatabaseConnectionWebGL();
#endif
var logEntry = new HttpLogEntry
{
RequestURL = url,
@@ -97,6 +157,10 @@ namespace UVC.Log
/// langword="null"/>일 수 없습니다.</param>
public static void LogHttpResponse(HttpLogEntry httpLogEntry)
{
#if UNITY_WEBGL && !UNITY_EDITOR
if (db == null) EnsureDatabaseConnectionWebGL();
#endif
lock (_dbLock)
{
try
@@ -123,6 +187,9 @@ namespace UVC.Log
/// <returns>기록된 MQTT 메시지를 나타내는 <see cref="MqttLogEntry"/> 객체입니다.</returns>
public static MqttLogEntry LogMqtt(string url, string port, string topic, string payload, string date, string? exception = null)
{
#if UNITY_WEBGL && !UNITY_EDITOR
if (db == null) EnsureDatabaseConnectionWebGL();
#endif
var logEntry = new MqttLogEntry
{
URL = url,
@@ -150,6 +217,11 @@ namespace UVC.Log
protected static async Task CheckDatabaseConnectionAsync()
{
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: 비동기 + 스레드 전환 제거, 동기 루틴 사용
EnsureDatabaseConnectionWebGL();
return;
#else
if (db == null)
{
string dbPath = string.Empty;
@@ -181,6 +253,7 @@ namespace UVC.Log
Debug.LogException(ex);
}
}
#endif
}
}

View File

@@ -256,9 +256,11 @@ namespace UVC.Network
bool isMainThread = PlayerLoopHelper.IsMainThread;
//var response = await request.GetFromJsonResultAsync<T>();
var response = await request.GetAsStringAsync();
if(!isMainThread) await UniTask.SwitchToThreadPool();
#if !UNITY_WEBGL || UNITY_EDITOR
if (!isMainThread) await UniTask.SwitchToThreadPool();
log.ResponseData = response;
log.ResponseDate = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
#endif
ServerLog.LogHttpResponse(log);
//T가 string이면
if (typeof(T) == typeof(string))
@@ -423,7 +425,9 @@ namespace UVC.Network
var now = DateTime.UtcNow;
bool isMainThread = PlayerLoopHelper.IsMainThread;
var response = await request.GetAsStringAsync();
#if !UNITY_WEBGL || UNITY_EDITOR
if (!isMainThread) await UniTask.SwitchToThreadPool();
#endif
var diff = DateTime.UtcNow - now;
log.ResponseData = response;
log.ResponseDate = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
@@ -495,8 +499,22 @@ namespace UVC.Network
case HTTPRequestStates.Finished:
if (resp.IsSuccess)
{
//System.IO.File.WriteAllBytes(savePath, resp.Data);
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: 백그라운드 쓰레드 불가 -> 완료 시 메모리 데이터로 파일 저장
try
{
System.IO.File.WriteAllBytes(savePath, resp.Data);
OnComplete?.Invoke();
}
catch (Exception ex)
{
ULog.Error($"Failed to write file on WebGL: {ex.Message}", ex);
OnError?.Invoke($"Failed to write file: {ex.Message}");
}
#else
//스트리밍 파일 기록은 OnDownloadStarted에서 처리됨
OnComplete.Invoke();
#endif
}
else
{
@@ -515,6 +533,15 @@ namespace UVC.Network
};
var request = SelectHTTPRequest(HTTPMethods.Get, url, onRequest);
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: 쓰레드 기반 스트리밍 미사용. 진행률 콜백만 연결.
request.DownloadSettings.OnDownloadProgress += (HTTPRequest req, long progress, long length) =>
{
ULog.Debug($"Download Progress! progress:{progress} length:{length}");
OnProgress?.Invoke(progress, length);
};
// DownloadStreamFactory/BlockingDownloadContentStream 사용 금지
#else
request.DownloadSettings.OnDownloadStarted += async (HTTPRequest req, HTTPResponse resp, DownloadContentStream stream) =>
{
@@ -533,7 +560,7 @@ namespace UVC.Network
};
request.DownloadSettings.DownloadStreamFactory = (req, resp, bufferAvailableHandler)
=> new BlockingDownloadContentStream(resp, req.DownloadSettings.ContentStreamMaxBuffered, bufferAvailableHandler);
#endif
return request.Send();
}

View File

@@ -1,4 +1,4 @@
using Best.MQTT;
using Best.MQTT;
using Best.MQTT.Packets.Builders;
using Cysharp.Threading.Tasks;
using System;
@@ -16,6 +16,7 @@ namespace UVC.network
/// <remarks>
/// 이 클래스는 스레드 안전한 방식으로 토픽 핸들러를 관리하며, 연결 끊김 시 자동 재연결 기능을 제공합니다.
/// 내부적으로 Best.MQTT 라이브러리를 사용하여 MQTT 프로토콜 통신을 구현합니다.
/// WebGL 플랫폼에서는 스레드풀이 지원되지 않으므로, 메시지 핸들러는 메인 스레드에서 실행됩니다.
/// </remarks>
public class MQTTService
{
@@ -321,26 +322,32 @@ namespace UVC.network
/// </remarks>
private void OnTopic(MQTTClient client, SubscriptionTopic topic, string topicName, ApplicationMessage message)
{
// 메인 스레드에서 실행 중인지 확인합니다.
bool isMainThread = PlayerLoopHelper.IsMainThread;
//Debug.Log($"MQTT OnTopic isMainThread={isMainThread}, onBackgroundThread:{onBackgroundThread}, {topic.Filter.OriginalFilter}");
if (isMainThread && onBackgroundThread)
DispatchTopic(() => OnTopicLogic(client, topic, topicName, message));
}
// 웹GL 대응: 스레드풀 미지원 시 메인 스레드로 포스트
private void DispatchTopic(Action work)
{
#if UNITY_WEBGL && !UNITY_EDITOR
// WebGL: 스레드풀 없음 → 다음 프레임에 메인 스레드로 실행
UniTask.Post(work);
#else
if (onBackgroundThread)
{
// 백그라운드 스레드에서 실행
UniTask.RunOnThreadPool(() => OnTopicLogic(client, topic, topicName, message)).Forget();
}
else if (!isMainThread && !onBackgroundThread)
{
// 메인 스레드에서 실행
UniTask.Post(() => OnTopicLogic(client, topic, topicName, message));
UniTask.RunOnThreadPool(work).Forget();
}
else
{
// 메인 스레드에서 실행
OnTopicLogic(client, topic, topicName, message);
if (PlayerLoopHelper.IsMainThread)
work();
else
UniTask.Post(work);
}
#endif
}
private void OnTopicLogic(MQTTClient client, SubscriptionTopic topic, string topicName, ApplicationMessage message)
{
//Debug.Log($"MQTT OnTopicLogic isMainThread={PlayerLoopHelper.IsMainThread}");

View File

@@ -328,7 +328,7 @@ namespace UVC.UI.Window
searchProgress =1f;
// UI 반영은 메인 스레드에서
await UniTask.SwitchToMainThread();
if (!PlayerLoopHelper.IsMainThread) await UniTask.SwitchToMainThread();
try
{

View File

@@ -108,12 +108,23 @@ 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
{
return null;
@@ -193,12 +204,23 @@ 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
{
return null;