데이터 즉시 업데이트 flag 추가

This commit is contained in:
logonkhi
2025-08-18 13:11:39 +09:00
parent 4328cd2de1
commit 696d91c684
11 changed files with 138 additions and 2703 deletions

View File

@@ -376,11 +376,32 @@ namespace UVC.Data.Core
/// <returns>업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면 0을 반환합니다.</returns>
public int UpdatedCount { get => addedList.Count + modifiedList.Count + removedList.Count; }
private bool isUpdateImmediately = false;
/// <summary>
/// 업데이트가 즉시 적용되어야 하는지 여부를 나타냅니다.
/// </summary>
/// <returns></returns>
public bool IsUpdateImmediately
{
get => isUpdateImmediately;
set
{
if (isUpdateImmediately == value) return;
isUpdateImmediately = value;
// 내부 DataObject에도 동일한 값을 설정합니다.
foreach (var item in this)
{
item.IsUpdateImmediately = value;
}
}
}
/// <summary>
/// 컬렉션의 모든 항목을 제거합니다.
/// </summary>
public new void Clear()
{
isUpdateImmediately = false;
// 내부 리스트와 변경 추적 리스트를 모두 비웁니다.
ReturnToDataObjectPool();
base.Clear();

View File

@@ -699,6 +699,7 @@ namespace UVC.Data.Core
RemoveAll();
Name = string.Empty;
IdKey = null;
isUpdateImmediately = false;
}
/// <summary>
@@ -899,6 +900,33 @@ namespace UVC.Data.Core
/// <returns>업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면 0을 반환합니다.</returns>
public int UpdatedCount { get => changedProperies.Count; }
private bool isUpdateImmediately = false;
/// <summary>
/// 업데이트가 즉시 적용되어야 하는지 여부를 나타냅니다.
/// </summary>
/// <returns></returns>
public bool IsUpdateImmediately
{
get => isUpdateImmediately;
set
{
if (isUpdateImmediately == value) return;
isUpdateImmediately = value;
foreach (var item in this)
{
if (item.Value is DataObject dataObject)
{
dataObject.IsUpdateImmediately = value;
}
else if (item.Value is DataArray dataArray)
{
dataArray.IsUpdateImmediately = value;
}
}
}
}
/// <summary>
/// DataObject의 내용을 문자열로 반환합니다.
/// 각 키-값 쌍이 "키:값" 형식으로 쉼표로 구분되어 표시됩니다.

View File

@@ -1,4 +1,4 @@
namespace UVC.Data.Core
namespace UVC.Data.Core
{
/// <summary>
/// 서로 다른 JSON 데이터 구조 간에 매핑 기능을 제공하는 클래스입니다.
@@ -31,7 +31,6 @@
public IDataObject GetUpdatedObject(bool fromPool = true);
/// <summary>
/// /// <summary>
/// 업데이트 된 속성의 수.
/// </summary>
/// <returns>업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면 0을 반환합니다.</returns>
@@ -54,5 +53,11 @@
/// 풀에서 재사용할 수 있도록 해야 합니다. 객체를 풀로 반환하기 전에 객체가 유효한 상태인지 확인하십시오.</remarks>
public void ReturnToPool();
/// <summary>
/// 업데이트가 즉시 적용되어야 하는지 여부를 나타냅니다.
/// </summary>
/// <returns></returns>
public bool IsUpdateImmediately { get; }
}
}

View File

@@ -85,6 +85,16 @@ namespace UVC.Data
{
dataObject.MarkAllAsUpdated();
var newData = dataObject.Clone(fromPool: false);
//데이터 즉시 업데이트 여부 설정
if (newData is DataObject dataObj)
{
dataObj.IsUpdateImmediately = dataObject.IsUpdateImmediately;
}
else if (newData is DataArray dataArray)
{
dataArray.IsUpdateImmediately = dataObject.IsUpdateImmediately;
}
dataObjects.Add(key, newData);
NotifyDataUpdate(key, newData.Clone(false));
return dataObject;
@@ -102,6 +112,15 @@ namespace UVC.Data
{
newDataObject = dataObject;
}
//데이터 즉시 업데이트 여부 설정
if (newDataObject is DataObject dataObj)
{
dataObj.IsUpdateImmediately = dataObject.IsUpdateImmediately;
}
else if (newDataObject is DataArray dataArray)
{
dataArray.IsUpdateImmediately = dataObject.IsUpdateImmediately;
}
bool shouldInvoke = !updatedDataOnly || newDataObject.UpdatedCount > 0;
if (shouldInvoke) NotifyDataUpdate(key, newDataObject == dataObject ? newDataObject.Clone(false) : newDataObject);
return newDataObject;

View File

@@ -163,7 +163,7 @@ namespace UVC.Data.Http
/// "AGV"와 "ALARM"을 각각 별개의 데이터로 간주하고 처리합니다.
/// 각 키에 대해 별도의 DataMapper나 Validator를 `HttpRequestConfig`에 설정할 수 있습니다.
/// </remarks>
public static void ProcessSplitResponse(HttpRequestConfig info, string jsonResponse, CancellationToken? cancellationToken = null)
public static void ProcessSplitResponse(HttpRequestConfig info, string jsonResponse, CancellationToken? cancellationToken = null, bool isUpdateImmediately = false)
{
JObject responseObject = JObject.Parse(jsonResponse);
// JSON 객체의 모든 프로퍼티(키-값 쌍)를 순회합니다.
@@ -207,6 +207,15 @@ namespace UVC.Data.Http
{
continue;
}
if (subMappedObject is DataObject dataObj)
{
dataObj.IsUpdateImmediately = isUpdateImmediately;
}
else if (subMappedObject is DataArray dataArray)
{
dataArray.IsUpdateImmediately = isUpdateImmediately;
}
// 분할된 데이터를 `subKey`를 키로 사용하여 DataRepository에 저장합니다.
var repoObject = DataRepository.Instance.AddOrUpdateData(subKey, subMappedObject, info.UpdatedDataOnly);

View File

@@ -102,6 +102,10 @@ namespace UVC.Data.Mqtt
public string LastMessage { get; private set; } = string.Empty;
//mqtt 연결 후 topic 별 첫 번째 메시지를 수신 했는지 여부를 저장하는 딕셔너리
private ConcurrentDictionary<string, bool> firstMessageReceived = new ConcurrentDictionary<string, bool>();
/// <summary>
/// MqttDataReceiver 인스턴스를 생성합니다.
/// </summary>
@@ -232,6 +236,14 @@ namespace UVC.Data.Mqtt
// 토픽이 infoList와 readyHandlerList에 존재하고, 준비 상태가 true인 경우에만 처리합니다.
if (configList.TryGetValue(topic, out var config))
{
bool isFirstMessage = false;
// 첫 번째 메시지 수신 여부를 확인하고, 아직 수신하지 않았다면 처리합니다.
if (!firstMessageReceived.ContainsKey(topic) || !firstMessageReceived[topic])
{
firstMessageReceived[topic] = true; // 첫 번째 메시지를 수신했음을 기록합니다.
isFirstMessage = true; // 첫 번째 메시지로 처리합니다.
}
IDataObject? mappedObject = null;
message = message.Trim();
LastMessage = message; // 마지막 메시지를 저장하여 나중에 사용할 수 있습니다.
@@ -249,6 +261,17 @@ namespace UVC.Data.Mqtt
}
//Debug.Log($"OnTopicMessageLogic topic: {topic}, mappedObject == null: {mappedObject == null}, config.DataMapper:{config.DataMapper == null}, config.Validator:{config.Validator == null}");
if (mappedObject == null) return;
//즉시 업데이트 여부를 설정합니다.
if (mappedObject is DataObject dataObj)
{
dataObj.IsUpdateImmediately = isFirstMessage;
}
else if (mappedObject is DataArray dataArray)
{
dataArray.IsUpdateImmediately = isFirstMessage;
}
// DataRepository는 내부적으로 데이터를 복사/업데이트하므로, mappedObject는 여기서 임시 객체가 됩니다.
var repoObject = DataRepository.Instance.AddOrUpdateData(topic, mappedObject, config.UpdatedDataOnly);
if (repoObject == mappedObject) repoObject = mappedObject.Clone(fromPool: false);
@@ -293,6 +316,7 @@ namespace UVC.Data.Mqtt
mqttWorker.RemoveListener(topic, OnTopicPacketMessage);
}
mqttWorker.Stop();
firstMessageReceived.Clear();
}
else
{
@@ -311,6 +335,7 @@ namespace UVC.Data.Mqtt
if (!UseMockup) mqttWorker.Dispose();
//else mockupMQTT?.Disconnect();
configList.Clear();
firstMessageReceived.Clear();
}
}

View File

@@ -1,12 +1,10 @@
#nullable enable
using EPOOutline;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UVC.Data;
using UVC.Data.Core;
using UVC.Extention;
using UVC.Factory.Playback;
namespace UVC.Factory.Component
@@ -65,12 +63,6 @@ namespace UVC.Factory.Component
private float agvFloorOffset = 200f; //agv의 층을 구별하기 위한 값
private float agvZOffset = -197.5f; //층을 옮긴 agv의 위치를 잡아주기 위한 값
// true일 경우, UpdatePositionAndRotation에서 즉시 위치와 회전을 변경합니다.
// 한번으로 않되서 3번 즉시 변경하도록 설정
private bool needUpdateImmediately = false;
private int maxImmediateUpdateCount = 3;
private int immediateUpdateCount = 0;
/// <summary>
/// AGV 객체가 생성될 때 처음 한 번 호출되는 초기화 메서드입니다.
/// </summary>
@@ -93,12 +85,12 @@ namespace UVC.Factory.Component
public override void OnPointerClick(PointerEventData eventData)
{
// 사용자가 AGV를 클릭했을 때 정보창에 표시될 데이터 항목과 순서를 정의합니다.
if(dataDisplaySetting == null) dataDisplaySetting = UserSetting.Get("AGV");
if (dataDisplaySetting == null) dataDisplaySetting = UserSetting.Get("AGV");
base.OnPointerClick(eventData);
}
private void OnChangeTimeScaleHandler(float timeScale)
{
{
this.timeScale = timeScale;
}
@@ -108,7 +100,7 @@ namespace UVC.Factory.Component
base.OnDestroy();
}
/// <summary>
/// 선택 된 효과로 외곽선을 표시합니다.
@@ -126,17 +118,6 @@ namespace UVC.Factory.Component
if (outlinable != null) outlinable.enabled = false;
}
/// <summary>
/// 즉시 객체의 상태를 업데이트합니다.
/// </summary>
/// <param name="count">업데이트 횟수. 기본값은 1입니다.</param>
public override void UpdateImmediately(int count = 3)
{
needUpdateImmediately = true; // 다음 데이터 업데이트에서 즉시 위치와 회전을 변경하도록 플래그를 설정합니다.
immediateUpdateCount = 0;
maxImmediateUpdateCount = count;
}
/// <summary>
/// AGVManager로부터 새로운 데이터를 받았을 때 호출되는 핵심 메서드입니다.
@@ -149,25 +130,22 @@ namespace UVC.Factory.Component
/// <param name="newData">AGV의 최신 정보가 담긴 데이터 객체입니다.</param>
protected override void ProcessData(DataObject newData)
{
// 새 데이터로 위치와 회전을 즉시 설정합니다.
UpdatePositionAndRotation(newData);
if (newData.IsUpdateImmediately)
{
transform.position = targetPosition;
transform.rotation = targetRotation;
}
// 처음 데이터를 받는 경우 (data가 null일 때)
if (data == null)
{
// 새 데이터로 위치와 회전을 즉시 설정합니다.
UpdatePositionAndRotation(newData);
// 받은 데이터를 내부 데이터 저장소에 저장합니다.
data = newData;
}
else // 이미 데이터가 있는 경우 (업데이트)
{
// 새 데이터를 기반으로 목표 위치와 회전을 갱신합니다.
UpdatePositionAndRotation(newData);
if (needUpdateImmediately)
{
transform.position = targetPosition;
transform.rotation = targetRotation;
immediateUpdateCount++;
if (immediateUpdateCount >= maxImmediateUpdateCount) needUpdateImmediately = false;
}
// 기존 데이터(data)에 새로운 데이터(newData)의 내용을 덮어씁니다.
foreach (var keyValue in newData)
{
@@ -232,7 +210,7 @@ namespace UVC.Factory.Component
{
float x = currentX.Value;
float y = currentY.Value;
if ((newX.HasValue && x != newX) || (newY.HasValue && y != newY))
{
Vector3 newTargetPosition = transform.position;
@@ -307,10 +285,10 @@ namespace UVC.Factory.Component
private void UpdateStats()
{
if(data == null) return;
if (data == null) return;
string? stats = data.GetString("VHL_STATE");
if (stats == null) return;
if(stats == "91" || stats == "92" || stats == "93" || stats == "94")//CHARGING
if (stats == "91" || stats == "92" || stats == "93" || stats == "94")//CHARGING
{
ChangeColor(new Color(0.7f, 0.12f, 0.0f, 1.0f));//orange
}
@@ -321,7 +299,7 @@ namespace UVC.Factory.Component
else
{
string? trayID = data.GetString("CARRIER_ID");
if(string.IsNullOrEmpty(trayID))
if (string.IsNullOrEmpty(trayID))
{
ChangeColor(Color.yellow); // 트레이 ID가 없으면 기본 색상으로 설정
}
@@ -334,9 +312,9 @@ namespace UVC.Factory.Component
private void ChangeColor(Color color)
{
if(color == Color.red && isRed && modelRenderer != null) return; // 이미 빨간색이면 변경하지 않음
if (color == Color.red && isRed && modelRenderer != null) return; // 이미 빨간색이면 변경하지 않음
isRed = color == Color.red;
if(modelRenderer != null) modelRenderer!.material.color = color;
if (modelRenderer != null) modelRenderer!.material.color = color;
}
/// <summary>
@@ -386,7 +364,6 @@ namespace UVC.Factory.Component
public override void UnregisterFactoryObject()
{
base.UnregisterFactoryObject();
needUpdateImmediately = false; // 파괴 시 즉시 위치와 회전 변경 플래그를 초기화합니다.
}
}
}

View File

@@ -222,17 +222,6 @@ namespace UVC.Factory.Component
}
}
/// <summary>
/// 즉시 객체의 상태를 업데이트합니다.
/// </summary>
public void UpdateImmediately(int count = 1)
{
if (pool == null) return;
foreach (var agv in pool.ActiveItems.Values)
{
if (agv != null) agv.UpdateImmediately(count);
}
}
/// <summary>
/// AGVManager가 파괴될 때 호출됩니다.

View File

@@ -145,7 +145,7 @@ namespace UVC.Factory.Playback
httpRequestConfig.AddSplitConfig("ALARM", DataMapperValidator.Get("ALARM"));
foreach (var item in list)
{
HttpDataProcessor.ProcessSplitResponse(httpRequestConfig, item.data);
HttpDataProcessor.ProcessSplitResponse(httpRequestConfig, item.data, null, true);
}
}
});