playback 버그 수정

This commit is contained in:
logonkhi
2025-07-28 19:59:35 +09:00
parent f5a36697ba
commit 231af33e6f
43 changed files with 537 additions and 325 deletions

View File

@@ -46,9 +46,10 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
modelObject: {fileID: 7493524444357289953} modelObject: {fileID: 7493524444357289953}
moveSpeed: 0.5 moveSpeed: 0.9
rotationSpeed: 0.5 rotationSpeed: 0.5
teleportDistanceThreshold: 5 teleportDistanceThreshold: 2
teleportRotationThreshold: 45
--- !u!1 &6358428858938227828 --- !u!1 &6358428858938227828
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -62,14 +62,14 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 0.6509434, g: 0.6509434, b: 0.6509434, a: 1} m_Color: {r: 0.122641504, g: 0.122641504, b: 0.122641504, a: 1}
m_RaycastTarget: 1 m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
m_OnCullStateChanged: m_OnCullStateChanged:
m_PersistentCalls: m_PersistentCalls:
m_Calls: [] m_Calls: []
m_Sprite: {fileID: -27720893, guid: e5829cbc100001646956a9c3ed4e33c5, type: 3} m_Sprite: {fileID: 21300000, guid: e5829cbc100001646956a9c3ed4e33c5, type: 3}
m_Type: 1 m_Type: 1
m_PreserveAspect: 0 m_PreserveAspect: 0
m_FillCenter: 1 m_FillCenter: 1
@@ -406,9 +406,9 @@ RectTransform:
m_Father: {fileID: 4678200209926945194} m_Father: {fileID: 4678200209926945194}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0} m_SizeDelta: {x: -5, y: 0}
m_Pivot: {x: 0, y: 1} m_Pivot: {x: 0, y: 1}
--- !u!114 &7897493695162819909 --- !u!114 &7897493695162819909
MonoBehaviour: MonoBehaviour:
@@ -530,7 +530,7 @@ RectTransform:
m_Father: {fileID: 5012119678434389537} m_Father: {fileID: 5012119678434389537}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 20, y: 20} m_SizeDelta: {x: 20, y: 20}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
@@ -555,7 +555,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1} m_Color: {r: 0.1981132, g: 0.1981132, b: 0.1981132, a: 1}
m_RaycastTarget: 1 m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
@@ -1174,7 +1174,7 @@ GameObject:
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
m_StaticEditorFlags: 0 m_StaticEditorFlags: 0
m_IsActive: 1 m_IsActive: 0
--- !u!224 &4678200209926945194 --- !u!224 &4678200209926945194
RectTransform: RectTransform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -1218,14 +1218,14 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 0, g: 0.05660377, b: 0.2735849, a: 1} m_Color: {r: 0, g: 0, b: 0, a: 1}
m_RaycastTarget: 1 m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
m_OnCullStateChanged: m_OnCullStateChanged:
m_PersistentCalls: m_PersistentCalls:
m_Calls: [] m_Calls: []
m_Sprite: {fileID: -27720893, guid: e5829cbc100001646956a9c3ed4e33c5, type: 3} m_Sprite: {fileID: 21300000, guid: e5829cbc100001646956a9c3ed4e33c5, type: 3}
m_Type: 1 m_Type: 1
m_PreserveAspect: 0 m_PreserveAspect: 0
m_FillCenter: 1 m_FillCenter: 1

View File

@@ -416,14 +416,14 @@ MonoBehaviour:
m_faceColor: m_faceColor:
serializedVersion: 2 serializedVersion: 2
rgba: 4294967295 rgba: 4294967295
m_fontSize: 14 m_fontSize: 12
m_fontSizeBase: 14 m_fontSizeBase: 12
m_fontWeight: 400 m_fontWeight: 400
m_enableAutoSizing: 0 m_enableAutoSizing: 0
m_fontSizeMin: 18 m_fontSizeMin: 18
m_fontSizeMax: 72 m_fontSizeMax: 72
m_fontStyle: 0 m_fontStyle: 0
m_HorizontalAlignment: 1 m_HorizontalAlignment: 4
m_VerticalAlignment: 512 m_VerticalAlignment: 512
m_textAlignment: 65535 m_textAlignment: 65535
m_characterSpacing: -1 m_characterSpacing: -1
@@ -763,8 +763,8 @@ MonoBehaviour:
m_faceColor: m_faceColor:
serializedVersion: 2 serializedVersion: 2
rgba: 4294967295 rgba: 4294967295
m_fontSize: 14 m_fontSize: 12
m_fontSizeBase: 14 m_fontSizeBase: 12
m_fontWeight: 400 m_fontWeight: 400
m_enableAutoSizing: 0 m_enableAutoSizing: 0
m_fontSizeMin: 18 m_fontSizeMin: 18
@@ -1963,7 +1963,7 @@ MonoBehaviour:
opacitySlider: {fileID: 4254908133387884904} opacitySlider: {fileID: 4254908133387884904}
progressBar: {fileID: 1875730371384184921} progressBar: {fileID: 1875730371384184921}
canvasGroup: {fileID: 5376035964741484593} canvasGroup: {fileID: 5376035964741484593}
dragger: {fileID: 2595405855187090975} uiDragger: {fileID: 2595405855187090975}
--- !u!222 &1131523558636582765 --- !u!222 &1131523558636582765
CanvasRenderer: CanvasRenderer:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -2295,10 +2295,10 @@ MonoBehaviour:
m_FillRect: {fileID: 2543547444167745800} m_FillRect: {fileID: 2543547444167745800}
m_HandleRect: {fileID: 8988622228856496252} m_HandleRect: {fileID: 8988622228856496252}
m_Direction: 0 m_Direction: 0
m_MinValue: 0 m_MinValue: 1
m_MaxValue: 1 m_MaxValue: 10
m_WholeNumbers: 0 m_WholeNumbers: 1
m_Value: 0 m_Value: 1
m_OnValueChanged: m_OnValueChanged:
m_PersistentCalls: m_PersistentCalls:
m_Calls: [] m_Calls: []
@@ -2516,8 +2516,8 @@ MonoBehaviour:
m_faceColor: m_faceColor:
serializedVersion: 2 serializedVersion: 2
rgba: 4294967295 rgba: 4294967295
m_fontSize: 14 m_fontSize: 12
m_fontSizeBase: 14 m_fontSizeBase: 12
m_fontWeight: 400 m_fontWeight: 400
m_enableAutoSizing: 0 m_enableAutoSizing: 0
m_fontSizeMin: 18 m_fontSizeMin: 18
@@ -2644,7 +2644,7 @@ MonoBehaviour:
m_HandleRect: {fileID: 1379976025267262066} m_HandleRect: {fileID: 1379976025267262066}
m_Direction: 0 m_Direction: 0
m_MinValue: 0 m_MinValue: 0
m_MaxValue: 1 m_MaxValue: 3600
m_WholeNumbers: 0 m_WholeNumbers: 0
m_Value: 0 m_Value: 0
m_OnValueChanged: m_OnValueChanged:
@@ -2938,14 +2938,14 @@ MonoBehaviour:
m_faceColor: m_faceColor:
serializedVersion: 2 serializedVersion: 2
rgba: 4294967295 rgba: 4294967295
m_fontSize: 14 m_fontSize: 12
m_fontSizeBase: 14 m_fontSizeBase: 12
m_fontWeight: 400 m_fontWeight: 400
m_enableAutoSizing: 0 m_enableAutoSizing: 0
m_fontSizeMin: 18 m_fontSizeMin: 18
m_fontSizeMax: 72 m_fontSizeMax: 72
m_fontStyle: 0 m_fontStyle: 0
m_HorizontalAlignment: 1 m_HorizontalAlignment: 4
m_VerticalAlignment: 512 m_VerticalAlignment: 512
m_textAlignment: 65535 m_textAlignment: 65535
m_characterSpacing: -1 m_characterSpacing: -1

View File

@@ -34,9 +34,9 @@ RectTransform:
- {fileID: 7953172251271919858} - {fileID: 7953172251271919858}
m_Father: {fileID: 1082724633838762814} m_Father: {fileID: 1082724633838762814}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 152, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 84, y: 40} m_SizeDelta: {x: 84, y: 40}
m_Pivot: {x: 0, y: 1} m_Pivot: {x: 0, y: 1}
--- !u!222 &71019378058243087 --- !u!222 &71019378058243087
@@ -60,14 +60,14 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 0.003921569, g: 0.003921569, b: 0.18039216, a: 1} m_Color: {r: 0.20754719, g: 0.20754719, b: 0.20754719, a: 1}
m_RaycastTarget: 1 m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
m_OnCullStateChanged: m_OnCullStateChanged:
m_PersistentCalls: m_PersistentCalls:
m_Calls: [] m_Calls: []
m_Sprite: {fileID: -27720893, guid: e5829cbc100001646956a9c3ed4e33c5, type: 3} m_Sprite: {fileID: 21300000, guid: e5829cbc100001646956a9c3ed4e33c5, type: 3}
m_Type: 1 m_Type: 1
m_PreserveAspect: 0 m_PreserveAspect: 0
m_FillCenter: 1 m_FillCenter: 1
@@ -392,7 +392,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1} m_Color: {r: 0.14901961, g: 0.14901961, b: 0.14901961, a: 1}
m_RaycastTarget: 1 m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
@@ -1232,8 +1232,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: -533.5, y: 90} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 20, y: 20} m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2449351397303790187 --- !u!222 &2449351397303790187
CanvasRenderer: CanvasRenderer:
@@ -1256,7 +1256,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1} m_Color: {r: 0.1509434, g: 0.1509434, b: 0.1509434, a: 1}
m_RaycastTarget: 1 m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
@@ -1580,7 +1580,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1} m_Color: {r: 1, g: 1, b: 1, a: 0.5019608}
m_RaycastTarget: 1 m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
@@ -2006,7 +2006,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 0, g: 0.011764706, b: 0.1764706, a: 1} m_Color: {r: 0.16037738, g: 0.16037738, b: 0.16037738, a: 1}
m_RaycastTarget: 1 m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
@@ -2085,14 +2085,14 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 0.11764706, g: 0.20392157, b: 0.4, a: 0.2} m_Color: {r: 0.1509434, g: 0.1509434, b: 0.1509434, a: 1}
m_RaycastTarget: 1 m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
m_OnCullStateChanged: m_OnCullStateChanged:
m_PersistentCalls: m_PersistentCalls:
m_Calls: [] m_Calls: []
m_Sprite: {fileID: 21300000, guid: 96604656ca6224348929ee3d5a225403, type: 3} m_Sprite: {fileID: 21300000, guid: e5829cbc100001646956a9c3ed4e33c5, type: 3}
m_Type: 1 m_Type: 1
m_PreserveAspect: 0 m_PreserveAspect: 0
m_FillCenter: 1 m_FillCenter: 1

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a3b43489d9940f844af7bd65d78eb67a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 138 B

After

Width:  |  Height:  |  Size: 138 B

View File

@@ -376,6 +376,7 @@ MonoBehaviour:
maxPitch: 85 maxPitch: 85
minYaw: -45 minYaw: -45
maxYaw: 45 maxYaw: 45
Enable: 1
--- !u!1 &410087039 --- !u!1 &410087039
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -58,8 +58,9 @@ namespace SampleProject
private void SetNetworkConfig() private void SetNetworkConfig()
{ {
URLList.Add("baseinfo", "http://localhost:8888/baseinfo"); URLList.Add("baseinfo", $"{Constants.API_DOMAIN}/baseinfo");
URLList.Add("playbackList", "http://localhost:8888/playback/list"); URLList.Add("playbackList", $"{Constants.API_DOMAIN}/playback/list");
URLList.Add("playbackFile", $"{Constants.API_DOMAIN}/playback");
var agvDataMask = new DataMask(); var agvDataMask = new DataMask();
agvDataMask.ObjectName = "AGV"; // AGV 객체의 이름을 설정합니다. agvDataMask.ObjectName = "AGV"; // AGV 객체의 이름을 설정합니다.
@@ -128,7 +129,7 @@ namespace SampleProject
//10초 후 정지 //10초 후 정지
//UniTask.Delay(TimeSpan.FromSeconds(10)).ContinueWith(() => //UniTask.Delay(TimeSpan.FromSeconds(10)).ContinueWith(() =>
//{ //{
// DataRepository.Instance.MqttReceiver.Stop(); // DataRepository.Instance.MqttReceiver.Exit();
//}); //});
} }

View File

@@ -1,9 +1,9 @@
namespace SampleProject.Config namespace SampleProject.Config
{ {
public static class Constants public static class Constants
{ {
public static string MQTT_DOMAIN = "localhost"; public static string MQTT_DOMAIN = "localhost";
public static int MQTT_PORT = 1883; public static int MQTT_PORT = 1883;
public static string PlaybackDomain = "http://localhost"; public static string API_DOMAIN = "http://localhost:8888";
} }
} }

View File

@@ -1,3 +1,4 @@
using Cysharp.Threading.Tasks;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
@@ -5,6 +6,7 @@ using UVC.Core;
using UVC.Data; using UVC.Data;
using UVC.Data.Core; using UVC.Data.Core;
using UVC.Data.Http; using UVC.Data.Http;
using UVC.Factory;
using UVC.Factory.Alarm; using UVC.Factory.Alarm;
using UVC.Factory.Component; using UVC.Factory.Component;
using UVC.Factory.Playback; using UVC.Factory.Playback;
@@ -29,16 +31,7 @@ namespace SampleProject
AppMain.Instance.Initialized += OnAppInitialized; AppMain.Instance.Initialized += OnAppInitialized;
} }
/// <summary>
/// AGV 관리자가 생성될 때 발생하는 이벤트를 처리합니다.
/// </summary>
/// <remarks>이 메서드는 AGV 관리자 생성과 관련된 필요한 초기화 또는 설정 작업을 수행하기 위한 것입니다.
/// 내부적으로 호출되며 외부 코드에서 직접 사용하도록 의도된 것이 아닙니다.
///</remarks>
internal void OnAGVCreated()
{
AlarmManager.Instance.Run();
}
private async void OnAppInitialized() private async void OnAppInitialized()
{ {
@@ -46,7 +39,7 @@ namespace SampleProject
// AGVManager 생성 시 이벤트 처리 // AGVManager 생성 시 이벤트 처리
AGVManager.Instance.OnAGVCreated += OnAGVCreated; AGVManager.Instance.OnAGVCreated += OnAGVCreated;
PlaybackService.Instance.OnStopPlayback += OnStopPlayback; PlaybackService.Instance.OnExitPlayback += OnExitPlayback;
await requestDataAsync(); await requestDataAsync();
@@ -59,22 +52,46 @@ namespace SampleProject
DataRepository.Instance.MqttReceiver.Start(); DataRepository.Instance.MqttReceiver.Start();
} }
/// <summary>
/// AGV 관리자가 생성될 때 발생하는 이벤트를 처리합니다.
/// </summary>
/// <remarks>이 메서드는 AGV 관리자 생성과 관련된 필요한 초기화 또는 설정 작업을 수행하기 위한 것입니다.
/// 내부적으로 호출되며 외부 코드에서 직접 사용하도록 의도된 것이 아닙니다.
///</remarks>
internal void OnAGVCreated()
{
AlarmManager.Instance.Run();
UILoading.Hide();
FactoryCameraController.Instance.Enable = true;
}
private async Task requestDataAsync() private async Task requestDataAsync()
{ {
//UILoading.Show();
Debug.Log("requestDataAsync");
UILoading.Show();
//Debug.Log("Requesting BaseInfo data..."); //Debug.Log("Requesting BaseInfo data...");
//var httpFetcher = DataRepository.Instance.HttpFetcher; //var httpFetcher = DataRepository.Instance.HttpFetcher;
//var splitRequest = new HttpRequestConfig(URLList.Get("baseinfo")) //var splitRequest = new HttpRequestConfig(URLList.Get("baseinfo"))
// .setSplitResponseByKey(true) // 응답을 키별로 분할 // .SetSplitResponseByKey(true) // 응답을 키별로 분할
// .AddSplitConfig("AGV", DataMapperValidator.Get("AGV")) // "AGV" 키에 대한 매퍼, Validator 설정 // .AddSplitConfig("AGV", DataMapperValidator.Get("AGV")) // "AGV" 키에 대한 매퍼, Validator 설정
// .AddSplitConfig("ALARM", DataMapperValidator.Get("ALARM")); // "ALARM" 키에 대한 매퍼, Validator 설정 // .AddSplitConfig("ALARM", DataMapperValidator.Get("ALARM")); // "ALARM" 키에 대한 매퍼, Validator 설정
//httpFetcher.Add("baseInfo", splitRequest); //httpFetcher.Add("baseInfo", splitRequest);
//await httpFetcher.Excute("baseInfo"); //await httpFetcher.Excute("baseInfo");
//Debug.Log("BaseInfo data request completed."); //Debug.Log("BaseInfo data request completed.");
//UILoading.Hide(); //UILoading.Hide();
//MqttReceiver 시작
DataRepository.Instance.MqttReceiver.Start();
if(AGVManager.Instance.Created)
{
await UniTask.Delay(1000);
UILoading.Hide();
}
} }
private async void OnStopPlayback() private async void OnExitPlayback()
{ {
await requestDataAsync(); await requestDataAsync();
} }

View File

@@ -1,4 +1,4 @@
#nullable enable #nullable enable
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -52,8 +52,8 @@ namespace UVC.Data.Http
/// ///
/// // 단일 요청 설정 및 등록 /// // 단일 요청 설정 및 등록
/// var singleRequest = new HttpRequestConfig("https://api.example.com/data") /// var singleRequest = new HttpRequestConfig("https://api.example.com/data")
/// .setDataMapper(dataMapper) /// .SetDataMapper(dataMapper)
/// .setSuccessHandler(data => { /// .SetSuccessHandler(data => {
/// // 데이터 처리 로직 /// // 데이터 처리 로직
/// ULog.Debug($"데이터 수신: {data?.ToString() ?? "null"}"); /// ULog.Debug($"데이터 수신: {data?.ToString() ?? "null"}");
/// }); /// });
@@ -61,12 +61,12 @@ namespace UVC.Data.Http
/// ///
/// // 반복 요청 설정 및 등록 /// // 반복 요청 설정 및 등록
/// var repeatingRequest = new HttpRequestConfig("https://api.example.com/status") /// var repeatingRequest = new HttpRequestConfig("https://api.example.com/status")
/// .setDataMapper(dataMapper) /// .SetDataMapper(dataMapper)
/// .setSuccessHandler(data => { /// .SetSuccessHandler(data => {
/// // 상태 데이터 처리 /// // 상태 데이터 처리
/// ULog.Debug($"상태 업데이트: {data?.ToString() ?? "null"}"); /// ULog.Debug($"상태 업데이트: {data?.ToString() ?? "null"}");
/// }) /// })
/// .setRepeat(true, 0, 5000); // 5초마다 무한 반복 /// .SetRepeat(true, 0, 5000); // 5초마다 무한 반복
/// httpFetcher.Add("statusMonitor", repeatingRequest); /// httpFetcher.Add("statusMonitor", repeatingRequest);
/// ///
/// // 응답 분할 요청 설정 (예: 응답이 {"AGV": [...], "ALARM": [...]} 형태) /// // 응답 분할 요청 설정 (예: 응답이 {"AGV": [...], "ALARM": [...]} 형태)
@@ -77,7 +77,7 @@ namespace UVC.Data.Http
/// var alarmMapper = new DataMapper(alarmMask); /// var alarmMapper = new DataMapper(alarmMask);
/// ///
/// var splitRequest = new HttpRequestConfig("https://api.example.com/baseinfo") /// var splitRequest = new HttpRequestConfig("https://api.example.com/baseinfo")
/// .setSplitResponseByKey(true) // 응답을 키별로 분할 /// .SetSplitResponseByKey(true) // 응답을 키별로 분할
/// .AddSplitConfig("AGV", agvMapper) // "AGV" 키에 대한 매퍼 설정 /// .AddSplitConfig("AGV", agvMapper) // "AGV" 키에 대한 매퍼 설정
/// .AddSplitConfig("ALARM", alarmMapper); // "ALARM" 키에 대한 매퍼 설정 /// .AddSplitConfig("ALARM", alarmMapper); // "ALARM" 키에 대한 매퍼 설정
/// httpFetcher.Add("baseInfo", splitRequest); /// httpFetcher.Add("baseInfo", splitRequest);

View File

@@ -207,7 +207,7 @@ namespace UVC.Data.Http
{ {
continue; continue;
} }
// 분할된 데이터를 `subKey`를 키로 사용하여 DataRepository에 저장합니다. // 분할된 데이터를 `subKey`를 키로 사용하여 DataRepository에 저장합니다.
var repoObject = DataRepository.Instance.AddOrUpdateData(subKey, subMappedObject, info.UpdatedDataOnly); var repoObject = DataRepository.Instance.AddOrUpdateData(subKey, subMappedObject, info.UpdatedDataOnly);

View File

@@ -1,4 +1,4 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -18,19 +18,19 @@ namespace UVC.Data.Http
/// <example> /// <example>
/// <code> /// <code>
/// var config = new HttpRequestConfig("https://api.example.com/data", "GET") /// var config = new HttpRequestConfig("https://api.example.com/data", "GET")
/// .setDataMapper(new DataMapper(dataMask)) /// .SetDataMapper(new DataMapper(dataMask))
/// .setSuccessHandler(data => Console.WriteLine(data)) // 성공 핸들러 예시 /// .SetSuccessHandler(data => Console.WriteLine(data)) // 성공 핸들러 예시
/// .setFailHandler(errorData => Console.Error.WriteLine(errorData)) // 실패 핸들러 예시 /// .SetFailHandler(errorData => Console.Error.WriteLine(errorData)) // 실패 핸들러 예시
/// .setRetry(5, 2000) /// .SetRetry(5, 2000)
/// .setRepeat(true, 10, 5000); /// .SetRepeat(true, 10, 5000);
/// ///
/// // 응답을 키별로 분할하는 설정 /// // 응답을 키별로 분할하는 설정
/// // 응답이 {"AGV": [...], "ALARM": [...]} 형태일 때 사용 /// // 응답이 {"AGV": [...], "ALARM": [...]} 형태일 때 사용
/// var splitConfig = new HttpRequestConfig("https://api.example.com/alldata") /// var splitConfig = new HttpRequestConfig("https://api.example.com/alldata")
/// .setSplitResponseByKey(true) // 이 옵션을 활성화 /// .SetSplitResponseByKey(true) // 이 옵션을 활성화
/// .AddSplitConfig("AGV", new DataMapper(agvMask), agvValidator) /// .AddSplitConfig("AGV", new DataMapper(agvMask), agvValidator)
/// .AddSplitConfig("ALARM", new DataMapper(alarmMask)) /// .AddSplitConfig("ALARM", new DataMapper(alarmMask))
/// .setSuccessHandler(splitData => { /// .SetSuccessHandler(splitData => {
/// // HttpDataFetcher 구현에 따라, 분할된 각 데이터가 처리된 후 이 핸들러가 호출될 수 있습니다. /// // HttpDataFetcher 구현에 따라, 분할된 각 데이터가 처리된 후 이 핸들러가 호출될 수 있습니다.
/// // 이 경우 핸들러의 IDataObject는 null일 수 있습니다. /// // 이 경우 핸들러의 IDataObject는 null일 수 있습니다.
/// Console.WriteLine("Split data processing completed."); /// Console.WriteLine("Split data processing completed.");
@@ -204,7 +204,7 @@ namespace UVC.Data.Http
/// </summary> /// </summary>
/// <param name="dataMapper">사용할 데이터 매퍼 객체</param> /// <param name="dataMapper">사용할 데이터 매퍼 객체</param>
/// <returns>현재 HttpRequestConfig 인스턴스 (메서드 체이닝용)</returns> /// <returns>현재 HttpRequestConfig 인스턴스 (메서드 체이닝용)</returns>
public HttpRequestConfig setDataMapper(DataMapper dataMapper) public HttpRequestConfig SetDataMapper(DataMapper dataMapper)
{ {
_dataMapper = dataMapper; _dataMapper = dataMapper;
return this; return this;
@@ -242,18 +242,18 @@ namespace UVC.Data.Http
/// ///
/// // 3. 검사기를 HTTP 파이프라인에 설정 /// // 3. 검사기를 HTTP 파이프라인에 설정
/// var pipelineInfo = new HttpRequestConfig("https://api.example.com/users", "get") /// var pipelineInfo = new HttpRequestConfig("https://api.example.com/users", "get")
/// .setDataMapper(userDataMapper) /// .SetDataMapper(userDataMapper)
/// .setValidator(validator) /// .SetValidator(validator)
/// .setSuccessHandler(userData => { /// .SetSuccessHandler(userData => {
/// // 여기에 도달하는 사용자 데이터는 모두 이메일이 유효하고 18세 이상입니다. /// // 여기에 도달하는 사용자 데이터는 모두 이메일이 유효하고 18세 이상입니다.
/// Console.WriteLine($"유효한 사용자: {userData["name"]}, {userData["email"]}"); /// Console.WriteLine($"유효한 사용자: {userData["name"]}, {userData["email"]}");
/// }) /// })
/// .setFailHandler(errorMsg => { /// .SetFailHandler(errorMsg => {
/// Console.WriteLine($"요청 실패: {errorMsg}"); /// Console.WriteLine($"요청 실패: {errorMsg}");
/// }); /// });
/// </code> /// </code>
/// </example> /// </example>
public HttpRequestConfig setValidator(DataValidator validator) public HttpRequestConfig SetValidator(DataValidator validator)
{ {
this._validator = validator; this._validator = validator;
return this; return this;
@@ -281,7 +281,7 @@ namespace UVC.Data.Http
/// </summary> /// </summary>
/// <param name="responseMask">HTTP response에 적용할 <see cref="HttpResponseMask"/>입니다.</param> /// <param name="responseMask">HTTP response에 적용할 <see cref="HttpResponseMask"/>입니다.</param>
/// <returns>지정된 response 마스크가 적용된 업데이트된 <see cref="HttpRequestConfig"/> 인스턴스입니다.</returns> /// <returns>지정된 response 마스크가 적용된 업데이트된 <see cref="HttpRequestConfig"/> 인스턴스입니다.</returns>
public HttpRequestConfig setResponseMask(HttpResponseMask responseMask) public HttpRequestConfig SetResponseMask(HttpResponseMask responseMask)
{ {
_responseMask = responseMask; _responseMask = responseMask;
return this; return this;
@@ -294,7 +294,7 @@ namespace UVC.Data.Http
/// </summary> /// </summary>
/// <param name="handler">응답 데이터를 처리할 콜백 함수</param> /// <param name="handler">응답 데이터를 처리할 콜백 함수</param>
/// <returns>현재 HttpRequestConfig 인스턴스 (메서드 체이닝용)</returns> /// <returns>현재 HttpRequestConfig 인스턴스 (메서드 체이닝용)</returns>
public HttpRequestConfig setSuccessHandler(Action<IDataObject?>? handler) public HttpRequestConfig SetSuccessHandler(Action<IDataObject?>? handler)
{ {
_successhandler = handler; _successhandler = handler;
return this; return this;
@@ -306,7 +306,7 @@ namespace UVC.Data.Http
/// </summary> /// </summary>
/// <param name="handler">실패 정보를 처리할 콜백 함수입니다. 실패 시 관련 데이터를 인자로 받습니다.</param> /// <param name="handler">실패 정보를 처리할 콜백 함수입니다. 실패 시 관련 데이터를 인자로 받습니다.</param>
/// <returns>현재 HttpRequestConfig 인스턴스 (메서드 체이닝용)</returns> /// <returns>현재 HttpRequestConfig 인스턴스 (메서드 체이닝용)</returns>
public HttpRequestConfig setFailHandler(Action<string>? handler) public HttpRequestConfig SetFailHandler(Action<string>? handler)
{ {
_failhandler = handler; _failhandler = handler;
return this; return this;
@@ -318,7 +318,7 @@ namespace UVC.Data.Http
/// <param name="maxRetryCount">최대 재시도 횟수 (기본값: 3)</param> /// <param name="maxRetryCount">최대 재시도 횟수 (기본값: 3)</param>
/// <param name="retryDelay">재시도 간 대기 시간(밀리초) (기본값: 1000)</param> /// <param name="retryDelay">재시도 간 대기 시간(밀리초) (기본값: 1000)</param>
/// <returns>현재 HttpRequestConfig 인스턴스 (메서드 체이닝용)</returns> /// <returns>현재 HttpRequestConfig 인스턴스 (메서드 체이닝용)</returns>
public HttpRequestConfig setRetry(int maxRetryCount = 3, int retryDelay = 1000) public HttpRequestConfig SetRetry(int maxRetryCount = 3, int retryDelay = 1000)
{ {
_maxRetryCount = maxRetryCount; _maxRetryCount = maxRetryCount;
_retryDelay = retryDelay; _retryDelay = retryDelay;
@@ -341,16 +341,16 @@ namespace UVC.Data.Http
/// <code> /// <code>
/// // 5초마다 10번 반복 요청, 변경된 데이터만 처리 /// // 5초마다 10번 반복 요청, 변경된 데이터만 처리
/// var pipelineInfo = new HttpRequestConfig("https://api.example.com/data", "GET") /// var pipelineInfo = new HttpRequestConfig("https://api.example.com/data", "GET")
/// .setHandler(data => ProcessData(data)) /// .SetHandler(data => ProcessData(data))
/// .setRepeat(true, 10, 5000, true); /// .SetRepeat(true, 10, 5000, true);
/// ///
/// // 3초마다 무한 반복, 모든 응답 데이터 처리 /// // 3초마다 무한 반복, 모든 응답 데이터 처리
/// var pipelineInfo = new HttpRequestConfig("https://api.example.com/status", "GET") /// var pipelineInfo = new HttpRequestConfig("https://api.example.com/status", "GET")
/// .setHandler(data => UpdateStatus(data)) /// .SetHandler(data => UpdateStatus(data))
/// .setRepeat(true, 0, 3000, false); /// .SetRepeat(true, 0, 3000, false);
/// </code> /// </code>
/// </example> /// </example>
public HttpRequestConfig setRepeat(bool repeat, int count = 0, int interval = 1000, bool updatedDataOnly = true) public HttpRequestConfig SetRepeat(bool repeat, int count = 0, int interval = 1000, bool updatedDataOnly = true)
{ {
_repeat = repeat; _repeat = repeat;
_repeatCount = count; _repeatCount = count;
@@ -366,11 +366,20 @@ namespace UVC.Data.Http
/// </summary> /// </summary>
/// <param name="split">분할 처리 여부</param> /// <param name="split">분할 처리 여부</param>
/// <returns>현재 HttpRequestConfig 인스턴스 (메서드 체이닝용)</returns> /// <returns>현재 HttpRequestConfig 인스턴스 (메서드 체이닝용)</returns>
public HttpRequestConfig setSplitResponseByKey(bool split) public HttpRequestConfig SetSplitResponseByKey(bool split)
{ {
_splitResponseByKey = split; _splitResponseByKey = split;
return this; return this;
} }
/// <summary>
/// 업데이트된 데이터만 처리할지 여부를 나타내는 값을 설정합니다.
/// </summary>
/// <param name="v">부울 값. <see langword="true"/>는 업데이트된 데이터만 처리하도록 지정합니다. 그렇지 않으면
/// <see langword="false"/>입니다.</param>
public void SetUpdatedDataOnly(bool v)
{
_updatedDataOnly = v;
}
} }
} }

View File

@@ -47,8 +47,8 @@ namespace UVC.Data.Mqtt
/// ///
/// // 4. MqttSubscriptionConfig 생성 및 설정 /// // 4. MqttSubscriptionConfig 생성 및 설정
/// var pipelineInfo = new MqttSubscriptionConfig("sensor/+/data") /// var pipelineInfo = new MqttSubscriptionConfig("sensor/+/data")
/// .setDataMapper(dataMapper) /// .SetDataMapper(dataMapper)
/// .setHandler(dataHandler); /// .SetHandler(dataHandler);
/// ///
/// // 5. MqttDataReceiver 인스턴스 생성 /// // 5. MqttDataReceiver 인스턴스 생성
/// var mqttReceiver = new MqttDataReceiver("mqtt.eclipseprojects.io", 1883); /// var mqttReceiver = new MqttDataReceiver("mqtt.eclipseprojects.io", 1883);
@@ -62,7 +62,7 @@ namespace UVC.Data.Mqtt
/// // ... 애플리케이션 로직 수행 ... /// // ... 애플리케이션 로직 수행 ...
/// ///
/// // 8. 파이프라인 중지 및 리소스 해제 /// // 8. 파이프라인 중지 및 리소스 해제
/// mqttReceiver.Stop(); /// mqttReceiver.Exit();
/// mqttReceiver.Dispose(); /// mqttReceiver.Dispose();
/// </code> /// </code>
/// </example> /// </example>

View File

@@ -1,4 +1,4 @@
#nullable enable #nullable enable
using System; using System;
using UVC.Data.Core; using UVC.Data.Core;
@@ -22,8 +22,8 @@ namespace UVC.Data.Mqtt
/// dataMask["timestamp"] = DateTime.Now; /// dataMask["timestamp"] = DateTime.Now;
/// ///
/// var config = new MqttSubscriptionConfig("device/status") /// var config = new MqttSubscriptionConfig("device/status")
/// .setDataMapper(new DataMapper(dataMask)) /// .SetDataMapper(new DataMapper(dataMask))
/// .setHandler(data => Console.WriteLine(data)); /// .SetHandler(data => Console.WriteLine(data));
/// </code> /// </code>
/// </example> /// </example>
public class MqttSubscriptionConfig public class MqttSubscriptionConfig
@@ -113,7 +113,7 @@ namespace UVC.Data.Mqtt
/// 핸들러는 메시지가 수신되고 DataMapper에 의해 변환된 후 호출됩니다. /// 핸들러는 메시지가 수신되고 DataMapper에 의해 변환된 후 호출됩니다.
/// UpdatedDataOnly 속성이 true인 경우, 데이터가 변경된 경우에만 호출됩니다. /// UpdatedDataOnly 속성이 true인 경우, 데이터가 변경된 경우에만 호출됩니다.
/// </remarks> /// </remarks>
public MqttSubscriptionConfig setHandler(Action<IDataObject?> handler) public MqttSubscriptionConfig SetHandler(Action<IDataObject?> handler)
{ {
_handler = handler; _handler = handler;
return this; return this;
@@ -137,8 +137,8 @@ namespace UVC.Data.Mqtt
/// dataMask["timestamp"] = ""; // 문자열 타입 지정 /// dataMask["timestamp"] = ""; // 문자열 타입 지정
/// ///
/// var pipelineInfo = new MqttSubscriptionConfig("sensor/data") /// var pipelineInfo = new MqttSubscriptionConfig("sensor/data")
/// .setDataMapper(new DataMapper(dataMask)) /// .SetDataMapper(new DataMapper(dataMask))
/// .setHandler(data => ProcessSensorData(data)); /// .SetHandler(data => ProcessSensorData(data));
/// </code> /// </code>
/// </example> /// </example>
public MqttSubscriptionConfig setDataMapper(DataMapper dataMapper) public MqttSubscriptionConfig setDataMapper(DataMapper dataMapper)
@@ -177,9 +177,9 @@ namespace UVC.Data.Mqtt
/// ///
/// // Validator를 파이프라인에 설정 /// // Validator를 파이프라인에 설정
/// var pipelineInfo = new MqttSubscriptionConfig("sensors/data") /// var pipelineInfo = new MqttSubscriptionConfig("sensors/data")
/// .setDataMapper(dataMapper) /// .SetDataMapper(dataMapper)
/// .setValidator(_validator) /// .SetValidator(_validator)
/// .setHandler(data => { /// .SetHandler(data => {
/// // 여기서 처리되는 데이터는 모두 유효성 검사를 통과한 데이터 /// // 여기서 처리되는 데이터는 모두 유효성 검사를 통과한 데이터
/// Console.WriteLine($"유효한 센서 데이터: {data["deviceId"]} - {data["temperature"]}°C"); /// Console.WriteLine($"유효한 센서 데이터: {data["deviceId"]} - {data["temperature"]}°C");
/// }); /// });

View File

@@ -40,9 +40,9 @@ namespace UVC.Data.Mqtt
/// <summary> /// <summary>
/// 스레드를 안전하게 종료시키기 위한 CancellationTokenSource 입니다. /// 스레드를 안전하게 종료시키기 위한 CancellationTokenSource 입니다.
/// Stop() 메서드가 호출되면 취소 신호를 보냅니다. /// Exit() 메서드가 호출되면 취소 신호를 보냅니다.
/// </summary> /// </summary>
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly Dictionary<string, List<MqttDataPacket>> topicBuffers = new Dictionary<string, List<MqttDataPacket>>(); private readonly Dictionary<string, List<MqttDataPacket>> topicBuffers = new Dictionary<string, List<MqttDataPacket>>();
private readonly Dictionary<string, Action<string, List<MqttDataPacket>>> listeners = new Dictionary<string, Action<string, List<MqttDataPacket>>>(); private readonly Dictionary<string, Action<string, List<MqttDataPacket>>> listeners = new Dictionary<string, Action<string, List<MqttDataPacket>>>();
@@ -101,6 +101,11 @@ namespace UVC.Data.Mqtt
public void Start() public void Start()
{ {
if (isRunning) return; if (isRunning) return;
if (cancellationTokenSource.IsCancellationRequested)
{
cancellationTokenSource.Dispose();
cancellationTokenSource = new CancellationTokenSource();
}
isRunning = true; isRunning = true;
workerThread = new Thread(Run); workerThread = new Thread(Run);
workerThread.IsBackground = true; // 메인 앱 종료 시 스레드 자동 종료 workerThread.IsBackground = true; // 메인 앱 종료 시 스레드 자동 종료
@@ -228,7 +233,7 @@ namespace UVC.Data.Mqtt
// 다음 전파 주기까지 대기합니다. // 다음 전파 주기까지 대기합니다.
if (cancellationToken.WaitHandle.WaitOne(propagationIntervalMs)) if (cancellationToken.WaitHandle.WaitOne(propagationIntervalMs))
{ {
break; // Stop() 호출 시 루프 종료 break; // Exit() 호출 시 루프 종료
} }
} }
catch (ObjectDisposedException) { break; } catch (ObjectDisposedException) { break; }
@@ -236,7 +241,7 @@ namespace UVC.Data.Mqtt
} }
/// <summary> /// <summary>
/// 직접 전파 모드의 메인 루프입니다. Stop()이 호출될 때까지 스레드를 대기시킵니다. /// 직접 전파 모드의 메인 루프입니다. Exit()이 호출될 때까지 스레드를 대기시킵니다.
/// </summary> /// </summary>
private void RunDirectPropagationLoop() private void RunDirectPropagationLoop()
{ {

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections; using System.Collections;
using UnityEngine; using UnityEngine;
using UVC.Core; using UVC.Core;
@@ -103,6 +103,8 @@ namespace UVC.Factory
private Coroutine focusCoroutine; // 현재 실행 중인 포커싱 코루틴을 저장할 변수 private Coroutine focusCoroutine; // 현재 실행 중인 포커싱 코루틴을 저장할 변수
public bool Enable = false; // 카메라 컨트롤 활성화 여부
void Start() void Start()
{ {
// 스크립트 시작 시, 회전의 기준이 되는 중심점을 카메라 앞쪽으로 초기화합니다. // 스크립트 시작 시, 회전의 기준이 되는 중심점을 카메라 앞쪽으로 초기화합니다.
@@ -207,6 +209,7 @@ namespace UVC.Factory
// 이를 통해 카메라의 떨림이나 끊김 현상을 줄일 수 있습니다. // 이를 통해 카메라의 떨림이나 끊김 현상을 줄일 수 있습니다.
void LateUpdate() void LateUpdate()
{ {
if (!Enable) return; // 카메라 컨트롤이 비활성화된 경우, 업데이트를 건너뜁니다.
HandlePanning(); HandlePanning();
HandleRotation(); HandleRotation();
HandleZoom(); HandleZoom();

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UVC.Data.Core; using UVC.Data.Core;
using UVC.Factory.Playback;
namespace UVC.Factory.Component namespace UVC.Factory.Component
{ {
@@ -26,17 +27,32 @@ namespace UVC.Factory.Component
// 움직임과 회전의 부드러움을 조절할 속도 변수입니다. // 움직임과 회전의 부드러움을 조절할 속도 변수입니다.
// Unity 인스펙터 창에서 실시간으로 값을 조절하며 최적의 움직임을 찾을 수 있습니다. // Unity 인스펙터 창에서 실시간으로 값을 조절하며 최적의 움직임을 찾을 수 있습니다.
[Tooltip("목표 지점까지의 이동 속도를 조절합니다.")] [Tooltip("목표 지점까지 도달하는 데 걸리는 시간(초)입니다. 작을수록 빠릅니다.")]
public float moveSpeed = 1.0f; [SerializeField]
private float moveSpeed = 0.9f;
[Tooltip("목표 방향까지의 회전 속도를 조절합니다.")] [Tooltip("목표 방향까지 도달하는 데 걸리는 시간(초)입니다. 작을수록 빠릅니다.")]
public float rotationSpeed = 2.0f; [SerializeField]
private float rotationSpeed = 0.5f;
[Tooltip("이 거리(미터)를 초과하면 보간 없이 즉시 위치를 변경합니다.")] [Tooltip("이 거리(미터)를 초과하면 보간 없이 즉시 위치를 변경합니다.")]
public float teleportDistanceThreshold = 5.0f; // 5미터 이상 차이나면 순간이동 [SerializeField]
private float teleportDistanceThreshold = 2.0f; // 1미터 이상 차이나면 순간이동
[Tooltip("이 각도를 초과하면 보간 없이 즉시 회전각을 변경합니다.")]
[SerializeField]
private float teleportRotationThreshold = 45.0f; // 5도 이상 차이나면 순간이동
private Renderer renderer; private Renderer? renderer;
private bool isRed = false;
private float timeScale = 1.0f; // 현재 시간 스케일, 기본값은 1.0f (실시간)
// SmoothDamp 함수가 사용하는 현재 속도 값입니다. 0으로 초기화해야 합니다.
private Vector3 velocity = Vector3.zero;
private float angularVelocity = 0;
/// <summary> /// <summary>
/// AGV 객체가 생성될 때 처음 한 번 호출되는 초기화 메서드입니다. /// AGV 객체가 생성될 때 처음 한 번 호출되는 초기화 메서드입니다.
@@ -61,6 +77,18 @@ namespace UVC.Factory.Component
"JOB_ID", "JOB_ID",
"TIMESTAMP", "TIMESTAMP",
}; };
PlaybackService.Instance.OnChangeTimeScale += OnChangeTimeScaleHandler;
}
private void OnChangeTimeScaleHandler(float timeScale)
{
this.timeScale = timeScale;
}
protected override void OnDestroy()
{
PlaybackService.Instance.OnChangeTimeScale -= OnChangeTimeScaleHandler;
base.OnDestroy();
} }
/// <summary> /// <summary>
@@ -118,8 +146,6 @@ namespace UVC.Factory.Component
bool changed = false; bool changed = false;
bool isTeleport = false;
float? newX = newData.GetFloat("X"); float? newX = newData.GetFloat("X");
float? newY = newData.GetFloat("Y"); float? newY = newData.GetFloat("Y");
float x = data.GetFloat("X").Value; float x = data.GetFloat("X").Value;
@@ -137,7 +163,7 @@ namespace UVC.Factory.Component
if (distanceToTarget > teleportDistanceThreshold) if (distanceToTarget > teleportDistanceThreshold)
{ {
transform.position = newTargetPosition; transform.position = newTargetPosition;
isTeleport = true; // 순간이동이 발생했음을 표시합니다. velocity = Vector3.zero; // 순간이동 후 속도 초기화
} }
// 새로운 목표 지점을 설정합니다. // 새로운 목표 지점을 설정합니다.
@@ -152,8 +178,18 @@ namespace UVC.Factory.Component
{ {
Quaternion newTargetRotation = Quaternion.Euler(0, newDegree.Value, 0); Quaternion newTargetRotation = Quaternion.Euler(0, newDegree.Value, 0);
// 거리가 설정된 임계값을 초과하면, 보간을 건너뛰고 즉시 위치/회전을 설정합니다 float distanceToTargetRotation = Quaternion.Angle(transform.rotation, newTargetRotation);
if (isTeleport) transform.rotation = newTargetRotation;
// 현재 회전과 새로운 목표 회전 사이의 각도 차이를 계산합니다.
if (distanceToTargetRotation > 0)
{
// 각도 차이가 설정된 임계값을 초과하면, 보간을 건너뛰고 즉시 회전을 설정합니다.
if (distanceToTargetRotation > teleportRotationThreshold)
{
transform.rotation = newTargetRotation;
angularVelocity = 0f;
}
}
// 새로운 목표 지점을 설정합니다. // 새로운 목표 지점을 설정합니다.
// (순간이동을 했든 안 했든, 다음 프레임부터의 보간을 위해 목표 지점은 항상 갱신되어야 합니다.) // (순간이동을 했든 안 했든, 다음 프레임부터의 보간을 위해 목표 지점은 항상 갱신되어야 합니다.)
@@ -174,7 +210,9 @@ namespace UVC.Factory.Component
private void ChangeColor(Color color) private void ChangeColor(Color color)
{ {
renderer.material.color = color; if(color == Color.red && isRed && renderer != null) return; // 이미 빨간색이면 변경하지 않음
isRed = color == Color.red;
if(renderer != null) renderer!.material.color = color;
} }
/// <summary> /// <summary>
@@ -183,36 +221,41 @@ namespace UVC.Factory.Component
/// </summary> /// </summary>
void Update() void Update()
{ {
// 현재 위치가 목표 위치와 다를 경우에만 이동 로직을 실행합니다. bool isMoving = Vector3.SqrMagnitude(transform.position - targetPosition) > 0.001f;
if (transform.position != targetPosition) bool isRotating = Quaternion.Angle(transform.rotation, targetRotation) > 0.01f;
// 이동과 회전이 모두 끝났으면 더 이상 계산할 필요가 없습니다.
if (!isMoving && !isRotating)
{ {
// 목표 지점과의 거리가 매우 가까우면 (0.01미터 미만) 그냥 목표 위치로 설정하여 미세한 떨림을 방지합니다. return;
if (Vector3.Distance(transform.position, targetPosition) < 0.01f)
{
// 현재 위치와 목표 위치 사이의 거리가 임계값을 초과하면 순간이동합니다.
transform.position = targetPosition;
}
else
{
// Vector3.Lerp를 사용하여 현재 위치에서 목표 위치로 부드럽게 이동시킵니다.
// Time.deltaTime * moveSpeed는 프레임 속도에 관계없이 일정한 속도를 보장합니다.
transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * moveSpeed);
}
} }
// 현재 회전이 목표 회전과 다를 경우에만 회전 로직을 실행합니다. float dampedTime = Time.deltaTime * timeScale;
if (transform.rotation != targetRotation) if (dampedTime <= 0) return;
// 위치 업데이트
if (isMoving)
{ {
// 목표 회전과의 각도 차이가 매우 작으면 (0.1도 미만) 그냥 목표 회전으로 설정합니다. transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref velocity, moveSpeed, Mathf.Infinity, dampedTime);
if (Quaternion.Angle(transform.rotation, targetRotation) < 0.01f) }
{ else
transform.rotation = targetRotation; {
} transform.position = targetPosition;
else velocity = Vector3.zero;
{ }
// Quaternion.Slerp를 사용하여 현재 회전에서 목표 회전으로 부드럽게 회전시킵니다.
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * rotationSpeed); // 회전 업데이트
} if (isRotating)
{
float currentAngle = transform.eulerAngles.y;
float targetAngle = targetRotation.eulerAngles.y;
float newAngle = Mathf.SmoothDampAngle(currentAngle, targetAngle, ref angularVelocity, rotationSpeed, Mathf.Infinity, dampedTime);
transform.rotation = Quaternion.Euler(0, newAngle, 0);
}
else
{
transform.rotation = targetRotation;
angularVelocity = 0f;
} }
} }
} }

View File

@@ -100,6 +100,8 @@ namespace UVC.Factory.Component
private bool created = false; private bool created = false;
public bool Created => created;
/// <summary> /// <summary>
/// AGVManager의 초기화 메서드입니다. /// AGVManager의 초기화 메서드입니다.
/// Awake 메서드에서 호출되며, MonoBehaviour가 생성될 때 한 번만 실행됩니다. /// Awake 메서드에서 호출되며, MonoBehaviour가 생성될 때 한 번만 실행됩니다.
@@ -168,7 +170,7 @@ namespace UVC.Factory.Component
var RemovedItems = arr.RemovedItems; var RemovedItems = arr.RemovedItems;
var ModifiedList = arr.ModifiedList; var ModifiedList = arr.ModifiedList;
//Debug.Log($"AGVManager received data: count:{arr.Count}, Added={AddedItems.Count}, Removed={RemovedItems.Count}, Modified={ModifiedList.Count}"); Debug.Log($"AGVManager received data: count:{arr.Count}, Added={AddedItems.Count}, Removed={RemovedItems.Count}, Modified={ModifiedList.Count}");
// 새로 추가된 AGV 처리 // 새로 추가된 AGV 처리
foreach (var item in AddedItems.ToList()) foreach (var item in AddedItems.ToList())

View File

@@ -1,3 +1,4 @@
#nullable enable
using UnityEngine; using UnityEngine;
using UVC.Data; using UVC.Data;
using UVC.Factory.Playback.UI; using UVC.Factory.Playback.UI;
@@ -9,9 +10,9 @@ namespace UVC.Factory.Playback
{ {
public class PlaybackCommand : ICommand public class PlaybackCommand : ICommand
{ {
public async void Execute(object parameter = null) public async void Execute(object? parameter = null)
{ {
FactoryCameraController.Instance.Enable = false;
var modalContent = new ModalContent(UIPlaybackListModal.PrefabPath) var modalContent = new ModalContent(UIPlaybackListModal.PrefabPath)
{ {
Title = "Playback List", Title = "Playback List",
@@ -19,21 +20,24 @@ namespace UVC.Factory.Playback
ShowCancelButton = false ShowCancelButton = false
}; };
object result = await UVC.UI.Modal.Modal.Open<bool>(modalContent); UIPlaybackListItemData? result = await UVC.UI.Modal.Modal.Open<UIPlaybackListItemData>(modalContent);
Debug.Log($"PlaybackCommand result:{result}"); Debug.Log($"PlaybackCommand result==null:{result==null}");
if (result != null) if (result != null)
{ {
UIPlaybackListItemData data = (UIPlaybackListItemData)result; UILoading.Show();
UIPlaybackListItemData data = result;
Debug.Log($"PlaybackCommand data:{data}"); Debug.Log($"PlaybackCommand data:{data}");
UIPlayback.Instance.Show();
DataRepository.Instance.MqttReceiver.Stop(); DataRepository.Instance.MqttReceiver.Stop();
await UIPlayback.Instance.SetData(data.date, data.time, data.sqlFileName); await PlaybackService.Instance.StartAsync(data);
FactoryCameraController.Instance.Enable = true;
UILoading.Hide();
} }
else else
{ {
UILoading.Show(); UILoading.Show();
PlaybackService.Instance.Stop(); PlaybackService.Instance.Exit();
FactoryCameraController.Instance.Enable = true;
UILoading.Hide(); UILoading.Hide();
} }

View File

@@ -49,7 +49,7 @@ namespace UVC.Factory.Playback
{ {
try try
{ {
return HttpRequester.Download($"{Constants.PlaybackDomain}/playback/{fileName}", savePath, OnComplete, OnProgress, OnError); return HttpRequester.Download($"{URLList.Get("playbackFile")}/{fileName}", savePath, OnComplete, OnProgress, OnError);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@@ -1,10 +1,11 @@
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using SQLite4Unity3d; using SQLite4Unity3d;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using UnityEngine; using UnityEngine;
using UVC.Factory.Playback;
using UVC.Util; using UVC.Util;
namespace UVC.Factory namespace UVC.Factory
@@ -32,7 +33,7 @@ namespace UVC.Factory
{ {
this.date = date; this.date = date;
this.sqliteFileName = sqliteFileName; this.sqliteFileName = sqliteFileName;
dbConnection = new SQLiteConnection(Path.Combine(Application.streamingAssetsPath, "playback", date, sqliteFileName)); dbConnection = new SQLiteConnection(Path.Combine(PlaybackService.PlaybackFolderPath, date, sqliteFileName));
} }
public void CloseDB() public void CloseDB()
@@ -78,6 +79,7 @@ namespace UVC.Factory
}; };
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecond(string selectTime, int second, bool orderAsc = true, int limit = 0) public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecond(string selectTime, int second, bool orderAsc = true, int limit = 0)
{ {
bool isMainThread = PlayerLoopHelper.IsMainThread;
List<PlaybackSQLiteDataEntity> result = await UniTask.RunOnThreadPool(() => List<PlaybackSQLiteDataEntity> result = await UniTask.RunOnThreadPool(() =>
{ {
DateTime date = DateTimeUtil.UtcParse(selectTime).AddSeconds(second); DateTime date = DateTimeUtil.UtcParse(selectTime).AddSeconds(second);
@@ -104,6 +106,7 @@ namespace UVC.Factory
queryBuilder.Clear(); queryBuilder.Clear();
return dbConnection.Query<PlaybackSQLiteDataEntity>(query); return dbConnection.Query<PlaybackSQLiteDataEntity>(query);
}); });
if (!isMainThread) await UniTask.SwitchToThreadPool();
return result; return result;
} }
@@ -122,6 +125,7 @@ namespace UVC.Factory
StringBuilder queryBuilder = new(); StringBuilder queryBuilder = new();
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecondBaseInfo(string selectTime, int second, bool orderAsc = false, int limit = 1) public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecondBaseInfo(string selectTime, int second, bool orderAsc = false, int limit = 1)
{ {
bool isMainThread = PlayerLoopHelper.IsMainThread;
List<PlaybackSQLiteDataEntity> result = await UniTask.RunOnThreadPool(() => List<PlaybackSQLiteDataEntity> result = await UniTask.RunOnThreadPool(() =>
{ {
DateTime date = DateTimeUtil.UtcParse(selectTime).AddSeconds(second); DateTime date = DateTimeUtil.UtcParse(selectTime).AddSeconds(second);
@@ -146,7 +150,7 @@ namespace UVC.Factory
queryBuilder.Clear(); queryBuilder.Clear();
return dbConnection.Query<PlaybackSQLiteDataEntity>(query); return dbConnection.Query<PlaybackSQLiteDataEntity>(query);
}); });
if (!isMainThread) await UniTask.SwitchToThreadPool();
return result; return result;
} }
} }

View File

@@ -1,14 +1,14 @@
#nullable enable #nullable enable
using Best.HTTP; using Best.HTTP;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UVC.Data;
using UVC.Data.Core; using UVC.Data.Core;
using UVC.Data.Http; using UVC.Data.Http;
using UVC.Json; using UVC.Factory.Playback.UI;
using UVC.Util; using UVC.Util;
namespace UVC.Factory.Playback namespace UVC.Factory.Playback
{ {
@@ -20,14 +20,33 @@ namespace UVC.Factory.Playback
static PlaybackService() { } static PlaybackService() { }
#endregion #endregion
public static readonly string PlaybackFolderPath = Path.Combine(Application.persistentDataPath, "playback");//streamingAssetsPath, "playback"); appData 폴더로 변경
private readonly PlaybackRepository repository; private readonly PlaybackRepository repository;
private string date; private string date;
private string time; private string time;
private string fileName; private string fileName;
public Action OnStopPlayback; public Action OnExitPlayback;
private float timeScale = 1.0f;
public float TimeScale
{
get => timeScale;
internal set
{
if (value < 1f) value = 1f;
if (timeScale != value)
{
timeScale = value;
//Time.timeScale = timeScale;
OnChangeTimeScale?.Invoke(timeScale);
}
}
}
public Action<float> OnChangeTimeScale;
public PlaybackService(PlaybackRepository repository) public PlaybackService(PlaybackRepository repository)
{ {
@@ -42,28 +61,29 @@ namespace UVC.Factory.Playback
public async UniTask DispatchBaseInfoData(string date, string time, string fileName, string minute = "00", string second = "00") public async UniTask DispatchBaseInfoData(string date, string time, string fileName, string minute = "00", string second = "00")
{ {
Debug.Log($"DispatchBaseInfoData {date} {time} {minute} {second} {fileName}"); await UniTask.RunOnThreadPool(async () =>
//헝가리 시간임
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);
//baseInfo 가져오기
List<PlaybackSQLiteDataEntity> list = await repository.SelectBySecondBaseInfo(date, fileName, formatTime);
if (list.Count > 0)
{ {
HttpRequestConfig httpRequestConfig = new HttpRequestConfig(""); //헝가리 시간임
httpRequestConfig.setSplitResponseByKey(true); this.date = date;
httpRequestConfig.AddSplitConfig("AGV", DataMapperValidator.Get("AGV")); this.time = time;
httpRequestConfig.AddSplitConfig("ALARM", DataMapperValidator.Get("ALARM")); this.fileName = fileName;
foreach (var item in list) DateTime dateTime = DateTimeUtil.UtcParse($"{date}T{int.Parse(time).ToString("00")}:{minute}:{second}.000Z");
string formatTime = DateTimeUtil.FormatTime(dateTime);
//baseInfo 가져오기
List <PlaybackSQLiteDataEntity> list = await repository.SelectBySecondBaseInfo(date, fileName, formatTime);
if (list.Count > 0)
{ {
HttpDataProcessor.ProcessSplitResponse(httpRequestConfig, item.data); HttpRequestConfig httpRequestConfig = new HttpRequestConfig("");
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);
}
} }
} });
} }
/// <summary> /// <summary>
@@ -72,45 +92,55 @@ namespace UVC.Factory.Playback
/// <param name="second">0 ~ 3600</param> /// <param name="second">0 ~ 3600</param>
public async UniTask DispatchRealTimeData(int second, int speed) public async UniTask DispatchRealTimeData(int second, int speed)
{ {
int newSecond = second; await UniTask.RunOnThreadPool(async () =>
if (newSecond > 36000) newSecond = 36000;
//utc 시간으로 변환
DateTime dateTime = DateTimeUtil.UtcParse($"{date}T{int.Parse(time).ToString("00")}:00:00.000Z").AddSeconds(newSecond);//.Add(-DateTimeUtil.UtcKoreaGap);
string formatTime = DateTimeUtil.FormatTime(dateTime);
List<PlaybackSQLiteDataEntity> list = await repository.SelectBySecondAsync(date, fileName, formatTime, 1);
if (list.Count > 0)
{ {
HttpRequestConfig httpRequestConfig = new HttpRequestConfig(""); int newSecond = second;
httpRequestConfig.setSplitResponseByKey(true); if (newSecond > 36000) newSecond = 36000;
httpRequestConfig.AddSplitConfig("AGV", DataMapperValidator.Get("AGV")); //utc 시간으로 변환
httpRequestConfig.AddSplitConfig("ALARM", DataMapperValidator.Get("ALARM")); DateTime dateTime = DateTimeUtil.UtcParse($"{date}T{int.Parse(time).ToString("00")}:00:00.000Z").AddSeconds(newSecond);//.Add(-DateTimeUtil.UtcKoreaGap);
foreach (var item in list) string formatTime = DateTimeUtil.FormatTime(dateTime);
List<PlaybackSQLiteDataEntity> list = await repository.SelectBySecondAsync(date, fileName, formatTime, 1);
//Debug.Log($"DispatchRealTimeData {date} {time} {formatTime} {newSecond} {list.Count}");
if (list.Count > 0)
{ {
HttpDataProcessor.ProcessSplitResponse(httpRequestConfig, item.data); HttpRequestConfig httpRequestConfig = new HttpRequestConfig("");
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);
}
} }
} });
} }
public async Task StartAsync(UIPlaybackListItemData data)
{
timeScale = 1.0f; //기본 시간 스케일 설정
UIPlayback.Instance.Show();
await UIPlayback.Instance.SetData(data.date, data.time, data.sqlFileName);
}
public void Stop() public void Exit()
{ {
OnStopPlayback?.Invoke(); OnExitPlayback?.Invoke();
} }
public HTTPRequest? ReadyData(string date, string time, string fileName, Action<long, long, float> OnProgress, Action<string> OnComplete) public HTTPRequest? ReadyData(string date, string time, string fileName, Action<long, long, float> OnProgress, Action<string> OnComplete)
{ {
//date : "2024-12-05" //date : "2024-12-05"
//fileName : "2024-12-05_0.sqlite.7z" //fileName : "2024-12-05_0.sqlite.7z"
string playbackPath = Path.Combine(Application.streamingAssetsPath, "playback"); string playbackPath = PlaybackService.PlaybackFolderPath;
string tempPath = Path.Combine(playbackPath, "temp");//한국 시간으로 변경하기 때문에 임시 폴더 만들어서 압축 해제 후 이동 string tempPath = Path.Combine(playbackPath, "temp");//한국 시간으로 변경하기 때문에 임시 폴더 만들어서 압축 해제 후 이동
string datePath = Path.Combine(playbackPath, date); string datePath = Path.Combine(playbackPath, date);
var fileNameArr = fileName.Split("."); var fileNameArr = fileName.Split(".");
string zipFilePath = Path.Combine(datePath, fileName); string zipFilePath = Path.Combine(datePath, fileName);
string sqlFilePath = Path.Combine(datePath, fileNameArr[0] + ".sqlite"); string sqlFilePath = Path.Combine(datePath, fileNameArr[0] + ".sqlite");
DateTime utcDateTime = DateTimeUtil.Parse(fileNameArr[0], "yyyy-MM-dd_H").Add(-DateTimeUtil.UtcKoreaGap); DateTime utcDateTime = DateTimeUtil.Parse(fileNameArr[0], "yyyy-MM-dd_H");//.Add(-DateTimeUtil.UtcKoreaGap);
string utcDatePath = Path.Combine(playbackPath, utcDateTime.ToString("yyyy-MM-dd")); string utcDatePath = Path.Combine(playbackPath, utcDateTime.ToString("yyyy-MM-dd"));
string utcFileName = utcDateTime.ToString("yyyy-MM-dd_H") + "." + fileNameArr[1] + "." + fileNameArr[2]; string utcFileName = utcDateTime.ToString("yyyy-MM-dd_H") + "." + fileNameArr[1] + "." + fileNameArr[2];
var utcFileNameArr = utcFileName.Split("."); var utcFileNameArr = utcFileName.Split(".");
@@ -144,15 +174,22 @@ namespace UVC.Factory.Playback
if (File.Exists(utcZipFilePath)) if (File.Exists(utcZipFilePath))
{ {
if (OnProgress != null) OnProgress.Invoke(50, 100, 0.5f); if (OnProgress != null) OnProgress.Invoke(50, 100, 0.5f);
//압축해제 후 //압축해제 후
var zipper = new Zipper(); var zipper = new Zipper();
string errorMessage = await zipper.Decompress(utcZipFilePath, tempPath, (long read, long total, float percent) => string errorMessage = await zipper.Decompress(utcZipFilePath, tempPath, (long read, long total, float percent) =>
{ {
if (OnProgress != null) if (OnProgress != null)
{ {
bool isComplte = false;
float percentRate = 0.5f + percent / 2;
if (percentRate > 0.99)
{
percentRate = 0.99f;
isComplte = true;
}
OnProgress.Invoke(downloadTotal + read, downloadTotal + total, 0.5f + percent / 2); OnProgress.Invoke(downloadTotal + read, downloadTotal + total, 0.5f + percent / 2);
if (0.5f + percent / 2 > 100f) if (isComplte)
{ {
Debug.Log($" DownloadReadData :{downloadTotal + read} , DownloadTotalData :{downloadTotal + total} ,DownloadPlaybackData OnProgress:{percent}"); Debug.Log($" DownloadReadData :{downloadTotal + read} , DownloadTotalData :{downloadTotal + total} ,DownloadPlaybackData OnProgress:{percent}");
} }
@@ -181,16 +218,20 @@ namespace UVC.Factory.Playback
// Debug.Log($"zipper3 errorMessage:{errorMessage} utcSqlFilePath:{utcSqlFilePath} sqlFilePath:{sqlFilePath} utcZipFilePath:{utcZipFilePath}"); // Debug.Log($"zipper3 errorMessage:{errorMessage} utcSqlFilePath:{utcSqlFilePath} sqlFilePath:{sqlFilePath} utcZipFilePath:{utcZipFilePath}");
//} //}
//압축해제 한 파일 이동 await UniTask.RunOnThreadPool(() =>
if (File.Exists(utcSqlFilePath))
{ {
//동일한 파일명이 있을경우 제거후 다시 //압축해제 한 파일 이동
File.Copy(utcSqlFilePath, sqlFilePath); if (File.Exists(utcSqlFilePath))
File.Delete(utcSqlFilePath); {
} //동일한 파일명이 있을경우 제거후 다시
File.Copy(utcSqlFilePath, sqlFilePath);
File.Delete(utcSqlFilePath);
}
//zip 파일 삭제
File.Delete(utcZipFilePath);
});
//zip 파일 삭제
File.Delete(utcZipFilePath);
if (OnComplete != null) OnComplete.Invoke(errorMessage); if (OnComplete != null) OnComplete.Invoke(errorMessage);
} }
}, },

View File

@@ -61,6 +61,8 @@ namespace UVC.Factory.Playback.UI
private UIPlaybackProgressBar progressBar; private UIPlaybackProgressBar progressBar;
[SerializeField] [SerializeField]
private CanvasGroup canvasGroup; private CanvasGroup canvasGroup;
[SerializeField]
private UIDragger uiDragger;
private bool isPlaying = false; private bool isPlaying = false;
private bool preparingData = false; private bool preparingData = false;
@@ -118,6 +120,7 @@ namespace UVC.Factory.Playback.UI
if (canvas.name == "ModalCanvas") if (canvas.name == "ModalCanvas")
{ {
transform.SetParent(canvas.transform, false); transform.SetParent(canvas.transform, false);
uiDragger.SetDragArea(canvas.transform as RectTransform);
break; break;
} }
} }
@@ -126,7 +129,7 @@ namespace UVC.Factory.Playback.UI
public void Hide() public void Hide()
{ {
Time.timeScale = 1; UpdateTimeScale(1);
IsTick = false; IsTick = false;
gameObject.SetActive(false); gameObject.SetActive(false);
} }
@@ -137,8 +140,8 @@ namespace UVC.Factory.Playback.UI
isPlaying = false; isPlaying = false;
UpdatePlayState(); UpdatePlayState();
Hide(); Hide();
PlaybackService.Instance.Stop(); PlaybackService.Instance.Exit();
UILoading.Hide();
} }
private void OnClickPlay() private void OnClickPlay()
@@ -156,11 +159,12 @@ namespace UVC.Factory.Playback.UI
{ {
if (isPlaying) if (isPlaying)
{ {
if (Time.timeScale != sliderSpeed.Value) Time.timeScale = sliderSpeed.Value; //if (Time.timeScale != sliderSpeed.Value) UpdateTimeScale(sliderSpeed.Value);
UpdateTimeScale(sliderSpeed.Value);
} }
else else
{ {
Time.timeScale = 1; UpdateTimeScale(1);
} }
} }
@@ -208,24 +212,23 @@ namespace UVC.Factory.Playback.UI
this.time = time; this.time = time;
this.fileName = fileName; this.fileName = fileName;
Debug.Log($"UIPlayback SetData {date} {time}"); Debug.Log($"UIPlayback SetData {date} {time}");
int timeInt = int.Parse(time); int timeInt = int.Parse(time);
dateTimeTxt0.text = dateTimeTxt1.text = date.Substring(2).Replace("-", "."); dateTimeTxt0.text = dateTimeTxt1.text = date.Substring(2).Replace("-", ".");
progressBar.Init(timeInt); progressBar.Init(timeInt);
sliderSpeed.Init(); sliderSpeed.Init();
Time.timeScale = 1;
UpdateTimeScale(1);
canvasGroup.alpha = opacitySlider.value = 1; canvasGroup.alpha = opacitySlider.value = 1;
preparingData = true; preparingData = true;
progressBar.Interactable = !preparingData; progressBar.Interactable = !preparingData;
isPlaying = false; isPlaying = false;
UpdatePlayState(); UpdatePlayState();
//UILoading.Show();
await UniTask.WaitForSeconds(0.5f);
await PlaybackService.Instance.DispatchBaseInfoData(date, time, fileName); await PlaybackService.Instance.DispatchBaseInfoData(date, time, fileName);
preparingData = false; preparingData = false;
progressBar.Interactable = !preparingData; progressBar.Interactable = !preparingData;
//UILoading.Hide();
await UniTask.WaitForSeconds(0.5f);
} }
private void UpdatePlayState() private void UpdatePlayState()
@@ -249,7 +252,6 @@ namespace UVC.Factory.Playback.UI
} }
progressBar.Value += 1; progressBar.Value += 1;
//PlaybackService.Instance.DispatchingTimelineEvent = false; //PlaybackService.Instance.DispatchingTimelineEvent = false;
PlaybackService.Instance.DispatchRealTimeData(progressBar.Value, sliderSpeed.Value).Forget(); PlaybackService.Instance.DispatchRealTimeData(progressBar.Value, sliderSpeed.Value).Forget();
if (isTick) if (isTick)
@@ -260,6 +262,11 @@ namespace UVC.Factory.Playback.UI
} }
} }
private void UpdateTimeScale(float timeScale)
{
PlaybackService.Instance.TimeScale = timeScale;
}
} }
} }

View File

@@ -1,4 +1,4 @@
using Best.HTTP; using Best.HTTP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -210,7 +210,7 @@ namespace UVC.Factory.Playback.UI
{ {
try try
{ {
string playbackPath = Path.Combine(Application.streamingAssetsPath, "playback"); string playbackPath = PlaybackService.PlaybackFolderPath;
string tempPath = Path.Combine(playbackPath, "temp"); string tempPath = Path.Combine(playbackPath, "temp");
string datePath = Path.Combine(playbackPath, data.date); string datePath = Path.Combine(playbackPath, data.date);
var fileNameArr = data.zipFileName.Split('.'); var fileNameArr = data.zipFileName.Split('.');

View File

@@ -1,3 +1,4 @@
#nullable enable
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -19,23 +20,24 @@ namespace UVC.Factory.Playback.UI
public static UIPlaybackListModal CreateFromPrefab(Transform parent = null) public static UIPlaybackListModal CreateFromPrefab(Transform parent = null)
{ {
GameObject prefab = Resources.Load(PrefabPath, typeof(GameObject)) as GameObject; GameObject prefab = Resources.Load<GameObject>(PrefabPath);
GameObject go = UnityEngine.Object.Instantiate(prefab); GameObject go = UnityEngine.Object.Instantiate(prefab);
UIPlaybackListModal modal = go.GetComponent<UIPlaybackListModal>(); UIPlaybackListModal modal = go.GetComponent<UIPlaybackListModal>();
return modal; return modal;
} }
private Dictionary<string, List<UIPlaybackListItemData>> data; private Dictionary<string, List<UIPlaybackListItemData>>? data;
public bool IsOkable => (selectedItem != null && selectedItem.status == UIPlaybackListItemStatus.Downloaded && UIPlaybackListItem.DownloadingItems.Count == 0); public bool IsOkable => (selectedItem != null && selectedItem.status == UIPlaybackListItemStatus.Downloaded && UIPlaybackListItem.DownloadingItems.Count == 0);
private UIPlaybackListItemData? selectedItem; private UIPlaybackListItemData? selectedItem = null;
[SerializeField] [SerializeField]
private TMP_Dropdown dropdownDate; private TMP_Dropdown dropdownDate;
[SerializeField] [SerializeField]
private ScrollRect scrollRectTime; private ScrollRect scrollRectTime;
private UIPlaybackListItemData? resultData = null;
public override async UniTask OnOpen(ModalContent content) public override async UniTask OnOpen(ModalContent content)
{ {
@@ -44,17 +46,15 @@ namespace UVC.Factory.Playback.UI
initContent(); initContent();
} }
public override object GetResult() public override object? GetResult()
{ {
if (data != null) data.Clear(); if (data != null) data.Clear();
data = null; data = null;
return selectedItem; return resultData;
} }
public override async UniTask OnClose(ModalContent content) public override async UniTask OnClose(ModalContent content)
{ {
await base.OnClose(content); await base.OnClose(content);
} }
@@ -63,7 +63,7 @@ namespace UVC.Factory.Playback.UI
{ {
confirmButton.interactable = false; confirmButton.interactable = false;
Dictionary<string, Dictionary<string, string>> data = await PlaybackService.Instance.RequestDataAsync(); Dictionary<string, Dictionary<string, string>>? data = await PlaybackService.Instance.RequestDataAsync();
dropdownDate.onValueChanged.AddListener(DropDownDateChanged); dropdownDate.onValueChanged.AddListener(DropDownDateChanged);
LocalSetData(); LocalSetData();
if (data != null) SetData(data); if (data != null) SetData(data);
@@ -81,7 +81,7 @@ namespace UVC.Factory.Playback.UI
Dictionary<string, List<UIPlaybackListItemData>> newData = new Dictionary<string, List<UIPlaybackListItemData>>(); Dictionary<string, List<UIPlaybackListItemData>> newData = new Dictionary<string, List<UIPlaybackListItemData>>();
var dateList = new List<TMP_Dropdown.OptionData>(); var dateList = new List<TMP_Dropdown.OptionData>();
string playbackPath = Path.Combine(Application.streamingAssetsPath, "playback"); string playbackPath = PlaybackService.PlaybackFolderPath;
DirectoryInfo di = new DirectoryInfo(playbackPath); DirectoryInfo di = new DirectoryInfo(playbackPath);
if (di.Exists) if (di.Exists)
{ {
@@ -171,7 +171,7 @@ namespace UVC.Factory.Playback.UI
var dateList = new List<TMP_Dropdown.OptionData>(); var dateList = new List<TMP_Dropdown.OptionData>();
//로컬에 저장 되 있는데 sqlite 파일 찾아서 추가 //로컬에 저장 되 있는데 sqlite 파일 찾아서 추가
string playbackPath = Path.Combine(Application.streamingAssetsPath, "playback"); string playbackPath = PlaybackService.PlaybackFolderPath;
DirectoryInfo di = new DirectoryInfo(playbackPath); DirectoryInfo di = new DirectoryInfo(playbackPath);
if (di.Exists) if (di.Exists)
{ {
@@ -307,5 +307,10 @@ namespace UVC.Factory.Playback.UI
confirmButton.interactable = IsOkable; confirmButton.interactable = IsOkable;
} }
public override void OnConfirmButtonClicked()
{
resultData = selectedItem;
}
} }
} }

View File

@@ -505,8 +505,11 @@ namespace UVC.Network
} }
break; break;
default: default:
ULog.Error(req.State.ToString(), new Exception(resp.Message)); string detailedError = req.Exception != null ? req.Exception.ToString() : (resp != null ? resp.Message : "Unknown error");
OnError?.Invoke(req.State.ToString()); string errorMsgDefault = $"Request failed! State: {req.State}, URL: {req.CurrentUri}, Error: {detailedError}";
ULog.Error(errorMsgDefault, req.Exception);
//ULog.Error(req.State.ToString(), req.Exception != null ? req.Exception: new Exception(resp?.Message));
OnError?.Invoke(errorMsgDefault);
break; break;
} }
}; };

View File

@@ -250,8 +250,8 @@ namespace UVC.Tests.Data
// HttpRequestConfig 설정 // HttpRequestConfig 설정
var info = new HttpRequestConfig("http://test.com") var info = new HttpRequestConfig("http://test.com")
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setSuccessHandler((data) => .SetSuccessHandler((data) =>
{ {
Debug.Log("핸들러 호출됨"); Debug.Log("핸들러 호출됨");
handlerCalled = true; handlerCalled = true;
@@ -308,8 +308,8 @@ namespace UVC.Tests.Data
// HttpRequestConfig 설정 // HttpRequestConfig 설정
var info = new HttpRequestConfig("http://test.com") var info = new HttpRequestConfig("http://test.com")
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setSuccessHandler((data) => .SetSuccessHandler((data) =>
{ {
handlerCalled = true; handlerCalled = true;
receivedData = data; receivedData = data;
@@ -385,8 +385,8 @@ namespace UVC.Tests.Data
// HttpRequestConfig 설정 // HttpRequestConfig 설정
var info = new HttpRequestConfig(agvUrl, "get") var info = new HttpRequestConfig(agvUrl, "get")
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setSuccessHandler((data) => .SetSuccessHandler((data) =>
{ {
handlerCalled = true; handlerCalled = true;
receivedData = data; receivedData = data;
@@ -439,8 +439,8 @@ namespace UVC.Tests.Data
// HttpRequestConfig 설정 // HttpRequestConfig 설정
var info = new HttpRequestConfig(alarmUrl, "get") var info = new HttpRequestConfig(alarmUrl, "get")
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setSuccessHandler((data) => .SetSuccessHandler((data) =>
{ {
handlerCalled = true; handlerCalled = true;
receivedData = data; receivedData = data;
@@ -522,8 +522,8 @@ namespace UVC.Tests.Data
{ {
string key = item.Key; string key = item.Key;
var info = new HttpRequestConfig(item.Value, "get") var info = new HttpRequestConfig(item.Value, "get")
.setDataMapper(new DataMapper(dataMasks[key])) .SetDataMapper(new DataMapper(dataMasks[key]))
.setSuccessHandler((data) => .SetSuccessHandler((data) =>
{ {
handlerCallCount++; handlerCallCount++;
results[key] = data; results[key] = data;
@@ -573,8 +573,8 @@ namespace UVC.Tests.Data
// HttpRequestConfig 설정 // HttpRequestConfig 설정
var info = new HttpRequestConfig(testUrl, "get") var info = new HttpRequestConfig(testUrl, "get")
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setSuccessHandler((data) => .SetSuccessHandler((data) =>
{ {
handlerCalled = true; handlerCalled = true;
receivedData = data; receivedData = data;
@@ -651,8 +651,8 @@ namespace UVC.Tests.Data
// HttpRequestConfig 설정 // HttpRequestConfig 설정
var info = new HttpRequestConfig(baseInfoUrl, "get") var info = new HttpRequestConfig(baseInfoUrl, "get")
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setSuccessHandler((data) => .SetSuccessHandler((data) =>
{ {
handlerCalled = true; handlerCalled = true;
receivedData = data; receivedData = data;
@@ -737,8 +737,8 @@ namespace UVC.Tests.Data
// 반복 실행 설정을 포함한 HttpRequestConfig 생성 // 반복 실행 설정을 포함한 HttpRequestConfig 생성
var info = new HttpRequestConfig(testUrl, "get") var info = new HttpRequestConfig(testUrl, "get")
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setSuccessHandler(async (data) => .SetSuccessHandler(async (data) =>
{ {
handlerCallCount++; handlerCallCount++;
if (data is DataObject dataObject) if (data is DataObject dataObject)
@@ -756,7 +756,7 @@ namespace UVC.Tests.Data
MockHttpRequester.SetResponse(testUrl, mockResponses[handlerCallCount]); MockHttpRequester.SetResponse(testUrl, mockResponses[handlerCallCount]);
} }
}) })
.setRepeat(true, expectedCallCount, repeatInterval, false); .SetRepeat(true, expectedCallCount, repeatInterval, false);
pipeLine.UseMockup = true; pipeLine.UseMockup = true;
pipeLine.Add("repeatTest", info); pipeLine.Add("repeatTest", info);
@@ -811,9 +811,9 @@ namespace UVC.Tests.Data
// 무한 반복 설정을 포함한 HttpRequestConfig 생성 // 무한 반복 설정을 포함한 HttpRequestConfig 생성
var info = new HttpRequestConfig(testUrl, "get") var info = new HttpRequestConfig(testUrl, "get")
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setSuccessHandler((data) => { handlerCallCount++; }) .SetSuccessHandler((data) => { handlerCallCount++; })
.setRepeat(true, 0, repeatInterval, false); // 무한 반복 (repeatCount = 0) .SetRepeat(true, 0, repeatInterval, false); // 무한 반복 (repeatCount = 0)
pipeLine.UseMockup = true; pipeLine.UseMockup = true;
pipeLine.Add("infiniteRepeatTest", info); pipeLine.Add("infiniteRepeatTest", info);
@@ -879,14 +879,14 @@ namespace UVC.Tests.Data
// 두 개의 반복 요청 설정 // 두 개의 반복 요청 설정
var info1 = new HttpRequestConfig(testUrl1, "get") var info1 = new HttpRequestConfig(testUrl1, "get")
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setSuccessHandler((data) => { handlerCallCount1++; }) .SetSuccessHandler((data) => { handlerCallCount1++; })
.setRepeat(true, 0, repeatInterval1, false); .SetRepeat(true, 0, repeatInterval1, false);
var info2 = new HttpRequestConfig(testUrl2, "get") var info2 = new HttpRequestConfig(testUrl2, "get")
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setSuccessHandler((data) => { handlerCallCount2++; }) .SetSuccessHandler((data) => { handlerCallCount2++; })
.setRepeat(true, 0, repeatInterval2, false); .SetRepeat(true, 0, repeatInterval2, false);
pipeLine.UseMockup = true; pipeLine.UseMockup = true;
pipeLine.Add("repeatTest1", info1); pipeLine.Add("repeatTest1", info1);
@@ -961,13 +961,13 @@ namespace UVC.Tests.Data
// 반복 횟수가 지정된 HttpRequestConfig 생성 // 반복 횟수가 지정된 HttpRequestConfig 생성
var info = new HttpRequestConfig(testUrl, "get") var info = new HttpRequestConfig(testUrl, "get")
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setSuccessHandler((data) => .SetSuccessHandler((data) =>
{ {
handlerCallCount++; handlerCallCount++;
receivedData.Add(data); receivedData.Add(data);
}) })
.setRepeat(true, repeatCount, repeatInterval, false); .SetRepeat(true, repeatCount, repeatInterval, false);
pipeLine.UseMockup = true; pipeLine.UseMockup = true;
pipeLine.Add("countedRepeatTest", info); pipeLine.Add("countedRepeatTest", info);
@@ -1162,14 +1162,14 @@ namespace UVC.Tests.Data
}); });
var info = new HttpRequestConfig(testUrl) var info = new HttpRequestConfig(testUrl)
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setValidator(validator) .SetValidator(validator)
.setSuccessHandler(data => .SetSuccessHandler(data =>
{ {
handlerCalled = true; handlerCalled = true;
receivedData = data; receivedData = data;
}) })
.setFailHandler((message) => .SetFailHandler((message) =>
{ {
Debug.LogError("Fail message: " + message); Debug.LogError("Fail message: " + message);
}); });
@@ -1212,9 +1212,9 @@ namespace UVC.Tests.Data
validator.AddValidator("status", value => value is string s && s == "active"); validator.AddValidator("status", value => value is string s && s == "active");
var info = new HttpRequestConfig(testUrl) var info = new HttpRequestConfig(testUrl)
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setValidator(validator) .SetValidator(validator)
.setSuccessHandler(data => .SetSuccessHandler(data =>
{ {
handlerCalled = true; // 이 핸들러는 호출되지 않아야 함 handlerCalled = true; // 이 핸들러는 호출되지 않아야 함
}); });
@@ -1266,9 +1266,9 @@ namespace UVC.Tests.Data
}); });
var info = new HttpRequestConfig(testUrl) var info = new HttpRequestConfig(testUrl)
.setDataMapper(dataMapper) .SetDataMapper(dataMapper)
.setValidator(validator) .SetValidator(validator)
.setSuccessHandler(data => .SetSuccessHandler(data =>
{ {
handlerCalled = true; handlerCalled = true;
receivedData = data; receivedData = data;

View File

@@ -1,4 +1,4 @@
#nullable enable #nullable enable
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
@@ -284,7 +284,7 @@ namespace UVC.Tests.Data
var pipelineInfo = new MqttSubscriptionConfig(topic, updatedDataOnly) var pipelineInfo = new MqttSubscriptionConfig(topic, updatedDataOnly)
.setDataMapper(new DataMapper(dataMasks[topic])) .setDataMapper(new DataMapper(dataMasks[topic]))
.setHandler(handlers[topic].HandleData); .SetHandler(handlers[topic].HandleData);
mqttReceiver.Add(pipelineInfo); mqttReceiver.Add(pipelineInfo);
} }
@@ -315,7 +315,7 @@ namespace UVC.Tests.Data
// AGV 토픽만 등록 // AGV 토픽만 등록
var agvInfo = new MqttSubscriptionConfig("AGV", true) var agvInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMasks["AGV"])) .setDataMapper(new DataMapper(dataMasks["AGV"]))
.setHandler(handlers["AGV"].HandleData); .SetHandler(handlers["AGV"].HandleData);
mqttReceiver.Add(agvInfo); mqttReceiver.Add(agvInfo);
mqttReceiver.Start(); mqttReceiver.Start();
@@ -350,7 +350,7 @@ namespace UVC.Tests.Data
// UpdatedDataOnly가 true인 AGV 토픽 추가 // UpdatedDataOnly가 true인 AGV 토픽 추가
var agvInfo = new MqttSubscriptionConfig("AGV", true) var agvInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMasks["AGV"])) .setDataMapper(new DataMapper(dataMasks["AGV"]))
.setHandler(handlers["AGV"].HandleData); .SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(agvInfo); testPipeLine.Add(agvInfo);
@@ -382,7 +382,7 @@ namespace UVC.Tests.Data
bool updatedDataOnly = topic != "ALL"; bool updatedDataOnly = topic != "ALL";
var pipelineInfo = new MqttSubscriptionConfig(topic, updatedDataOnly) var pipelineInfo = new MqttSubscriptionConfig(topic, updatedDataOnly)
.setDataMapper(new DataMapper(dataMasks[topic])) .setDataMapper(new DataMapper(dataMasks[topic]))
.setHandler(handlers[topic].HandleData); .SetHandler(handlers[topic].HandleData);
testPipeLine.Add(pipelineInfo); testPipeLine.Add(pipelineInfo);
} }
@@ -410,7 +410,7 @@ namespace UVC.Tests.Data
var pipelineInfo = new MqttSubscriptionConfig("AGV", true) var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMasks["AGV"])) .setDataMapper(new DataMapper(dataMasks["AGV"]))
.setHandler(handlers["AGV"].HandleData); .SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(pipelineInfo); testPipeLine.Add(pipelineInfo);
@@ -434,7 +434,7 @@ namespace UVC.Tests.Data
var pipelineInfo = new MqttSubscriptionConfig("AGV", true) var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMasks["AGV"])) .setDataMapper(new DataMapper(dataMasks["AGV"]))
.setHandler(handlers["AGV"].HandleData); .SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(pipelineInfo); testPipeLine.Add(pipelineInfo);
@@ -453,7 +453,7 @@ namespace UVC.Tests.Data
var pipelineInfo = new MqttSubscriptionConfig("AGV", true) var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMasks["AGV"])) .setDataMapper(new DataMapper(dataMasks["AGV"]))
.setHandler(handlers["AGV"].HandleData); .SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(pipelineInfo); testPipeLine.Add(pipelineInfo);
@@ -484,7 +484,7 @@ namespace UVC.Tests.Data
// 4. UpdatedDataOnly=true로 토픽 등록 // 4. UpdatedDataOnly=true로 토픽 등록
var pipelineInfo = new MqttSubscriptionConfig("AGV", true) var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMask)) .setDataMapper(new DataMapper(dataMask))
.setHandler(handler.HandleData); .SetHandler(handler.HandleData);
pipeline.Add(pipelineInfo); pipeline.Add(pipelineInfo);
@@ -599,7 +599,7 @@ namespace UVC.Tests.Data
var pipelineInfo = new MqttSubscriptionConfig("test_topic") var pipelineInfo = new MqttSubscriptionConfig("test_topic")
.setDataMapper(dataMapper) .setDataMapper(dataMapper)
.setValidator(validator) .setValidator(validator)
.setHandler(handler.HandleData); .SetHandler(handler.HandleData);
testPipeLine.Add(pipelineInfo); testPipeLine.Add(pipelineInfo);
@@ -638,7 +638,7 @@ namespace UVC.Tests.Data
var pipelineInfo = new MqttSubscriptionConfig("test_topic") var pipelineInfo = new MqttSubscriptionConfig("test_topic")
.setDataMapper(dataMapper) .setDataMapper(dataMapper)
.setValidator(validator) .setValidator(validator)
.setHandler(handler.HandleData); .SetHandler(handler.HandleData);
testPipeLine.Add(pipelineInfo); testPipeLine.Add(pipelineInfo);
@@ -675,7 +675,7 @@ namespace UVC.Tests.Data
var pipelineInfo = new MqttSubscriptionConfig("test_topic") var pipelineInfo = new MqttSubscriptionConfig("test_topic")
.setDataMapper(dataMapper) .setDataMapper(dataMapper)
.setValidator(validator) .setValidator(validator)
.setHandler(handler.HandleData); .SetHandler(handler.HandleData);
testPipeLine.Add(pipelineInfo); testPipeLine.Add(pipelineInfo);

View File

@@ -1,24 +1,25 @@
using System; #nullable enable
using System;
using UnityEngine; using UnityEngine;
namespace UVC.UI.Commands namespace UVC.UI.Commands
{ {
public class ActionCommand : ICommand public class ActionCommand : ICommand
{ {
private readonly Action _action; private readonly Action? _action;
private readonly Action<object> _actionWithParam; private readonly Action<object?>? _actionWithParam;
public ActionCommand(Action action) public ActionCommand(Action action)
{ {
_action = action ?? throw new ArgumentNullException(nameof(action)); _action = action ?? throw new ArgumentNullException(nameof(action));
} }
public ActionCommand(Action<object> actionWithParam) public ActionCommand(Action<object?> actionWithParam)
{ {
_actionWithParam = actionWithParam ?? throw new ArgumentNullException(nameof(actionWithParam)); _actionWithParam = actionWithParam ?? throw new ArgumentNullException(nameof(actionWithParam));
} }
public void Execute(object parameter = null) public void Execute(object? parameter = null)
{ {
_action?.Invoke(); _action?.Invoke();
_actionWithParam?.Invoke(parameter); _actionWithParam?.Invoke(parameter);
@@ -31,7 +32,7 @@ namespace UVC.UI.Commands
// 또는, ICommand<T> 인터페이스를 고려할 수도 있습니다 (아래 2번 방법). // 또는, ICommand<T> 인터페이스를 고려할 수도 있습니다 (아래 2번 방법).
public class ActionCommand<T> : ICommand<T> // ICommand<T>를 구현 public class ActionCommand<T> : ICommand<T> // ICommand<T>를 구현
{ {
private readonly Action<T> _action; private readonly Action<T>? _action;
private readonly T _defaultParameter; private readonly T _defaultParameter;
private bool _useDefaultParameterForParameterlessExecute; private bool _useDefaultParameterForParameterlessExecute;
@@ -39,7 +40,7 @@ namespace UVC.UI.Commands
{ {
_action = action ?? throw new ArgumentNullException(nameof(action)); _action = action ?? throw new ArgumentNullException(nameof(action));
_useDefaultParameterForParameterlessExecute = true; // 기본적으로 default(T) 사용 _useDefaultParameterForParameterlessExecute = true; // 기본적으로 default(T) 사용
_defaultParameter = default(T); _defaultParameter = default(T)!;
} }
public ActionCommand(Action<T> action, T defaultParameter, bool useDefaultForParameterless = true) public ActionCommand(Action<T> action, T defaultParameter, bool useDefaultForParameterless = true)
@@ -52,7 +53,7 @@ namespace UVC.UI.Commands
// ICommand<T>의 Start(T parameter) 구현 // ICommand<T>의 Start(T parameter) 구현
public void Execute(T parameter) public void Execute(T parameter)
{ {
_action.Invoke(parameter); _action?.Invoke(parameter);
} }
// ICommand<T> 인터페이스에 의해 추가된 파라미터 없는 Start() // ICommand<T> 인터페이스에 의해 추가된 파라미터 없는 Start()
@@ -75,7 +76,7 @@ namespace UVC.UI.Commands
// ICommand의 Start(object parameter = null) 구현 // ICommand의 Start(object parameter = null) 구현
void ICommand.Execute(object parameter) // 명시적 인터페이스 구현 void ICommand.Execute(object? parameter) // 명시적 인터페이스 구현
{ {
if (parameter is T typedParameter) if (parameter is T typedParameter)
{ {
@@ -91,7 +92,7 @@ namespace UVC.UI.Commands
else else
{ {
// T가 참조 타입이면 default(T)는 null. 값 타입이면 0, false 등. // T가 참조 타입이면 default(T)는 null. 값 타입이면 0, false 등.
Execute(default(T)); Execute(default(T)!);
} }
} }
else else

View File

@@ -1,6 +1,6 @@
using UnityEngine; #nullable enable
using UnityEngine;
using UVC.Locale; using UVC.Locale;
using UVC.Log;
namespace UVC.UI.Commands namespace UVC.UI.Commands
{ {
@@ -15,7 +15,7 @@ namespace UVC.UI.Commands
_languageCode = languageCode; _languageCode = languageCode;
} }
public void Execute(object parameter = null) public void Execute(object? parameter = null)
{ {
string targetLanguage = _languageCode; string targetLanguage = _languageCode;

View File

@@ -1,4 +1,5 @@
using UVC.Log; #nullable enable
using UVC.Log;
namespace UVC.UI.Commands namespace UVC.UI.Commands
{ {
@@ -12,7 +13,7 @@ namespace UVC.UI.Commands
_message = message; _message = message;
} }
public void Execute(object parameter = null) public void Execute(object? parameter = null)
{ {
string finalMessage = _message; string finalMessage = _message;
if (parameter != null) if (parameter != null)

View File

@@ -1,14 +1,21 @@
namespace UVC.UI.Commands #nullable enable
namespace UVC.UI.Commands
{ {
public interface ICommand public interface ICommand
{ {
void Execute(object parameter = null); void Execute(object? parameter = null);
} }
public interface ICommand<T> : ICommand public interface ICommand<T> : ICommand
{ {
void Execute(T parameter); void Execute(T parameter);
void Execute() => Execute(default(T)); // 기본 Start 구현 제공 가능 void Execute()
{
// CS8604: T가 참조형이고 null이 허용되지 않을 때 경고가 발생하므로,
// T가 null 허용 타입이거나 값 타입일 때만 default(T)를 전달합니다.
// 그렇지 않으면, 명시적으로 default(T)를 전달하되, T가 null 허용 타입임을 명시합니다.
Execute(default(T)!);
}
} }
} }

View File

@@ -1,4 +1,5 @@
using UnityEngine; #nullable enable
using UnityEngine;
namespace UVC.UI.Commands.Mono namespace UVC.UI.Commands.Mono
{ {
@@ -9,7 +10,7 @@ namespace UVC.UI.Commands.Mono
{ {
// MonoCommand는 MonoBehaviour를 상속받아 Unity의 생명주기를 활용할 수 있습니다. // MonoCommand는 MonoBehaviour를 상속받아 Unity의 생명주기를 활용할 수 있습니다.
// ICommand 인터페이스를 구현하여 명령 패턴을 따릅니다. // ICommand 인터페이스를 구현하여 명령 패턴을 따릅니다.
public virtual void Execute(object parameter = null) public virtual void Execute(object? parameter = null)
{ {
// 기본 실행 로직 (필요시 override 가능) // 기본 실행 로직 (필요시 override 가능)
Debug.Log("MonoCommand executed."); Debug.Log("MonoCommand executed.");

View File

@@ -1,11 +1,12 @@
using UnityEngine; #nullable enable
using UnityEngine;
namespace UVC.UI.Commands namespace UVC.UI.Commands
{ {
// 애플리케이션 종료 커맨드 // 애플리케이션 종료 커맨드
public class QuitApplicationCommand : ICommand public class QuitApplicationCommand : ICommand
{ {
public void Execute(object parameter = null) public void Execute(object? parameter = null)
{ {
// 파라미터는 여기서는 사용되지 않을 수 있음 // 파라미터는 여기서는 사용되지 않을 수 있음
if (parameter != null) if (parameter != null)

View File

@@ -42,7 +42,7 @@ namespace UVC.UI.Loading
private bool animatting = false; private bool animatting = false;
private Transform loadingImageTransform; private Transform loadingImageTransform;
private float loadingSpeed = 1.5f; private float loadingSpeed = -1.5f;
private float rotationSpeed = -1.0f; private float rotationSpeed = -1.0f;
private void Awake() private void Awake()

View File

@@ -1,4 +1,4 @@
#nullable enable #nullable enable
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using System; // System.Type 사용을 위해 추가 using System; // System.Type 사용을 위해 추가
using System.Threading; using System.Threading;
@@ -234,19 +234,19 @@ namespace UVC.UI.Modal
// 기존 리스너 제거 후 새 리스너 추가 // 기존 리스너 제거 후 새 리스너 추가
modalView.confirmButton.onClick.RemoveAllListeners(); modalView.confirmButton.onClick.RemoveAllListeners();
// content.ShowConfirmButton 여부는 ModalView.OnOpen에서 버튼 자체의 활성화로 처리 // content.ShowConfirmButton 여부는 ModalView.OnOpen에서 버튼 자체의 활성화로 처리
modalView.confirmButton.onClick.AddListener(() => _ = HandleModalActionAsync(content, tcs, true, modalView)); modalView.confirmButton.onClick.AddListener(() => _ = HandleModalActionAsync(content, tcs, true, modalView, "confirm"));
} }
if (modalView.cancelButton != null) if (modalView.cancelButton != null)
{ {
modalView.cancelButton.onClick.RemoveAllListeners(); modalView.cancelButton.onClick.RemoveAllListeners();
modalView.cancelButton.onClick.AddListener(() => _ = HandleModalActionAsync(content, tcs, false, modalView)); modalView.cancelButton.onClick.AddListener(() => _ = HandleModalActionAsync(content, tcs, false, modalView, "close"));
} }
if (modalView.closeButton != null) if (modalView.closeButton != null)
{ {
modalView.closeButton.onClick.RemoveAllListeners(); modalView.closeButton.onClick.RemoveAllListeners();
modalView.closeButton.onClick.AddListener(() => _ = HandleModalActionAsync(content, tcs, false, modalView)); modalView.closeButton.onClick.AddListener(() => _ = HandleModalActionAsync(content, tcs, false, modalView, "close"));
} }
} }
@@ -263,7 +263,8 @@ namespace UVC.UI.Modal
ModalContent content, ModalContent content,
UniTaskCompletionSource<T> tcs, UniTaskCompletionSource<T> tcs,
bool isConfirmAction, bool isConfirmAction,
ModalView modalViewContext) ModalView modalViewContext,
string buttonType)
{ {
// 📜 이야기: 이 함수는 사용자가 버튼을 눌렀을 때 실행돼요. // 📜 이야기: 이 함수는 사용자가 버튼을 눌렀을 때 실행돼요.
// 그런데 만약 이전에 처리하던 약속 증서(activeTcs)와 지금 받은 증서(tcs)가 다르거나, // 그런데 만약 이전에 처리하던 약속 증서(activeTcs)와 지금 받은 증서(tcs)가 다르거나,
@@ -277,6 +278,19 @@ namespace UVC.UI.Modal
// 📜 이야기: 사용자가 버튼을 하나 눌렀으니, 다른 버튼들은 잠깐 못 누르게 막아요. (실수로 두 번 누르는 것 방지) // 📜 이야기: 사용자가 버튼을 하나 눌렀으니, 다른 버튼들은 잠깐 못 누르게 막아요. (실수로 두 번 누르는 것 방지)
modalViewContext.SetAllButtonsInteractable(false); modalViewContext.SetAllButtonsInteractable(false);
if(buttonType == "confirm")
{
modalViewContext.OnConfirmButtonClicked();
}
else if (buttonType == "cancel")
{
modalViewContext.OnCancelButtonClicked();
}
else if (buttonType == "close")
{
modalViewContext.OnCloseButtonClicked();
}
// 📜 이야기: 이제 모달을 닫고 뒷정리를 할 시간이에요! // 📜 이야기: 이제 모달을 닫고 뒷정리를 할 시간이에요!
// CleanupCurrentModalResources 조수에게 "이 모달 뷰를 사용했고, 사용자는 '확인'(또는 '취소')을 눌렀어요" 라고 알려주며 뒷정리를 부탁해요. // CleanupCurrentModalResources 조수에게 "이 모달 뷰를 사용했고, 사용자는 '확인'(또는 '취소')을 눌렀어요" 라고 알려주며 뒷정리를 부탁해요.
// 이 뒷정리 과정에서 '약속 증서'에 최종 결과가 기록될 거예요. // 이 뒷정리 과정에서 '약속 증서'에 최종 결과가 기록될 거예요.

View File

@@ -1,4 +1,5 @@
using Cysharp.Threading.Tasks; #nullable enable
using Cysharp.Threading.Tasks;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
@@ -260,7 +261,7 @@ namespace UVC.UI.Modal
/// // Modal.Open<string>(...) 이렇게 호출하면, 입력된 문자열을 받을 수 있어요. /// // Modal.Open<string>(...) 이렇게 호출하면, 입력된 문자열을 받을 수 있어요.
/// </code> /// </code>
/// </example> /// </example>
public virtual object GetResult() public virtual object? GetResult()
{ {
return null; return null;
} }
@@ -277,5 +278,25 @@ namespace UVC.UI.Modal
if (closeButton != null) closeButton.interactable = interactable; if (closeButton != null) closeButton.interactable = interactable;
} }
public virtual void OnConfirmButtonClicked()
{
// 확인 버튼이 눌렸을 때 호출되는 메서드예요.
// 기본적으로는 아무것도 하지 않지만, 필요하면 재정의해서 특별한 동작을 추가할 수 있어요.
// 예: ULog.Debug("확인 버튼이 눌렸어요!");
}
public virtual void OnCancelButtonClicked()
{
// 취소 버튼이 눌렸을 때 호출되는 메서드예요.
// 기본적으로는 아무것도 하지 않지만, 필요하면 재정의해서 특별한 동작을 추가할 수 있어요.
// 예: ULog.Debug("취소 버튼이 눌렸어요!");
}
public virtual void OnCloseButtonClicked()
{
// 닫기 버튼이 눌렸을 때 호출되는 메서드예요.
// 기본적으로는 아무것도 하지 않지만, 필요하면 재정의해서 특별한 동작을 추가할 수 있어요.
// 예: ULog.Debug("닫기 버튼이 눌렸어요!");
}
} }
} }

View File

@@ -94,13 +94,25 @@ namespace UVC.UI
dragArea = GetComponentInParent<Canvas>()?.transform as RectTransform; dragArea = GetComponentInParent<Canvas>()?.transform as RectTransform;
if (dragArea == null) if (dragArea == null)
{ {
Debug.LogError("<b>[UIDragger]</b> 드래그 영역(dragArea)으로 사용할 Canvas를 찾을 수 없습니다.", this); Debug.Log("<b>[UIDragger]</b> 드래그 영역(dragArea)으로 사용할 Canvas를 찾을 수 없습니다.", this);
enabled = false; enabled = false;
return; return;
} }
} }
} }
/// <summary>
/// 드래그가 허용되는 영역을 설정합니다.
/// </summary>
/// <remarks>이 메서드를 활성화하면 지정된 영역 내에서 드래그 기능이 활성화됩니다.
///</remarks>
/// <param name="area">드래그 영역의 경계를 정의하는 <see cref="RectTransform"/>입니다. null일 수 없습니다.</param>
public void SetDragArea(RectTransform area)
{
dragArea = area;
enabled = true;
}
/// <summary> /// <summary>
/// 드래그가 시작될 때 호출됩니다. (IBeginDragHandler) /// 드래그가 시작될 때 호출됩니다. (IBeginDragHandler)
/// </summary> /// </summary>

View File

@@ -12,8 +12,8 @@ PlayerSettings:
targetDevice: 2 targetDevice: 2
useOnDemandResources: 0 useOnDemandResources: 0
accelerometerFrequency: 60 accelerometerFrequency: 60
companyName: DefaultCompany companyName: UVC
productName: Test productName: XRBase
defaultCursor: {fileID: 0} defaultCursor: {fileID: 0}
cursorHotspot: {x: 0, y: 0} cursorHotspot: {x: 0, y: 0}
m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}
@@ -856,7 +856,7 @@ PlayerSettings:
m_RenderingPath: 1 m_RenderingPath: 1
m_MobileRenderingPath: 1 m_MobileRenderingPath: 1
metroPackageName: Test metroPackageName: Test
metroPackageVersion: metroPackageVersion: 1.0.0.0
metroCertificatePath: metroCertificatePath:
metroCertificatePassword: metroCertificatePassword:
metroCertificateSubject: metroCertificateSubject:
@@ -864,7 +864,7 @@ PlayerSettings:
metroCertificateNotAfter: 0000000000000000 metroCertificateNotAfter: 0000000000000000
metroApplicationDescription: Test metroApplicationDescription: Test
wsaImages: {} wsaImages: {}
metroTileShortName: metroTileShortName: Test
metroTileShowName: 0 metroTileShowName: 0
metroMediumTileShowName: 0 metroMediumTileShowName: 0
metroLargeTileShowName: 0 metroLargeTileShowName: 0